blob: d99b144dd0feaa5df539a8c1e6a86f59b8fdd408 [file]
// 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
extern crate rustc_abi;
extern crate rustc_hir;
extern crate rustc_middle;
extern crate rustc_span;
// TODO(b/381888123): Seperate out enum generation.
use crate::format_cc_ident;
use crate::generate_doc_comment;
use crate::generate_function::{generate_thunk_call, Param};
use crate::{
crate_features, generate_const, generate_deprecated_tag, generate_must_use_tag,
generate_trait_thunks, generate_unsupported_def, get_layout, get_scalar_int_type,
get_tag_size_with_padding, is_bridged_type, is_copy, is_exported,
is_public_or_supported_export, RsSnippet, SortedByDef, TraitThunks,
};
use arc_anyhow::{Context, Result};
use code_gen_utils::{expect_format_cc_type_name, make_rs_ident, CcInclude};
use database::code_snippet::{ApiSnippets, CcPrerequisites, CcSnippet};
use database::{AdtCoreBindings, BindingsGenerator, FullyQualifiedName, SugaredTy, TypeLocation};
use error_report::{anyhow, bail, ensure};
use itertools::Itertools;
use proc_macro2::{Ident, Literal, TokenStream};
use query_compiler::post_analysis_typing_env;
use quote::{format_ident, quote};
use rustc_abi::{FieldsShape, VariantIdx, Variants};
use rustc_hir::attrs::AttributeKind;
use rustc_hir::{self as hir, Attribute, ItemKind};
use rustc_middle::mir::interpret::Scalar;
use rustc_middle::mir::ConstValue;
use rustc_middle::ty::{self, Ty, TyCtxt, TyKind, TypeFlags};
use rustc_span::def_id::{CrateNum, DefId, LocalDefId, LOCAL_CRATE};
use rustc_span::symbol::sym;
use std::collections::{BTreeSet, HashMap, HashSet};
use std::iter::once;
use std::rc::Rc;
fn has_type_or_const_vars() -> TypeFlags {
TypeFlags::HAS_TY_PARAM
| TypeFlags::HAS_CT_PARAM
| TypeFlags::HAS_TY_INFER
| TypeFlags::HAS_CT_INFER
| TypeFlags::HAS_TY_PLACEHOLDER
| TypeFlags::HAS_CT_PLACEHOLDER
| TypeFlags::HAS_TY_BOUND
| TypeFlags::HAS_CT_BOUND
}
pub(crate) fn adt_core_bindings_needs_drop<'tcx>(
bindings: &AdtCoreBindings<'tcx>,
tcx: TyCtxt<'tcx>,
) -> bool {
bindings.self_ty.needs_drop(tcx, post_analysis_typing_env(tcx, bindings.def_id))
}
/// Returns the Rust underlying type of the `cpp_enum` struct specified by the given def id.
fn cpp_enum_rust_underlying_type(tcx: TyCtxt, def_id: DefId) -> Result<Ty> {
let fields = tcx.adt_def(def_id).all_fields().collect::<Vec<_>>();
if fields.len() != 1 {
return Err(anyhow!(
"Expected exactly one field in cpp_enum struct, got {:?}",
fields.len()
));
}
let field_def_id = fields[0].did;
let field_ty = tcx.type_of(field_def_id).instantiate_identity();
Ok(field_ty)
}
/// Returns the C++ underlying type of the `cpp_enum` struct specified by the given def id.
fn cpp_enum_cpp_underlying_type(db: &dyn BindingsGenerator, def_id: DefId) -> Result<CcSnippet> {
let tcx = db.tcx();
let field_middle_ty = cpp_enum_rust_underlying_type(tcx, def_id)?;
let field_hir_ty = match tcx.hir_node_by_def_id(def_id.expect_local()) {
rustc_hir::Node::Item(hir_item) => match hir_item.kind {
ItemKind::Struct(_, _, variant_data) => {
if variant_data.fields().len() != 1 {
return Err(anyhow!(
"Expected one field in cpp_enum hir item, got {:?}",
variant_data.fields().len()
));
}
Some(variant_data.fields()[0].ty)
}
_ => {
// ItemKind is not Struct.
return Err(anyhow!(
"Unexpected `ItemKind` in cpp_enum hir item: {:?}",
hir_item.kind
));
}
},
_ => None, // HIR node is not an Item.
};
db.format_ty_for_cc(SugaredTy::new(field_middle_ty, field_hir_ty), TypeLocation::Other)
}
/// Returns a string representation of the value of a given numeric Scalar having a given TyKind.
pub fn scalar_value_to_string(tcx: TyCtxt, scalar: Scalar, kind: TyKind) -> Result<String> {
let scalar = match scalar {
Scalar::Int(i) => i,
Scalar::Ptr(..) => bail!("Pointer values cannot be used as scalar constants."),
};
// Print positive integers directly if they fit in an i64, since `int` is guaranteed to be at
// least 16 bits wide.
if matches!(kind, TyKind::Uint(_)) {
let value: u128 = scalar.to_bits_unchecked();
if value < (i16::MAX as u128) {
return Ok((value as i16).to_string());
}
}
use ty::FloatTy::*;
use ty::IntTy::*;
use ty::TyKind;
use ty::UintTy::*;
Ok(match kind {
TyKind::Bool => scalar.try_to_bool().unwrap().to_string(),
TyKind::Int(I8) => scalar.to_i8().to_string(),
TyKind::Int(I16) => scalar.to_i16().to_string(),
TyKind::Int(I32) => format!("INT32_C({})", scalar.to_i32()),
TyKind::Uint(U8) => scalar.to_u8().to_string(),
TyKind::Uint(U16) => format!("UINT16_C({})", scalar.to_u16()),
TyKind::Uint(U32) => format!("UINT32_C({})", scalar.to_u32()),
TyKind::Uint(U64) => format!("UINT64_C({})", scalar.to_u64()),
TyKind::Float(F32) => format!("{}f", scalar.to_f32()),
TyKind::Float(F64) => format!("{}L", scalar.to_f64()),
TyKind::Uint(Usize) => format!("UINT64_C({})", scalar.to_target_usize(tcx)),
// Signed integer minimums cannot be expressed with literals, as `-<int>` parses as a unary
// minus operator applied to an out-of-range (for signed types) integer literal.
TyKind::Int(I64) => {
let value = scalar.to_i64();
if value == i64::MIN {
"INT64_MIN".to_string()
} else {
format!("INT64_C({value})")
}
}
TyKind::Int(ty::IntTy::Isize) => {
let value = scalar.to_target_isize(tcx);
if value == i64::MIN {
"INT64_MIN".to_string()
} else {
format!("INT64_C({value})")
}
}
_ => bail!("Unsupported constant type: {:?}", kind),
})
}
/// Formats a struct that is annotated with the `cpp_enum` attribute.
///
/// The Rust definition for an item annotation with `cpp_enum` is expected to be a repr-transparent
/// struct with a single field. Example:
///
/// ```rs
/// #[crubit_annotate::cpp_enum("enum class")]
/// #[repr(transparent)]
/// pub struct MyEnum(i32);
///
/// impl MyEnum {
/// pub const VARIANT_0: MyEnum = MyEnum(0);
/// pub const VARIANT_1: MyEnum = MyEnum(1);
/// // ...
/// }
/// ```
///
/// This will generate (approximately) the following C++ code:
///
/// ```c++
/// enum class MyEnum : std::int32_t {
/// VARIANT_0 = 0,
/// VARIANT_1 = 1,
/// // ...
/// };
/// ```
fn generate_cpp_enum<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
core: Rc<AdtCoreBindings<'tcx>>,
) -> ApiSnippets {
let tcx = db.tcx();
let enumeration_cc_name = &core.cc_short_name;
let mut main_api_prereqs = CcPrerequisites::default();
main_api_prereqs.includes.insert(db.support_header("annotations_internal.h"));
// Generate relevant attributes.
let rs_type = core.rs_fully_qualified_name.to_string();
let mut attributes = vec![quote! {CRUBIT_INTERNAL_RUST_TYPE(#rs_type)}];
if let Some(tag) = generate_must_use_tag(tcx, core.def_id) {
attributes.push(tag);
}
if let Some(tag) = generate_deprecated_tag(tcx, core.def_id) {
attributes.push(tag);
}
// Generate the enumerator list.
let enumerator_lines: Vec<TokenStream> = tcx
.inherent_impls(core.def_id)
.iter()
.copied()
.sorted_by_def(tcx)
.flat_map(|impl_id| tcx.associated_items(impl_id).in_definition_order())
.filter_map(|assoc_item| {
if !is_exported(tcx, assoc_item.def_id) {
return None;
}
let ty::AssocKind::Const { name } = assoc_item.kind else {
db.fatal_errors().report(&format!(
"C++ enums can only have `const`s as public items, found: {:?}",
assoc_item.kind
));
return None;
};
let enumerator_name = format_cc_ident(db, name.as_str()).unwrap();
let value_kind = *cpp_enum_rust_underlying_type(tcx, core.def_id).unwrap().kind();
let scalar = match tcx.const_eval_poly(assoc_item.def_id).unwrap() {
ConstValue::Scalar(scalar) => scalar,
other => {
panic!("Unexpected non-scalar ConstValue type in cpp_enum: {other:?}")
}
};
let enumerator_value = scalar_value_to_string(tcx, scalar, value_kind)
.unwrap()
.parse::<TokenStream>()
.unwrap();
Some(quote! { #enumerator_name = #enumerator_value, })
})
.collect();
let doc_comment = generate_doc_comment(db, core.def_id);
let keyword = &core.keyword;
let underlying_cc_type_snippet = cpp_enum_cpp_underlying_type(db, core.def_id).unwrap();
let underlying_cc_type = underlying_cc_type_snippet.tokens;
let main_api = CcSnippet {
tokens: quote! {
__NEWLINE__ #doc_comment
#keyword #(#attributes)* #enumeration_cc_name : #underlying_cc_type {
#( __NEWLINE__ #enumerator_lines)*
};
__NEWLINE__
},
prereqs: main_api_prereqs + underlying_cc_type_snippet.prereqs,
};
let cc_details = CcSnippet::default();
let rs_details = RsSnippet::new(quote! {});
ApiSnippets { main_api, cc_details, rs_details }
}
fn generate_associated_item<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
assoc_item: &ty::AssocItem,
member_function_names: &mut HashSet<String>,
) -> Option<ApiSnippets> {
let tcx = db.tcx();
let def_id = assoc_item.def_id;
if !is_exported(tcx, def_id) {
return None;
}
let result = match assoc_item.kind {
ty::AssocKind::Fn { .. } => {
let result = db.generate_function(def_id);
if result.is_ok() {
let cpp_name = FullyQualifiedName::new(db, def_id).cpp_name.unwrap().to_string();
member_function_names.insert(cpp_name);
}
result
}
ty::AssocKind::Const { .. } => generate_const(db, def_id),
// TODO: b/405132277 - Rust does not support inherent associated types, but should support
// associated types when adding traits.
ty::AssocKind::Type { .. } => Err(anyhow!(
"Associated types are not yet supported, found {:?}. See b/405132277.",
assoc_item.opt_name()
)),
};
let result = result.and_then(|snippet| {
snippet.resolve_feature_requirements(crate_features(db, db.source_crate_num()))
});
match result {
Err(err) => {
if crubit_attr::get_attrs(tcx, def_id).unwrap().must_bind {
let self_name = crate::item_name(db, tcx.parent(def_id));
let item_name = crate::item_name(db, def_id);
let must_bind_message = format!(
"Failed to generate bindings for `{self_name}::{item_name}`:\n\
{err:?}\n\
This is a hard error because `{self_name}::{item_name}` was annotated with \
`#[crubit_annotate::must_bind]`"
);
db.fatal_errors().report(&must_bind_message);
}
Some(generate_unsupported_def(db, def_id, err))
}
Ok(result) => Some(result),
}
}
fn erase_regions<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> {
#[rustversion::since(2025-09-10)]
return tcx.erase_and_anonymize_regions(ty);
#[rustversion::before(2025-09-10)]
return tcx.erase_regions(ty);
}
pub fn from_trait_impls_by_argument<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
crate_num: CrateNum,
) -> Rc<HashMap<Ty<'tcx>, Vec<DefId>>> {
let tcx = db.tcx();
let from_trait = tcx.get_diagnostic_item(sym::From).expect("Could not find From trait");
let impls_iter: Box<dyn Iterator<Item = DefId>> = if crate_num == LOCAL_CRATE {
Box::new(tcx.local_trait_impls(from_trait).iter().map(|impl_id| impl_id.to_def_id()))
} else {
Box::new(
tcx.implementations_of_trait((crate_num, from_trait))
.iter()
.map(|(impl_id, _)| *impl_id),
)
};
let mut map: HashMap<Ty<'tcx>, Vec<DefId>> = HashMap::new();
for from_impl_id in impls_iter {
#[rustversion::since(2025-10-17)]
let middle_trait_header = tcx.impl_trait_header(from_impl_id);
#[rustversion::before(2025-10-17)]
let middle_trait_header = tcx
.impl_trait_header(from_impl_id)
.expect("DefId for an `From` trait impl lacked a trait header");
let trait_ref = middle_trait_header.trait_ref.instantiate_identity();
let ty = trait_ref.args.type_at(1);
// We want to check if our type has type variables and constant variables, but not
// region variables. Region variables are fine and we'll replace them with 'static.
if ty.flags().contains(has_type_or_const_vars()) {
continue;
}
// We want to work in region-erased types because that's what we will be querying by
// for lookup.
let from_self_ty = erase_regions(tcx, ty);
map.entry(from_self_ty).or_default().push(from_impl_id);
}
Rc::new(map)
}
fn generate_into_impls<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
core: &AdtCoreBindings<'tcx>,
) -> ApiSnippets {
let tcx = db.tcx();
let cc_struct_name = &core.cc_short_name;
let into_trait = tcx.get_diagnostic_item(sym::Into).expect("Could not find Into trait");
let from_map = db.from_trait_impls_by_argument(core.def_id.krate);
let from_impls = from_map.get(&core.self_ty).into_iter().flat_map(|vec| vec.iter()).filter_map(
|from_impl_id| {
#[rustversion::since(2025-10-17)]
let middle_trait_header = tcx.impl_trait_header(*from_impl_id);
#[rustversion::before(2025-10-17)]
let middle_trait_header = tcx
.impl_trait_header(*from_impl_id)
.expect("DefId for a `From` trait impl lacked a trait header");
let trait_ref = middle_trait_header.trait_ref.instantiate_identity();
let from_middle_ty = trait_ref.args.type_at(0);
// If our type contains type variables or constant variables (but not region variables),
// we can't generate an `into` impl.
if from_middle_ty.flags().contains(has_type_or_const_vars()) {
return None;
}
let sugar_ty = SugaredTy::missing_hir(from_middle_ty);
// We know that our type will always appear in FnReturn position for the `into` method.
// If our type isn't C++-compatible, we can't generate an `into` impl.
let cc_ty = db.format_ty_for_cc(sugar_ty, TypeLocation::FnReturn).ok()?;
Some((from_middle_ty, cc_ty, *from_impl_id))
},
);
let into_impls =
tcx.non_blanket_impls_for_ty(into_trait, core.self_ty).filter_map(|into_impl_id| {
#[rustversion::since(2025-10-17)]
let middle_trait_header = tcx.impl_trait_header(into_impl_id);
#[rustversion::before(2025-10-17)]
let middle_trait_header = tcx
.impl_trait_header(into_impl_id)
.expect("DefId for an `Into` trait impl lacked a trait header");
// Index 0 of our trait ref is the self type, so index 1 is the type we're converting
// into.
let into_middle_ty =
middle_trait_header.trait_ref.instantiate_identity().args.type_at(1);
let sugar_ty = SugaredTy::missing_hir(into_middle_ty);
// If our type isn't Cxx compatible, we can't generate an `into` impl.
let cc_ty = db.format_ty_for_cc(sugar_ty, TypeLocation::FnReturn).ok()?;
Some((into_middle_ty, cc_ty, into_impl_id))
});
from_impls
.chain(into_impls)
.filter_map(|(middle_ty, cc_ty, def_id)| {
let mut prereqs = CcPrerequisites::default();
let cc_ty = cc_ty.into_tokens(&mut prereqs);
// Delay converting this type until we've successfully generated the thunks.
// We generate thunks for `into` here. This relies on the blanket impls of for `Into` in the stdlib to work.
let TraitThunks {
method_name_to_cc_thunk_name,
cc_thunk_decls,
rs_thunk_impls: rs_details,
} = generate_trait_thunks(db, into_trait, &[middle_ty], core).ok()?;
let thunk_name = method_name_to_cc_thunk_name
.into_values()
.exactly_one()
.expect("Expecting a single `into` method");
let cc_thunk_decls = cc_thunk_decls.into_tokens(&mut prereqs);
let doc_comment = generate_doc_comment(db, def_id);
let sugar_self_ty = SugaredTy::missing_hir(core.self_ty);
let self_cpp_ty = db
.format_ty_for_cc(
sugar_self_ty,
TypeLocation::FnParam { is_self_param: true, elided_is_output: true },
)
.expect(
"ADT's self type should be C++-convertible after generate_adt_core succeeds",
);
let self_cpp_ty = self_cpp_ty.into_tokens(&mut prereqs);
let impl_body = generate_thunk_call(
db,
def_id,
thunk_name.clone(),
SugaredTy::missing_hir(middle_ty),
/*takes_self_by_copy=*/ is_copy(tcx, def_id, core.self_ty),
/*has_self_param=*/ true,
&[Param {
cc_name: format_ident!("self"),
cpp_type: self_cpp_ty,
ty: sugar_self_ty,
}],
)
.expect("Self type of `Into` impl should be bridgeable");
let impl_body_tokens = impl_body.into_tokens(&mut prereqs);
prereqs.move_defs_to_fwd_decls();
Some(ApiSnippets {
main_api: CcSnippet {
tokens: quote! {
__NEWLINE__ #doc_comment
explicit operator #cc_ty ( ) ; __NEWLINE__
__NEWLINE__
},
prereqs,
},
cc_details: CcSnippet::new(quote! {
#cc_thunk_decls
inline #cc_struct_name :: operator #cc_ty ( ) {
#impl_body_tokens
}
}),
rs_details,
})
})
.collect()
}
/// Formats an algebraic data type (an ADT - a struct, an enum, or a union)
/// represented by `core`. This function is infallible - after
/// `generate_adt_core` returns success we have committed to emitting C++
/// bindings for the ADT.
pub fn generate_adt<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
core: Rc<AdtCoreBindings<'tcx>>,
) -> ApiSnippets {
let tcx = db.tcx();
let adt_cc_name = &core.cc_short_name;
// Handle `cpp_enum` structs.
let crubit_attrs = crubit_attr::get_attrs(tcx, core.def_id).unwrap_or_default();
if crubit_attrs.cpp_enum.is_some() {
return generate_cpp_enum(db, core);
}
let default_ctor_snippets = db.generate_default_ctor(core.clone()).unwrap_or_else(|err| err);
let destructor_snippets = if adt_core_bindings_needs_drop(&core, 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,
mut cc_thunk_decls,
rs_thunk_impls: rs_details,
} = generate_trait_thunks(db, drop_trait_id, &[], &core)
.expect("`generate_adt_core` should have already validated `Drop` support");
// Don't introduce additional feature prerequisites for the `Drop` trait impl, as this
// will cause type generation to fail based on an API that isn't even user-accessible.
cc_thunk_decls.prereqs.required_features = flagset::FlagSet::empty();
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.generate_copy_ctor_and_assignment_operator(core.clone()).unwrap_or_else(|err| err);
let move_ctor_and_assignment_snippets =
db.generate_move_ctor_and_assignment_operator(core.clone()).unwrap_or_else(|err| err);
let relocating_ctor_snippets = generate_relocating_ctor(db, core.clone());
let tuple_struct_ctor = generate_tuple_struct_ctor(db, core.clone()).unwrap_or_default();
let mut member_function_names = HashSet::<String>::new();
let impl_items_snippets = tcx
.inherent_impls(core.def_id)
.iter()
.copied()
.sorted_by_def(tcx)
.flat_map(|impl_id| tcx.associated_items(impl_id).in_definition_order())
.flat_map(|assoc_item| generate_associated_item(db, assoc_item, &mut member_function_names))
.collect();
let into_operator_snippets = generate_into_impls(db, core.as_ref());
let ApiSnippets {
main_api: public_functions_main_api,
cc_details: public_functions_cc_details,
rs_details: public_functions_rs_details,
} = [
default_ctor_snippets,
tuple_struct_ctor,
destructor_snippets,
move_ctor_and_assignment_snippets,
copy_ctor_and_assignment_snippets,
relocating_ctor_snippets,
impl_items_snippets,
into_operator_snippets,
]
.into_iter()
.collect();
let ApiSnippets {
main_api: fields_main_api,
cc_details: fields_cc_details,
rs_details: fields_rs_details,
} = generate_fields(db, &core, &member_function_names);
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_hir::attrs::ReprPacked { .. }))
{
attributes.push(quote! { __attribute__((packed)) })
}
// Additional attributes
if let Some(tag) = generate_must_use_tag(tcx, core.def_id) {
attributes.push(tag);
}
if let Some(tag) = generate_deprecated_tag(tcx, core.def_id) {
attributes.push(tag);
}
let doc_comment = generate_doc_comment(db, core.def_id);
let keyword = &core.keyword;
let mut prereqs = CcPrerequisites::default();
prereqs.includes.insert(db.support_header("annotations_internal.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(&core.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(core.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;
let mut extern_c_decls = BTreeSet::new();
let public_functions_rs_details =
public_functions_rs_details.into_tokens(&mut extern_c_decls);
let fields_rs_details = fields_rs_details.into_tokens(&mut extern_c_decls);
RsSnippet {
tokens: quote! {
const _: () = assert!(::std::mem::size_of::<#adt_rs_name>() == #size);
const _: () = assert!(::std::mem::align_of::<#adt_rs_name>() == #alignment);
#public_functions_rs_details
#fields_rs_details
},
extern_c_decls,
}
};
ApiSnippets { main_api, cc_details, rs_details }
}
/// Implementation of `BindingsGenerator::generate_adt_core`.
pub fn generate_adt_core<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
def_id: DefId,
) -> Result<Rc<AdtCoreBindings<'tcx>>> {
let tcx = db.tcx();
// Note: we erase regions in order to get bindings regardless of what lifetime parameters are
// present. We want to generate bindings for functions regardless of their lifetime bounds, as
// C++ cannot special-case the availability of a function based on lifetimes.
let self_ty = erase_regions(tcx, tcx.type_of(def_id).instantiate_identity());
assert!(self_ty.is_adt());
assert!(is_public_or_supported_export(db, def_id), "Caller should verify");
let fully_qualified_name = FullyQualifiedName::new(db, def_id);
let rs_fully_qualified_name = fully_qualified_name.format_for_rs();
let cpp_name = format_cc_ident(db, fully_qualified_name.cpp_name.unwrap().as_str())
.context("Error formatting item name")?;
// The check below ensures that `generate_trait_thunks` will succeed for the
// `Drop`, `Default`, and/or `Clone` trait. Ideally we would directly check
// if `generate_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
// `generate_adt_core`.
fully_qualified_name.format_for_cc(db).with_context(|| {
format!("Error formatting the fully-qualified C++ name of `{cpp_name}`")
})?;
let adt_def = self_ty.ty_adt_def().expect("`def_id` needs to identify an ADT");
let crubit_attrs = crubit_attr::get_attrs(tcx, def_id).unwrap_or_default();
let keyword = match adt_def.adt_kind() {
ty::AdtKind::Struct => match crubit_attrs.cpp_enum {
Some(cpp_enum_symbol) => {
let s = cpp_enum_symbol.as_str();
match s {
"enum" => quote! { enum },
"enum class" => quote! { enum class },
_ => panic!("Unsupported `cpp_enum` tag: {s}"),
}
}
None => quote! { struct },
},
ty::AdtKind::Enum => quote! { struct },
ty::AdtKind::Union => quote! { union },
};
// Verify that `cpp_enum` structs are also repr-transparent.
if crubit_attrs.cpp_enum.is_some() {
ensure!(
adt_def.repr().transparent(),
"`cpp_enum` struct must be annotated with `#[repr(transparent)]`"
)
}
let layout = get_layout(tcx, self_ty)
.with_context(|| format!("Error computing the layout of #{cpp_name}"))?;
ensure!(
layout.backend_repr().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: cpp_name,
rs_fully_qualified_name,
self_ty,
alignment_in_bytes,
size_in_bytes,
}))
}
fn hir_fields_per_variant<'tcx>(
tcx: TyCtxt<'tcx>,
local_def_id: LocalDefId,
) -> Vec<&'tcx [hir::FieldDef<'tcx>]> {
let hir::Node::Item(item) = tcx.hir_node_by_def_id(local_def_id) else {
panic!("internal error: def_id referring to an ADT was not a HIR Item.");
};
match &item.kind {
hir::ItemKind::Struct(_, _, variant) | hir::ItemKind::Union(_, _, variant) => {
vec![variant.fields()]
}
hir::ItemKind::Enum(_, _, enum_info) => {
enum_info.variants.iter().map(|variant| variant.data.fields()).collect()
}
_ => {
panic!("internal error: def_id referring to a non-enum ADT was not a struct or union.")
}
}
}
struct IndexedVariantField<'tcx> {
index: usize,
field_def: &'tcx ty::FieldDef,
hir_field_ty: Option<&'tcx hir::Ty<'tcx>>,
}
/// Given ADT bindings, iterates over the variants of that ADT and the fields of each variant.
/// For each field, iteration always provides the middle FieldDef and it's index within it's variant.
/// The hir type of the field will optionally be included if it is available.
fn variant_fields_iter<'tcx>(
tcx: TyCtxt<'tcx>,
core: &AdtCoreBindings<'tcx>,
) -> impl Iterator<Item = impl Iterator<Item = IndexedVariantField<'tcx>>> {
// If our underlying iterator is None, we produce an infinite stream of None.
// This exists to present one type with the desired behavior without boxing.
struct RepeatedNone<'tcx> {
underlying: Option<std::vec::IntoIter<&'tcx [hir::FieldDef<'tcx>]>>,
}
impl<'tcx> Iterator for RepeatedNone<'tcx> {
type Item = Option<&'tcx [hir::FieldDef<'tcx>]>;
fn next(&mut self) -> Option<Self::Item> {
match self.underlying {
Some(ref mut variant_iter) => Some(variant_iter.next()),
// If we don't have a variant iterator, we just want to produce an infinite stream of None, so we don't limit our zip below.
None => Some(None),
}
}
}
let hir_fields = core
.def_id
.as_local()
.map(|local_def_id| hir_fields_per_variant(tcx, local_def_id).into_iter());
core.self_ty
.ty_adt_def()
.expect("`core.def_id` needs to identify an ADT")
.variants()
.iter()
.zip(RepeatedNone { underlying: hir_fields })
.map(|(variant, hir_fields)| {
variant.fields.iter().enumerate().map(move |(index, field_def)| {
let hir_field_ty = hir_fields.map(|hir_fields| {
let hir_field = hir_fields
.get(index)
.expect("HIR ADT had fewer fields than rustc_middle for this variant");
assert!(field_def.did == hir_field.def_id.to_def_id());
hir_field.ty
});
IndexedVariantField { index, field_def, hir_field_ty }
})
})
}
fn anonymous_field_ident(index: usize) -> Ident {
format_ident!("__field{index}")
}
fn generate_tuple_struct_ctor<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
core: Rc<AdtCoreBindings<'tcx>>,
) -> Option<ApiSnippets> {
let tcx = db.tcx();
let TyKind::Adt(adt_def, adt_generic_args) = core.self_ty.kind() else {
panic!("Attempted to generate constructor for a non-ADT type: {:?}", core.self_ty)
};
if !adt_def.has_ctor() {
// If this is not a struct with a constructor, don't generate a C++ constructor.
return None;
}
if tcx
.get_all_attrs(core.def_id)
.iter()
.any(|attr| matches!(attr, Attribute::Parsed(AttributeKind::NonExhaustive(_))))
{
// If the definition is marked #[non_exhaustive], don't generate a C++ constructor.
// #[non_exhaustive] tuple structs do not have a public synthesized constructor.
return None;
}
let default_trait_id = tcx.get_diagnostic_item(sym::Default).expect("Default trait not found");
let clone_trait_id = tcx.lang_items().copy_trait().expect("Copy trait not found");
let unpin_trait_id = tcx.lang_items().unpin_trait().expect("Unpin trait not found");
let field_tys = variant_fields_iter(tcx, core.as_ref())
.next()
.expect("Tuple structs must have one variant")
.map(|IndexedVariantField { field_def, hir_field_ty, .. }| {
if field_def.vis != ty::Visibility::Public {
// If our synthesized constructor would have a non public visibility, don't generate it as
// we can't mirror that visibility in C++.
return None;
}
let ty = field_def.ty(tcx, adt_generic_args);
let is_default =
query_compiler::does_type_implement_trait(tcx, ty, default_trait_id, []);
let is_clone = query_compiler::does_type_implement_trait(tcx, ty, clone_trait_id, []);
let is_unpin = query_compiler::does_type_implement_trait(tcx, ty, unpin_trait_id, []);
let is_movable_in_cpp = (is_default && is_unpin) || is_clone;
if !is_movable_in_cpp {
// If one of our fields isn't movable in C++, we can't generate a C++ constructor.
return None;
}
Some(SugaredTy::new(ty, hir_field_ty))
})
.collect::<Option<Vec<_>>>()?;
let explicit = (field_tys.len() == 1).then_some(quote! { explicit });
let mut main_api_prereqs = CcPrerequisites::default();
let adt_cc_name = &core.cc_short_name;
let initializer_list = (0..field_tys.len()).map(|i| {
let cc_name = anonymous_field_ident(i);
quote! { #cc_name ( std::move ( #cc_name ) ) }
});
// If we fail to convert a field type, don't generate a constructor.
// Our uncovertible fields will be replaced by a blob of bytes that we do not want to appear
// in our API, so opt to avoid presenting a constructor for types that contain a blob of
// bytes.
let main_api_params = field_tys
.into_iter()
.enumerate()
.map(|(i, field_ty)| {
let cpp_type = db.format_ty_for_cc(field_ty, TypeLocation::Other)?;
let cc_name = anonymous_field_ident(i);
let cpp_type = cpp_type.into_tokens(&mut main_api_prereqs);
Ok(quote! { #cpp_type #cc_name })
})
.collect::<Result<Vec<TokenStream>>>()
.ok()?;
let mut prereqs = main_api_prereqs.clone();
prereqs.move_defs_to_fwd_decls();
Some(ApiSnippets {
main_api: CcSnippet {
prereqs,
tokens: quote! {
__NEWLINE__ __COMMENT__ "Synthesized tuple constructor"
#explicit #adt_cc_name (
#( #main_api_params ),*
) : #( #initializer_list ),* { }
__NEWLINE__
},
},
..Default::default()
})
}
/// Returns the body of the C++ struct that represents the given ADT.
fn generate_fields<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
core: &AdtCoreBindings<'tcx>,
member_function_names: &HashSet<String>,
) -> ApiSnippets {
let tcx = db.tcx();
let TyKind::Adt(adt_def, adt_generic_args) = core.self_ty.kind() else {
panic!("Attempted to generate fields for a non-ADT type: {:?}", core.self_ty)
};
let repr_attrs = db.repr_attrs(core.def_id);
struct FieldTypeInfo {
size: u64,
cpp_type: CcSnippet,
}
struct Field {
type_info: Result<FieldTypeInfo>,
cc_name: Ident,
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 `generate_adt_core`");
let err_fields = |err| {
vec![Field {
type_info: Err(err),
cc_name: format_ident!("__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![],
}]
};
let layout_variants = layout.variants();
// If the ADT has one variant, then just use the fields in `layout.fields`.
// If the ADT has multiple variants, then we need to use the layout of each
// variant. The `layout.fields` just contains the tag.
let fields_shape = match layout_variants {
Variants::Single { .. } | Variants::Empty => vec![&layout.fields],
Variants::Multiple { tag: _, tag_encoding: _, tag_field: _, variants } => {
variants.iter().map(|variant| &variant.fields).collect_vec()
}
};
// Used for generating enum bindings.
let is_supported_enum = adt_def.is_enum() && repr_attrs.contains(&rustc_hir::attrs::ReprC);
let tag_size_with_padding =
if is_supported_enum { get_tag_size_with_padding(layout) } else { 0 };
let variant_sizes = match layout_variants {
Variants::Multiple { tag: _, tag_encoding: _, tag_field: _, variants } => {
variants.iter().map(|layout| layout.size.bytes() - tag_size_with_padding).collect_vec()
}
Variants::Single { .. } | Variants::Empty => {
vec![core.alignment_in_bytes]
}
};
// The size of each variant. Note for enums, this removes the size (and padding)
// for the tag.
let layout_size = match layout_variants {
Variants::Single { .. } | Variants::Empty => vec![layout.size().bytes()],
Variants::Multiple { tag: _, tag_encoding: _, tag_field: _, variants } => variants
.iter()
.map(|variant| variant.size.bytes() - tag_size_with_padding)
.collect_vec(),
};
let variants_fields: Vec<Vec<Field>> = match adt_def.adt_kind() {
// Handle cases of unsupported ADTs.
ty::AdtKind::Enum if (!repr_attrs.contains(&rustc_hir::attrs::ReprC)) => {
vec![err_fields(anyhow!("No support for bindings of individual non-repr(C) `enum`s"))]
}
// Otherwise, get the fields and determine the memory layout.
_ => {
let mut variants_fields = variant_fields_iter(tcx, core)
.map(|field_iter| {
field_iter
.map(|IndexedVariantField { index, field_def, hir_field_ty }| {
let ty =
SugaredTy::new(field_def.ty(tcx, adt_generic_args), hir_field_ty);
let size =
get_layout(tcx, ty.mid()).map(|layout| layout.size().bytes());
let type_info = size.and_then(|size| {
if is_bridged_type(db, ty.mid())?.is_some() {
bail!(
"Field is a bridged type and might not be layout-compatible
with the C++ type (b/400633609)"
);
}
Ok(FieldTypeInfo {
size,
cpp_type: db
.format_ty_for_cc(ty, TypeLocation::Other)?
.resolve_feature_requirements(crate_features(
db,
db.source_crate_num(),
))?,
})
});
let name = field_def.ident(tcx).to_string();
let cc_name = code_gen_utils::unkeyword_cpp_ident(&name).to_string();
let cc_name = if member_function_names.contains(&cc_name) {
// TODO: Handle the case of name_ itself also being taken? e.g. the
// Rust struct struct S {a: i32, a_:
// i32} impl S { fn a() {} fn a_()
// {} fn a__(){}.
format!("{cc_name}_")
} else {
cc_name
};
let cc_name = format_cc_ident(db, cc_name.as_str())
.unwrap_or_else(|_err| anonymous_field_ident(index));
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) =
generate_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: generate_doc_comment(db, field_def.did),
attributes,
}
})
.collect_vec()
})
.collect_vec();
for (variant_index, variant_fields) in fields_shape.iter().enumerate() {
match variant_fields {
// Struct/Enum case
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`.
variants_fields[variant_index][index].offset = offset.bytes();
if is_supported_enum {
// Find the offset for the variant, and take it into
// account.
variants_fields[variant_index][index].offset -=
tag_size_with_padding;
}
}
// 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.
variants_fields[variant_index].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() {
variants_fields[variant_index][index].offset =
layout.fields().offset(index).bytes();
}
}
unexpected => panic!("Unexpected FieldsShape: {unexpected:?}"),
}
}
for (variant_index, variant_fields) in variants_fields.iter_mut().enumerate() {
let next_offsets = variant_fields
.iter()
.map(|Field { offset, .. }| *offset)
.skip(1)
.chain(once(layout_size[variant_index]))
.collect_vec();
for (field, next_offset) in variant_fields.iter_mut().zip(next_offsets) {
field.offset_of_next_field = next_offset;
}
}
variants_fields
}
};
let cc_details = if variants_fields.is_empty() {
CcSnippet::default()
} else {
let adt_cc_name = &core.cc_short_name;
let cc_assertions: TokenStream = match adt_def.adt_kind() {
ty::AdtKind::Struct | ty::AdtKind::Union => {
variants_fields
.iter()
.flatten()
// 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()
}
ty::AdtKind::Enum => {
// Check if each variant has the tag (and appropriate padding) in the front.
if !is_supported_enum {
variants_fields
.iter()
.flatten()
// 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()
} else {
let variant_offset_assertions: TokenStream = adt_def.variants().iter_enumerated().map(|(variant_index, variant_def)| {
let cc_variant_struct_name = format_cc_ident(db, variant_def.ident(tcx).as_str())
.unwrap_or_else(|_err| format_ident!("err_field"));
let tag_unsuffixed = Literal::u64_unsuffixed(tag_size_with_padding);
// If the variant has no fields, don't bother generating any assertions.
if variant_sizes[variant_index.index()] == 0 {
quote! {}
} else {
quote! { static_assert(#tag_unsuffixed == offsetof(#adt_cc_name, #cc_variant_struct_name)); }
}
}).collect();
// Check for each field's offsets within the variant.
let variant_field_assertions: TokenStream = variants_fields
.iter()
.enumerate()
.flat_map(|(variant_index, fields_for_variant)| {
let variant_def = adt_def.variant(VariantIdx::from_usize(variant_index));
let cc_variant = variant_def.ident(tcx);
let qualified_struct_name =
expect_format_cc_type_name(&format!("{}::__crubit_{}_struct", adt_cc_name, cc_variant));
// If the variant has no fields, don't bother generating any assertions.
if variant_def.fields.is_empty() {
quote! {}
} else {
//
fields_for_variant.iter().filter(|field| field.type_info.is_ok() && field.size() != 0 ).flat_map(move |Field { cc_name, offset, .. }| {
let offset = Literal::u64_unsuffixed(*offset);
quote! { static_assert(#offset == offsetof(#qualified_struct_name, #cc_name)); }
}).collect()
}
}).collect();
quote! {#variant_offset_assertions #variant_field_assertions }
}
}
};
CcSnippet::with_include(
quote! {
inline void #adt_cc_name::__crubit_field_offset_assertions() {
#cc_assertions
}
},
CcInclude::cstddef(),
)
};
let rs_details: RsSnippet = if is_supported_enum {
// Offsets for enums is an experimental feature.
// TODO(b/355642210): Add these assertions once they're not
// experiemtnal. let adt_rs_name =
// &core.rs_fully_qualified_name; variants_fields
// .iter()
// .enumerate()
// .map(|(variant_index, fields)| {
// let variant_def =
// adt_def.variant(VariantIdx::from_usize(variant_index)); let
// variant_name = make_rs_ident(variant_def.ident(tcx).as_str());
// let variant_offset_assertions: TokenStream = fields
// .iter()
// .map(|Field { rs_name, offset, .. }| {
// let expected_offset =
// Literal::u64_unsuffixed(*offset); let
// actual_offset = quote! {
// ::core::mem::offset_of!(#adt_rs_name, #variant_name.#rs_name)
// }; quote! { const _: () =
// assert!(#actual_offset == #expected_offset); } })
// .collect();
// variant_offset_assertions
// })
// .collect()
RsSnippet::default()
} else {
let adt_rs_name = &core.rs_fully_qualified_name;
variants_fields
.iter()
.flatten()
// 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) };
RsSnippet::new(
quote! { const _: () = assert!(#actual_offset == #expected_offset); },
)
})
.collect()
};
let main_api = {
let assertions_method_decl = if variants_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 always_omit_padding = repr_attrs.contains(&rustc_hir::attrs::ReprC)
&& variants_fields.iter().flatten().all(|field| field.type_info.is_ok());
let mut prereqs = CcPrerequisites::default();
#[derive(Debug, Default)]
struct CcFieldVisState {
is_public: Option<bool>,
}
impl CcFieldVisState {
fn public() -> Self {
Self { is_public: Some(true) }
}
/// Ensures the current field visibility matches `is_public` by returning tokens to
/// switch from `private:` to `public:` or vice versa. If the current access specifier
/// already matches the requested one, no specifier is returned.
fn set_is_public(&mut self, is_public: bool) -> TokenStream {
if self.is_public == Some(is_public) {
quote! {}
} else {
self.is_public = Some(is_public);
if is_public {
quote! { public: }
} else {
quote! { private: }
}
}
}
}
// Takes a field and converts it to a token stream.
let get_field_tokens = |field: Field,
prereqs: &mut CcPrerequisites,
current_visibility: &mut CcFieldVisState|
-> TokenStream {
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 visibility = current_visibility.set_is_public(false);
let size = Literal::u64_unsuffixed(size);
let tokens = quote! {
#visibility __NEWLINE__
__COMMENT__ #msg
unsigned char #cc_name[#size];
};
tokens
} 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 }) => {
let padding = match adt_def.adt_kind() {
ty::AdtKind::Struct | ty::AdtKind::Enum => {
assert!((field.offset + size) <= field.offset_of_next_field);
field.offset_of_next_field - field.offset - size
}
ty::AdtKind::Union => field.offset,
};
// Visibility specifier needed by the current field.
// We have to update this field's visibility before calculating its padding,
// since the padding may update the current visibility to private.
let visibility = current_visibility.set_is_public(field.is_public);
let cpp_type = cpp_type.into_tokens(prereqs);
let doc_comment = field.doc_comment;
let attributes = field.attributes;
let tokens = match adt_def.adt_kind() {
ty::AdtKind::Struct => {
// 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);
let padding_visibility = current_visibility.set_is_public(false);
quote! { #padding_visibility unsigned char #ident[#padding]; }
};
quote! {
#visibility __NEWLINE__
// The anonymous union gives more control over when exactly
// the field constructors and destructors run. For example,
// this lets us initialize the fields for the first time via
// memcpy, in the move or UnsafeRelocateTag constructor, and lets
// us destroy them only by calling into Rust.
// See also b/288138612.
union { __NEWLINE__
#doc_comment
#(#attributes)*
#cpp_type #cc_name;
};
#padding
}
}
ty::AdtKind::Union => {
if repr_attrs.contains(&rustc_hir::attrs::ReprC) {
quote! {
#visibility __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! {
#visibility __NEWLINE__
#doc_comment
struct {
#internal_padding
#cpp_type value;
} #cc_name;
}
}
}
ty::AdtKind::Enum => {
quote! {
#visibility __NEWLINE__ #cpp_type #cc_name;
}
}
};
tokens
}
}
};
// For structs and unions, we can just flatten the fields variant. For enums, we
// need to handle each variant separately.
let fields = match adt_def.adt_kind() {
ty::AdtKind::Struct | ty::AdtKind::Union => {
let mut current_visibility = CcFieldVisState::public();
variants_fields
.into_iter()
.flatten()
.map(|field| get_field_tokens(field, &mut prereqs, &mut current_visibility))
.collect()
}
ty::AdtKind::Enum if !is_supported_enum => variants_fields
.into_iter()
.flatten()
.map(|field| get_field_tokens(field, &mut prereqs, &mut Default::default()))
.collect(),
ty::AdtKind::Enum => {
// We need three things:
// 1. A representation of the tag (tag_enum).
// 2. A representation of the fields in each variant (variant_structs).
// 3. A union of the results of (2) (variants_union).
// Step 1 is ignored if there is only one variant.
// See https://doc.rust-lang.org/reference/type-layout.html#reprc-enums-with-fields
// Get tokens for the tag, if it exists.
let tag_enum = match layout_variants {
Variants::Single { .. } | Variants::Empty => quote! {},
Variants::Multiple { tag, .. } => {
let tag_ty = get_scalar_int_type(db.tcx(), *tag);
let tag_tokens = db
.format_ty_for_cc(
// An enum cannot have repr(c_char), or any other alias, so there's
// never sugar.
SugaredTy::new(tag_ty, None),
TypeLocation::Other,
)
.expect("discriminant should be a integer type.")
.into_tokens(&mut prereqs);
let variant_enum_fields: TokenStream = adt_def
.variants()
.iter_enumerated()
.map(|(variant_index, variant_def)| {
let cc_variant_name =
format_cc_ident(db, variant_def.name.as_str())
.unwrap_or_else(|_err| format_ident!("err_field"));
let tag_value = Literal::u128_unsuffixed(
adt_def.discriminant_for_variant(tcx, variant_index).val,
);
quote! {
__NEWLINE__ #cc_variant_name = #tag_value,
}
})
.collect();
quote! {
__NEWLINE__ enum class Tag : #tag_tokens {
#variant_enum_fields
}; __NEWLINE__
}
}
};
let mut tokens_per_variant: Vec<TokenStream> =
Vec::with_capacity(variants_fields.len());
for fields_for_variant in variants_fields.into_iter() {
let mut current_visibility = CcFieldVisState::default();
tokens_per_variant.push(
fields_for_variant
.into_iter()
.map(|field| {
get_field_tokens(field, &mut prereqs, &mut current_visibility)
})
.collect(),
);
}
// We need to get the alignment of each variant struct.
let variant_alignments = match layout_variants {
Variants::Multiple { tag: _, tag_encoding: _, tag_field: _, variants } => {
variants
.iter()
.map(|layout| layout.align.abi.bytes() - tag_size_with_padding)
.collect_vec()
}
Variants::Single { .. } | Variants::Empty => {
vec![core.alignment_in_bytes]
}
};
let variant_structs: TokenStream = adt_def
.variants()
.iter_enumerated()
.map(|(variant_index, variant_def)| {
// Get the variant name.
let cc_variant_struct_name = format_cc_ident(
db,
format!("__crubit_{}_struct", variant_def.ident(tcx).as_str()).as_ref(),
)
.unwrap_or_else(|_err| format_ident!("err_struct"));
// Get the corresponding field tokens.
let fields_for_variant = &tokens_per_variant[variant_index.index()];
// Get the aligment of the variant...
let variant_alignment =
Literal::u64_unsuffixed(variant_alignments[variant_index.index()]);
// Create the actual struct, if the variant has size. Otherwise, make
// a note that the variant is empty.
if variant_sizes[variant_index.index()] == 0 {
let cc_variant_name = format_cc_ident(db, variant_def.name.as_str())
.unwrap_or_else(|_err| format_ident!("err_field"));
let msg = format!(
"Variant {} has no size, so no struct is generated.",
cc_variant_name
);
quote! {__NEWLINE__
__COMMENT__ #msg}
} else {
quote! {
__NEWLINE__
struct alignas(#variant_alignment) #cc_variant_struct_name {
#fields_for_variant
};
}
}
})
.collect();
let variants_union_fields: TokenStream = adt_def
.variants()
.iter_enumerated()
.map(|(variant_index, variant_def)| {
// Get the variant name.
let cc_variant_name = format_cc_ident(db, variant_def.name.as_str())
.unwrap_or_else(|_err| format_ident!("err_field"));
let cc_variant_struct_type = format_cc_ident(
db,
format!("__crubit_{}_struct", variant_def.ident(tcx).as_str()).as_ref(),
)
.unwrap_or_else(|_err| format_ident!("err_struct"));
// If the variant has no fields (i.e. the struct is empty), we can skip
// this declaration.
if variant_sizes[variant_index.index()] == 0 {
quote! {}
} else {
quote! {
#cc_variant_struct_type #cc_variant_name; __NEWLINE__
}
}
})
.collect();
let variants_union: TokenStream = {
let has_no_fields =
variant_sizes.iter().all(|size_of_variant| *size_of_variant == 0);
if has_no_fields {
// If there are no fields in any variant, we must skip this union
quote! {}
} else {
quote! {
public: union {
#variants_union_fields
};
}
}
};
// Combine everything together.
quote! {
#variant_structs __NEWLINE__
#tag_enum __NEWLINE__
public: Tag tag; __NEWLINE__
#variants_union
}
}
};
CcSnippet {
prereqs,
tokens: quote! {
#fields
#assertions_method_decl
},
}
};
ApiSnippets { main_api, cc_details, rs_details }
}
/// Generates the `(UnsafeRelocateTag, T&&)` constructor for the given ADT.
fn generate_relocating_ctor<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
core: Rc<AdtCoreBindings<'tcx>>,
) -> ApiSnippets {
let adt_cc_name = &core.cc_short_name;
let main_api = CcSnippet::with_include(
quote! {
#adt_cc_name(::crubit::UnsafeRelocateTag, #adt_cc_name&& value) {
// This is a bit tricky. Note that the lifetime of `this` has already begun,
// so memcpy is only being used to copy the object representation.
//
// Second, note that the current type is trivially relocatable
// (because it came from Rust).
//
// Finally, note that none of the fields are initialized yet. (Each is in a
// union.)
//
// So while `memcpy` doesn't usually work, it does here.
memcpy(this, &value, sizeof(value));
}
},
db.support_header("internal/slot.h"),
);
ApiSnippets { main_api, ..Default::default() }
}