| // Part of the Crubit project, under the Apache License v2.0 with LLVM |
| // Exceptions. See /LICENSE for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| |
| use arc_anyhow::Result; |
| use code_gen_utils::{expect_format_cc_ident, make_rs_ident}; |
| use crubit_abi_type::CrubitAbiTypeToCppTokens; |
| use database::code_snippet::{Thunk, ThunkImpl}; |
| use database::db::BindingsGenerator; |
| use database::rs_snippet::{ |
| format_generic_params, unique_lifetimes, BridgeRsTypeKind, Mutability, RsTypeKind, |
| }; |
| use error_report::{anyhow, bail}; |
| use ir::*; |
| use itertools::Itertools; |
| use proc_macro2::{Ident, TokenStream}; |
| use quote::{format_ident, quote}; |
| use std::borrow::Cow; |
| use std::fmt::Write; |
| use std::rc::Rc; |
| use unicode_ident::is_xid_continue; |
| |
| /// If we know the original C++ function is codegenned and already compatible |
| /// with `extern "C"` calling convention we skip creating/calling the C++ thunk |
| /// since we can call the original C++ directly. |
| pub fn can_skip_cc_thunk(db: &dyn BindingsGenerator, func: &Func) -> bool { |
| // ## Inline functions |
| // |
| // Inline functions may not be codegenned in the C++ library since Clang doesn't |
| // know if Rust calls the function or not. Therefore in order to make inline |
| // functions callable from Rust we need to generate a C++ file that defines |
| // a thunk that delegates to the original inline function. When compiled, |
| // Clang will emit code for this thunk and Rust code will call the |
| // thunk when the user wants to call the original inline function. |
| // |
| // This is not great runtime-performance-wise in regular builds (inline function |
| // will not be inlined, there will always be a function call), but it is |
| // correct. ThinLTO builds will be able to see through the thunk and inline |
| // code across the language boundary. For non-ThinLTO builds we plan to |
| // implement <internal link> which removes the runtime performance overhead. |
| if func.is_inline { |
| return false; |
| } |
| // ## Member functions (or descendants) of class templates |
| // |
| // A thunk is required to force/guarantee template instantiation. |
| if func.is_member_or_descendant_of_class_template { |
| return false; |
| } |
| // ## Virtual functions |
| // |
| // When calling virtual `A::Method()`, it's not necessarily the case that we'll |
| // specifically call the concrete `A::Method` impl. For example, if this is |
| // called on something whose dynamic type is some subclass `B` with an |
| // overridden `B::Method`, then we'll call that. |
| // |
| // We must reuse the C++ dynamic dispatching system. In this case, the easiest |
| // way to do it is by resorting to a C++ thunk, whose implementation will do |
| // the lookup. |
| // |
| // In terms of runtime performance, since this only occurs for virtual function |
| // calls, which are already slow, it may not be such a big deal. We can |
| // benchmark it later. :) |
| if let Some(meta) = &func.member_func_metadata { |
| if let Some(inst_meta) = &meta.instance_method_metadata { |
| if inst_meta.is_virtual { |
| return false; |
| } |
| } |
| } |
| // ## Custom calling convention requires a thunk. |
| // |
| // The thunk has the "C" calling convention, and internally can call the |
| // C++ function using any of the calling conventions supported by the C++ |
| // compiler (which might not always match the set supported by Rust - e.g., |
| // abi.rs doesn't contain "swiftcall" from |
| // clang::FunctionType::getNameForCallConv) |
| if !func.has_c_calling_convention { |
| return false; |
| } |
| |
| // ## Returning structs by value. |
| // |
| // Returning a struct by value requires an explicit thunk, because |
| // `rs_bindings_from_cc` may not preserve the ABI of structs (e.g. when |
| // replacing field types with an opaque blob of bytes - see b/270454629). |
| // |
| // Note: if the RsTypeKind cannot be parsed / rs_type_kind returns Err, then |
| // bindings generation will fail for this function, so it doesn't really matter |
| // what we do here. |
| if let Ok(return_type) = db.rs_type_kind(func.return_type.clone()) { |
| if !return_type.is_c_abi_compatible_by_value() { |
| return false; |
| } |
| } |
| // ## Nontrivial parameter types. |
| // |
| // If the function accepts a struct by value, then in the underlying ABI, it is |
| // actually passed by pointer. |
| // |
| // Because there's no way to upgrade an lvalue (e.g. pointer) to a prvalue, we |
| // cannot implement guaranteed copy/move elision for inline functions for |
| // now: any thunk we generate would need to invoke the correct function as |
| // if by magic. |
| // |
| // And so for now, we always use C++11 semantics, via an intermediate thunk. |
| // |
| // (As a side effect, this, like return values, means that support is |
| // ABI-agnostic.) |
| for param in &func.params { |
| if let Ok(param_type) = db.rs_type_kind(param.type_.clone()) { |
| if !param_type.is_c_abi_compatible_by_value() { |
| return false; |
| } |
| } |
| } |
| |
| true |
| } |
| |
| pub fn generate_function_thunk( |
| db: &dyn BindingsGenerator, |
| func: &Func, |
| param_idents: &[Ident], |
| param_types: &[RsTypeKind], |
| return_type: &RsTypeKind, |
| ) -> Result<Thunk> { |
| // The first parameter is the output parameter, if any. |
| let mut param_types = param_types.iter(); |
| let mut param_idents = param_idents.iter(); |
| let mut out_param = None; |
| let mut out_param_ident = None; |
| let mut return_type_fragment = return_type.format_as_return_type_fragment(db, None); |
| if func.rs_name == UnqualifiedIdentifier::Constructor { |
| // For constructors, inject MaybeUninit into the type of `__this_` parameter. |
| let Some(first_param) = param_types.next() else { |
| bail!("Constructors should have at least one parameter (__this), but none were found.") |
| }; |
| let RsTypeKind::Reference { mutability: Mutability::Mut, .. } = first_param else { |
| bail!( |
| "Expected first constructor parameter to be a mutable reference, got: {}", |
| first_param.display(db) |
| ) |
| }; |
| out_param = Some(quote! { *mut ::core::ffi::c_void }); |
| out_param_ident = Some(param_idents.next().unwrap().clone()); |
| } else if return_type.is_crubit_abi_bridge_type() { |
| out_param = Some(quote! { *mut ::core::ffi::c_uchar }); |
| out_param_ident = Some(make_rs_ident("__return_abi_buffer")); |
| return_type_fragment = None; |
| } else if !return_type.is_c_abi_compatible_by_value() { |
| // For return types that can't be passed by value, create a new out parameter. |
| out_param = Some(quote! { *mut ::core::ffi::c_void }); |
| out_param_ident = Some(make_rs_ident("__return")); |
| return_type_fragment = None; |
| } |
| |
| // Of the remaining lifetimes, collect them. |
| let lifetimes: Vec<_> = unique_lifetimes(param_types.clone()).collect(); |
| |
| let thunk_ident = thunk_ident(func); |
| |
| let generic_params = format_generic_params(&lifetimes, std::iter::empty::<syn::Ident>()); |
| let param_idents = |
| out_param_ident.as_ref().into_iter().chain(param_idents).cloned().collect_vec(); |
| let param_types = out_param |
| .into_iter() |
| .chain(param_types.map(|param_type| { |
| if param_type.is_crubit_abi_bridge_type() { |
| quote! { *const ::core::ffi::c_uchar } |
| } else if !param_type.is_c_abi_compatible_by_value() { |
| let param_type_tokens = param_type.to_token_stream(db); |
| quote! {&mut #param_type_tokens} |
| } else { |
| param_type.to_token_stream(db) |
| } |
| })) |
| .collect_vec(); |
| |
| Ok(Thunk::Function { |
| mangled_name: can_skip_cc_thunk(db, func).then(|| func.mangled_name.clone()), |
| thunk_ident, |
| generic_params, |
| param_idents, |
| param_types, |
| return_type_fragment, |
| }) |
| } |
| |
| // Converts `mangled_name` into a string that can be used within an identifier. |
| // All characters in the result are guaranteed to be from the XID_Continue class (though not |
| // necessarily XID_Start, so the fragment can't be used at the start of an identifier). |
| // |
| // The escaping scheme is not collision-free, i.e. two different inputs may map to the same output. |
| // In practice, though, collisions are extremely unlikely, and other aspects of the way we create |
| // thunk names may also cause (very unlikely) collisions. |
| fn ident_fragment_from_mangled_name(mangled_name: &str) -> Cow<str> { |
| // LLVM identifiers use the `\01` prefix to suppress mangling: |
| // https://llvm.org/docs/LangRef.html#identifiers |
| // We won't be passing the name to LLVM anyway, so we simply strip the prefix if present. |
| let mangled_name = mangled_name.strip_prefix('\u{1}').unwrap_or(mangled_name); |
| |
| if mangled_name.chars().all(is_xid_continue) { |
| return mangled_name.into(); |
| } |
| |
| let mut ident_name = String::new(); |
| |
| for c in mangled_name.chars() { |
| if is_xid_continue(c) { |
| ident_name.push(c); |
| } else { |
| let _ = write!(ident_name, "_u{}_", c as u32); |
| } |
| } |
| |
| ident_name.into() |
| } |
| |
| pub fn thunk_ident(func: &Func) -> Ident { |
| let odr_suffix = if func.is_member_or_descendant_of_class_template { |
| func.owning_target.convert_to_cc_identifier() |
| } else { |
| String::new() |
| }; |
| format_ident!( |
| "__rust_thunk__{}{odr_suffix}", |
| ident_fragment_from_mangled_name(func.mangled_name.as_ref()) |
| ) |
| } |
| |
| pub fn generate_function_thunk_impl( |
| db: &dyn BindingsGenerator, |
| func: &Func, |
| ) -> Result<Option<ThunkImpl>> { |
| if can_skip_cc_thunk(db, func) { |
| return Ok(None); |
| } |
| let ir = db.ir(); |
| let thunk_ident = thunk_ident(func); |
| let implementation_function = match &func.rs_name { |
| UnqualifiedIdentifier::Operator(op) => { |
| let name = syn::parse_str::<TokenStream>(&op.name)?; |
| quote! { operator #name } |
| } |
| UnqualifiedIdentifier::Identifier(id) => { |
| let fn_ident = expect_format_cc_ident(&id.identifier); |
| match func.member_func_metadata.as_ref() { |
| Some(meta) => { |
| if meta.instance_method_metadata.is_some() { |
| quote! { #fn_ident } |
| } else { |
| let record: &Rc<Record> = ir.find_decl(meta.record_id)?; |
| let record_ident = |
| expect_format_cc_ident(record.cc_name.identifier.as_ref()); |
| let namespace_qualifier = ir.namespace_qualifier(record).format_for_cc()?; |
| quote! { #namespace_qualifier #record_ident :: #fn_ident } |
| } |
| } |
| None => { |
| let namespace_qualifier = ir.namespace_qualifier(func).format_for_cc()?; |
| quote! { #namespace_qualifier #fn_ident } |
| } |
| } |
| } |
| // Use `destroy_at` to avoid needing to spell out the class name. Destructor identiifers |
| // use the name of the type itself, without namespace qualification, template |
| // parameters, or aliases. We do not need to use that naming scheme anywhere else in |
| // the bindings, and it can be difficult (impossible?) to spell in the general case. By |
| // using destroy_at, we avoid needing to determine or remember what the correct spelling |
| // is. Similar arguments apply to `construct_at`. |
| UnqualifiedIdentifier::Constructor => { |
| quote! { crubit::construct_at } |
| } |
| UnqualifiedIdentifier::Destructor => quote! {std::destroy_at}, |
| }; |
| |
| let mut param_idents = |
| func.params.iter().map(|p| expect_format_cc_ident(&p.identifier.identifier)).collect_vec(); |
| |
| let mut conversion_externs = quote! {}; |
| let mut conversion_stmts = quote! {}; |
| let convert_ident = |ident: &TokenStream| -> TokenStream { |
| let ident = format_ident!("__converted_{}", ident.to_string()); |
| quote! { #ident } |
| }; |
| let mut param_types = func |
| .params |
| .iter() |
| .map(|p| { |
| let cpp_type = cpp_type_name::format_cpp_type(&p.type_, ir)?; |
| let arg_type = db.rs_type_kind(p.type_.clone())?; |
| if let RsTypeKind::BridgeType { bridge_type, .. } = arg_type.unalias() { |
| let BridgeRsTypeKind::BridgeVoidConverters { rust_to_cpp_converter, .. } = |
| bridge_type |
| else { |
| return Ok(quote! { const unsigned char* }); |
| }; |
| |
| let convert_function = expect_format_cc_ident(rust_to_cpp_converter); |
| let ident = expect_format_cc_ident(&p.identifier.identifier); |
| let cpp_ident = convert_ident(&ident); |
| conversion_externs.extend(quote! { |
| extern "C" void #convert_function(void* rust_struct, void* cpp_struct); |
| }); |
| conversion_stmts.extend(quote! { |
| ::crubit::LazyInit<#cpp_type> #cpp_ident; |
| }); |
| conversion_stmts.extend(quote! { |
| #convert_function(#ident, &#cpp_ident.val); |
| }); |
| Ok(quote! { void* }) |
| } else if !arg_type.is_c_abi_compatible_by_value() { |
| // non-Unpin types are wrapped by a pointer in the thunk. |
| Ok(quote! {#cpp_type *}) |
| } else { |
| Ok(cpp_type) |
| } |
| }) |
| .collect::<Result<Vec<_>>>()?; |
| |
| let arg_expressions = func |
| .params |
| .iter() |
| .map(|p| { |
| let mut ident = expect_format_cc_ident(&p.identifier.identifier); |
| if db.rs_type_kind(p.type_.clone())?.is_pointer_bridge_type() { |
| let formatted_ident = convert_ident(&ident); |
| ident = quote! { &(#formatted_ident.val) }; |
| } |
| match &p.type_.variant { |
| CcTypeVariant::Pointer(pointer) => match pointer.kind { |
| PointerTypeKind::RValueRef => Ok(quote! { std::move(*#ident) }), |
| PointerTypeKind::LValueRef => Ok(quote! { *#ident }), |
| PointerTypeKind::Nullable | PointerTypeKind::NonNull => Ok(quote! { #ident }), |
| }, |
| CcTypeVariant::FuncPointer { non_null, .. } => { |
| if *non_null { |
| Ok(quote! { *#ident }) |
| } else { |
| Ok(quote! { #ident }) |
| } |
| } |
| _ => { |
| let rs_type_kind = db.rs_type_kind(p.type_.clone())?; |
| // non-Unpin types are wrapped by a pointer in the thunk. |
| if rs_type_kind.is_crubit_abi_bridge_type() { |
| let crubit_abi_type = db.crubit_abi_type(rs_type_kind)?; |
| let crubit_abi_type_tokens = CrubitAbiTypeToCppTokens(&crubit_abi_type); |
| Ok(quote! { ::crubit::internal::Decode<#crubit_abi_type_tokens>(#ident) }) |
| } else if !rs_type_kind.is_c_abi_compatible_by_value() { |
| Ok(quote! { std::move(* #ident) }) |
| } else if rs_type_kind.is_primitive() || rs_type_kind.referent().is_some() { |
| Ok(quote! { #ident }) |
| } else { |
| Ok(quote! { std::move( #ident) }) |
| } |
| } |
| } |
| }) |
| .collect::<Result<Vec<_>>>()?; |
| |
| // Here, we add a `__return` parameter if the return type can't be passed by |
| // value across `extern "C"` ABI. (We do this after the arg_expressions |
| // computation, so that it's only in the parameter list, not the argument |
| // list.) |
| let return_type_kind = db.rs_type_kind(func.return_type.clone())?; |
| let is_return_value_c_abi_compatible = return_type_kind.is_c_abi_compatible_by_value(); |
| let return_type_cpp_spelling = cpp_type_name::format_cpp_type(&func.return_type, ir)?; |
| |
| let return_type_name = if return_type_kind.is_crubit_abi_bridge_type() { |
| param_idents.insert(0, expect_format_cc_ident("__return_abi_buffer")); |
| param_types.insert(0, quote! {unsigned char *}); |
| quote! { void } |
| } else if !is_return_value_c_abi_compatible { |
| param_idents.insert(0, expect_format_cc_ident("__return")); |
| // In order to be modified, the return type can't be const. |
| let mut cc_return_type = func.return_type.clone(); |
| cc_return_type.is_const = false; |
| let return_type_name = cpp_type_name::format_cpp_type(&cc_return_type, &ir)?; |
| if let RsTypeKind::BridgeType { |
| bridge_type: BridgeRsTypeKind::BridgeVoidConverters { cpp_to_rust_converter, .. }, |
| .. |
| } = return_type_kind.unalias() |
| { |
| let convert_function = expect_format_cc_ident(cpp_to_rust_converter); |
| conversion_externs.extend(quote! { |
| extern "C" void #convert_function(void* cpp_struct, void* rust_struct); |
| }); |
| param_types.insert(0, quote! {void *}); |
| } else { |
| param_types.insert(0, quote! {#return_type_name *}); |
| } |
| quote! {void} |
| } else { |
| return_type_cpp_spelling.clone() |
| }; |
| |
| let this_ref_qualification = |
| func.member_func_metadata.as_ref().and_then(|meta| match &func.rs_name { |
| UnqualifiedIdentifier::Constructor | UnqualifiedIdentifier::Destructor => None, |
| UnqualifiedIdentifier::Identifier(_) | UnqualifiedIdentifier::Operator(_) => meta |
| .instance_method_metadata |
| .as_ref() |
| .map(|instance_method| instance_method.reference), |
| }); |
| let (implementation_function, arg_expressions) = |
| if let Some(this_ref_qualification) = this_ref_qualification { |
| let this_param = func |
| .params |
| .first() |
| .ok_or_else(|| anyhow!("Instance methods must have `__this` param."))?; |
| |
| let this_arg = expect_format_cc_ident(&this_param.identifier.identifier); |
| let this_dot = if this_ref_qualification == ir::ReferenceQualification::RValue { |
| quote! {std::move(*#this_arg).} |
| } else { |
| quote! {#this_arg->} |
| }; |
| ( |
| quote! { #this_dot #implementation_function}, |
| arg_expressions.iter().skip(1).cloned().collect_vec(), |
| ) |
| } else { |
| (implementation_function, arg_expressions) |
| }; |
| |
| let return_expr = quote! {#implementation_function( #( #arg_expressions ),* )}; |
| let return_stmt = if return_type_kind.is_crubit_abi_bridge_type() { |
| let out_param = ¶m_idents[0]; |
| let crubit_abi_type = db.crubit_abi_type(return_type_kind)?; |
| let crubit_abi_type_tokens = CrubitAbiTypeToCppTokens(&crubit_abi_type); |
| quote! { |
| ::crubit::internal::Encode<#crubit_abi_type_tokens>(#out_param, #return_expr) |
| } |
| } else if !is_return_value_c_abi_compatible { |
| let out_param = ¶m_idents[0]; |
| if let RsTypeKind::BridgeType { |
| bridge_type: BridgeRsTypeKind::BridgeVoidConverters { cpp_to_rust_converter, .. }, |
| .. |
| } = return_type_kind.unalias() |
| { |
| let convert_function = expect_format_cc_ident(cpp_to_rust_converter); |
| quote! { |
| auto __original_cpp_struct = #return_expr; |
| #convert_function(&__original_cpp_struct, #out_param) |
| } |
| } else { |
| // Explicitly use placement `new` so that we get guaranteed copy elision in |
| // C++17. |
| quote! {new(#out_param) auto(#return_expr)} |
| } |
| } else { |
| match &func.return_type.variant { |
| CcTypeVariant::Primitive(Primitive::Void) => return_expr, |
| CcTypeVariant::Pointer(PointerType { kind: PointerTypeKind::LValueRef, .. }) => { |
| quote! { return & #return_expr } |
| } |
| CcTypeVariant::Pointer(PointerType { |
| kind: PointerTypeKind::RValueRef, |
| pointee_type, |
| .. |
| }) => { |
| // The code below replicates bits of `format_cpp_type`, but formats an rvalue |
| // reference (which `format_cpp_type` would format as a pointer). |
| // `const_fragment` from `format_cpp_type` is ignored - it is not applicable for |
| // references. |
| let nested_type = cpp_type_name::format_cpp_type(pointee_type, ir)?; |
| quote! { |
| #nested_type && lvalue = #return_expr; |
| return &lvalue |
| } |
| } |
| CcTypeVariant::FuncPointer { non_null: true, .. } => quote! { return & #return_expr }, |
| _ => quote! { return #return_expr }, |
| } |
| }; |
| |
| Ok(Some(ThunkImpl::Function { |
| conversion_externs, |
| return_type_name, |
| thunk_ident, |
| param_types, |
| param_idents, |
| conversion_stmts, |
| return_stmt, |
| })) |
| } |