/* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! This module generates the UniFFI C++ code that sits between the Rust scaffolding code and the //! generated JS code. The main responsibility of the generated C++ code is to implement the //! UniFFI WebIDL interface. //! //! The general strategy is to take the generalized component interface and convert it into types //! that can be easily rendered by the UniFFIScaffolding.cpp template -- an intermediate //! representation of sorts. //! //! In many cases this means converting a type from `uniffi_bindgen::interface` into another type //! that represents the same concept, but is easier for the templates to render. In this case, the //! new type name has `Cpp` appended to it ([FfiFunction] is converted to [FfiFunctionCpp]). use std::collections::HashSet; use heck::{ToSnakeCase, ToUpperCamelCase}; use rinja::Template; use uniffi_bindgen::interface::{ AsType, Callable, CallbackInterface, ComponentInterface, FfiDefinition, FfiFunction, FfiType, }; use super::shared::*; use crate::{CallbackIds, Component, FunctionIds, ObjectIds}; #[derive(Template)] #[template(path = "UniFFIScaffolding.cpp", escape = "none")] pub struct CPPScaffoldingTemplate { all_ffi_definitions: CombinedItems>, all_pointer_types: CombinedItems>, all_callback_interfaces: CombinedItems>, all_scaffolding_calls: CombinedItems>, } impl CPPScaffoldingTemplate { pub fn new( components: &[Component], fixture_components: &[Component], function_ids: &FunctionIds<'_>, object_ids: &ObjectIds<'_>, callback_ids: &CallbackIds<'_>, ) -> Self { Self { all_ffi_definitions: Self::all_ffi_definitions(components, fixture_components), all_pointer_types: CombinedItems::new( Self::pointer_types(object_ids, components), Self::pointer_types(object_ids, fixture_components), ), all_callback_interfaces: CombinedItems::new( Self::callback_interfaces(callback_ids, components), Self::callback_interfaces(callback_ids, fixture_components), ), all_scaffolding_calls: CombinedItems::new( Self::scaffolding_calls(function_ids, components), Self::scaffolding_calls(function_ids, fixture_components), ), } } fn all_ffi_definitions( components: &[Component], fixture_components: &[Component], ) -> CombinedItems> { // Track which FFI definition's we've seen and don't add them twice. // This avoids duplicate definitions for shared FFI types like `CallbackInterfaceFree`. // // The code below ordered so that duplicated definitions get added to the components side // of `CombinedItems` rather than the fixtures side. This way if fixtures are disabled, we // don't see missing definition errors. let mut seen_names = HashSet::new(); CombinedItems::new( Self::ffi_definitions(components) .into_iter() .filter(|ffi_def| seen_names.insert(ffi_def.name().to_owned())) .collect(), Self::ffi_definitions(fixture_components) .into_iter() .filter(|ffi_def| seen_names.insert(ffi_def.name().to_owned())) .collect(), ) } fn ffi_definitions(components: &[Component]) -> Vec { components .iter() .flat_map(|c| c.ci.ffi_definitions()) .map(|ffi_definition| match ffi_definition { FfiDefinition::Function(ffi_func) => FfiDefinitionCpp::Function(FfiFunctionCpp { name: ffi_func.name().to_snake_case(), arg_types: ffi_func .arguments() .iter() .map(|a| ffi_type_name(&a.type_())) .chain( ffi_func .has_rust_call_status_arg() .then(|| "RustCallStatus*".to_owned()), ) .collect(), return_type: return_type(ffi_func.return_type()), }), FfiDefinition::CallbackFunction(ffi_callback) => { FfiDefinitionCpp::CallbackFunction(FfiCallbackFunctionCpp { name: ffi_callback.name().to_upper_camel_case(), arg_types: ffi_callback .arguments() .into_iter() .map(|a| ffi_type_name(&a.type_())) .chain( ffi_callback .has_rust_call_status_arg() .then(|| "RustCallStatus*".to_owned()), ) .collect(), return_type: return_type(ffi_callback.return_type()), }) } FfiDefinition::Struct(ffi_struct) => FfiDefinitionCpp::Struct(FfiStructCpp { name: ffi_struct.name().to_upper_camel_case(), fields: ffi_struct .fields() .into_iter() .map(|f| FfiFieldCpp { name: f.name().to_snake_case(), type_: ffi_type_name(&f.type_()), }) .collect(), }), }) .collect() } fn pointer_types(object_ids: &ObjectIds<'_>, components: &[Component]) -> Vec { components .iter() .flat_map(|c| { c.ci.object_definitions() .iter() .map(move |obj| PointerType { object_id: object_ids.get(&c.ci, obj), name: pointer_type(&c.ci.namespace(), obj.name()), ffi_value_class: pointer_ffi_value_class(&c.ci.namespace(), obj.name()), label: format!("{}::{}", c.ci.namespace(), obj.name()), clone_fn: obj.ffi_object_clone().name().to_string(), free_fn: obj.ffi_object_free().name().to_string(), }) }) .collect() } fn callback_interfaces( callback_ids: &CallbackIds<'_>, components: &[Component], ) -> Vec { components .iter() .flat_map(|c| { c.ci.callback_interface_definitions() .iter() .map(move |cbi| { let cbi_name = cbi.name().to_upper_camel_case(); CallbackInterfaceCpp { id: callback_ids.get(&c.ci, cbi), name: format!("{}:{}", c.ci.namespace(), cbi.name()), js_handler_var: format!("gCallbackInterfaceJsHandler{cbi_name}"), vtable: Self::callback_interface_vtable(&c.ci, cbi), free_fn: format!("callbackInterfaceFree{cbi_name}"), init_fn: cbi.ffi_init_callback().name().to_owned(), } }) }) .collect() } fn callback_interface_vtable( ci: &ComponentInterface, cbi: &CallbackInterface, ) -> CallbackInterfaceVTable { let cbi_name = cbi.name().to_upper_camel_case(); let cbi_name_snake = cbi.name().to_snake_case(); CallbackInterfaceVTable { type_: ffi_type_name(&cbi.vtable()), var_name: format!("kCallbackInterfaceVtable{cbi_name}"), method_handlers: cbi .vtable_methods() .iter() .map(|(_, method)| { let method_name = method.name().to_upper_camel_case(); let method_name_snake = method.name().to_snake_case(); CallbackMethodHandler { fn_name: format!("callback_interface_{cbi_name_snake}_{method_name_snake}"), class_name: format!("CallbackInterfaceMethod{cbi_name}{method_name}"), arguments: method .arguments() .iter() .map(|arg| { let ffi_type = arg.as_type().into(); CallbackMethodArgument { name: arg.name().to_snake_case(), ffi_type: ffi_type_name(&ffi_type), ffi_value_class: ffi_value_class(ci, &ffi_type), } }) .collect(), } }) .collect(), } } fn scaffolding_calls( function_ids: &FunctionIds<'_>, components: &[Component], ) -> Vec { let mut calls: Vec = components .iter() .flat_map(|c| { exposed_functions(&c.ci).map(move |(callable, ffi_func)| { ScaffoldingCall::new(&c.ci, callable, ffi_func, function_ids) }) }) .collect(); calls.sort_by_key(|c| c.function_id); calls } } /// Combines fixture and non-fixture template items struct CombinedItems { item: T, fixture_item: T, } impl CombinedItems { fn new(item: T, fixture_item: T) -> Self { Self { item, fixture_item } } /// Iterate over child items /// Each item is the tuple (preprocssor_condition, , preprocssor_condition_end), where /// `preprocssor_condition` is the preprocessor preprocssor_condition that should control if /// the items are included. fn iter(&self) -> impl Iterator { vec![ ("".to_string(), &self.item, "".to_string()), ( "#ifdef MOZ_UNIFFI_FIXTURES".to_string(), &self.fixture_item, "#endif /* MOZ_UNIFFI_FIXTURES */".to_string(), ), ] .into_iter() } } enum FfiDefinitionCpp { Function(FfiFunctionCpp), CallbackFunction(FfiCallbackFunctionCpp), Struct(FfiStructCpp), } impl FfiDefinitionCpp { fn name(&self) -> &str { match self { Self::Function(f) => &f.name, Self::CallbackFunction(c) => &c.name, Self::Struct(s) => &s.name, } } } struct FfiFunctionCpp { name: String, arg_types: Vec, return_type: String, } struct FfiCallbackFunctionCpp { name: String, arg_types: Vec, return_type: String, } struct FfiStructCpp { name: String, fields: Vec, } struct FfiFieldCpp { name: String, type_: String, } struct PointerType { object_id: usize, name: String, ffi_value_class: String, label: String, clone_fn: String, free_fn: String, } struct CallbackInterfaceCpp { id: usize, name: String, /// Static variable that stores a reference to the JS UniFFICallbackHandler object js_handler_var: String, vtable: CallbackInterfaceVTable, free_fn: String, init_fn: String, } /// Represents the vtable for a callback interface /// /// "vtable" just means a struct whose fields are function pointers -- one for each method. struct CallbackInterfaceVTable { /// FFI struct name type_: String, /// Name of the static variable storing the vtable var_name: String, /// Functions to handle the callback interface methods /// /// These are then stored in the vtable fields method_handlers: Vec, } /// Code to handle a single callback interface method struct CallbackMethodHandler { /// C++ function to handle the method fn_name: String, /// UniffiCallbackMethodHandlerBase subclass for this method class_name: String, arguments: Vec, } struct CallbackMethodArgument { name: String, ffi_type: String, ffi_value_class: String, } struct ScaffoldingCall { handler_class_name: String, function_id: usize, ffi_func_name: String, return_type: Option, arguments: Vec, async_info: Option, } impl ScaffoldingCall { fn new( ci: &ComponentInterface, callable: &dyn Callable, ffi_func: &FfiFunction, function_ids: &FunctionIds, ) -> Self { let handler_class_name = format!( "ScaffoldingCallHandler{}", ffi_func.name().to_upper_camel_case() ); let arguments = ffi_func .arguments() .into_iter() .map(|a| ScaffoldingCallArgument { var_name: format!("m{}", a.name().to_upper_camel_case()), ffi_value_class: ffi_value_class(ci, &a.type_()), }) .collect::>(); let async_info = callable.is_async().then(|| ScaffoldingCallAsyncInfo { poll_fn: callable.ffi_rust_future_poll(ci), complete_fn: callable.ffi_rust_future_complete(ci), free_fn: callable.ffi_rust_future_free(ci), }); Self { handler_class_name, function_id: function_ids.get(ci, ffi_func), ffi_func_name: ffi_func.name().to_owned(), // Make sure to use the callable here, not the ffi_func. For async functions, the FFI // function always returns a handle. return_type: callable .return_type() .map(|return_type| FfiType::from(return_type)) .map(|return_type| ScaffoldingCallReturnType { ffi_value_class: ffi_value_class(ci, &return_type), }), arguments, async_info, } } fn is_async(&self) -> bool { self.async_info.is_some() } } struct ScaffoldingCallReturnType { ffi_value_class: String, } struct ScaffoldingCallArgument { var_name: String, ffi_value_class: String, } struct ScaffoldingCallAsyncInfo { poll_fn: String, complete_fn: String, free_fn: String, } fn ffi_value_class(ci: &ComponentInterface, ffi_type: &FfiType) -> String { match ffi_type { FfiType::RustArcPtr(name) => { // Check if this is an external type for ty in ci.iter_external_types() { let external_ty_name = ty.name().expect("External type without name"); let crate_name = ty.module_path().expect("External type without module path"); if external_ty_name == name { return pointer_ffi_value_class(crate_name_to_namespace(&crate_name), name); } } pointer_ffi_value_class(ci.namespace(), name) } FfiType::UInt8 | FfiType::Int8 | FfiType::UInt16 | FfiType::Int16 | FfiType::UInt32 | FfiType::Int32 | FfiType::UInt64 | FfiType::Int64 => format!("FfiValueInt<{}>", ffi_type_name(ffi_type)), FfiType::Float32 | FfiType::Float64 => { format!("FfiValueFloat<{}>", ffi_type_name(ffi_type)) } FfiType::RustBuffer(_) => "FfiValueRustBuffer".to_owned(), _ => format!("FfiConverter<{}>", ffi_type_name(ffi_type)), } } fn pointer_type(namespace: &str, name: &str) -> String { format!( "k{}{}PointerType", namespace.to_upper_camel_case(), name.to_upper_camel_case() ) } fn pointer_ffi_value_class(namespace: &str, name: &str) -> String { format!( "FfiValueObjectHandle{}{}", namespace.to_upper_camel_case(), name.to_upper_camel_case() ) } // C++ type for an FFI value fn ffi_type_name(ffi_type: &FfiType) -> String { match ffi_type { FfiType::UInt8 => "uint8_t".to_owned(), FfiType::Int8 => "int8_t".to_owned(), FfiType::UInt16 => "uint16_t".to_owned(), FfiType::Int16 => "int16_t".to_owned(), FfiType::UInt32 => "uint32_t".to_owned(), FfiType::Int32 => "int32_t".to_owned(), FfiType::UInt64 => "uint64_t".to_owned(), FfiType::Int64 => "int64_t".to_owned(), FfiType::Float32 => "float".to_owned(), FfiType::Float64 => "double".to_owned(), FfiType::RustBuffer(_) => "RustBuffer".to_owned(), FfiType::RustArcPtr(_) => "void*".to_owned(), FfiType::ForeignBytes => "ForeignBytes".to_owned(), FfiType::Handle => "uint64_t".to_owned(), FfiType::RustCallStatus => "RustCallStatus".to_owned(), FfiType::Callback(name) | FfiType::Struct(name) => name.to_owned(), FfiType::VoidPointer => "void*".to_owned(), FfiType::MutReference(inner) | FfiType::Reference(inner) => { format!("{}*", ffi_type_name(inner.as_ref())) } } } fn return_type(ffi_type: Option<&FfiType>) -> String { match ffi_type { Some(t) => ffi_type_name(t), None => "void".to_owned(), } } // Iterate over functions, methods, and constructors exposed to JS // // Generates `&dyn Callable` items, since of these is a different type, but they all implement // `Callable`. // // Also generates `&FfiFunction` for each item. There should probably be a method on `Callable` // that returns this and there's a PR to do so, but in the meantime we need to use this workaround. pub fn exposed_functions( ci: &ComponentInterface, ) -> impl Iterator { ci.function_definitions() .into_iter() .map(|f| (f as &dyn Callable, f.ffi_func())) .chain(ci.object_definitions().into_iter().flat_map(|o| { o.methods() .into_iter() .map(|m| (m as &dyn Callable, m.ffi_func())) .chain( o.constructors() .into_iter() .map(|c| (c as &dyn Callable, c.ffi_func())), ) })) }