| // 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 |
| #![feature(rustc_private)] |
| #![deny(rustc::internal)] |
| |
| extern crate rustc_attr; |
| extern crate rustc_hir; |
| extern crate rustc_infer; |
| extern crate rustc_middle; |
| extern crate rustc_span; |
| extern crate rustc_target; |
| extern crate rustc_trait_selection; |
| extern crate rustc_type_ir; |
| |
| use arc_anyhow::{Context, Error, Result}; |
| use code_gen_utils::{ |
| escape_non_identifier_chars, format_cc_ident, format_cc_includes, make_rs_ident, CcInclude, |
| NamespaceQualifier, |
| }; |
| use error_report::{anyhow, bail, ensure, ErrorReporting}; |
| use itertools::Itertools; |
| use proc_macro2::{Ident, Literal, TokenStream}; |
| use quote::{format_ident, quote, ToTokens}; |
| use rustc_attr::find_deprecation; |
| use rustc_hir::def::{DefKind, Res}; |
| use rustc_hir::{AssocItemKind, HirId, Item, ItemKind, Node, Safety, UseKind, UsePath}; |
| use rustc_infer::infer::TyCtxtInferExt; |
| use rustc_middle::dep_graph::DepContext; |
| use rustc_middle::mir::Mutability; |
| use rustc_middle::ty::{self, Ty, TyCtxt}; // See <internal link>/ty.html#import-conventions |
| use rustc_span::def_id::{DefId, LocalDefId, LOCAL_CRATE}; |
| use rustc_span::symbol::{kw, sym, Symbol}; |
| use rustc_target::abi::{Abi, FieldsShape, Integer, Layout, Primitive, Scalar}; |
| use rustc_target::spec::PanicStrategy; |
| use rustc_trait_selection::infer::InferCtxtExt; |
| use rustc_type_ir::RegionKind; |
| use std::collections::{BTreeSet, HashMap, HashSet}; |
| use std::hash::{Hash, Hasher}; |
| use std::iter::once; |
| use std::ops::AddAssign; |
| use std::rc::Rc; |
| use std::slice; |
| |
| memoized::query_group! { |
| trait BindingsGenerator<'tcx> { |
| /// Compilation context for the crate that the bindings should be generated |
| /// for. |
| #[input] |
| fn tcx(&self) -> TyCtxt<'tcx>; |
| |
| /// Format specifier for `#include` Crubit C++ support library headers, |
| /// using `{header}` as the place holder. Example: |
| /// `<crubit/support/{header}>` results in `#include |
| /// <crubit/support/hdr.h>`. |
| #[input] |
| fn crubit_support_path_format(&self) -> Rc<str>; |
| |
| /// A map from a crate name to the include paths of the corresponding C++ |
| /// headers This is used when formatting a type exported from another |
| /// crate. |
| // TODO(b/271857814): A crate name might not be globally unique - the key needs to also cover |
| // a "hash" of the crate version and compilation flags. |
| #[input] |
| fn crate_name_to_include_paths(&self) -> Rc<HashMap<Rc<str>, Vec<CcInclude>>>; |
| |
| /// Error collector for generating reports of errors encountered during the generation of bindings. |
| #[input] |
| fn errors(&self) -> Rc<dyn ErrorReporting>; |
| |
| // TODO(b/262878759): Provide a set of enabled/disabled Crubit features. |
| #[input] |
| fn _features(&self) -> (); |
| |
| fn support_header(&self, suffix: &'tcx str) -> CcInclude; |
| |
| fn repr_attrs(&self, did: DefId) -> Rc<[rustc_attr::ReprAttr]>; |
| |
| fn format_ty_for_cc( |
| &self, |
| ty: SugaredTy<'tcx>, |
| location: TypeLocation, |
| ) -> Result<CcSnippet>; |
| |
| fn format_default_ctor( |
| &self, |
| core: Rc<AdtCoreBindings<'tcx>>, |
| ) -> Result<ApiSnippets, ApiSnippets>; |
| fn format_copy_ctor_and_assignment_operator( |
| &self, |
| core: Rc<AdtCoreBindings<'tcx>>, |
| ) -> Result<ApiSnippets, ApiSnippets>; |
| fn format_move_ctor_and_assignment_operator( |
| &self, |
| core: Rc<AdtCoreBindings<'tcx>>, |
| ) -> Result<ApiSnippets, ApiSnippets>; |
| |
| fn format_item(&self, def_id: LocalDefId) -> Result<Option<ApiSnippets>>; |
| fn format_fn(&self, local_def_id: LocalDefId) -> Result<ApiSnippets>; |
| fn format_adt_core(&self, def_id: DefId) -> Result<Rc<AdtCoreBindings<'tcx>>>; |
| } |
| pub struct Database; |
| } |
| |
| fn support_header<'tcx>(db: &dyn BindingsGenerator<'tcx>, suffix: &'tcx str) -> CcInclude { |
| CcInclude::support_lib_header(db.crubit_support_path_format(), suffix.into()) |
| } |
| |
| pub struct Output { |
| pub h_body: TokenStream, |
| pub rs_body: TokenStream, |
| } |
| |
| pub fn generate_bindings(db: &Database) -> Result<Output> { |
| let tcx = db.tcx(); |
| match tcx.sess().panic_strategy() { |
| PanicStrategy::Unwind => bail!("No support for panic=unwind strategy (b/254049425)"), |
| PanicStrategy::Abort => (), |
| }; |
| |
| let top_comment = { |
| let crate_name = tcx.crate_name(LOCAL_CRATE); |
| let txt = format!( |
| "Automatically @generated C++ bindings for the following Rust crate:\n\ |
| {crate_name}" |
| ); |
| quote! { __COMMENT__ #txt __NEWLINE__ } |
| }; |
| |
| let Output { h_body, rs_body } = format_crate(db).unwrap_or_else(|err| { |
| let txt = format!("Failed to generate bindings for the crate: {err}"); |
| let src = quote! { __COMMENT__ #txt }; |
| Output { h_body: src.clone(), rs_body: src } |
| }); |
| |
| let h_body = quote! { |
| #top_comment |
| |
| // TODO(b/251445877): Replace `#pragma once` with include guards. |
| __HASH_TOKEN__ pragma once __NEWLINE__ |
| __NEWLINE__ |
| |
| #h_body |
| }; |
| |
| let rs_body = quote! { |
| #top_comment |
| |
| // `rust_builtin_type_abi_assumptions.md` documents why the generated |
| // bindings need to relax the `improper_ctypes_definitions` warning |
| // for `char` (and possibly for other built-in types in the future). |
| #![allow(improper_ctypes_definitions)] __NEWLINE__ |
| |
| __NEWLINE__ |
| |
| #rs_body |
| }; |
| |
| Ok(Output { h_body, rs_body }) |
| } |
| |
| #[derive(Clone, Debug, Default)] |
| struct CcPrerequisites { |
| /// Set of `#include`s that a `CcSnippet` depends on. For example if |
| /// `CcSnippet::tokens` expands to `std::int32_t`, then `includes` |
| /// need to cover the `#include <cstdint>`. |
| includes: BTreeSet<CcInclude>, |
| |
| /// Set of local definitions that a `CcSnippet` depends on. For example if |
| /// `CcSnippet::tokens` expands to `void foo(S s) { ... }` then the |
| /// definition of `S` should have appeared earlier - in this case `defs` |
| /// will include the `LocalDefId` corresponding to `S`. Note that the |
| /// definition of `S` is covered by `ApiSnippets::main_api` (i.e. the |
| /// predecessor of a toposort edge is `ApiSnippets::main_api` - it is not |
| /// possible to depend on `ApiSnippets::cc_details`). |
| defs: HashSet<LocalDefId>, |
| |
| /// Set of forward declarations that a `CcSnippet` depends on. For example |
| /// if `CcSnippet::tokens` expands to `void foo(S* s)` then a forward |
| /// declaration of `S` should have appeared earlier - in this case |
| /// `fwd_decls` will include the `LocalDefId` corresponding to `S`. |
| /// Note that in this particular example the *definition* of `S` does |
| /// *not* need to appear earlier (and therefore `defs` will *not* |
| /// contain `LocalDefId` corresponding to `S`). |
| fwd_decls: HashSet<LocalDefId>, |
| } |
| |
| impl CcPrerequisites { |
| #[cfg(test)] |
| fn is_empty(&self) -> bool { |
| let &Self { ref includes, ref defs, ref fwd_decls } = self; |
| includes.is_empty() && defs.is_empty() && fwd_decls.is_empty() |
| } |
| |
| /// Weakens all dependencies to only require a forward declaration. Example |
| /// usage scenarios: |
| /// - Computing prerequisites of pointer types (the pointee type can just be |
| /// forward-declared), |
| /// - Computing prerequisites of function declarations (parameter types and |
| /// return type can just be forward-declared). |
| fn move_defs_to_fwd_decls(&mut self) { |
| self.fwd_decls.extend(std::mem::take(&mut self.defs)) |
| } |
| } |
| |
| impl AddAssign for CcPrerequisites { |
| fn add_assign(&mut self, rhs: Self) { |
| let Self { mut includes, defs, fwd_decls } = rhs; |
| |
| // `BTreeSet::append` is used because it _seems_ to be more efficient than |
| // calling `extend`. This is because `extend` takes an iterator |
| // (processing each `rhs` include one-at-a-time) while `append` steals |
| // the whole backing data store from `rhs.includes`. OTOH, this is a bit |
| // speculative, since the (expected / guessed) performance difference is |
| // not documented at |
| // https://doc.rust-lang.org/std/collections/struct.BTreeSet.html#method.append |
| self.includes.append(&mut includes); |
| |
| self.defs.extend(defs); |
| self.fwd_decls.extend(fwd_decls); |
| } |
| } |
| |
| #[derive(Clone, Debug, Default)] |
| struct CcSnippet { |
| tokens: TokenStream, |
| prereqs: CcPrerequisites, |
| } |
| |
| impl CcSnippet { |
| /// Consumes `self` and returns its `tokens`, while preserving |
| /// its `prereqs` into `prereqs_accumulator`. |
| fn into_tokens(self, prereqs_accumulator: &mut CcPrerequisites) -> TokenStream { |
| let Self { tokens, prereqs } = self; |
| *prereqs_accumulator += prereqs; |
| tokens |
| } |
| |
| /// Creates a new CcSnippet (with no `CcPrerequisites`). |
| fn new(tokens: TokenStream) -> Self { |
| Self { tokens, ..Default::default() } |
| } |
| |
| /// Creates a CcSnippet that depends on a single `CcInclude`. |
| fn with_include(tokens: TokenStream, include: CcInclude) -> Self { |
| let mut prereqs = CcPrerequisites::default(); |
| prereqs.includes.insert(include); |
| Self { tokens, prereqs } |
| } |
| } |
| |
| impl AddAssign for CcSnippet { |
| fn add_assign(&mut self, rhs: Self) { |
| self.tokens.extend(rhs.into_tokens(&mut self.prereqs)); |
| } |
| } |
| |
| /// Represents the fully qualified name of a Rust item (e.g. of a `struct` or a |
| /// function). |
| struct FullyQualifiedName { |
| /// Name of the crate that defines the item. |
| /// For example, this would be `std` for `std::cmp::Ordering`. |
| krate: Symbol, |
| |
| /// Path to the module where the item is located. |
| /// For example, this would be `cmp` for `std::cmp::Ordering`. |
| /// The path may contain multiple modules - e.g. `foo::bar::baz`. |
| mod_path: NamespaceQualifier, |
| |
| /// Name of the item. |
| /// For example, this would be: |
| /// * `Some("Ordering")` for `std::cmp::Ordering`. |
| /// * `None` for `ItemKind::Use` - e.g.: `use submodule::*` |
| name: Option<Symbol>, |
| |
| /// The fully-qualified C++ type to use for this, if this was originally a |
| /// C++ type. |
| /// |
| /// For example, if a type has `#[__crubit::annotate(cpp_type="x::y")]`, |
| /// then cpp_type will be `Some(x::y)`. |
| cpp_type: Option<Symbol>, |
| |
| /// The C++ name to use for the symbol. |
| /// |
| /// For example, the following struct |
| /// ``` |
| /// #[__crubit::annotate(cpp_name="Bar")] |
| /// struct Foo { ... } |
| /// ``` |
| /// will be generated as a C++ struct named `Bar` instead of `Foo`. |
| cpp_name: Option<Symbol>, |
| } |
| |
| impl FullyQualifiedName { |
| /// Computes a `FullyQualifiedName` for `def_id`. |
| /// |
| /// May panic if `def_id` is an invalid id. |
| // TODO(b/259724276): This function's results should be memoized. |
| fn new(tcx: TyCtxt, def_id: DefId) -> Self { |
| let krate = tcx.crate_name(def_id.krate); |
| |
| // Crash OK: these attributes are introduced by crubit itself, and "should |
| // never" be malformed. |
| let attributes = crubit_attr::get(tcx, def_id).unwrap(); |
| let cpp_type = attributes.cpp_type; |
| |
| let mut full_path = tcx.def_path(def_id).data; // mod_path + name |
| let name = full_path.pop().expect("At least the item's name should be present"); |
| let name = name.data.get_opt_name(); |
| let cpp_name = attributes.cpp_name.map(|s| Symbol::intern(s.as_str())).or(name); |
| |
| let mod_path = NamespaceQualifier::new( |
| full_path |
| .into_iter() |
| .filter_map(|p| p.data.get_opt_name()) |
| .map(|s| Rc::<str>::from(s.as_str())), |
| ); |
| |
| Self { krate, mod_path, name, cpp_type, cpp_name } |
| } |
| |
| fn format_for_cc(&self) -> Result<TokenStream> { |
| if let Some(path) = self.cpp_type { |
| let path = format_cc_ident(path.as_str())?; |
| return Ok(quote! {#path}); |
| } |
| |
| let name = self.cpp_name.as_ref().unwrap_or_else(|| { |
| self.name.as_ref().expect("`format_for_cc` can't be called on name-less item kinds") |
| }); |
| |
| let top_level_ns = format_cc_ident(self.krate.as_str())?; |
| let ns_path = self.mod_path.format_for_cc()?; |
| let name = format_cc_ident(name.as_str())?; |
| Ok(quote! { :: #top_level_ns :: #ns_path #name }) |
| } |
| |
| fn format_for_rs(&self) -> TokenStream { |
| let name = |
| self.name.as_ref().expect("`format_for_rs` can't be called on name-less item kinds"); |
| |
| let krate = make_rs_ident(self.krate.as_str()); |
| let mod_path = self.mod_path.format_for_rs(); |
| let name = make_rs_ident(name.as_str()); |
| quote! { :: #krate :: #mod_path #name } |
| } |
| } |
| |
| mod sugared_ty { |
| use super::*; |
| /// A Ty, optionally attached to its `hir::Ty` counterpart, if any. |
| /// |
| /// The rustc_hir::Ty is used only for detecting type aliases (or other |
| /// optional sugar), unrelated to the actual concrete type. It |
| /// necessarily disappears if, for instance, the type is plugged in from |
| /// a generic. There's no way to tell, in the bindings for |
| /// Vec<c_char>::len(), that `T` came from the type alias |
| /// `c_char`, instead of a plain `i8` or `u8`. |
| #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] |
| pub(super) struct SugaredTy<'tcx> { |
| mid: Ty<'tcx>, |
| /// The HirId of the corresponding HirTy. We store it as a HirId so that |
| /// it's hashable. |
| hir_id: Option<HirId>, |
| } |
| |
| impl<'tcx> std::fmt::Display for SugaredTy<'tcx> { |
| fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { |
| std::fmt::Display::fmt(&self.mid, f) |
| } |
| } |
| |
| impl<'tcx> SugaredTy<'tcx> { |
| pub fn new(mid: Ty<'tcx>, hir: Option<&rustc_hir::Ty<'tcx>>) -> Self { |
| Self { mid, hir_id: hir.map(|hir| hir.hir_id) } |
| } |
| |
| /// Returns the rustc_middle::Ty this represents. |
| pub fn mid(&self) -> Ty<'tcx> { |
| self.mid |
| } |
| |
| /// Returns the rustc_hir::Ty this represents, if any. |
| pub fn hir(&self, db: &dyn BindingsGenerator<'tcx>) -> Option<&'tcx rustc_hir::Ty<'tcx>> { |
| let hir_id = self.hir_id?; |
| let hir_ty = db.tcx().hir_node(hir_id).expect_ty(); |
| debug_assert_eq!(hir_ty.hir_id, hir_id); |
| Some(hir_ty) |
| } |
| } |
| } |
| use sugared_ty::SugaredTy; |
| |
| /// Whether functions using `extern "C"` ABI can safely handle values of type |
| /// `ty` (e.g. when passing by value arguments or return values of such type). |
| fn is_c_abi_compatible_by_value(ty: Ty) -> bool { |
| match ty.kind() { |
| // `improper_ctypes_definitions` warning doesn't complain about the following types: |
| ty::TyKind::Bool | |
| ty::TyKind::Float{..} | |
| ty::TyKind::Int{..} | |
| ty::TyKind::Uint{..} | |
| ty::TyKind::Never | |
| ty::TyKind::RawPtr{..} | |
| ty::TyKind::Ref{..} | |
| ty::TyKind::FnPtr{..} => true, |
| ty::TyKind::Tuple(types) if types.len() == 0 => true, |
| |
| // Crubit assumes that `char` is compatible with a certain `extern "C"` ABI. |
| // See `rust_builtin_type_abi_assumptions.md` for more details. |
| ty::TyKind::Char => true, |
| |
| // Crubit's C++ bindings for tuples, structs, and other ADTs may not preserve |
| // their ABI (even if they *do* preserve their memory layout). For example: |
| // - In System V ABI replacing a field with a fixed-length array of bytes may affect |
| // whether the whole struct is classified as an integer and passed in general purpose |
| // registers VS classified as SSE2 and passed in floating-point registers like xmm0). |
| // See also b/270454629. |
| // - To replicate field offsets, Crubit may insert explicit padding fields. These |
| // extra fields may also impact the ABI of the generated bindings. |
| // |
| // TODO(lukasza): In the future, some additional performance gains may be realized by |
| // returning `true` in a few limited cases (this may require additional complexity to |
| // ensure that `format_adt` never injects explicit padding into such structs): |
| // - `#[repr(C)]` structs and unions, |
| // - `#[repr(transparent)]` struct that wraps an ABI-safe type, |
| // - Discriminant-only enums (b/259984090). |
| ty::TyKind::Tuple{..} | // An empty tuple (`()` - the unit type) is handled above. |
| ty::TyKind::Adt{..} => false, |
| |
| // These kinds of reference-related types are not implemented yet - `is_c_abi_compatible_by_value` |
| // should never need to handle them, because `format_ty_for_cc` fails for such types. |
| // |
| // TODO(b/258235219): When implementing support for references we should |
| // consider returning `true` for `TyKind::Ref` and document the rationale |
| // for such decision - maybe something like this will be sufficient: |
| // - In general `TyKind::Ref` should have the same ABI as `TyKind::RawPtr` |
| // - References to slices (`&[T]`) or strings (`&str`) rely on assumptions |
| // spelled out in `rust_builtin_type_abi_assumptions.md`.. |
| ty::TyKind::Str | |
| ty::TyKind::Array{..} | |
| ty::TyKind::Slice{..} => |
| unimplemented!(), |
| |
| // `format_ty_for_cc` is expected to fail for other kinds of types |
| // and therefore `is_c_abi_compatible_by_value` should never be called for |
| // these other types |
| _ => unimplemented!(), |
| } |
| } |
| |
| /// Location where a type is used. |
| #[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] |
| enum TypeLocation { |
| /// The top-level return type. |
| /// |
| /// The "top-level" part can be explained by looking at an example of `fn |
| /// foo() -> *const T`: |
| /// - The top-level return type `*const T` is in the `FnReturn` location |
| /// - The nested pointee type `T` is in the `Other` location |
| FnReturn, |
| |
| /// The top-level parameter type. |
| /// |
| /// The "top-level" part can be explained by looking at an example of: |
| /// `fn foo(param: *const T)`: |
| /// - The top-level parameter type `*const T` is in the `FnParam` location |
| /// - The nested pointee type `T` is in the `Other` location |
| // TODO(b/278141494, b/278141418): Once `const` and `static` items are supported, |
| // we may want to apply parameter-like formatting to their types (e.g. have |
| // `format_ty_for_cc` emit `T&` rather than `T*`). |
| FnParam, |
| |
| /// Other location (e.g. pointee type, field type, etc.). |
| Other, |
| } |
| |
| fn format_pointer_or_reference_ty_for_cc<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| pointee: SugaredTy<'tcx>, |
| mutability: rustc_middle::mir::Mutability, |
| pointer_sigil: TokenStream, |
| ) -> Result<CcSnippet> { |
| let tcx = db.tcx(); |
| let const_qualifier = match mutability { |
| Mutability::Mut => quote! {}, |
| Mutability::Not => quote! { const }, |
| }; |
| if pointee.mid().is_c_void(tcx) { |
| return Ok(CcSnippet { tokens: quote! { #const_qualifier void* }, ..Default::default() }); |
| } |
| let CcSnippet { tokens, mut prereqs } = db.format_ty_for_cc(pointee, TypeLocation::Other)?; |
| prereqs.move_defs_to_fwd_decls(); |
| Ok(CcSnippet { prereqs, tokens: quote! { #tokens #const_qualifier #pointer_sigil } }) |
| } |
| |
| /// Formats `ty` into a `CcSnippet` that represents how the type should be |
| /// spelled in a C++ declaration of a function parameter or field. |
| fn format_ty_for_cc<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| ty: SugaredTy<'tcx>, |
| location: TypeLocation, |
| ) -> Result<CcSnippet> { |
| let tcx = db.tcx(); |
| fn cstdint(tokens: TokenStream) -> CcSnippet { |
| CcSnippet::with_include(tokens, CcInclude::cstdint()) |
| } |
| fn keyword(tokens: TokenStream) -> CcSnippet { |
| CcSnippet::new(tokens) |
| } |
| |
| if let Some(alias) = format_core_alias_for_cc(db, ty) { |
| return Ok(alias); |
| } |
| |
| Ok(match ty.mid().kind() { |
| ty::TyKind::Never => match location { |
| TypeLocation::FnReturn => keyword(quote! { void }), |
| _ => { |
| // TODO(b/254507801): Maybe translate into `crubit::Never`? |
| bail!("The never type `!` is only supported as a return type (b/254507801)"); |
| } |
| }, |
| ty::TyKind::Tuple(types) => { |
| if types.len() == 0 { |
| match location { |
| TypeLocation::FnReturn => keyword(quote! { void }), |
| _ => { |
| // TODO(b/254507801): Maybe translate into `crubit::Unit`? |
| bail!("`()` / `void` is only supported as a return type (b/254507801)"); |
| } |
| } |
| } else { |
| // TODO(b/254099023): Add support for tuples. |
| bail!("Tuples are not supported yet: {} (b/254099023)", ty); |
| } |
| } |
| |
| // https://rust-lang.github.io/unsafe-code-guidelines/layout/scalars.html#bool documents |
| // that "Rust's bool has the same layout as C17's _Bool". The details (e.g. size, valid |
| // bit patterns) are implementation-defined, but this is okay, because `bool` in the |
| // `extern "C"` functions in the generated `..._cc_api.h` will also be the C17's _Bool. |
| ty::TyKind::Bool => keyword(quote! { bool }), |
| |
| // https://rust-lang.github.io/unsafe-code-guidelines/layout/scalars.html#fixed-width-floating-point-types |
| // documents that "When the platforms' "math.h" header defines the __STDC_IEC_559__ macro, |
| // Rust's floating-point types are safe to use directly in C FFI where the appropriate C |
| // types are expected (f32 for float, f64 for double)." |
| // |
| // TODO(b/255768062): Generated bindings should explicitly check `__STDC_IEC_559__` |
| ty::TyKind::Float(ty::FloatTy::F32) => keyword(quote! { float }), |
| ty::TyKind::Float(ty::FloatTy::F64) => keyword(quote! { double }), |
| |
| // ABI compatibility and other details are described in the doc comments in |
| // `crubit/support/rs_std/rs_char.h` and `crubit/support/rs_std/char_test.cc` (search for |
| // "Layout tests"). |
| ty::TyKind::Char => { |
| // Asserting that the target architecture meets the assumption from Crubit's |
| // `rust_builtin_type_abi_assumptions.md` - we assume that Rust's `char` has the |
| // same ABI as `u32`. |
| let layout = tcx |
| .layout_of(ty::ParamEnv::empty().and(ty.mid())) |
| .expect("`layout_of` is expected to succeed for the builtin `char` type") |
| .layout; |
| assert_eq!(4, layout.align().abi.bytes()); |
| assert_eq!(4, layout.size().bytes()); |
| assert!(matches!( |
| layout.abi(), |
| Abi::Scalar(Scalar::Initialized { |
| value: Primitive::Int(Integer::I32, /* signedness = */ false), |
| .. |
| }) |
| )); |
| |
| CcSnippet::with_include( |
| quote! { rs_std::rs_char }, |
| db.support_header("rs_std/rs_char.h"), |
| ) |
| } |
| |
| // https://rust-lang.github.io/unsafe-code-guidelines/layout/scalars.html#isize-and-usize |
| // documents that "Rust's signed and unsigned fixed-width integer types {i,u}{8,16,32,64} |
| // have the same layout the C fixed-width integer types from the <stdint.h> header |
| // {u,}int{8,16,32,64}_t. These fixed-width integer types are therefore safe to use |
| // directly in C FFI where the corresponding C fixed-width integer types are expected. |
| // |
| // https://rust-lang.github.io/unsafe-code-guidelines/layout/scalars.html#layout-compatibility-with-c-native-integer-types |
| // documents that "Rust does not support C platforms on which the C native integer type are |
| // not compatible with any of Rust's fixed-width integer type (e.g. because of |
| // padding-bits, lack of 2's complement, etc.)." |
| ty::TyKind::Int(ty::IntTy::I8) => cstdint(quote! { std::int8_t }), |
| ty::TyKind::Int(ty::IntTy::I16) => cstdint(quote! { std::int16_t }), |
| ty::TyKind::Int(ty::IntTy::I32) => cstdint(quote! { std::int32_t }), |
| ty::TyKind::Int(ty::IntTy::I64) => cstdint(quote! { std::int64_t }), |
| ty::TyKind::Uint(ty::UintTy::U8) => cstdint(quote! { std::uint8_t }), |
| ty::TyKind::Uint(ty::UintTy::U16) => cstdint(quote! { std::uint16_t }), |
| ty::TyKind::Uint(ty::UintTy::U32) => cstdint(quote! { std::uint32_t }), |
| ty::TyKind::Uint(ty::UintTy::U64) => cstdint(quote! { std::uint64_t }), |
| |
| // https://rust-lang.github.io/unsafe-code-guidelines/layout/scalars.html#isize-and-usize |
| // documents that "The isize and usize types are [...] layout compatible with C's uintptr_t |
| // and intptr_t types.". |
| ty::TyKind::Int(ty::IntTy::Isize) => cstdint(quote! { std::intptr_t }), |
| ty::TyKind::Uint(ty::UintTy::Usize) => cstdint(quote! { std::uintptr_t }), |
| |
| ty::TyKind::Int(ty::IntTy::I128) | ty::TyKind::Uint(ty::UintTy::U128) => { |
| // Note that "the alignment of Rust's {i,u}128 is unspecified and allowed to |
| // change" according to |
| // https://rust-lang.github.io/unsafe-code-guidelines/layout/scalars.html#fixed-width-integer-types |
| // |
| // TODO(b/254094650): Consider mapping this to Clang's (and GCC's) `__int128` |
| // or to `absl::in128`. |
| bail!("C++ doesn't have a standard equivalent of `{ty}` (b/254094650)"); |
| } |
| |
| ty::TyKind::Adt(adt, substs) => { |
| ensure!(substs.len() == 0, "Generic types are not supported yet (b/259749095)"); |
| ensure!( |
| is_directly_public(tcx, adt.did()), |
| "Not directly public type (re-exports are not supported yet - b/262052635)" |
| ); |
| |
| let def_id = adt.did(); |
| let mut prereqs = CcPrerequisites::default(); |
| if def_id.krate == LOCAL_CRATE { |
| prereqs.defs.insert(def_id.expect_local()); |
| } else { |
| let other_crate_name = tcx.crate_name(def_id.krate); |
| let crate_name_to_include_paths = db.crate_name_to_include_paths(); |
| let includes = crate_name_to_include_paths |
| .get(other_crate_name.as_str()) |
| .ok_or_else(|| { |
| anyhow!( |
| "Type `{ty}` comes from the `{other_crate_name}` crate, \ |
| but no `--bindings-from-dependency` was specified for this crate" |
| ) |
| })?; |
| prereqs.includes.extend(includes.iter().cloned()); |
| } |
| |
| // Verify if definition of `ty` can be succesfully imported and bail otherwise. |
| db.format_adt_core(def_id).with_context(|| { |
| format!("Failed to generate bindings for the definition of `{ty}`") |
| })?; |
| |
| CcSnippet { tokens: FullyQualifiedName::new(tcx, def_id).format_for_cc()?, prereqs } |
| } |
| |
| ty::TyKind::RawPtr(pointee_mid, mutbl) => { |
| let mut pointee_hir = None; |
| if let Some(hir) = ty.hir(db) { |
| if let rustc_hir::TyKind::Ptr(mut_p) = hir.kind { |
| pointee_hir = Some(mut_p.ty); |
| } |
| } |
| let pointee = SugaredTy::new(*pointee_mid, pointee_hir); |
| format_pointer_or_reference_ty_for_cc(db, pointee, *mutbl, quote! { * }).with_context( |
| || format!("Failed to format the pointee of the pointer type `{ty}`"), |
| )? |
| } |
| |
| ty::TyKind::Ref(region, referent_mid, mutability) => { |
| let mut referent_hir = None; |
| if let Some(hir) = ty.hir(db) { |
| if let rustc_hir::TyKind::Ref(_, mut_p, ..) = &hir.kind { |
| referent_hir = Some(mut_p.ty); |
| } |
| } |
| let referent = SugaredTy::new(*referent_mid, referent_hir); |
| match location { |
| TypeLocation::FnReturn | TypeLocation::FnParam => (), |
| TypeLocation::Other => bail!( |
| "Can't format `{ty}`, because references are only supported in \ |
| function parameter types and return types (b/286256327)", |
| ), |
| }; |
| let lifetime = format_region_as_cc_lifetime(region); |
| format_pointer_or_reference_ty_for_cc(db, referent, *mutability, quote! { & #lifetime }) |
| .with_context(|| { |
| format!("Failed to format the referent of the reference type `{ty}`") |
| })? |
| } |
| |
| ty::TyKind::FnPtr(sig) => { |
| let sig = match sig.no_bound_vars() { |
| None => bail!("Generic functions are not supported yet (b/259749023)"), |
| Some(sig) => sig, |
| }; |
| check_fn_sig(&sig)?; |
| is_thunk_required(&sig).context("Function pointers can't have a thunk")?; |
| |
| // `is_thunk_required` check above implies `extern "C"` (or `"C-unwind"`). |
| // This assertion reinforces that the generated C++ code doesn't need |
| // to use calling convention attributes like `_stdcall`, etc. |
| assert!(matches!(sig.abi, rustc_target::spec::abi::Abi::C { .. })); |
| |
| // C++ references are not rebindable and therefore can't be used to replicate |
| // semantics of Rust field types (or, say, element types of Rust |
| // arrays). Because of this, C++ references are only used for |
| // top-level return types and parameter types (and pointers are used |
| // in other locations). |
| let ptr_or_ref_sigil = match location { |
| TypeLocation::FnReturn | TypeLocation::FnParam => quote! { & }, |
| TypeLocation::Other => quote! { * }, |
| }; |
| |
| let mut prereqs = CcPrerequisites::default(); |
| prereqs.includes.insert(db.support_header("internal/cxx20_backports.h")); |
| |
| let mut sig_hir = None; |
| if let Some(hir) = ty.hir(db) { |
| if let rustc_hir::TyKind::BareFn(bare_fn) = &hir.kind { |
| sig_hir = Some(bare_fn.decl); |
| } |
| } |
| let ret_type = format_ret_ty_for_cc(db, &sig, sig_hir)?.into_tokens(&mut prereqs); |
| let param_types = format_param_types_for_cc(db, &sig, sig_hir)? |
| .into_iter() |
| .map(|snippet| snippet.into_tokens(&mut prereqs)); |
| let tokens = quote! { |
| crubit::type_identity_t< |
| #ret_type( #( #param_types ),* ) |
| > #ptr_or_ref_sigil |
| }; |
| |
| CcSnippet { tokens, prereqs } |
| } |
| |
| // TODO(b/260268230, b/260729464): When recursively processing nested types (e.g. an |
| // element type of an Array, a referent of a Ref, a parameter type of an FnPtr, etc), one |
| // should also 1) propagate `CcPrerequisites::defs`, 2) cover `CcPrerequisites::defs` in |
| // `test_format_ty_for_cc...`. For ptr/ref it might be possible to use |
| // `CcPrerequisites::move_defs_to_fwd_decls`. |
| _ => bail!("The following Rust type is not supported yet: {ty}"), |
| }) |
| } |
| |
| /// Returns `Some(CcSnippet)` if `ty` is a special-cased alias type from |
| /// `core::ffi` (AKA `std::ffi`). |
| /// |
| /// TODO(b/283258442): Also handle `libc` aliases. |
| fn format_core_alias_for_cc<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| ty: SugaredTy<'tcx>, |
| ) -> Option<CcSnippet> { |
| let tcx = db.tcx(); |
| let hir_ty = ty.hir(db)?; |
| let rustc_hir::TyKind::Path(rustc_hir::QPath::Resolved(None, path)) = &hir_ty.kind else { |
| return None; |
| }; |
| let rustc_hir::def::Res::Def(rustc_hir::def::DefKind::TyAlias, alias_def_id) = &path.res else { |
| return None; |
| }; |
| let def_path = tcx.def_path(*alias_def_id); |
| |
| // Note: the `std::ffi` aliases are still originally defined in `core::ffi`, so |
| // we only need to check for a crate name of `core` here. |
| if tcx.crate_name(def_path.krate) != sym::core { |
| return None; |
| }; |
| let [module, item] = def_path.data.as_slice() else { |
| return None; |
| }; |
| if module.data != rustc_hir::definitions::DefPathData::TypeNs(sym::ffi) { |
| return None; |
| }; |
| let rustc_hir::definitions::DefPathData::TypeNs(item) = item.data else { |
| return None; |
| }; |
| let cpp_type = match item.as_str() { |
| "c_char" => quote! { char}, |
| "c_schar" => quote! { signed char}, |
| "c_uchar" => quote! { unsigned char}, |
| "c_short" => quote! { short}, |
| "c_ushort" => quote! { unsigned short}, |
| "c_int" => quote! { int}, |
| "c_uint" => quote! { unsigned int}, |
| "c_long" => quote! { long}, |
| "c_ulong" => quote! { unsigned long}, |
| "c_longlong" => quote! { long long}, |
| "c_ulonglong" => quote! { unsigned long long}, |
| _ => return None, |
| }; |
| Some(CcSnippet::new(cpp_type)) |
| } |
| |
| /// Returns the C++ return type. |
| /// |
| /// `sig_hir` is the optional HIR `FnDecl`, if available. This is used to |
| /// retrieve alias information. |
| fn format_ret_ty_for_cc<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| sig_mid: &ty::FnSig<'tcx>, |
| sig_hir: Option<&rustc_hir::FnDecl<'tcx>>, |
| ) -> Result<CcSnippet> { |
| let hir = sig_hir.and_then(|sig_hir| match sig_hir.output { |
| rustc_hir::FnRetTy::Return(hir_ty) => Some(hir_ty), |
| _ => None, |
| }); |
| db.format_ty_for_cc(SugaredTy::new(sig_mid.output(), hir), TypeLocation::FnReturn) |
| .context("Error formatting function return type") |
| } |
| |
| /// Returns the C++ parameter types. |
| /// |
| /// `sig_hir` is the optional HIR FnSig, if available. This is used to retrieve |
| /// alias information. |
| fn format_param_types_for_cc<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| sig_mid: &ty::FnSig<'tcx>, |
| sig_hir: Option<&rustc_hir::FnDecl<'tcx>>, |
| ) -> Result<Vec<CcSnippet>> { |
| if let Some(sig_hir) = sig_hir { |
| assert_eq!( |
| sig_mid.inputs().len(), |
| sig_hir.inputs.len(), |
| "internal error: MIR and HIR function signatures do not line up" |
| ); |
| } |
| sig_mid |
| .inputs() |
| .iter() |
| .enumerate() |
| .map(|(i, &mid)| { |
| let hir = sig_hir.map(|sig_hir| &sig_hir.inputs[i]); |
| db.format_ty_for_cc(SugaredTy::new(mid, hir), TypeLocation::FnParam) |
| .with_context(|| format!("Error handling parameter #{i}")) |
| }) |
| .collect() |
| } |
| |
| /// Formats `ty` for Rust - to be used in `..._cc_api_impl.rs` (e.g. as a type |
| /// of a parameter in a Rust thunk). Because `..._cc_api_impl.rs` is a |
| /// distinct, separate crate, the returned `TokenStream` uses crate-qualified |
| /// names whenever necessary - for example: `target_crate::SomeStruct` rather |
| /// than just `SomeStruct`. |
| // |
| // TODO(b/259724276): This function's results should be memoized. |
| fn format_ty_for_rs(tcx: TyCtxt, ty: Ty) -> Result<TokenStream> { |
| Ok(match ty.kind() { |
| ty::TyKind::Bool |
| | ty::TyKind::Float(_) |
| | ty::TyKind::Char |
| | ty::TyKind::Int(_) |
| | ty::TyKind::Uint(_) |
| | ty::TyKind::FnPtr(_) |
| | ty::TyKind::Never => ty |
| .to_string() |
| .parse() |
| .expect("rustc_middle::ty::Ty::to_string() should produce no parsing errors"), |
| ty::TyKind::Tuple(types) => { |
| if types.len() == 0 { |
| quote! { () } |
| } else { |
| // TODO(b/254099023): Add support for tuples. |
| bail!("Tuples are not supported yet: {} (b/254099023)", ty); |
| } |
| } |
| ty::TyKind::Adt(adt, substs) => { |
| ensure!(substs.len() == 0, "Generic types are not supported yet (b/259749095)"); |
| FullyQualifiedName::new(tcx, adt.did()).format_for_rs() |
| } |
| ty::TyKind::RawPtr(pointee_ty, mutbl) => { |
| let qualifier = match mutbl { |
| Mutability::Mut => quote! { mut }, |
| Mutability::Not => quote! { const }, |
| }; |
| let ty = format_ty_for_rs(tcx, *pointee_ty).with_context(|| { |
| format!("Failed to format the pointee of the pointer type `{ty}`") |
| })?; |
| quote! { * #qualifier #ty } |
| } |
| ty::TyKind::Ref(region, referent_ty, mutability) => { |
| let mutability = match mutability { |
| Mutability::Mut => quote! { mut }, |
| Mutability::Not => quote! {}, |
| }; |
| let ty = format_ty_for_rs(tcx, *referent_ty).with_context(|| { |
| format!("Failed to format the referent of the reference type `{ty}`") |
| })?; |
| let lifetime = format_region_as_rs_lifetime(region); |
| quote! { & #lifetime #mutability #ty } |
| } |
| _ => bail!("The following Rust type is not supported yet: {ty}"), |
| }) |
| } |
| |
| fn format_region_as_cc_lifetime(region: &ty::Region) -> TokenStream { |
| let name = |
| region.get_name().expect("Caller should use `liberate_and_deanonymize_late_bound_regions`"); |
| let name = name |
| .as_str() |
| .strip_prefix('\'') |
| .expect("All Rust lifetimes are expected to begin with the \"'\" character"); |
| |
| // TODO(b/286299326): Use `$a` or `$(foo)` or `$static` syntax below. |
| quote! { [[clang::annotate_type("lifetime", #name)]] } |
| } |
| |
| fn format_region_as_rs_lifetime(region: &ty::Region) -> TokenStream { |
| let name = |
| region.get_name().expect("Caller should use `liberate_and_deanonymize_late_bound_regions`"); |
| let lifetime = syn::Lifetime::new(name.as_str(), proc_macro2::Span::call_site()); |
| quote! { #lifetime } |
| } |
| |
| #[derive(Clone, Debug, Default)] |
| struct ApiSnippets { |
| /// Main API - for example: |
| /// - A C++ declaration of a function (with a doc comment), |
| /// - A C++ definition of a struct (with a doc comment). |
| main_api: CcSnippet, |
| |
| /// C++ implementation details - for example: |
| /// - A C++ declaration of an `extern "C"` thunk, |
| /// - C++ `static_assert`s about struct size, aligment, and field offsets. |
| cc_details: CcSnippet, |
| |
| /// Rust implementation details - for exmaple: |
| /// - A Rust implementation of an `extern "C"` thunk, |
| /// - Rust `assert!`s about struct size, aligment, and field offsets. |
| rs_details: TokenStream, |
| } |
| |
| impl FromIterator<ApiSnippets> for ApiSnippets { |
| fn from_iter<I: IntoIterator<Item = ApiSnippets>>(iter: I) -> Self { |
| let mut result = ApiSnippets::default(); |
| for ApiSnippets { main_api, cc_details, rs_details } in iter.into_iter() { |
| result.main_api += main_api; |
| result.cc_details += cc_details; |
| result.rs_details.extend(rs_details); |
| } |
| result |
| } |
| } |
| |
| /// Similar to `TyCtxt::liberate_and_name_late_bound_regions` but also replaces |
| /// anonymous regions with new names. |
| fn liberate_and_deanonymize_late_bound_regions<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| sig: ty::PolyFnSig<'tcx>, |
| fn_def_id: DefId, |
| ) -> ty::FnSig<'tcx> { |
| let mut anon_count: u32 = 0; |
| let mut translated_kinds: HashMap<ty::BoundVar, ty::BoundRegionKind> = HashMap::new(); |
| let region_f = |br: ty::BoundRegion| { |
| let new_kind: &ty::BoundRegionKind = translated_kinds.entry(br.var).or_insert_with(|| { |
| let name = br.kind.get_name().unwrap_or_else(|| { |
| anon_count += 1; |
| Symbol::intern(&format!("'__anon{anon_count}")) |
| }); |
| let id = br.kind.get_id().unwrap_or(fn_def_id); |
| ty::BoundRegionKind::BrNamed(id, name) |
| }); |
| ty::Region::new_late_param(tcx, fn_def_id, *new_kind) |
| }; |
| tcx.instantiate_bound_regions_uncached(sig, region_f) |
| } |
| |
| /// Returns the rustc_middle and rustc_hir function signatures. |
| /// |
| /// In the case of rustc_hir, this returns the `FnDecl`, not the |
| /// `rustc_hir::FnSig`, because the `FnDecl` type is used for both function |
| /// pointers and actual functions. This makes it a more useful vocabulary type. |
| /// `FnDecl` does drop information, but that information is already on the |
| /// rustc_middle `FnSig`, so there is no loss. |
| fn get_fn_sig(tcx: TyCtxt, local_def_id: LocalDefId) -> (ty::FnSig, &rustc_hir::FnDecl) { |
| let def_id = local_def_id.to_def_id(); |
| let sig_mid = liberate_and_deanonymize_late_bound_regions( |
| tcx, |
| tcx.fn_sig(def_id).instantiate_identity(), |
| def_id, |
| ); |
| let sig_hir = tcx.hir_node_by_def_id(local_def_id).fn_sig().unwrap(); |
| (sig_mid, sig_hir.decl) |
| } |
| |
| /// Formats a C++ function declaration of a thunk that wraps a Rust function |
| /// identified by `fn_def_id`. `format_thunk_impl` may panic if `fn_def_id` |
| /// doesn't identify a function. |
| fn format_thunk_decl<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| fn_def_id: DefId, |
| sig_mid: &ty::FnSig<'tcx>, |
| sig_hir: Option<&rustc_hir::FnDecl<'tcx>>, |
| thunk_name: &TokenStream, |
| ) -> Result<CcSnippet> { |
| let tcx = db.tcx(); |
| |
| let mut prereqs = CcPrerequisites::default(); |
| let main_api_ret_type = format_ret_ty_for_cc(db, sig_mid, sig_hir)?.into_tokens(&mut prereqs); |
| |
| let mut thunk_params = { |
| let cpp_types = format_param_types_for_cc(db, sig_mid, sig_hir)?; |
| sig_mid |
| .inputs() |
| .iter() |
| .zip(cpp_types.into_iter()) |
| .map(|(&ty, cpp_type)| -> Result<TokenStream> { |
| let cpp_type = cpp_type.into_tokens(&mut prereqs); |
| if is_c_abi_compatible_by_value(ty) { |
| Ok(quote! { #cpp_type }) |
| } else { |
| // Rust thunk will move a value via memcpy - we need to `ensure` that |
| // invoking the C++ destructor (on the moved-away value) is safe. |
| ensure!( |
| !ty.needs_drop(tcx, tcx.param_env(fn_def_id)), |
| "Only trivially-movable and trivially-destructible types \ |
| may be passed by value over the FFI boundary" |
| ); |
| Ok(quote! { #cpp_type* }) |
| } |
| }) |
| .collect::<Result<Vec<_>>>()? |
| }; |
| |
| let thunk_ret_type: TokenStream; |
| if is_c_abi_compatible_by_value(sig_mid.output()) { |
| thunk_ret_type = main_api_ret_type; |
| } else { |
| thunk_ret_type = quote! { void }; |
| thunk_params.push(quote! { #main_api_ret_type* __ret_ptr }); |
| }; |
| Ok(CcSnippet { |
| prereqs, |
| tokens: quote! { |
| namespace __crubit_internal { |
| extern "C" #thunk_ret_type #thunk_name ( #( #thunk_params ),* ); |
| } |
| }, |
| }) |
| } |
| |
| /// Formats a thunk implementation in Rust that provides an `extern "C"` ABI for |
| /// calling a Rust function identified by `fn_def_id`. `format_thunk_impl` may |
| /// panic if `fn_def_id` doesn't identify a function. |
| /// |
| /// `fully_qualified_fn_name` specifies how the thunk can identify the function |
| /// to call. Examples of valid arguments: |
| /// - `::crate_name::some_module::free_function` |
| /// - `::crate_name::some_module::SomeStruct::method` |
| /// - `<::crate_name::some_module::SomeStruct as |
| /// ::core::default::Default>::default` |
| fn format_thunk_impl<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| fn_def_id: DefId, |
| sig: &ty::FnSig<'tcx>, |
| thunk_name: &str, |
| fully_qualified_fn_name: TokenStream, |
| ) -> Result<TokenStream> { |
| let param_names_and_types: Vec<(Ident, Ty)> = { |
| let param_names = tcx.fn_arg_names(fn_def_id).iter().enumerate().map(|(i, ident)| { |
| if ident.as_str().is_empty() { |
| format_ident!("__param_{i}") |
| } else if ident.name == kw::SelfLower { |
| format_ident!("__self") |
| } else { |
| make_rs_ident(ident.as_str()) |
| } |
| }); |
| let param_types = sig.inputs().iter().copied(); |
| param_names.zip(param_types).collect_vec() |
| }; |
| |
| let mut thunk_params = param_names_and_types |
| .iter() |
| .map(|(param_name, ty)| { |
| let rs_type = format_ty_for_rs(tcx, *ty) |
| .with_context(|| format!("Error handling parameter `{param_name}`"))?; |
| Ok(if is_c_abi_compatible_by_value(*ty) { |
| quote! { #param_name: #rs_type } |
| } else { |
| quote! { #param_name: &mut ::core::mem::MaybeUninit<#rs_type> } |
| }) |
| }) |
| .collect::<Result<Vec<_>>>()?; |
| |
| let mut thunk_ret_type = format_ty_for_rs(tcx, sig.output())?; |
| let mut thunk_body = { |
| let fn_args = param_names_and_types.iter().map(|(rs_name, ty)| { |
| if is_c_abi_compatible_by_value(*ty) { |
| quote! { #rs_name } |
| } else if let Safety::Unsafe = sig.safety { |
| // The whole call will be wrapped in `unsafe` below. |
| quote! { #rs_name.assume_init_read() } |
| } else { |
| quote! { unsafe { #rs_name.assume_init_read() } } |
| } |
| }); |
| quote! { |
| #fully_qualified_fn_name( #( #fn_args ),* ) |
| } |
| }; |
| // Wrap the call in an unsafe block, for the sake of RFC #2585 |
| // `unsafe_block_in_unsafe_fn`. |
| if let Safety::Unsafe = sig.safety { |
| thunk_body = quote! {unsafe {#thunk_body}}; |
| } |
| if !is_c_abi_compatible_by_value(sig.output()) { |
| thunk_params.push(quote! { |
| __ret_slot: &mut ::core::mem::MaybeUninit<#thunk_ret_type> |
| }); |
| thunk_ret_type = quote! { () }; |
| thunk_body = quote! { __ret_slot.write(#thunk_body); }; |
| }; |
| |
| let generic_params = { |
| let regions = sig |
| .inputs() |
| .iter() |
| .copied() |
| .chain(std::iter::once(sig.output())) |
| .flat_map(|ty| { |
| ty.walk().filter_map(|generic_arg| match generic_arg.unpack() { |
| ty::GenericArgKind::Const(_) | ty::GenericArgKind::Type(_) => None, |
| ty::GenericArgKind::Lifetime(region) => Some(region), |
| }) |
| }) |
| .filter(|region| match region.kind() { |
| RegionKind::ReStatic => false, |
| RegionKind::ReLateParam(_) => true, |
| _ => panic!("Unexpected region kind: {region}"), |
| }) |
| .sorted_by_key(|region| { |
| region |
| .get_name() |
| .expect("Caller should use `liberate_and_deanonymize_late_bound_regions`") |
| }) |
| .dedup() |
| .collect_vec(); |
| if regions.is_empty() { |
| quote! {} |
| } else { |
| let lifetimes = regions.into_iter().map(|region| format_region_as_rs_lifetime(®ion)); |
| quote! { < #( #lifetimes ),* > } |
| } |
| }; |
| |
| let thunk_name = make_rs_ident(thunk_name); |
| let unsafe_qualifier = if let Safety::Unsafe = sig.safety { |
| quote! {unsafe} |
| } else { |
| quote! {} |
| }; |
| Ok(quote! { |
| #[no_mangle] |
| #unsafe_qualifier extern "C" fn #thunk_name #generic_params ( |
| #( #thunk_params ),* |
| ) -> #thunk_ret_type { |
| #thunk_body |
| } |
| }) |
| } |
| |
| fn check_fn_sig(sig: &ty::FnSig) -> Result<()> { |
| if sig.c_variadic { |
| // TODO(b/254097223): Add support for variadic functions. |
| bail!("C variadic functions are not supported (b/254097223)"); |
| } |
| |
| Ok(()) |
| } |
| |
| /// Returns `Ok(())` if no thunk is required. |
| /// Otherwise returns an error the describes why the thunk is needed. |
| fn is_thunk_required(sig: &ty::FnSig) -> Result<()> { |
| match sig.abi { |
| // "C" ABI is okay: Before https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html a |
| // Rust panic that "escapes" a "C" ABI function leads to Undefined Behavior. This is |
| // unfortunate, but Crubit's `panics_and_exceptions.md` documents that `-Cpanic=abort` |
| // is the only supported configuration. |
| // |
| // After https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html a Rust panic that |
| // tries to "escape" a "C" ABI function will terminate the program. This is okay. |
| rustc_target::spec::abi::Abi::C { unwind: false } => (), |
| |
| // "C-unwind" ABI is okay: After |
| // https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html a new "C-unwind" ABI may be |
| // used by Rust functions that want to safely propagate Rust panics through frames that |
| // may belong to another language. |
| rustc_target::spec::abi::Abi::C { unwind: true } => (), |
| |
| // All other ABIs trigger thunk generation. This covers Rust ABI functions, but also |
| // ABIs that theoretically are understood both by C++ and Rust (e.g. see |
| // `format_cc_call_conv_as_clang_attribute` in `rs_bindings_from_cc/src_code_gen.rs`). |
| _ => bail!("Calling convention other than `extern \"C\"` requires a thunk"), |
| }; |
| |
| ensure!(is_c_abi_compatible_by_value(sig.output()), "Return type requires a thunk"); |
| for (i, param_ty) in sig.inputs().iter().enumerate() { |
| ensure!(is_c_abi_compatible_by_value(*param_ty), "Type of parameter #{i} requires a thunk"); |
| } |
| |
| Ok(()) |
| } |
| |
| #[derive(Debug, Eq, PartialEq)] |
| enum FunctionKind { |
| /// Free function (i.e. not a method). |
| Free, |
| |
| /// Static method (i.e. the first parameter is not named `self`). |
| StaticMethod, |
| |
| /// Instance method taking `self` by value (i.e. `self: Self`). |
| MethodTakingSelfByValue, |
| |
| /// Instance method taking `self` by reference (i.e. `&self` or `&mut |
| /// self`). |
| MethodTakingSelfByRef, |
| } |
| |
| impl FunctionKind { |
| fn has_self_param(&self) -> bool { |
| match self { |
| FunctionKind::MethodTakingSelfByValue | FunctionKind::MethodTakingSelfByRef => true, |
| FunctionKind::Free | FunctionKind::StaticMethod => false, |
| } |
| } |
| } |
| |
| /// Returns the C++ deprecated tag for the item identified by `def_id`, if it is |
| /// deprecated. Otherwise, returns None. |
| fn format_deprecated_tag(tcx: TyCtxt, def_id: DefId) -> Option<TokenStream> { |
| if let Some(deprecated_attr) = tcx.get_attr(def_id, rustc_span::symbol::sym::deprecated) { |
| if let Some((deprecation, _span)) = |
| find_deprecation(tcx.sess(), tcx.features(), slice::from_ref(deprecated_attr)) |
| { |
| let cc_deprecated_tag = match deprecation.note { |
| None => quote! {[[deprecated]]}, |
| Some(note_symbol) => { |
| let note = note_symbol.as_str(); |
| quote! {[[deprecated(#note)]]} |
| } |
| }; |
| return Some(cc_deprecated_tag); |
| } |
| } |
| None |
| } |
| |
| fn format_use( |
| db: &dyn BindingsGenerator<'_>, |
| using_name: &str, |
| use_path: &UsePath, |
| use_kind: &UseKind, |
| ) -> Result<ApiSnippets> { |
| let tcx = db.tcx(); |
| |
| // TODO(b/350772554): Support multiple items with the same name in `use` |
| // statements.` |
| if use_path.res.len() != 1 { |
| bail!( |
| "use statements which resolve to multiple items with the same name are not supported yet" |
| ); |
| } |
| |
| match use_kind { |
| UseKind::Single => {} |
| // TODO(b/350772554): Implement `pub use foo::{x,y}` and `pub use foo::*` |
| UseKind::Glob | UseKind::ListStem => { |
| bail!("Unsupported use kind: {use_kind:?}"); |
| } |
| }; |
| let (def_kind, def_id) = match use_path.res[0] { |
| // TODO(b/350772554): Support PrimTy. |
| Res::Def(def_kind, def_id) => (def_kind, def_id), |
| _ => { |
| bail!( |
| "Unsupported use statement that refers to this type of the entity: {:#?}", |
| use_path.res[0] |
| ); |
| } |
| }; |
| ensure!( |
| is_directly_public(tcx, def_id), |
| "Not directly public type (re-exports are not supported yet - b/262052635)" |
| ); |
| |
| match def_kind { |
| DefKind::Fn => { |
| let mut prereqs; |
| // TODO(b/350772554): Support exporting private functions. |
| if let Some(local_id) = def_id.as_local() { |
| if let Ok(snippet) = db.format_fn(local_id) { |
| prereqs = snippet.main_api.prereqs; |
| } else { |
| bail!("Ignoring the use because the bindings for the target is not generated"); |
| } |
| } else { |
| bail!("Unsupported checking for external function"); |
| } |
| let fully_qualified_fn_name = FullyQualifiedName::new(tcx, def_id); |
| let unqualified_rust_fn_name = |
| fully_qualified_fn_name.name.expect("Functions are assumed to always have a name"); |
| let formatted_fully_qualified_fn_name = fully_qualified_fn_name.format_for_cc()?; |
| let cpp_name = crubit_attr::get(tcx, def_id).unwrap().cpp_name; |
| let main_api_fn_name = |
| format_cc_ident(cpp_name.unwrap_or(unqualified_rust_fn_name).as_str()) |
| .context("Error formatting function name")?; |
| let using_name = format_cc_ident(using_name).context("Error formatting using name")?; |
| |
| prereqs.defs.insert(def_id.expect_local()); |
| let tokens = if format!("{}", using_name) == format!("{}", main_api_fn_name) { |
| quote! {using #formatted_fully_qualified_fn_name;} |
| } else { |
| // TODO(b/350772554): Support function alias. |
| bail!("Unsupported function alias"); |
| }; |
| Ok(ApiSnippets { |
| main_api: CcSnippet { prereqs, tokens }, |
| cc_details: CcSnippet::default(), |
| rs_details: quote! {}, |
| }) |
| } |
| DefKind::Struct | DefKind::Enum => { |
| // This points directly to a type definition, not an alias or compound data |
| // type, so we can drop the hir type. |
| let use_type = SugaredTy::new(tcx.type_of(def_id).instantiate_identity(), None); |
| create_type_alias(db, def_id, using_name, use_type) |
| } |
| _ => bail!( |
| "Unsupported use statement that refers to this type of the entity: {:#?}", |
| use_path.res |
| ), |
| } |
| } |
| |
| fn format_type_alias( |
| db: &dyn BindingsGenerator<'_>, |
| local_def_id: LocalDefId, |
| ) -> Result<ApiSnippets> { |
| let tcx = db.tcx(); |
| let def_id: DefId = local_def_id.to_def_id(); |
| let Item { kind: ItemKind::TyAlias(hir_ty, ..), .. } = tcx.hir().expect_item(local_def_id) |
| else { |
| panic!("called format_type_alias on a non-type-alias"); |
| }; |
| let alias_type = SugaredTy::new(tcx.type_of(def_id).instantiate_identity(), Some(*hir_ty)); |
| create_type_alias(db, def_id, tcx.item_name(def_id).as_str(), alias_type) |
| } |
| |
| fn create_type_alias<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| def_id: DefId, |
| alias_name: &str, |
| alias_type: SugaredTy<'tcx>, |
| ) -> Result<ApiSnippets> { |
| let cc_bindings = format_ty_for_cc(db, alias_type, TypeLocation::Other)?; |
| let mut main_api_prereqs = CcPrerequisites::default(); |
| let actual_type_name = cc_bindings.into_tokens(&mut main_api_prereqs); |
| |
| let alias_name = format_cc_ident(alias_name).context("Error formatting type alias name")?; |
| |
| let mut attributes = vec![]; |
| if let Some(cc_deprecated_tag) = format_deprecated_tag(db.tcx(), def_id) { |
| attributes.push(cc_deprecated_tag); |
| } |
| |
| let tokens = quote! {using #alias_name #(#attributes)* = #actual_type_name;}; |
| |
| Ok(ApiSnippets { |
| main_api: CcSnippet { prereqs: main_api_prereqs, tokens }, |
| cc_details: CcSnippet::default(), |
| rs_details: quote! {}, |
| }) |
| } |
| |
| /// Formats a function with the given `local_def_id`. |
| /// |
| /// Will panic if `local_def_id` |
| /// - is invalid |
| /// - doesn't identify a function, |
| fn format_fn(db: &dyn BindingsGenerator<'_>, local_def_id: LocalDefId) -> Result<ApiSnippets> { |
| let tcx = db.tcx(); |
| let def_id: DefId = local_def_id.to_def_id(); // Convert LocalDefId to DefId. |
| |
| ensure!( |
| tcx.generics_of(def_id).count() == 0, |
| "Generic functions are not supported yet (b/259749023)" |
| ); |
| |
| let (sig_mid, sig_hir) = get_fn_sig(tcx, local_def_id); |
| check_fn_sig(&sig_mid)?; |
| // TODO(b/262904507): Don't require thunks for mangled extern "C" functions. |
| let needs_thunk = is_thunk_required(&sig_mid).is_err() |
| || (tcx.get_attr(def_id, rustc_span::symbol::sym::no_mangle).is_none() |
| && tcx.get_attr(def_id, rustc_span::symbol::sym::export_name).is_none()); |
| let thunk_name = { |
| let symbol_name = { |
| // Call to `mono` is ok - `generics_of` have been checked above. |
| let instance = ty::Instance::mono(tcx, def_id); |
| tcx.symbol_name(instance).name |
| }; |
| if needs_thunk { |
| format!("__crubit_thunk_{}", &escape_non_identifier_chars(symbol_name)) |
| } else { |
| symbol_name.to_string() |
| } |
| }; |
| |
| let fully_qualified_fn_name = FullyQualifiedName::new(tcx, def_id); |
| let unqualified_rust_fn_name = |
| fully_qualified_fn_name.name.expect("Functions are assumed to always have a name"); |
| let attribute = crubit_attr::get(tcx, def_id).unwrap(); |
| let cpp_name = attribute.cpp_name; |
| // The generated C++ function name. |
| let main_api_fn_name = format_cc_ident(cpp_name.unwrap_or(unqualified_rust_fn_name).as_str()) |
| .context("Error formatting function name")?; |
| |
| let mut main_api_prereqs = CcPrerequisites::default(); |
| let main_api_ret_type = |
| format_ret_ty_for_cc(db, &sig_mid, Some(sig_hir))?.into_tokens(&mut main_api_prereqs); |
| |
| struct Param<'tcx> { |
| cc_name: TokenStream, |
| cpp_type: TokenStream, |
| ty: Ty<'tcx>, |
| } |
| let params = { |
| let names = tcx.fn_arg_names(def_id).iter(); |
| let cpp_types = format_param_types_for_cc(db, &sig_mid, Some(sig_hir))?; |
| names |
| .enumerate() |
| .zip(sig_mid.inputs().iter()) |
| .zip(cpp_types) |
| .map(|(((i, name), &ty), cpp_type)| { |
| let cc_name = format_cc_ident(name.as_str()) |
| .unwrap_or_else(|_err| format_cc_ident(&format!("__param_{i}")).unwrap()); |
| let cpp_type = cpp_type.into_tokens(&mut main_api_prereqs); |
| Param { cc_name, cpp_type, ty } |
| }) |
| .collect_vec() |
| }; |
| |
| let self_ty: Option<Ty> = match tcx.impl_of_method(def_id) { |
| Some(impl_id) => match tcx.impl_subject(impl_id).instantiate_identity() { |
| ty::ImplSubject::Inherent(ty) => Some(ty), |
| ty::ImplSubject::Trait(_) => panic!("Trait methods should be filtered by caller"), |
| }, |
| None => None, |
| }; |
| |
| let method_kind = match tcx.hir_node_by_def_id(local_def_id) { |
| Node::Item(_) => FunctionKind::Free, |
| Node::ImplItem(_) => match tcx.fn_arg_names(def_id).first() { |
| Some(arg_name) if arg_name.name == kw::SelfLower => { |
| let self_ty = self_ty.expect("ImplItem => non-None `self_ty`"); |
| if params[0].ty == self_ty { |
| FunctionKind::MethodTakingSelfByValue |
| } else { |
| match params[0].ty.kind() { |
| ty::TyKind::Ref(_, referent_ty, _) if *referent_ty == self_ty => { |
| FunctionKind::MethodTakingSelfByRef |
| } |
| _ => bail!("Unsupported `self` type"), |
| } |
| } |
| } |
| _ => FunctionKind::StaticMethod, |
| }, |
| other => panic!("Unexpected HIR node kind: {other:?}"), |
| }; |
| let method_qualifiers = match method_kind { |
| FunctionKind::Free | FunctionKind::StaticMethod => quote! {}, |
| FunctionKind::MethodTakingSelfByValue => quote! { && }, |
| FunctionKind::MethodTakingSelfByRef => match params[0].ty.kind() { |
| ty::TyKind::Ref(region, _, mutability) => { |
| let lifetime_annotation = format_region_as_cc_lifetime(region); |
| let mutability = match mutability { |
| Mutability::Mut => quote! {}, |
| Mutability::Not => quote! { const }, |
| }; |
| quote! { #mutability #lifetime_annotation } |
| } |
| _ => panic!("Expecting TyKind::Ref for MethodKind...Self...Ref"), |
| }, |
| }; |
| |
| let struct_name = match self_ty { |
| Some(ty) => match ty.kind() { |
| ty::TyKind::Adt(adt, substs) => { |
| assert_eq!(0, substs.len(), "Callers should filter out generics"); |
| Some(FullyQualifiedName::new(tcx, adt.did())) |
| } |
| _ => panic!("Non-ADT `impl`s should be filtered by caller"), |
| }, |
| None => None, |
| }; |
| let needs_definition = unqualified_rust_fn_name.as_str() != thunk_name; |
| let main_api_params = params |
| .iter() |
| .skip(if method_kind.has_self_param() { 1 } else { 0 }) |
| .map(|Param { cc_name, cpp_type, .. }| quote! { #cpp_type #cc_name }) |
| .collect_vec(); |
| let main_api = { |
| let doc_comment = { |
| let doc_comment = format_doc_comment(tcx, local_def_id); |
| quote! { __NEWLINE__ #doc_comment } |
| }; |
| |
| let mut prereqs = main_api_prereqs.clone(); |
| prereqs.move_defs_to_fwd_decls(); |
| |
| let static_ = if method_kind == FunctionKind::StaticMethod { |
| quote! { static } |
| } else { |
| quote! {} |
| }; |
| let extern_c = if !needs_definition { |
| quote! { extern "C" } |
| } else { |
| quote! {} |
| }; |
| |
| let mut attributes = vec![]; |
| // Attribute: must_use |
| if let Some(must_use_attr) = tcx.get_attr(def_id, rustc_span::symbol::sym::must_use) { |
| match must_use_attr.value_str() { |
| None => attributes.push(quote! {[[nodiscard]]}), |
| Some(symbol) => { |
| let message = symbol.as_str(); |
| attributes.push(quote! {[[nodiscard(#message)]]}); |
| } |
| }; |
| } |
| // Attribute: deprecated |
| if let Some(cc_deprecated_tag) = format_deprecated_tag(tcx, def_id) { |
| attributes.push(cc_deprecated_tag); |
| } |
| // Also check the impl block to which this function belongs (if there is one). |
| // Note: parent_def_id can be Some(...) even if the function is not inside an |
| // impl block. |
| if let Some(parent_def_id) = tcx.opt_parent(def_id) { |
| if let Some(cc_deprecated_tag) = format_deprecated_tag(tcx, parent_def_id) { |
| attributes.push(cc_deprecated_tag); |
| } |
| } |
| |
| CcSnippet { |
| prereqs, |
| tokens: quote! { |
| __NEWLINE__ |
| #doc_comment |
| #extern_c #(#attributes)* #static_ |
| #main_api_ret_type #main_api_fn_name ( |
| #( #main_api_params ),* |
| ) #method_qualifiers; |
| __NEWLINE__ |
| }, |
| } |
| }; |
| let cc_details = if !needs_definition { |
| CcSnippet::default() |
| } else { |
| let thunk_name = format_cc_ident(&thunk_name).context("Error formatting thunk name")?; |
| let struct_name = match struct_name.as_ref() { |
| None => quote! {}, |
| Some(fully_qualified_name) => { |
| let name = fully_qualified_name.cpp_name.expect("Structs always have a name"); |
| let name = format_cc_ident(name.as_str()) |
| .expect("Caller of format_fn should verify struct via format_adt_core"); |
| quote! { #name :: } |
| } |
| }; |
| |
| let mut prereqs = main_api_prereqs; |
| let thunk_decl = format_thunk_decl(db, def_id, &sig_mid, Some(sig_hir), &thunk_name)? |
| .into_tokens(&mut prereqs); |
| |
| let mut thunk_args = params |
| .iter() |
| .enumerate() |
| .map(|(i, Param { cc_name, ty, .. })| { |
| if i == 0 && method_kind.has_self_param() { |
| if method_kind == FunctionKind::MethodTakingSelfByValue { |
| quote! { this } |
| } else { |
| quote! { *this } |
| } |
| } else if is_c_abi_compatible_by_value(*ty) { |
| quote! { #cc_name } |
| } else { |
| quote! { & #cc_name } |
| } |
| }) |
| .collect_vec(); |
| let impl_body: TokenStream; |
| if is_c_abi_compatible_by_value(sig_mid.output()) { |
| impl_body = quote! { |
| return __crubit_internal :: #thunk_name( #( #thunk_args ),* ); |
| }; |
| } else { |
| if let Some(adt_def) = sig_mid.output().ty_adt_def() { |
| let core = db.format_adt_core(adt_def.did())?; |
| db.format_move_ctor_and_assignment_operator(core).map_err(|_| { |
| anyhow!("Can't pass the return type by value without a move constructor") |
| })?; |
| } |
| thunk_args.push(quote! { __ret_slot.Get() }); |
| impl_body = quote! { |
| crubit::ReturnValueSlot<#main_api_ret_type> __ret_slot; |
| __crubit_internal :: #thunk_name( #( #thunk_args ),* ); |
| return std::move(__ret_slot).AssumeInitAndTakeValue(); |
| }; |
| prereqs.includes.insert(CcInclude::utility()); // for `std::move` |
| prereqs.includes.insert(db.support_header("internal/return_value_slot.h")); |
| }; |
| CcSnippet { |
| prereqs, |
| tokens: quote! { |
| __NEWLINE__ |
| #thunk_decl |
| inline #main_api_ret_type #struct_name #main_api_fn_name ( |
| #( #main_api_params ),* ) #method_qualifiers { |
| #impl_body |
| } |
| __NEWLINE__ |
| }, |
| } |
| }; |
| |
| let rs_details = if !needs_thunk { |
| quote! {} |
| } else { |
| let fully_qualified_fn_name = match struct_name.as_ref() { |
| None => fully_qualified_fn_name.format_for_rs(), |
| Some(struct_name) => { |
| let fn_name = make_rs_ident(unqualified_rust_fn_name.as_str()); |
| let struct_name = struct_name.format_for_rs(); |
| quote! { #struct_name :: #fn_name } |
| } |
| }; |
| format_thunk_impl(tcx, def_id, &sig_mid, &thunk_name, fully_qualified_fn_name)? |
| }; |
| Ok(ApiSnippets { main_api, cc_details, rs_details }) |
| } |
| |
| /// Represents bindings for the "core" part of an algebraic data type (an ADT - |
| /// a struct, an enum, or a union) in a way that supports later injecting the |
| /// other parts like so: |
| /// |
| /// ``` |
| /// quote! { |
| /// #keyword #alignment #name final { |
| /// #core |
| /// #decls_of_other_parts // (e.g. struct fields, methods, etc.) |
| /// } |
| /// } |
| /// ``` |
| /// |
| /// `keyword`, `name` are stored separately, to support formatting them as a |
| /// forward declaration - e.g. `struct SomeStruct`. |
| #[derive(Clone)] |
| struct AdtCoreBindings<'tcx> { |
| /// DefId of the ADT. |
| def_id: DefId, |
| |
| /// C++ tag - e.g. `struct`, `class`, `enum`, or `union`. This isn't always |
| /// a direct mapping from Rust (e.g. a Rust `enum` might end up being |
| /// represented as an opaque C++ `struct`). |
| keyword: TokenStream, |
| |
| /// C++ translation of the ADT identifier - e.g. `SomeStruct`. |
| /// |
| /// A _short_ name is sufficient (i.e. there is no need to use a |
| /// namespace-qualified name), for `CcSnippet`s that are emitted into |
| /// the same namespace as the ADT. (This seems to be all the snippets |
| /// today.) |
| cc_short_name: TokenStream, |
| |
| /// Rust spelling of the ADT type - e.g. |
| /// `::some_crate::some_module::SomeStruct`. |
| rs_fully_qualified_name: TokenStream, |
| |
| self_ty: Ty<'tcx>, |
| alignment_in_bytes: u64, |
| size_in_bytes: u64, |
| } |
| |
| // AdtCoreBindings are a pure (and memoized...) function of the def_id. |
| impl<'tcx> PartialEq for AdtCoreBindings<'tcx> { |
| fn eq(&self, other: &Self) -> bool { |
| self.def_id == other.def_id |
| } |
| } |
| |
| impl<'tcx> Eq for AdtCoreBindings<'tcx> {} |
| impl<'tcx> Hash for AdtCoreBindings<'tcx> { |
| fn hash<H: Hasher>(&self, state: &mut H) { |
| self.def_id.hash(state); |
| } |
| } |
| |
| impl<'tcx> AdtCoreBindings<'tcx> { |
| fn needs_drop(&self, tcx: TyCtxt<'tcx>) -> bool { |
| self.self_ty.needs_drop(tcx, tcx.param_env(self.def_id)) |
| } |
| } |
| |
| /// Like `TyCtxt::is_directly_public`, but works not only with `LocalDefId`, but |
| /// also with `DefId`. |
| fn is_directly_public(tcx: TyCtxt, def_id: DefId) -> bool { |
| match def_id.as_local() { |
| None => { |
| // This mimics the checks in `try_print_visible_def_path_recur` in |
| // `compiler/rustc_middle/src/ty/print/pretty.rs`. |
| let actual_parent = tcx.opt_parent(def_id); |
| let visible_parent = tcx.visible_parent_map(()).get(&def_id).copied(); |
| actual_parent == visible_parent |
| } |
| Some(local_def_id) => tcx.effective_visibilities(()).is_directly_public(local_def_id), |
| } |
| } |
| |
| fn get_layout<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Result<Layout<'tcx>> { |
| let param_env = match ty.ty_adt_def() { |
| None => ty::ParamEnv::empty(), |
| Some(adt_def) => tcx.param_env(adt_def.did()), |
| }; |
| |
| tcx.layout_of(param_env.and(ty)).map(|ty_and_layout| ty_and_layout.layout).map_err( |
| |layout_err| { |
| // Have to use `.map_err`, because `LayoutError` doesn't satisfy the |
| // `anyhow::context::ext::StdError` trait bound. |
| anyhow!("Error computing the layout: {layout_err}") |
| }, |
| ) |
| } |
| |
| /// Formats the core of an algebraic data type (an ADT - a struct, an enum, or a |
| /// union) represented by `def_id`. |
| /// |
| /// The "core" means things that are necessary for a succesful binding (e.g. |
| /// inability to generate a correct C++ destructor means that the ADT cannot |
| /// have any bindings). "core" excludes things that are A) infallible (e.g. |
| /// struct or union fields which can always be translated into private, opaque |
| /// blobs of bytes) or B) optional (e.g. a problematic instance method |
| /// can just be ignored, unlike a problematic destructor). The split between |
| /// fallible "core" and non-fallible "rest" is motivated by the need to avoid |
| /// cycles / infinite recursion (e.g. when processing fields that refer back to |
| /// the struct type, possible with an indirection of a pointer). |
| /// |
| /// `format_adt_core` is used both to 1) format bindings for the core of an ADT, |
| /// and 2) check if formatting would have succeeded (e.g. when called from |
| /// `format_ty`). The 2nd case is needed for ADTs defined in any crate - this |
| /// is why the `def_id` parameter is a DefId rather than LocalDefId. |
| fn format_adt_core<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| def_id: DefId, |
| ) -> Result<Rc<AdtCoreBindings<'tcx>>> { |
| let tcx = db.tcx(); |
| let self_ty = tcx.type_of(def_id).instantiate_identity(); |
| assert!(self_ty.is_adt()); |
| assert!(is_directly_public(tcx, def_id), "Caller should verify"); |
| |
| let attribute = crubit_attr::get(tcx, def_id).unwrap(); |
| |
| let item_name = attribute.cpp_name.unwrap_or_else(|| tcx.item_name(def_id)); |
| let rs_fully_qualified_name = format_ty_for_rs(tcx, self_ty)?; |
| let cc_short_name = |
| format_cc_ident(item_name.as_str()).context("Error formatting item name")?; |
| |
| // The check below ensures that `format_trait_thunks` will succeed for the |
| // `Drop`, `Default`, and/or `Clone` trait. Ideally we would directly check |
| // if `format_trait_thunks` or `format_ty_for_cc(..., self_ty, ...)` |
| // succeeds, but this would lead to infinite recursion, so we only replicate |
| // `format_ty_for_cc` / `TyKind::Adt` checks that are outside of |
| // `format_adt_core`. |
| FullyQualifiedName::new(tcx, def_id).format_for_cc().with_context(|| { |
| format!("Error formatting the fully-qualified C++ name of `{item_name}") |
| })?; |
| |
| let adt_def = self_ty.ty_adt_def().expect("`def_id` needs to identify an ADT"); |
| let keyword = match adt_def.adt_kind() { |
| ty::AdtKind::Struct | ty::AdtKind::Enum => quote! { struct }, |
| ty::AdtKind::Union => quote! { union }, |
| }; |
| |
| let layout = get_layout(tcx, self_ty) |
| .with_context(|| format!("Error computing the layout of #{item_name}"))?; |
| ensure!(layout.abi().is_sized(), "Bindings for dynamically sized types are not supported."); |
| let alignment_in_bytes = { |
| // Only the ABI-mandated alignment is considered (i.e. `AbiAndPrefAlign::pref` |
| // is ignored), because 1) Rust's `std::mem::align_of` returns the |
| // ABI-mandated alignment and 2) the generated C++'s `alignas(...)` |
| // should specify the minimal/mandatory alignment. |
| layout.align().abi.bytes() |
| }; |
| let size_in_bytes = layout.size().bytes(); |
| ensure!(size_in_bytes != 0, "Zero-sized types (ZSTs) are not supported (b/258259459)"); |
| |
| Ok(Rc::new(AdtCoreBindings { |
| def_id, |
| keyword, |
| cc_short_name, |
| rs_fully_qualified_name, |
| self_ty, |
| alignment_in_bytes, |
| size_in_bytes, |
| })) |
| } |
| |
| fn repr_attrs(db: &dyn BindingsGenerator<'_>, def_id: DefId) -> Rc<[rustc_attr::ReprAttr]> { |
| let tcx = db.tcx(); |
| let attrs: Vec<_> = tcx |
| .get_attrs(def_id, sym::repr) |
| .flat_map(|attr| rustc_attr::parse_repr_attr(tcx.sess(), attr)) |
| .collect(); |
| attrs.into() |
| } |
| |
| fn format_fields<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| core: &AdtCoreBindings<'tcx>, |
| ) -> ApiSnippets { |
| let tcx = db.tcx(); |
| |
| // TODO(b/259749095): Support non-empty set of generic parameters. |
| let substs_ref = ty::List::empty(); |
| |
| struct FieldTypeInfo { |
| size: u64, |
| cpp_type: CcSnippet, |
| } |
| struct Field { |
| type_info: Result<FieldTypeInfo>, |
| cc_name: TokenStream, |
| rs_name: TokenStream, |
| is_public: bool, |
| index: usize, |
| offset: u64, |
| offset_of_next_field: u64, |
| doc_comment: TokenStream, |
| attributes: Vec<TokenStream>, |
| } |
| impl Field { |
| fn size(&self) -> u64 { |
| match self.type_info { |
| Err(_) => self.offset_of_next_field - self.offset, |
| Ok(FieldTypeInfo { size, .. }) => size, |
| } |
| } |
| } |
| |
| let layout = get_layout(tcx, core.self_ty) |
| .expect("Layout should be already verified by `format_adt_core`"); |
| let adt_def = core.self_ty.ty_adt_def().expect("`core.def_id` needs to identify an ADT"); |
| let fields: Vec<Field> = if core.self_ty.is_enum() { |
| vec![Field { |
| type_info: Err(anyhow!("No support for bindings of individual `enum` fields")), |
| cc_name: quote! { __opaque_blob_of_bytes }, |
| rs_name: quote! { __opaque_blob_of_bytes }, |
| is_public: false, |
| index: 0, |
| offset: 0, |
| offset_of_next_field: core.size_in_bytes, |
| doc_comment: quote! {}, |
| attributes: vec![], |
| }] |
| } else { |
| let rustc_hir::Node::Item(item) = tcx.hir_node_by_def_id(core.def_id.expect_local()) else { |
| panic!("internal error: def_id referring to an ADT was not a HIR Item."); |
| }; |
| let variants = match item.kind { |
| rustc_hir::ItemKind::Struct(variants, _) => variants, |
| rustc_hir::ItemKind::Union(variants, _) => variants, |
| _ => panic!( |
| "internal error: def_id referring to a non-enum ADT was not a struct or union." |
| ), |
| }; |
| let hir_fields: Vec<_> = variants.fields().iter().sorted_by_key(|f| f.span).collect(); |
| |
| let mut fields = core |
| .self_ty |
| .ty_adt_def() |
| .expect("`core.def_id` needs to identify an ADT") |
| .all_fields() |
| .sorted_by_key(|f| tcx.def_span(f.did)) |
| .enumerate() |
| .map(|(index, field_def)| { |
| // *Not* using zip, in order to crash on length mismatch. |
| let hir_field = |
| hir_fields.get(index).expect("HIR ADT had fewer fields than rustc_middle"); |
| assert!(field_def.did == hir_field.def_id.to_def_id()); |
| let ty = SugaredTy::new(field_def.ty(tcx, substs_ref), Some(hir_field.ty)); |
| let size = get_layout(tcx, ty.mid()).map(|layout| layout.size().bytes()); |
| let type_info = size.and_then(|size| { |
| Ok(FieldTypeInfo { |
| size, |
| cpp_type: db.format_ty_for_cc(ty, TypeLocation::Other)?, |
| }) |
| }); |
| let name = field_def.ident(tcx); |
| let cc_name = format_cc_ident(name.as_str()) |
| .unwrap_or_else(|_err| format_ident!("__field{index}").into_token_stream()); |
| let rs_name = { |
| let name_starts_with_digit = name |
| .as_str() |
| .chars() |
| .next() |
| .expect("Empty names are unexpected (here and in general)") |
| .is_ascii_digit(); |
| if name_starts_with_digit { |
| let index = Literal::usize_unsuffixed(index); |
| quote! { #index } |
| } else { |
| let name = make_rs_ident(name.as_str()); |
| quote! { #name } |
| } |
| }; |
| |
| // `offset` and `offset_of_next_field` will be fixed by FieldsShape::Arbitrary |
| // branch below. |
| let offset = 0; |
| let offset_of_next_field = 0; |
| |
| // Populate attributes. |
| let mut attributes = vec![]; |
| if let Some(cc_deprecated_tag) = format_deprecated_tag(tcx, field_def.did) { |
| attributes.push(cc_deprecated_tag); |
| } |
| |
| Field { |
| type_info, |
| cc_name, |
| rs_name, |
| is_public: field_def.vis == ty::Visibility::Public, |
| index, |
| offset, |
| offset_of_next_field, |
| doc_comment: format_doc_comment(tcx, field_def.did.expect_local()), |
| attributes, |
| } |
| }) |
| .collect_vec(); |
| |
| // Determine the memory layout |
| match layout.fields() { |
| FieldsShape::Arbitrary { offsets, .. } => { |
| for (index, offset) in offsets.iter().enumerate() { |
| // Documentation of `FieldsShape::Arbitrary says that the offsets are |
| // "ordered to match the source definition order". |
| // We can coorelate them with elements |
| // of the `fields` vector because we've explicitly `sorted_by_key` using |
| // `def_span`. |
| fields[index].offset = offset.bytes(); |
| } |
| // Sort by offset first; ZSTs in the same offset are sorted by source order. |
| // Use `field_size` to ensure ZSTs at the same offset as |
| // non-ZSTs sort first to avoid weird offset issues later on. |
| fields.sort_by_key(|field| { |
| let field_size = field.type_info.as_ref().map(|info| info.size).unwrap_or(0); |
| (field.offset, field_size, field.index) |
| }); |
| } |
| FieldsShape::Union(num_fields) => { |
| // Compute the offset of each field |
| for index in 0..num_fields.get() { |
| fields[index].offset = layout.fields().offset(index).bytes(); |
| } |
| } |
| unexpected => panic!("Unexpected FieldsShape: {unexpected:?}"), |
| } |
| |
| let next_offsets = fields |
| .iter() |
| .map(|Field { offset, .. }| *offset) |
| .skip(1) |
| .chain(once(core.size_in_bytes)) |
| .collect_vec(); |
| for (field, next_offset) in fields.iter_mut().zip(next_offsets) { |
| field.offset_of_next_field = next_offset; |
| } |
| fields |
| }; |
| |
| let cc_details = if fields.is_empty() { |
| CcSnippet::default() |
| } else { |
| let adt_cc_name = &core.cc_short_name; |
| let cc_assertions: TokenStream = fields |
| .iter() |
| // TODO(b/298660437): Add support for ZST fields. |
| .filter(|field| field.size() != 0) |
| .map(|Field { cc_name, offset, .. }| { |
| let offset = Literal::u64_unsuffixed(*offset); |
| quote! { static_assert(#offset == offsetof(#adt_cc_name, #cc_name)); } |
| }) |
| .collect(); |
| CcSnippet::with_include( |
| quote! { |
| inline void #adt_cc_name::__crubit_field_offset_assertions() { |
| #cc_assertions |
| } |
| }, |
| CcInclude::cstddef(), |
| ) |
| }; |
| let rs_details: TokenStream = { |
| let adt_rs_name = &core.rs_fully_qualified_name; |
| fields |
| .iter() |
| // TODO(b/298660437): Even though we don't generate bindings for ZST fields, we'd still |
| // like to make sure we computed the offset of ZST fields correctly on the Rust side, |
| // so we still emit offset assertions for ZST fields here. |
| // TODO(b/298660437): Remove the comment above when ZST fields are supported. |
| .filter(|field| field.is_public) |
| .map(|Field { rs_name, offset, .. }| { |
| let expected_offset = Literal::u64_unsuffixed(*offset); |
| let actual_offset = quote! { ::core::mem::offset_of!(#adt_rs_name, #rs_name) }; |
| quote! { const _: () = assert!(#actual_offset == #expected_offset); } |
| }) |
| .collect() |
| }; |
| let main_api = { |
| let assertions_method_decl = if fields.is_empty() { |
| quote! {} |
| } else { |
| // We put the assertions in a method so that they can read private member |
| // variables. |
| quote! { private: static void __crubit_field_offset_assertions(); } |
| }; |
| |
| // If all fields are known, and the type is repr(C), then we don't need padding |
| // fields, and can instead use the natural padding from alignment. |
| // |
| // Note: it does need to be repr(C) to be guaranteed, since the compiler might |
| // reasonably place a field later than it has to for layout |
| // randomization purposes. For example, in `#[repr(align(4))] struct |
| // Foo(i8);` there are four different places the `i8` could be. |
| // If it was placed in the second byte, for any reason, then we would need |
| // explicit padding bytes. |
| let repr_attrs = db.repr_attrs(core.def_id); |
| let always_omit_padding = repr_attrs.contains(&rustc_attr::ReprC) |
| && fields.iter().all(|field| field.type_info.is_ok()); |
| |
| let mut prereqs = CcPrerequisites::default(); |
| let fields: TokenStream = fields |
| .into_iter() |
| .map(|field| { |
| let cc_name = &field.cc_name; |
| match field.type_info { |
| Err(ref err) => { |
| let size = field.size(); |
| let msg = |
| format!("Field type has been replaced with a blob of bytes: {err:#}"); |
| |
| // Empty arrays are ill-formed, but also unnecessary for padding. |
| if size > 0 { |
| let size = Literal::u64_unsuffixed(size); |
| quote! { |
| private: __NEWLINE__ |
| __COMMENT__ #msg |
| unsigned char #cc_name[#size]; |
| } |
| } else { |
| // TODO(b/258259459): Generate bindings for ZST fields. |
| let msg = format!( |
| "Skipped bindings for field `{cc_name}`: \ |
| ZST fields are not supported (b/258259459)" |
| ); |
| quote! {__NEWLINE__ __COMMENT__ #msg} |
| } |
| } |
| Ok(FieldTypeInfo { cpp_type, size }) => { |
| // Only structs require no overlaps. |
| let padding = match adt_def.adt_kind() { |
| ty::AdtKind::Struct => { |
| assert!((field.offset + size) <= field.offset_of_next_field); |
| field.offset_of_next_field - field.offset - size |
| } |
| ty::AdtKind::Union => field.offset, |
| ty::AdtKind::Enum => todo!(), |
| }; |
| |
| // Omit explicit padding if: |
| // 1. The type is repr(C) and has known types for all fields, so we can |
| // reuse the natural repr(C) padding. |
| // 2. There is no padding |
| // TODO(jeanpierreda): also omit padding for the final field? |
| let padding = if always_omit_padding || padding == 0 { |
| quote! {} |
| } else { |
| let padding = Literal::u64_unsuffixed(padding); |
| let ident = format_ident!("__padding{}", field.index); |
| quote! { private: unsigned char #ident[#padding]; } |
| }; |
| let visibility = if field.is_public { |
| quote! { public: } |
| } else { |
| quote! { private: } |
| }; |
| let cpp_type = cpp_type.into_tokens(&mut prereqs); |
| let doc_comment = field.doc_comment; |
| let attributes = field.attributes; |
| |
| match adt_def.adt_kind() { |
| ty::AdtKind::Struct => quote! { |
| #visibility __NEWLINE__ |
| // The anonymous union gives more control over when exactly |
| // the field constructors and destructors run. See also |
| // b/288138612. |
| union { __NEWLINE__ |
| #doc_comment |
| #(#attributes)* |
| #cpp_type #cc_name; |
| }; |
| #padding |
| }, |
| ty::AdtKind::Union => { |
| if repr_attrs.contains(&rustc_attr::ReprC) { |
| quote! { |
| __NEWLINE__ |
| #doc_comment |
| #cpp_type #cc_name; |
| } |
| } else { |
| let internal_padding = if field.offset == 0 { |
| quote! {} |
| } else { |
| let internal_padding_size = Literal::u64_unsuffixed(field.offset); |
| quote! {char __crubit_internal_padding[#internal_padding_size]} |
| }; |
| quote! { |
| __NEWLINE__ |
| #doc_comment |
| struct { |
| #internal_padding |
| #cpp_type value; |
| } #cc_name; |
| } |
| } |
| } |
| ty::AdtKind::Enum => todo!(), |
| } |
| } |
| } |
| }) |
| .collect(); |
| |
| CcSnippet { |
| prereqs, |
| tokens: quote! { |
| #fields |
| #assertions_method_decl |
| }, |
| } |
| }; |
| |
| ApiSnippets { main_api, cc_details, rs_details } |
| } |
| |
| fn does_type_implement_trait<'tcx>(tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>, trait_id: DefId) -> bool { |
| assert!(tcx.is_trait(trait_id)); |
| |
| let generics = tcx.generics_of(trait_id); |
| assert!(generics.has_self); |
| assert_eq!( |
| generics.count(), |
| 1, // Only `Self` |
| "Generic traits are not supported yet (b/286941486)", |
| ); |
| let substs = [self_ty]; |
| |
| tcx.infer_ctxt() |
| .build() |
| .type_implements_trait(trait_id, substs, tcx.param_env(trait_id)) |
| .must_apply_modulo_regions() |
| } |
| |
| struct TraitThunks { |
| method_name_to_cc_thunk_name: HashMap<Symbol, TokenStream>, |
| cc_thunk_decls: CcSnippet, |
| rs_thunk_impls: TokenStream, |
| } |
| |
| fn format_trait_thunks<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| trait_id: DefId, |
| adt: &AdtCoreBindings<'tcx>, |
| ) -> Result<TraitThunks> { |
| let tcx = db.tcx(); |
| assert!(tcx.is_trait(trait_id)); |
| |
| let self_ty = adt.self_ty; |
| let is_drop_trait = Some(trait_id) == tcx.lang_items().drop_trait(); |
| if is_drop_trait { |
| // To support "drop glue" we don't require that `self_ty` directly implements |
| // the `Drop` trait. Instead we require the caller to check |
| // `needs_drop`. |
| assert!(self_ty.needs_drop(tcx, tcx.param_env(adt.def_id))); |
| } else if !does_type_implement_trait(tcx, self_ty, trait_id) { |
| let trait_name = tcx.item_name(trait_id); |
| bail!("`{self_ty}` doesn't implement the `{trait_name}` trait"); |
| } |
| |
| let mut method_name_to_cc_thunk_name = HashMap::new(); |
| let mut cc_thunk_decls = CcSnippet::default(); |
| let mut rs_thunk_impls = quote! {}; |
| let methods = tcx |
| .associated_items(trait_id) |
| .in_definition_order() |
| .filter(|item| item.kind == ty::AssocKind::Fn); |
| for method in methods { |
| let substs = { |
| let generics = tcx.generics_of(method.def_id); |
| if generics.own_params.iter().any(|p| p.kind.is_ty_or_const()) { |
| // Note that lifetime-generic methods are ok: |
| // * they are handled by `format_thunk_decl` and `format_thunk_impl` |
| // * the lifetimes are erased by `ty::Instance::mono` and *seem* to be erased by |
| // `ty::Instance::new` |
| panic!( |
| "So far callers of `format_trait_thunks` didn't need traits with \ |
| methods that are type-generic or const-generic" |
| ); |
| } |
| assert!(generics.has_self); |
| tcx.mk_args_trait(self_ty, std::iter::empty()) |
| }; |
| |
| let thunk_name = { |
| let instance = ty::Instance::new(method.def_id, substs); |
| let symbol = tcx.symbol_name(instance); |
| format!("__crubit_thunk_{}", &escape_non_identifier_chars(symbol.name)) |
| }; |
| method_name_to_cc_thunk_name.insert(method.name, format_cc_ident(&thunk_name)?); |
| |
| let sig_mid = liberate_and_deanonymize_late_bound_regions( |
| tcx, |
| tcx.fn_sig(method.def_id).instantiate(tcx, substs), |
| method.def_id, |
| ); |
| // TODO(b/254096006): Preserve the HIR here, if possible? |
| // Cannot in general (e.g. blanket impl from another crate), but should be able |
| // to for traits defined or implemented in the current crate. |
| let sig_hir = None; |
| |
| cc_thunk_decls.add_assign({ |
| let thunk_name = format_cc_ident(&thunk_name)?; |
| format_thunk_decl(db, method.def_id, &sig_mid, sig_hir, &thunk_name)? |
| }); |
| |
| rs_thunk_impls.extend({ |
| let struct_name = &adt.rs_fully_qualified_name; |
| if is_drop_trait { |
| // Manually formatting (instead of depending on `format_thunk_impl`) |
| // to avoid https://doc.rust-lang.org/error_codes/E0040.html |
| let thunk_name = make_rs_ident(&thunk_name); |
| quote! { |
| #[no_mangle] |
| extern "C" fn #thunk_name( |
| __self: &mut ::core::mem::MaybeUninit<#struct_name> |
| ) { |
| unsafe { __self.assume_init_drop() }; |
| } |
| } |
| } else { |
| let fully_qualified_fn_name = { |
| let fully_qualified_trait_name = |
| FullyQualifiedName::new(tcx, trait_id).format_for_rs(); |
| let method_name = make_rs_ident(method.name.as_str()); |
| quote! { <#struct_name as #fully_qualified_trait_name>::#method_name } |
| }; |
| format_thunk_impl( |
| tcx, |
| method.def_id, |
| &sig_mid, |
| &thunk_name, |
| fully_qualified_fn_name, |
| )? |
| } |
| }); |
| } |
| |
| Ok(TraitThunks { method_name_to_cc_thunk_name, cc_thunk_decls, rs_thunk_impls }) |
| } |
| |
| /// Formats a default constructor for an ADT if possible (i.e. if the `Default` |
| /// trait is implemented for the ADT). Returns an error otherwise (e.g. if |
| /// there is no `Default` impl, then the default constructor will be |
| /// `=delete`d in the returned snippet). |
| fn format_default_ctor<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| core: Rc<AdtCoreBindings<'tcx>>, |
| ) -> Result<ApiSnippets, ApiSnippets> { |
| fn fallible_format_default_ctor<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| core: Rc<AdtCoreBindings<'tcx>>, |
| ) -> Result<ApiSnippets> { |
| let tcx = db.tcx(); |
| let trait_id = tcx |
| .get_diagnostic_item(sym::Default) |
| .ok_or(anyhow!("Couldn't find `core::default::Default`"))?; |
| let TraitThunks { |
| method_name_to_cc_thunk_name, |
| cc_thunk_decls, |
| rs_thunk_impls: rs_details, |
| } = format_trait_thunks(db, trait_id, &core)?; |
| |
| let cc_struct_name = &core.cc_short_name; |
| let main_api = CcSnippet::new(quote! { |
| __NEWLINE__ __COMMENT__ "Default::default" |
| #cc_struct_name(); __NEWLINE__ __NEWLINE__ |
| }); |
| let cc_details = { |
| let thunk_name = method_name_to_cc_thunk_name |
| .into_values() |
| .exactly_one() |
| .expect("Expecting a single `default` method"); |
| |
| let mut prereqs = CcPrerequisites::default(); |
| let cc_thunk_decls = cc_thunk_decls.into_tokens(&mut prereqs); |
| |
| let tokens = quote! { |
| #cc_thunk_decls |
| inline #cc_struct_name::#cc_struct_name() { |
| __crubit_internal::#thunk_name(this); |
| } |
| }; |
| CcSnippet { tokens, prereqs } |
| }; |
| Ok(ApiSnippets { main_api, cc_details, rs_details }) |
| } |
| fallible_format_default_ctor(db, core.clone()).map_err(|err| { |
| let msg = format!("{err:#}"); |
| let adt_cc_name = &core.cc_short_name; |
| ApiSnippets { |
| main_api: CcSnippet::new(quote! { |
| __NEWLINE__ __COMMENT__ #msg |
| #adt_cc_name() = delete; __NEWLINE__ |
| }), |
| ..Default::default() |
| } |
| }) |
| } |
| |
| /// Formats the copy constructor and the copy-assignment operator for an ADT if |
| /// possible (i.e. if the `Clone` trait is implemented for the ADT). Returns an |
| /// error otherwise (e.g. if there is no `Clone` impl, then the copy constructor |
| /// and assignment operator will be `=delete`d in the returned snippet). |
| fn format_copy_ctor_and_assignment_operator<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| core: Rc<AdtCoreBindings<'tcx>>, |
| ) -> Result<ApiSnippets, ApiSnippets> { |
| fn fallible_format_copy_ctor_and_assignment_operator<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| core: Rc<AdtCoreBindings<'tcx>>, |
| ) -> Result<ApiSnippets> { |
| let tcx = db.tcx(); |
| let cc_struct_name = &core.cc_short_name; |
| |
| let is_copy = { |
| // TODO(b/259749095): Once generic ADTs are supported, `is_copy_modulo_regions` |
| // might need to be replaced with a more thorough check - see |
| // b/258249993#comment4. |
| core.self_ty.is_copy_modulo_regions(tcx, tcx.param_env(core.def_id)) |
| }; |
| if is_copy { |
| let msg = "Rust types that are `Copy` get trivial, `default` C++ copy constructor \ |
| and assignment operator."; |
| let main_api = CcSnippet::new(quote! { |
| __NEWLINE__ __COMMENT__ #msg |
| #cc_struct_name(const #cc_struct_name&) = default; __NEWLINE__ |
| #cc_struct_name& operator=(const #cc_struct_name&) = default; |
| }); |
| let cc_details = CcSnippet::with_include( |
| quote! { |
| static_assert(std::is_trivially_copy_constructible_v<#cc_struct_name>); |
| static_assert(std::is_trivially_copy_assignable_v<#cc_struct_name>); |
| }, |
| CcInclude::type_traits(), |
| ); |
| |
| return Ok(ApiSnippets { main_api, cc_details, rs_details: quote! {} }); |
| } |
| |
| let trait_id = tcx |
| .lang_items() |
| .clone_trait() |
| .ok_or_else(|| anyhow!("Can't find the `Clone` trait"))?; |
| let TraitThunks { |
| method_name_to_cc_thunk_name, |
| cc_thunk_decls, |
| rs_thunk_impls: rs_details, |
| } = format_trait_thunks(db, trait_id, &core)?; |
| let main_api = CcSnippet::new(quote! { |
| __NEWLINE__ __COMMENT__ "Clone::clone" |
| #cc_struct_name(const #cc_struct_name&); __NEWLINE__ |
| __NEWLINE__ __COMMENT__ "Clone::clone_from" |
| #cc_struct_name& operator=(const #cc_struct_name&); __NEWLINE__ __NEWLINE__ |
| }); |
| let cc_details = { |
| // `unwrap` calls are okay because `Clone` trait always has these methods. |
| let clone_thunk_name = method_name_to_cc_thunk_name.get(&sym::clone).unwrap(); |
| let clone_from_thunk_name = method_name_to_cc_thunk_name.get(&sym::clone_from).unwrap(); |
| |
| let mut prereqs = CcPrerequisites::default(); |
| let cc_thunk_decls = cc_thunk_decls.into_tokens(&mut prereqs); |
| |
| let tokens = quote! { |
| #cc_thunk_decls |
| inline #cc_struct_name::#cc_struct_name(const #cc_struct_name& other) { |
| __crubit_internal::#clone_thunk_name(other, this); |
| } |
| inline #cc_struct_name& #cc_struct_name::operator=(const #cc_struct_name& other) { |
| if (this != &other) { |
| __crubit_internal::#clone_from_thunk_name(*this, other); |
| } |
| return *this; |
| } |
| }; |
| CcSnippet { tokens, prereqs } |
| }; |
| Ok(ApiSnippets { main_api, cc_details, rs_details }) |
| } |
| fallible_format_copy_ctor_and_assignment_operator(db, core.clone()).map_err(|err| { |
| let msg = format!("{err:#}"); |
| let adt_cc_name = &core.cc_short_name; |
| ApiSnippets { |
| main_api: CcSnippet::new(quote! { |
| __NEWLINE__ __COMMENT__ #msg |
| #adt_cc_name(const #adt_cc_name&) = delete; __NEWLINE__ |
| #adt_cc_name& operator=(const #adt_cc_name&) = delete; |
| }), |
| ..Default::default() |
| } |
| }) |
| } |
| |
| /// Formats the move constructor and the move-assignment operator for an ADT if |
| /// possible (it depends on various factors like `needs_drop`, `is_unpin` and |
| /// implementations of `Default` and/or `Clone` traits). Returns an error |
| /// otherwise (the error's `ApiSnippets` contain a `=delete`d declaration). |
| fn format_move_ctor_and_assignment_operator<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| core: Rc<AdtCoreBindings<'tcx>>, |
| ) -> Result<ApiSnippets, ApiSnippets> { |
| fn fallible_format_move_ctor_and_assignment_operator<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| core: Rc<AdtCoreBindings<'tcx>>, |
| ) -> Result<ApiSnippets> { |
| let tcx = db.tcx(); |
| let adt_cc_name = &core.cc_short_name; |
| if core.needs_drop(tcx) { |
| let has_default_ctor = db.format_default_ctor(core.clone()).is_ok(); |
| let is_unpin = core.self_ty.is_unpin(tcx, tcx.param_env(core.def_id)); |
| if has_default_ctor && is_unpin { |
| let main_api = CcSnippet::new(quote! { |
| #adt_cc_name(#adt_cc_name&&); __NEWLINE__ |
| #adt_cc_name& operator=(#adt_cc_name&&); __NEWLINE__ |
| }); |
| let mut prereqs = CcPrerequisites::default(); |
| prereqs.includes.insert(db.support_header("internal/memswap.h")); |
| prereqs.includes.insert(CcInclude::utility()); // for `std::move` |
| let tokens = quote! { |
| inline #adt_cc_name::#adt_cc_name(#adt_cc_name&& other) |
| : #adt_cc_name() { |
| *this = std::move(other); |
| } |
| inline #adt_cc_name& #adt_cc_name::operator=(#adt_cc_name&& other) { |
| crubit::MemSwap(*this, other); |
| return *this; |
| } |
| }; |
| let cc_details = CcSnippet { tokens, prereqs }; |
| Ok(ApiSnippets { main_api, cc_details, ..Default::default() }) |
| } else if db.format_copy_ctor_and_assignment_operator(core).is_ok() { |
| // The class will have a custom copy constructor and copy assignment operator |
| // and *no* move constructor nor move assignment operator. This |
| // way, when a move is requested, a copy is performed instead |
| // (this is okay, this is what happens if a copyable pre-C++11 |
| // class is compiled in C++11 mode and moved). |
| // |
| // We can't use the `=default` move constructor, because it is elementwise and |
| // semantically incorrect. Â We can't `=delete` the move constructor because it |
| // would make `SomeStruct(MakeSomeStruct())` select the deleted move constructor |
| // and fail to compile. |
| Ok(ApiSnippets::default()) |
| } else { |
| bail!( |
| "C++ moves are deleted \ |
| because there's no non-destructive implementation available." |
| ); |
| } |
| } else { |
| let main_api = CcSnippet::new(quote! { |
| // The generated bindings have to follow Rust move semantics: |
| // * All Rust types are memcpy-movable (e.g. <internal link>/constructors.html says |
| // that "Every type must be ready for it to be blindly memcopied to somewhere |
| // else in memory") |
| // * The only valid operation on a moved-from non-`Copy` Rust struct is to assign to |
| // it. |
| // |
| // The generated C++ bindings below match the required semantics because they: |
| // * Generate trivial` C++ move constructor and move assignment operator. Per |
| // <internal link>/cpp/language/move_constructor#Trivial_move_constructor: "A trivial |
| // move constructor is a constructor that performs the same action as the trivial |
| // copy constructor, that is, makes a copy of the object representation as if by |
| // std::memmove." |
| // * Generate trivial C++ destructor. |
| // |
| // In particular, note that the following C++ code and Rust code are exactly |
| // equivalent (except that in Rust, reuse of `y` is forbidden at compile time, |
| // whereas in C++, it's only prohibited by convention): |
| // * C++, assumming trivial move constructor and trivial destructor: |
| // `auto x = std::move(y);` |
| // * Rust, assumming non-`Copy`, no custom `Drop` or drop glue: |
| // `let x = y;` |
| // |
| // TODO(b/258251148): If the ADT provides a custom `Drop` impls or requires drop |
| // glue, then extra care should be taken to ensure the C++ destructor can handle |
| // the moved-from object in a way that meets Rust move semantics. For example, the |
| // generated C++ move constructor might need to assign `Default::default()` to the |
| // moved-from object. |
| #adt_cc_name(#adt_cc_name&&) = default; __NEWLINE__ |
| #adt_cc_name& operator=(#adt_cc_name&&) = default; __NEWLINE__ |
| __NEWLINE__ |
| }); |
| let cc_details = CcSnippet::with_include( |
| quote! { |
| static_assert(std::is_trivially_move_constructible_v<#adt_cc_name>); |
| static_assert(std::is_trivially_move_assignable_v<#adt_cc_name>); |
| }, |
| CcInclude::type_traits(), |
| ); |
| Ok(ApiSnippets { main_api, cc_details, ..Default::default() }) |
| } |
| } |
| fallible_format_move_ctor_and_assignment_operator(db, core.clone()).map_err(|err| { |
| let msg = format!("{err:#}"); |
| let adt_cc_name = &core.cc_short_name; |
| ApiSnippets { |
| main_api: CcSnippet::new(quote! { |
| __NEWLINE__ __COMMENT__ #msg |
| #adt_cc_name(#adt_cc_name&&) = delete; __NEWLINE__ |
| #adt_cc_name& operator=(#adt_cc_name&&) = delete; |
| }), |
| ..Default::default() |
| } |
| }) |
| } |
| |
| /// Formats an algebraic data type (an ADT - a struct, an enum, or a union) |
| /// represented by `core`. This function is infallible - after |
| /// `format_adt_core` returns success we have committed to emitting C++ bindings |
| /// for the ADT. |
| fn format_adt<'tcx>( |
| db: &dyn BindingsGenerator<'tcx>, |
| core: Rc<AdtCoreBindings<'tcx>>, |
| ) -> ApiSnippets { |
| let tcx = db.tcx(); |
| let adt_cc_name = &core.cc_short_name; |
| |
| // `format_adt` should only be called for local ADTs. |
| let local_def_id = core.def_id.expect_local(); |
| |
| let default_ctor_snippets = db.format_default_ctor(core.clone()).unwrap_or_else(|err| err); |
| |
| let destructor_snippets = if core.needs_drop(tcx) { |
| let drop_trait_id = |
| tcx.lang_items().drop_trait().expect("`Drop` trait should be present if `needs_drop"); |
| let TraitThunks { |
| method_name_to_cc_thunk_name, |
| cc_thunk_decls, |
| rs_thunk_impls: rs_details, |
| } = format_trait_thunks(db, drop_trait_id, &core) |
| .expect("`format_adt_core` should have already validated `Drop` support"); |
| let drop_thunk_name = method_name_to_cc_thunk_name |
| .into_values() |
| .exactly_one() |
| .expect("Expecting a single `drop` method"); |
| let main_api = CcSnippet::new(quote! { |
| __NEWLINE__ __COMMENT__ "Drop::drop" |
| ~#adt_cc_name(); __NEWLINE__ |
| __NEWLINE__ |
| }); |
| let cc_details = { |
| let mut prereqs = CcPrerequisites::default(); |
| let cc_thunk_decls = cc_thunk_decls.into_tokens(&mut prereqs); |
| let tokens = quote! { |
| #cc_thunk_decls |
| inline #adt_cc_name::~#adt_cc_name() { |
| __crubit_internal::#drop_thunk_name(*this); |
| } |
| }; |
| CcSnippet { tokens, prereqs } |
| }; |
| ApiSnippets { main_api, cc_details, rs_details } |
| } else { |
| let main_api = CcSnippet::new(quote! { |
| __NEWLINE__ __COMMENT__ "No custom `Drop` impl and no custom \"drop glue\" required" |
| ~#adt_cc_name() = default; __NEWLINE__ |
| }); |
| let cc_details = CcSnippet::with_include( |
| quote! { static_assert(std::is_trivially_destructible_v<#adt_cc_name>); }, |
| CcInclude::type_traits(), |
| ); |
| ApiSnippets { main_api, cc_details, ..Default::default() } |
| }; |
| |
| let copy_ctor_and_assignment_snippets = |
| db.format_copy_ctor_and_assignment_operator(core.clone()).unwrap_or_else(|err| err); |
| |
| let move_ctor_and_assignment_snippets = |
| db.format_move_ctor_and_assignment_operator(core.clone()).unwrap_or_else(|err| err); |
| |
| let impl_items_snippets = tcx |
| .inherent_impls(core.def_id) |
| .into_iter() |
| .flatten() |
| .map(|impl_id| tcx.hir().expect_item(impl_id.expect_local())) |
| .flat_map(|item| match &item.kind { |
| ItemKind::Impl(impl_) => impl_.items, |
| other => panic!("Unexpected `ItemKind` from `inherent_impls`: {other:?}"), |
| }) |
| .sorted_by_key(|impl_item_ref| { |
| let def_id = impl_item_ref.id.owner_id.def_id; |
| tcx.def_span(def_id) |
| }) |
| .filter_map(|impl_item_ref| { |
| let def_id = impl_item_ref.id.owner_id.def_id; |
| if !tcx.effective_visibilities(()).is_directly_public(def_id) { |
| return None; |
| } |
| let result = match impl_item_ref.kind { |
| AssocItemKind::Fn { .. } => db.format_fn(def_id).map(Some), |
| other => Err(anyhow!("Unsupported `impl` item kind: {other:?}")), |
| }; |
| result.unwrap_or_else(|err| Some(format_unsupported_def(db, def_id, err))) |
| }) |
| .collect(); |
| |
| let ApiSnippets { |
| main_api: public_functions_main_api, |
| cc_details: public_functions_cc_details, |
| rs_details: public_functions_rs_details, |
| } = [ |
| default_ctor_snippets, |
| destructor_snippets, |
| move_ctor_and_assignment_snippets, |
| copy_ctor_and_assignment_snippets, |
| impl_items_snippets, |
| ] |
| .into_iter() |
| .collect(); |
| |
| let ApiSnippets { |
| main_api: fields_main_api, |
| cc_details: fields_cc_details, |
| rs_details: fields_rs_details, |
| } = format_fields(db, &core); |
| |
| let alignment = Literal::u64_unsuffixed(core.alignment_in_bytes); |
| let size = Literal::u64_unsuffixed(core.size_in_bytes); |
| let main_api = { |
| let rs_type = core.rs_fully_qualified_name.to_string(); |
| let mut attributes = vec![ |
| quote! {CRUBIT_INTERNAL_RUST_TYPE(#rs_type)}, |
| quote! {alignas(#alignment)}, |
| quote! {[[clang::trivial_abi]]}, |
| ]; |
| if db |
| .repr_attrs(core.def_id) |
| .iter() |
| .any(|repr| matches!(repr, rustc_attr::ReprPacked { .. })) |
| { |
| attributes.push(quote! { __attribute__((packed)) }) |
| } |
| |
| // Attribute: must_use |
| if let Some(must_use_attr) = tcx.get_attr(core.def_id, rustc_span::symbol::sym::must_use) { |
| match must_use_attr.value_str() { |
| None => attributes.push(quote! {[[nodiscard]]}), |
| Some(symbol) => { |
| let message = symbol.as_str(); |
| attributes.push(quote! {[[nodiscard(#message)]]}); |
| } |
| } |
| } |
| |
| // Attribute: deprecated |
| if let Some(cc_deprecated_tag) = format_deprecated_tag(tcx, core.def_id) { |
| attributes.push(cc_deprecated_tag); |
| } |
| |
| let doc_comment = format_doc_comment(tcx, core.def_id.expect_local()); |
| let keyword = &core.keyword; |
| |
| let mut prereqs = CcPrerequisites::default(); |
| prereqs.includes.insert(db.support_header("internal/attribute_macros.h")); |
| let public_functions_main_api = public_functions_main_api.into_tokens(&mut prereqs); |
| let fields_main_api = fields_main_api.into_tokens(&mut prereqs); |
| prereqs.fwd_decls.remove(&local_def_id); |
| |
| CcSnippet { |
| prereqs, |
| tokens: quote! { |
| __NEWLINE__ #doc_comment |
| #keyword #(#attributes)* #adt_cc_name final { |
| public: __NEWLINE__ |
| #public_functions_main_api |
| #fields_main_api |
| }; |
| __NEWLINE__ |
| }, |
| } |
| }; |
| let cc_details = { |
| let mut prereqs = CcPrerequisites::default(); |
| let public_functions_cc_details = public_functions_cc_details.into_tokens(&mut prereqs); |
| let fields_cc_details = fields_cc_details.into_tokens(&mut prereqs); |
| prereqs.defs.insert(local_def_id); |
| CcSnippet { |
| prereqs, |
| tokens: quote! { |
| __NEWLINE__ |
| static_assert( |
| sizeof(#adt_cc_name) == #size, |
| "Verify that ADT layout didn't change since this header got generated"); |
| static_assert( |
| alignof(#adt_cc_name) == #alignment, |
| "Verify that ADT layout didn't change since this header got generated"); |
| __NEWLINE__ |
| #public_functions_cc_details |
| #fields_cc_details |
| }, |
| } |
| }; |
| let rs_details = { |
| let adt_rs_name = &core.rs_fully_qualified_name; |
| quote! { |
| const _: () = assert!(::std::mem::size_of::<#adt_rs_name>() == |