blob: 57b0587b0885510ace54aa0a47a06771a5ae07de [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
#![allow(clippy::collapsible_else_if)]
use arc_anyhow::{Context, Result};
use code_gen_utils::{expect_format_cc_type_name, make_rs_ident};
use cpp_type_name::{cpp_tagless_type_name_for_record, cpp_type_name_for_record};
use database::code_snippet::{
ApiSnippets, AssertableTrait, Assertion, BitPadding, BitfieldComment, DeriveAttr,
DocCommentAttr, Feature, FieldDefinition, FieldType, GeneratedItem, MustUseAttr,
NoUniqueAddressAccessor, RecursivelyPinnedAttr, SizeofImpl, StructOrUnion, Thunk, ThunkImpl,
UpcastImpl, UpcastImplBody, Visibility,
};
use database::db;
use database::rs_snippet::{should_derive_clone, should_derive_copy, RsTypeKind};
use database::BindingsGenerator;
use error_report::{bail, ensure};
use flagset::FlagSet;
use generate_comment::generate_doc_comment;
use ir::*;
use itertools::Itertools;
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use quote::ToTokens;
use std::collections::HashMap;
use std::iter;
use std::num::NonZeroUsize;
use std::rc::Rc;
/// Returns whether fields of type `ty` need to be wrapped in `ManuallyDrop<T>`
/// to prevent the fields from being destructed twice (once by the C++
/// destructor calkled from the `impl Drop` of the struct and once by `drop` on
/// the Rust side).
///
/// A type is safe to destroy twice if it implements `Copy`. Fields of such
/// don't need to be wrapped in `ManuallyDrop<T>` even if the struct
/// containing the fields provides an `impl Drop` that calles into a C++
/// destructor (in addition to dropping the fields on the Rust side).
///
/// Note that it is not enough to just be `!needs_drop<T>()`: Rust only
/// guarantees that it is safe to use-after-destroy for `Copy` types. See
/// e.g. the documentation for
/// [`drop_in_place`](https://doc.rust-lang.org/std/ptr/fn.drop_in_place.html):
///
/// > if `T` is not `Copy`, using the pointed-to value after calling
/// > `drop_in_place` can cause undefined behavior
///
/// For non-Copy union fields, failing to use `ManuallyDrop<T>` would
/// additionally cause a compile-time error until https://github.com/rust-lang/rust/issues/55149 is stabilized.
fn needs_manually_drop(ty: &RsTypeKind) -> bool {
!ty.implements_copy()
}
/// Generates Rust source code for a given incomplete record declaration.
pub fn generate_incomplete_record(
db: &dyn BindingsGenerator,
incomplete_record: Rc<IncompleteRecord>,
) -> Result<ApiSnippets> {
// If the record won't have bindings, we default to `public` to keep going anyway.
let visibility = db
.has_bindings(ir::Item::IncompleteRecord(incomplete_record.clone()))
.unwrap_or_default()
.visibility;
let cc_type = expect_format_cc_type_name(incomplete_record.cc_name.identifier.as_ref());
let namespace_qualifier = db.ir().namespace_qualifier(&incomplete_record).format_for_cc()?;
Ok(ApiSnippets {
generated_items: HashMap::from([(
incomplete_record.id,
GeneratedItem::ForwardDeclare {
visibility,
ident: make_rs_ident(incomplete_record.rs_name.identifier.as_ref()),
symbol: quote! {#namespace_qualifier #cc_type}.to_string(),
},
)]),
..Default::default()
})
}
fn make_rs_field_ident(field: &Field, field_index: usize) -> Ident {
match field.rust_identifier.as_ref() {
None => make_rs_ident(&format!("__unnamed_field{}", field_index)),
Some(Identifier { identifier }) => make_rs_ident(identifier),
}
}
/// Gets the type of `field` for layout purposes.
///
/// Note that `get_field_rs_type_kind_for_layout` may return Err even if
/// `rs_type_kind` returns Ok.
///
/// In particular, this happens if the field has an unknown size. For example,
/// if it is an error type, or uses an attribute which is not
/// supported (with the current Crubit features), such as
/// `[[no_unique_address]]`.
///
/// Such unsupported fields should be replaced with a typeless, unaligned block
/// of memory, of a size that can fill up space to the next field.
///
/// See docs/struct_layout
fn get_field_rs_type_kind_for_layout(
db: &dyn BindingsGenerator,
record: &Record,
field: &Field,
) -> Result<RsTypeKind> {
if field.is_no_unique_address {
bail!("`[[no_unique_address]]` attribute was present.");
}
match &field.unknown_attr {
Err(e) => bail!("{e}"),
Ok(None) => (),
Ok(Some(unknown_attr)) => {
// Both the template definition and its instantiation should enable experimental
// features.
for target in record.defining_target().into_iter().chain([&record.owning_target]) {
let enabled_features = db.ir().target_crubit_features(target);
ensure!(
enabled_features.contains(crubit_feature::CrubitFeature::Experimental),
"unknown field attributes are only supported with experimental features \
enabled on {target}\nUnknown attribute: {unknown_attr}`"
);
}
}
}
let type_kind = match &field.type_ {
Ok(t) => db.rs_type_kind(t.clone())?,
Err(e) => bail!("{e}"),
};
if let RsTypeKind::Error { error, .. } = type_kind {
return Err(error.clone());
}
if type_kind.is_bridge_type() {
bail!("Bridge-by-value types are not supported in struct fields.")
}
for target in record.defining_target().into_iter().chain([&record.owning_target]) {
let enabled_features = db.ir().target_crubit_features(target);
let (missing_features, reason) = type_kind.required_crubit_features(db, enabled_features);
ensure!(
missing_features.is_empty(),
"missing features: [{missing_features}]: {reason}",
missing_features = missing_features.into_iter().map(|f| f.aspect_hint()).join(", ")
);
}
// In supported, we replace nontrivial fields with opaque blobs.
// This is because we likely don't want the `ManuallyDrop<T>` solution to be the
// one users get.
//
// Users can still work around this with accessor functions.
if record.should_implement_drop() && !record.is_union() && needs_manually_drop(&type_kind) {
for target in record.defining_target().into_iter().chain([&record.owning_target]) {
let enabled_features = db.ir().target_crubit_features(target);
ensure!(
enabled_features.contains(crubit_feature::CrubitFeature::Experimental),
"nontrivial fields would be destroyed in the wrong order"
);
}
}
Ok(type_kind)
}
fn collect_unqualified_member_functions_from_all_bases(
db: &dyn BindingsGenerator,
record: &Record,
) -> Rc<[Rc<Func>]> {
let ir = db.ir();
record
.unambiguous_public_bases
.iter()
.flat_map(|base_class| {
let Ok(item) = ir.find_decl::<Item>(base_class.base_record_id) else {
return vec![];
};
match item {
Item::Record(base_record) => {
db.collect_unqualified_member_functions(base_record.clone()).to_vec()
}
_ => vec![],
}
})
.collect()
}
/// Implementation of `BindingsGenerator::collect_unqualified_member_functions`.
pub fn collect_unqualified_member_functions(
db: &dyn BindingsGenerator,
record: Rc<Record>,
) -> Rc<[Rc<Func>]> {
let ir = db.ir();
record
.child_item_ids
.iter()
.filter_map(|id| {
let Ok(child_item) = ir.find_decl::<Item>(*id) else {
return None;
};
if let Item::Func(member_function) = child_item {
if let UnqualifiedIdentifier::Identifier(_) = &member_function.rs_name {
return Some(member_function.clone());
}
}
None
})
.collect()
}
/// Removes functions that are ambiguous from the list of inherited functions.
///
/// Ambiguous functions are functions that have the same name as a function in
/// the base class.
fn filter_out_ambiguous_member_functions(
db: &dyn BindingsGenerator,
derived_record: Rc<Record>,
inherited_functions: Rc<[Rc<Func>]>,
) -> Rc<[Rc<Func>]> {
let derived_member_functions = db
.collect_unqualified_member_functions(derived_record.clone())
.iter()
.map(|func| (func.rs_name.clone(), func.clone()))
.collect::<HashMap<_, _>>();
let mut func_counter = HashMap::<_, (&Rc<Func>, u32)>::new();
for func in inherited_functions.iter() {
let Ok(Some(_)) = db.generate_function(func.clone(), None) else {
continue;
};
let unqualified_name = &func.rs_name;
if derived_member_functions.contains_key(unqualified_name) {
continue;
}
func_counter
.entry(unqualified_name.clone())
.and_modify(|pair| pair.1 += 1)
.or_insert((func, 1));
}
func_counter
.values()
.filter_map(|(func, count)| if *count == 1 { Some((*func).clone()) } else { None })
// Sort by name to make the output deterministic.
.sorted_by_key(|func| func.rs_name.identifier_as_str().unwrap().to_string())
.collect()
}
#[allow(clippy::too_many_arguments)]
fn field_definition(
db: &dyn BindingsGenerator,
record: &Record,
field: Option<&ir::Field>,
field_index: usize,
prev_end: usize,
offset: usize,
end: usize,
desc: &[BitfieldComment],
override_alignment: &mut bool,
fields_that_must_be_copy: &mut Vec<TokenStream>,
) -> Result<FieldDefinition> {
// opaque blob representations are always unaligned, even though the actual C++
// field might be aligned. To put the current field at the right offset, we
// might need to insert some extra padding.
//
// No padding should be needed if the type of the current field is
// known (i.e. if the current field is correctly aligned based on
// its original type).
//
// We also don't need padding if we're in a union.
let padding_size_in_bits = if record.is_union()
|| field.map(|f| get_field_rs_type_kind_for_layout(db, record, f).is_ok()).unwrap_or(false)
{
0
} else {
let padding_start = (prev_end + 7) / 8 * 8; // round up to byte boundary
offset - padding_start
};
let padding = NonZeroUsize::new(padding_size_in_bits).map(BitPadding);
// Bitfields get represented by private padding to ensure overall
// struct layout is compatible.
let Some(field) = field else {
*override_alignment = true;
return Ok(FieldDefinition::Bitfield {
field_index,
desc: desc.to_vec(),
padding,
bits: BitPadding(
NonZeroUsize::new(end - offset)
.expect("Bit padding should always be greater than 0"),
),
});
};
let ident = make_rs_field_ident(field, field_index);
let field_rs_type_kind = get_field_rs_type_kind_for_layout(db, record, field);
let doc_comment = match &field_rs_type_kind {
Ok(_) => generate_doc_comment(field.doc_comment.as_deref(), None, db.environment()),
Err(msg) => {
use std::fmt::Write;
let mut new_text = field
.doc_comment
.as_deref()
.map(|doc_comment| format!("{doc_comment}\n\n"))
.unwrap_or_default();
let _ = write!(
&mut new_text,
"Reason for representing this field as a blob of bytes:\n{msg:#}"
);
generate_doc_comment(Some(new_text.as_str()), None, db.environment())
}
};
let visibility = if field.access == AccessSpecifier::Public && field_rs_type_kind.is_ok() {
db::type_visibility(db, &record.owning_target, field_rs_type_kind.clone().unwrap())
.unwrap_or_default()
} else {
Visibility::PubCrate
};
let field_type = match field_rs_type_kind {
Err(_) => {
*override_alignment = true;
FieldType::Erased(BitPadding(
NonZeroUsize::new(end - field.offset)
.expect("Bit padding should always be greater than 0"),
))
}
Ok(type_kind) => {
let ty = type_kind.to_token_stream(db);
let mut wrap_in_manually_drop = false;
if record.should_implement_drop() || record.is_union() {
if needs_manually_drop(&type_kind) {
// TODO(b/212690698): Avoid (somewhat unergonomic) ManuallyDrop
// if we can ask Rust to preserve field destruction order if the
// destructor is the SpecialMemberFunc::NontrivialMembers
// case.
wrap_in_manually_drop = true;
} else {
fields_that_must_be_copy.push(ty.clone());
}
};
FieldType::Type { needs_manually_drop: wrap_in_manually_drop, ty }
}
};
Ok(FieldDefinition::Field { field_index, padding, doc_comment, visibility, ident, field_type })
}
/// Implementation of `BindingsGenerator::generate_record`.
pub fn generate_record(db: &dyn BindingsGenerator, record: Rc<Record>) -> Result<ApiSnippets> {
let record_rs_type_kind = db.rs_type_kind(record.as_ref().into())?;
if matches!(
&record_rs_type_kind,
RsTypeKind::Record { uniform_repr_template_type: Some(_), .. }
) {
return Ok(ApiSnippets::default());
}
if record_rs_type_kind.as_c9_co().is_some() {
return Ok(ApiSnippets::default());
}
if record_rs_type_kind.is_bridge_type() {
return Ok(ApiSnippets::default());
}
let ir = db.ir();
let crate_root_path = ir.crate_root_path_tokens();
let ident = make_rs_ident(record.rs_name.identifier.as_ref());
let namespace_qualifier = ir.namespace_qualifier(&record).format_for_rs();
let qualified_ident = {
quote! { #crate_root_path:: #namespace_qualifier #ident }
};
struct FieldWithLayout<'a> {
/// The IR field. Note that bitfields are represented as `None`.
ir: Option<&'a ir::Field>,
/// The offset of the field in the struct.
offset: usize,
/// The offset of the end of the field or `None` for opaque fields.
end: Option<usize>,
description: Vec<BitfieldComment>,
}
let fields_with_bounds: Vec<FieldWithLayout> = record
.fields
.iter()
.filter_map(|field| {
let size = NonZeroUsize::new(field.size)?;
Some(FieldWithLayout {
// We don't represent bitfields directly in Rust. We drop the field itself here
// and only retain the offset information. Adjacent bitfields then get merged in
// the next step.
ir: if field.is_bitfield { None } else { Some(field) },
offset: field.offset,
// We retain the end offset of fields only if we have a matching Rust type
// to represent them. Otherwise we'll fill up all the space to the next field.
// See: docs/struct_layout
end: match get_field_rs_type_kind_for_layout(db, &record, field) {
// Regular field
Ok(_rs_type) => Some(field.offset + field.size),
// Opaque field
Err(_error) => {
if record.is_union() {
Some(field.size)
} else {
None
}
}
},
description: vec![BitfieldComment {
field_name: field.rust_identifier.as_ref().map(|i| i.identifier.clone()),
bits: size,
}],
})
})
// Merge consecutive bitfields. This is necessary because they may share storage in the
// same byte.
.coalesce(|first, second| {
if first.ir.is_none() && second.ir.is_none() {
Ok(FieldWithLayout {
ir: None,
offset: first.offset,
end: second.end,
description: [first.description, second.description].concat(),
})
} else {
Err((first, second))
}
})
.collect();
let mut override_alignment = record.override_alignment;
let mut fields_that_must_be_copy = vec![];
// Pair up fields with the preceeding and following fields (if any):
// - the end offset of the previous field determines if we need to insert
// padding.
// - the start offset of the next field may be need to grow the current field to
// there.
// This uses two separate `map` invocations on purpose to limit available state.
let field_definitions = iter::once(None)
.chain(fields_with_bounds.iter().map(Some))
.chain(iter::once(None))
.tuple_windows()
.enumerate()
.map(|(index, (prev, cur, next))| {
let cur = cur.unwrap();
let prev_end = prev.and_then(|p| p.end).unwrap_or(cur.offset);
let next_offset = next.map(|n| n.offset);
let end = cur.end.or(next_offset).unwrap_or(record.size_align.size * 8);
if let Some(&FieldWithLayout { ir: Some(prev_ir), end: Some(prev_end), .. }) = prev {
assert!(
record.is_union() || prev_end <= cur.offset,
"Unexpected offset+size for field {:?} in record {}",
prev_ir,
record.cc_name
);
}
field_definition(
db,
&record,
cur.ir,
index,
prev_end,
cur.offset,
end,
&cur.description,
&mut override_alignment,
&mut fields_that_must_be_copy,
)
})
.collect::<Result<Vec<_>>>()?;
let field_offset_assertions = Assertion::FieldOffsets {
qualified_ident: qualified_ident.clone(),
fields_and_expected_offsets: fields_with_bounds
.iter()
.enumerate()
.filter_map(|(field_index, field_with_layout)| {
let field = field_with_layout.ir?;
let field_ident = make_rs_field_ident(field, field_index);
// The assertion below reinforces that the division by 8 on the next line is
// justified (because the bitfields have been coallesced / filtered out
// earlier).
assert_eq!(field.offset % 8, 0);
let expected_offset = field.offset / 8;
Some((field_ident, expected_offset))
})
.collect(),
};
let mut api_snippets = ApiSnippets::default();
let recursively_pinned_attr = if record.is_unpin() {
None
} else {
// negative_impls are necessary for universal initialization due to Rust's
// coherence rules: PhantomPinned isn't enough to prove to Rust that a
// blanket impl that requires Unpin doesn't apply. See http://<internal link>=h.f6jp8ifzgt3n
api_snippets.features |= Feature::negative_impls;
Some(RecursivelyPinnedAttr { pinned_drop: record.should_implement_drop() })
};
// Adjust the struct to also include base class subobjects, vtables, etc.
let head_padding = if let Some(first_field) = record.fields.first() {
first_field.offset / 8
} else {
record.size_align.size
};
// Prevent direct initialization for non-aggregate structs.
//
// Technically, any implicit-lifetime type is going to be fine to initialize
// using direct initialization of the fields, even if it is not an aggregate,
// because this is "just" setting memory to the appropriate values, and
// implicit-lifetime types can automatically begin their lifetime without
// running a constructor at all.
//
// However, not all types used in interop are implicit-lifetime. For example,
// while any `Unpin` C++ value is, some `!Unpin` structs (e.g. `std::list`)
// will not be. So for consistency, we apply the same rule for both
// implicit-lifetime and non-implicit-lifetime types: the C++ rule, that the
// type must be an *aggregate* type.
//
// TODO(b/232969667): Protect unions from direct initialization, too.
let allow_direct_init = record.is_aggregate || record.is_union();
let head_padding =
if head_padding > 0 || !allow_direct_init { Some(head_padding) } else { None };
api_snippets.cc_details.push(cc_struct_layout_assertion(db, &record)?);
let fully_qualified_cc_name = cpp_tagless_type_name_for_record(&record, ir)?.to_string();
let mut items = vec![];
let mut nested_items = vec![];
for &child_item_id in &record.child_item_ids {
let item = ir.find_untyped_decl(child_item_id);
api_snippets.append(db.generate_item(item.clone())?);
if item.place_in_nested_module_if_nested_in_record()
&& db.has_bindings(item.clone()).is_ok()
{
nested_items.push(child_item_id);
} else {
items.push(child_item_id);
}
}
let mut indirect_functions = vec![];
filter_out_ambiguous_member_functions(
db,
record.clone(),
collect_unqualified_member_functions_from_all_bases(db, &record),
)
.iter()
.filter_map(|unambiguous_base_class_member_function| -> Option<ApiSnippets> {
let item = ir.find_untyped_decl(unambiguous_base_class_member_function.id);
let Item::Func(ir_func) = item else { panic!("Unexpected item type: {:?}", item) };
let generated_func =
db.generate_function(ir_func.clone(), Some(record.clone())).ok().flatten()?;
Some((*generated_func.snippets).clone())
})
.for_each(|mut func_snippets| {
// After generating the functions pertaining to our record, we pull them out of the
// generated_items list and add them to the indirect_functions list.
assert_eq!(
func_snippets.generated_items.len(),
1,
"Expected exactly one generated item per function"
);
for (_itemid, generated_item) in func_snippets.generated_items.drain() {
let GeneratedItem::Func(generated_func) = generated_item else {
unreachable!("generate_function only creates GeneratedItem::Func");
};
indirect_functions.push(generated_func);
}
api_snippets.append(func_snippets);
});
// Both the template definition and its instantiation should enable experimental
// features.
let mut crubit_features = ir.target_crubit_features(&record.owning_target);
if let Some(defining_target) = record.defining_target() {
crubit_features |= ir.target_crubit_features(defining_target);
}
let mut upcast_impls = vec![];
if crubit_features.contains(crubit_feature::CrubitFeature::Experimental) {
let (new_upcast_impls, thunks, thunk_impls) = cc_struct_upcast_impl(db, &record, ir)?;
upcast_impls = new_upcast_impls;
api_snippets.thunks.extend(thunks);
api_snippets.cc_details.extend(thunk_impls);
}
let no_unique_address_accessors =
if crubit_features.contains(crubit_feature::CrubitFeature::Experimental) {
cc_struct_no_unique_address_impl(db, &record)?
} else {
vec![]
};
let incomplete_definition = if crubit_features.contains(crubit_feature::CrubitFeature::Wrapper)
{
Some(quote! {
forward_declare::unsafe_define!(forward_declare::symbol!(#fully_qualified_cc_name), #qualified_ident);
})
} else {
None
};
let cxx_impl = if fully_qualified_cc_name.contains('<') {
// cxx can't parse templated type names.
// In particular, it can only parse ::-delimited idents.
None
} else {
Some(database::code_snippet::CxxExternTypeImpl {
id: Rc::from(fully_qualified_cc_name.as_ref()),
kind: if record.is_unpin() {
database::code_snippet::CxxKind::Trivial
} else {
database::code_snippet::CxxKind::Opaque
},
})
};
let record_tokens = database::code_snippet::Record {
doc_comment_attr: generate_doc_comment(
record.doc_comment.as_deref(),
Some(&record.source_loc),
db.environment(),
),
derive_attr: generate_derives(&record),
recursively_pinned_attr,
must_use_attr: record.nodiscard.clone().map(MustUseAttr),
align: if override_alignment && record.size_align.alignment > 1 {
Some(record.size_align.alignment)
} else {
None
},
crubit_annotation: DocCommentAttr(
format!("CRUBIT_ANNOTATE: cpp_type={fully_qualified_cc_name}").into(),
),
visibility: db
.has_bindings(ir::Item::Record(record.clone()))
.unwrap_or_default()
.visibility,
struct_or_union: if record.is_union() {
StructOrUnion::Union
} else {
StructOrUnion::Struct
},
ident,
head_padding,
field_definitions,
implements_send: record.trait_derives.send,
implements_sync: record.trait_derives.sync,
cxx_impl,
incomplete_definition,
upcast_impls,
no_unique_address_accessors,
items,
nested_items,
indirect_functions,
};
api_snippets.features |= Feature::negative_impls;
let record_trait_assertions = {
let mut assert_impls = FlagSet::empty();
let mut assert_not_impls = FlagSet::empty();
if should_derive_clone(&record) {
assert_impls |= AssertableTrait::Clone;
} else {
// Can't `assert_not_impl_any!` here, because `Clone` may be
// implemented rather than derived.
}
if should_derive_copy(&record) {
assert_impls |= AssertableTrait::Copy;
} else {
assert_not_impls |= AssertableTrait::Copy;
}
if record.should_implement_drop() {
assert_impls |= AssertableTrait::Drop;
} else {
assert_not_impls |= AssertableTrait::Drop;
}
Assertion::Impls {
type_name: record_rs_type_kind.to_token_stream(db),
all_of: assert_impls,
none_of: assert_not_impls,
}
};
api_snippets.assertions.push(rs_size_align_assertions(qualified_ident, &record.size_align));
api_snippets.assertions.push(record_trait_assertions);
api_snippets.assertions.push(field_offset_assertions);
api_snippets.assertions.extend(fields_that_must_be_copy.into_iter().map(
|formatted_field_type| Assertion::Impls {
type_name: formatted_field_type,
all_of: AssertableTrait::Copy.into(),
none_of: FlagSet::empty(),
},
));
api_snippets.generated_items.insert(record.id, GeneratedItem::Record(Box::new(record_tokens)));
Ok(api_snippets)
}
pub fn rs_size_align_assertions(type_name: TokenStream, size_align: &ir::SizeAlign) -> Assertion {
Assertion::SizeAlign { type_name, size: size_align.size, alignment: size_align.alignment }
}
pub fn generate_derives(record: &Record) -> DeriveAttr {
let mut derives = vec![];
if should_derive_clone(record) {
derives.push(quote! { Clone });
}
if should_derive_copy(record) {
derives.push(quote! { Copy });
derives.push(quote! { ::ctor::MoveAndAssignViaCopy });
}
if record.trait_derives.debug == TraitImplPolarity::Positive {
derives.push(quote! { Debug });
}
for custom_trait in &record.trait_derives.custom {
// Breaks for paths right now...
derives.push(make_rs_ident(custom_trait).to_token_stream());
}
DeriveAttr(derives)
}
fn cc_struct_layout_assertion(db: &dyn BindingsGenerator, record: &Record) -> Result<ThunkImpl> {
let namespace_qualifier = db.ir().namespace_qualifier(record).format_for_cc()?;
let fields_and_expected_offsets: Vec<(TokenStream, usize)> = record
.fields
.iter()
.filter_map(|field| {
if field.access != AccessSpecifier::Public {
return None;
}
// https://en.cppreference.com/w/cpp/types/offsetof points out that "if member is [...]
// a bit-field [...] the behavior [of `offsetof` macro] is undefined.". In such
// scenario clang reports an error: cannot compute offset of bit-field 'field_name'.
if field.is_bitfield {
return None;
}
// The IR contains the offset in bits, while `CRUBIT_OFFSET_OF` returns the
// offset in bytes, so we need to convert. We can assert that
// `field.offset` is always at field boundaries, because the
// bitfields have been filtered out earlier.
assert_eq!(field.offset % 8, 0);
let expected_offset = field.offset / 8;
let field_ident =
expect_format_cc_type_name(&field.cpp_identifier.as_ref()?.identifier);
Some((field_ident, expected_offset))
})
.collect();
// only use CRUBIT_SIZEOF for alignment > 1, so as to simplify the generated
// code.
let sizeof_impl = if record.size_align.alignment > 1 {
SizeofImpl::RoundUpToAlignment
} else {
SizeofImpl::Builtin
};
Ok(ThunkImpl::LayoutAssertion {
tag_kind: if record.is_anon_record_with_typedef { None } else { Some(record.record_type) },
namespace_qualifier,
record_ident: record.cc_name.identifier.clone(),
sizeof_impl,
size: record.size_align.size,
alignment: record.size_align.alignment,
fields_and_expected_offsets,
})
}
/// Returns the accessor functions for no_unique_address member variables.
fn cc_struct_no_unique_address_impl(
db: &dyn BindingsGenerator,
record: &Record,
) -> Result<Vec<NoUniqueAddressAccessor>> {
let mut no_unique_address_accessors = vec![];
for field in &record.fields {
if field.access != AccessSpecifier::Public || !field.is_no_unique_address {
continue;
}
// `[[no_unique_address]]` cannot be applied to a bitfield.
// See e.g. https://en.cppreference.com/w/cpp/language/attributes/no_unique_address
// Indeed, this is a compilation error in Clang.
assert_eq!(field.offset % 8, 0, "invalid subobject: [[no_unique_address]] on a bitfield");
// Can't use `get_field_rs_type_kind_for_layout` here, because we want to dig
// into no_unique_address fields, despite laying them out as opaque
// blobs of bytes.
let Ok(cpp_type) = field.type_.as_ref() else {
continue;
};
let type_ident = db.rs_type_kind(cpp_type.clone()).with_context(|| {
format!("Failed to format type for field {field:?} on record {record:?}")
})?;
no_unique_address_accessors.push(NoUniqueAddressAccessor {
doc_comment: if field.size == 0 {
// These fields are not generated at all, so they need to be documented here.
generate_doc_comment(field.doc_comment.as_deref(), None, db.environment())
} else {
// all other fields already have a doc-comment at the point they were defined.
None
},
field: make_rs_ident(
&field
.rust_identifier
.as_ref()
.expect("Unnamed fields can't be annotated with [[no_unique_address]]")
.identifier,
),
type_: type_ident.to_token_stream(db),
byte_offset: field.offset / 8,
});
}
Ok(no_unique_address_accessors)
}
type UpcastImplResult = Result<UpcastImpl, String>;
/// Returns the implementation of base class conversions, for converting a type
/// to its unambiguous public base classes.
fn cc_struct_upcast_impl(
db: &dyn BindingsGenerator,
record: &Rc<Record>,
ir: &IR,
) -> Result<(Vec<UpcastImplResult>, Vec<Thunk>, Vec<ThunkImpl>)> {
let mut thunks = vec![];
let mut thunk_impls = vec![];
let mut upcast_impls = vec![];
let derived_name = db.rs_type_kind(record.as_ref().into())?.to_token_stream(db);
for base in &record.unambiguous_public_bases {
let base_record: &Rc<Record> = ir
.find_decl(base.base_record_id)
.with_context(|| format!("Can't find a base record of {:?}", record))?;
let Ok(base_type) = db.rs_type_kind(base_record.as_ref().into()) else {
// The base type is unknown to Crubit, so don't generate upcast code for it.
upcast_impls.push(Err(format!(
"'{}' cannot be upcasted to '{}' because the base type doesn't have Crubit bindings.",
&record.cc_name,
&base_record.cc_name,
)));
continue;
};
if let RsTypeKind::Error { .. } = base_type {
continue;
}
if base_type.is_bridge_type() {
// The base class isn't directly represented in Rust, so we can't upcast to it.
continue;
}
let base_name = base_type.to_token_stream(db);
let body = if let Some(offset) = base.offset {
UpcastImplBody::PointerOffset { offset }
} else {
let cast_fn_name = make_rs_ident(&format!(
"__crubit_dynamic_upcast__{derived}__to__{base}_{odr_suffix}",
derived = record.mangled_cc_name,
base = base_record.mangled_cc_name,
odr_suffix = record.owning_target.convert_to_cc_identifier(),
));
let base_cc_name = cpp_type_name_for_record(base_record.as_ref(), ir)?;
let derived_cc_name = cpp_type_name_for_record(record.as_ref(), ir)?;
thunks.push(Thunk::Upcast {
cast_fn_name: cast_fn_name.clone(),
derived_name: derived_name.clone(),
base_name: base_name.clone(),
});
thunk_impls.push(ThunkImpl::Upcast {
base_cc_name: base_cc_name.clone(),
cast_fn_name: cast_fn_name.clone(),
derived_cc_name: derived_cc_name.clone(),
});
UpcastImplBody::CastThunk {
crate_root_path: ir.crate_root_path().as_deref().map(make_rs_ident),
cast_fn_name,
}
};
upcast_impls.push(Ok(UpcastImpl {
base_name: base_name.clone(),
derived_name: derived_name.clone(),
body,
}));
}
Ok((upcast_impls, thunks, thunk_impls))
}