Support type bridging by value in rs_bindings_from cc.
PiperOrigin-RevId: 666007202
Change-Id: I6f0d0a51c630ff21bb176639f02ee8e1ae873710
diff --git a/rs_bindings_from_cc/generate_bindings/generate_func.rs b/rs_bindings_from_cc/generate_bindings/generate_func.rs
index cdd2846..39ce478 100644
--- a/rs_bindings_from_cc/generate_bindings/generate_func.rs
+++ b/rs_bindings_from_cc/generate_bindings/generate_func.rs
@@ -1706,6 +1706,7 @@
} else if !return_type.is_c_abi_compatible_by_value() {
// For return types that can't be passed by value, create a new out parameter.
// The lifetime doesn't matter, so we can insert a new anonymous lifetime here.
+ // TODO(yongheng): Switch to `void*`.
out_param = Some(quote! {
&mut ::core::mem::MaybeUninit< #return_type >
});
@@ -1872,16 +1873,44 @@
let mut param_idents =
func.params.iter().map(|p| crate::format_cc_ident(&p.identifier.identifier)).collect_vec();
+ let mut conversion_externs = quote! {};
+ let mut conversion_stmts = quote! {};
+ let convert_ident = |ident: &TokenStream| -> TokenStream {
+ let ident = format_ident!("__converted_{}", ident.to_string());
+ quote! { #ident }
+ };
let mut param_types = func
.params
.iter()
.map(|p| {
- let formatted = crate::format_cpp_type(&p.type_.cpp_type, &ir)?;
- if !db.rs_type_kind(p.type_.rs_type.clone())?.is_c_abi_compatible_by_value() {
+ let cpp_type = crate::format_cpp_type(&p.type_.cpp_type, &ir)?;
+ let arg_type = db.rs_type_kind(p.type_.rs_type.clone())?;
+ if arg_type.is_bridge_type() {
+ match &arg_type {
+ RsTypeKind::BridgeType { rust_to_cpp_converter, .. } => {
+ let convert_function = crate::format_cc_ident(rust_to_cpp_converter);
+ let ident = crate::format_cc_ident(&p.identifier.identifier);
+ let cpp_ident = convert_ident(&ident);
+ conversion_externs.extend(quote! {
+ extern "C" void #convert_function(void* rust_struct, void* cpp_struct);
+ });
+ conversion_stmts.extend(quote! {
+ ::crubit::LazyInit<#cpp_type> #cpp_ident;
+ });
+ conversion_stmts.extend(quote! {
+ #convert_function(#ident, &#cpp_ident.val);
+ });
+ Ok(quote! { void* })
+ }
+ _ => {
+ bail!("Invalid bridge type: {:?}", arg_type);
+ }
+ }
+ } else if !arg_type.is_c_abi_compatible_by_value() {
// non-Unpin types are wrapped by a pointer in the thunk.
- Ok(quote! {#formatted *})
+ Ok(quote! {#cpp_type *})
} else {
- Ok(formatted)
+ Ok(cpp_type)
}
})
.collect::<Result<Vec<_>>>()?;
@@ -1890,7 +1919,11 @@
.params
.iter()
.map(|p| {
- let ident = crate::format_cc_ident(&p.identifier.identifier);
+ let mut ident = crate::format_cc_ident(&p.identifier.identifier);
+ if db.rs_type_kind(p.type_.rs_type.clone())?.is_bridge_type() {
+ let formatted_ident = convert_ident(&ident);
+ ident = quote! { &(#formatted_ident.val) };
+ }
match p.type_.cpp_type.name.as_deref() {
Some("&") => Ok(quote! { * #ident }),
Some("&&") => Ok(quote! { std::move(* #ident) }),
@@ -1910,8 +1943,8 @@
// value across `extern "C"` ABI. (We do this after the arg_expressions
// computation, so that it's only in the parameter list, not the argument
// list.)
- let is_return_value_c_abi_compatible =
- db.rs_type_kind(func.return_type.rs_type.clone())?.is_c_abi_compatible_by_value();
+ let return_type_kind = db.rs_type_kind(func.return_type.rs_type.clone())?;
+ let is_return_value_c_abi_compatible = return_type_kind.is_c_abi_compatible_by_value();
let return_type_name = if !is_return_value_c_abi_compatible {
param_idents.insert(0, crate::format_cc_ident("__return"));
@@ -1919,7 +1952,18 @@
let mut cc_return_type = func.return_type.cpp_type.clone();
cc_return_type.is_const = false;
let return_type_name = crate::format_cpp_type(&cc_return_type, &ir)?;
- param_types.insert(0, quote! {#return_type_name *});
+ match &return_type_kind {
+ RsTypeKind::BridgeType { cpp_to_rust_converter, .. } => {
+ let convert_function = crate::format_cc_ident(cpp_to_rust_converter);
+ conversion_externs.extend(quote! {
+ extern "C" void #convert_function(void* cpp_struct, void* rust_struct);
+ });
+ param_types.insert(0, quote! {void *});
+ }
+ _ => {
+ param_types.insert(0, quote! {#return_type_name *});
+ }
+ };
quote! {void}
} else {
crate::format_cpp_type(&func.return_type.cpp_type, &ir)?
@@ -1956,10 +2000,21 @@
let return_expr = quote! {#implementation_function( #( #arg_expressions ),* )};
let return_stmt = if !is_return_value_c_abi_compatible {
- // Explicitly use placement `new` so that we get guaranteed copy elision in
- // C++17.
let out_param = ¶m_idents[0];
- quote! {new(#out_param) auto(#return_expr)}
+ match &return_type_kind {
+ RsTypeKind::BridgeType { cpp_to_rust_converter, .. } => {
+ let convert_function = crate::format_cc_ident(cpp_to_rust_converter);
+ quote! {
+ auto __original_cpp_struct = #return_expr;
+ #convert_function(&__original_cpp_struct, #out_param)
+ }
+ }
+ _ => {
+ // Explicitly use placement `new` so that we get guaranteed copy elision in
+ // C++17.
+ quote! {new(#out_param) auto(#return_expr)}
+ }
+ }
} else {
match func.return_type.cpp_type.name.as_deref() {
Some("void") => return_expr,
@@ -1984,7 +2039,10 @@
};
Ok(quote! {
+ #conversion_externs
+
extern "C" #return_type_name #thunk_ident( #( #param_types #param_idents ),* ) {
+ #conversion_stmts
#return_stmt;
}
})
diff --git a/rs_bindings_from_cc/generate_bindings/generate_record.rs b/rs_bindings_from_cc/generate_bindings/generate_record.rs
index b5ac025..845a802 100644
--- a/rs_bindings_from_cc/generate_bindings/generate_record.rs
+++ b/rs_bindings_from_cc/generate_bindings/generate_record.rs
@@ -246,6 +246,10 @@
/// Generates Rust source code for a given `Record` and associated assertions as
/// a tuple.
pub fn generate_record(db: &Database, record: &Rc<Record>) -> Result<GeneratedItem> {
+ // If the record has a bridge type, we don't need to generate any bindings.
+ if record.bridge_type_info.is_some() {
+ return Ok(GeneratedItem::default());
+ }
let ir = db.ir();
let crate_root_path = crate::crate_root_path_tokens(&ir);
let ident = make_rs_ident(record.rs_name.as_ref());
diff --git a/rs_bindings_from_cc/generate_bindings/lib.rs b/rs_bindings_from_cc/generate_bindings/lib.rs
index 45fbcfd..84da6b4 100644
--- a/rs_bindings_from_cc/generate_bindings/lib.rs
+++ b/rs_bindings_from_cc/generate_bindings/lib.rs
@@ -1086,7 +1086,12 @@
if ty.type_args.len() != 1 {
bail!("Missing pointee/referent type (need exactly 1 type argument): {:?}", ty);
}
- Ok(Rc::new(get_type_args()?.remove(0)))
+ // TODO(b/351976044): Support bridge types by pointer/reference.
+ let pointee = get_type_args()?.pop().unwrap();
+ if pointee.is_bridge_type() {
+ bail!("Bridging types are not supported as pointee/referent types.");
+ }
+ Ok(Rc::new(pointee))
};
let get_lifetime = || -> Result<Lifetime> {
if ty.lifetime_args.len() != 1 {
@@ -1145,7 +1150,13 @@
rs_imported_crate_name(&incomplete_record.owning_target, &ir),
)),
},
- Item::Record(record) => RsTypeKind::new_record(record.clone(), &ir)?,
+ Item::Record(record) => {
+ if record.bridge_type_info.is_some() {
+ RsTypeKind::new_bridge_type(record.clone())?
+ } else {
+ RsTypeKind::new_record(record.clone(), &ir)?
+ }
+ }
Item::Enum(enum_) => RsTypeKind::new_enum(enum_.clone(), &ir)?,
Item::TypeAlias(type_alias) => new_type_alias(db, type_alias.clone())?,
Item::TypeMapOverride(type_map_override) => {
@@ -1421,6 +1432,15 @@
"internal/sizeof.h".into(),
));
};
+
+ for record in ir.records() {
+ if record.bridge_type_info.is_some() {
+ internal_includes.insert(CcInclude::SupportLibHeader(
+ crubit_support_path_format.into(),
+ "internal/lazy_init.h".into(),
+ ));
+ }
+ }
for crubit_header in ["internal/cxx20_backports.h", "internal/offsetof.h"] {
internal_includes.insert(CcInclude::SupportLibHeader(
crubit_support_path_format.into(),
diff --git a/rs_bindings_from_cc/generate_bindings/rs_snippet.rs b/rs_bindings_from_cc/generate_bindings/rs_snippet.rs
index 8445004..d961486 100644
--- a/rs_bindings_from_cc/generate_bindings/rs_snippet.rs
+++ b/rs_bindings_from_cc/generate_bindings/rs_snippet.rs
@@ -341,6 +341,12 @@
Slice(Rc<RsTypeKind>),
/// Nullable T, using the rust Option type.
Option(Rc<RsTypeKind>),
+ BridgeType {
+ name: Rc<str>,
+ cpp_to_rust_converter: Rc<str>,
+ rust_to_cpp_converter: Rc<str>,
+ original_type: Rc<Record>,
+ },
Other {
name: Rc<str>,
type_args: Rc<[RsTypeKind]>,
@@ -367,6 +373,19 @@
Ok(RsTypeKind::Enum { enum_, crate_path })
}
+ pub fn new_bridge_type(original_record: Rc<Record>) -> Result<Self> {
+ if let Some(bridge_type_info) = &original_record.bridge_type_info {
+ Ok(RsTypeKind::BridgeType {
+ name: bridge_type_info.bridge_type.clone(),
+ cpp_to_rust_converter: bridge_type_info.cpp_to_rust_converter.clone(),
+ rust_to_cpp_converter: bridge_type_info.rust_to_cpp_converter.clone(),
+ original_type: original_record,
+ })
+ } else {
+ bail!("Record does not have bridge type info: {:?}", original_record);
+ }
+ }
+
pub fn new_type_map_override(
db: &dyn BindingsGenerator,
type_map_override: &TypeMapOverride,
@@ -404,6 +423,7 @@
RsTypeKind::IncompleteRecord { .. } => false,
RsTypeKind::Record { record, .. } => record.is_unpin(),
RsTypeKind::TypeAlias { underlying_type, .. } => underlying_type.is_unpin(),
+ RsTypeKind::BridgeType { .. } => true,
_ => true,
}
}
@@ -417,6 +437,10 @@
matches!(self, RsTypeKind::Pointer { .. })
}
+ pub fn is_bridge_type(&self) -> bool {
+ matches!(self, RsTypeKind::BridgeType { .. })
+ }
+
/// Returns the features required to use this type which are not already
/// enabled.
///
@@ -504,6 +528,7 @@
RsTypeKind::Primitive { .. } => require_feature(CrubitFeature::Supported, None),
RsTypeKind::Slice { .. } => require_feature(CrubitFeature::Supported, None),
RsTypeKind::Option { .. } => require_feature(CrubitFeature::Supported, None),
+ RsTypeKind::BridgeType { .. } => require_feature(CrubitFeature::Experimental, None),
// Fallback case, we can't really give a good error message here.
RsTypeKind::Other { .. } => require_feature(CrubitFeature::Experimental, None),
}
@@ -534,6 +559,7 @@
// TODO(b/274177296): Return `true` for structs where bindings replicate the type of
// all the fields.
RsTypeKind::Record { .. } => false,
+ RsTypeKind::BridgeType { .. } => false,
RsTypeKind::Other { is_same_abi, .. } => *is_same_abi,
_ => true,
}
@@ -553,6 +579,7 @@
RsTypeKind::TypeAlias { underlying_type, .. } => {
underlying_type.is_move_constructible()
}
+ RsTypeKind::BridgeType { .. } => true,
_ => true,
}
}
@@ -656,6 +683,8 @@
RsTypeKind::TypeAlias { underlying_type, .. } => underlying_type.implements_copy(),
RsTypeKind::Option(t) => t.implements_copy(),
RsTypeKind::Slice(t) => t.implements_copy(),
+ // We cannot get the information of the Rust type so we assume it is not Copy.
+ RsTypeKind::BridgeType { .. } => false,
RsTypeKind::Other { type_args, .. } => {
// All types that may appear here without `type_args` (e.g.
// primitive types like `i32`) implement `Copy`. Generic types
@@ -795,6 +824,7 @@
// TODO(jeanpierreda): This should likely be `::core::option::Option`.
quote! {Option<#type_arg>}
}
+ RsTypeKind::BridgeType { .. } => self.to_token_stream(),
RsTypeKind::Other { name, type_args, .. } => {
let name: TokenStream = name.parse().expect("Invalid RsType::name in the IR");
let generic_params =
@@ -888,6 +918,10 @@
// TODO(jeanpierreda): This should likely be `::core::option::Option`.
quote! {Option<#t>}
}
+ RsTypeKind::BridgeType { name, .. } => {
+ let ident = make_rs_ident(name);
+ quote! { #ident }
+ }
RsTypeKind::Other { name, type_args, .. } => {
let name: TokenStream = name.parse().expect("Invalid RsType::name in the IR");
let generic_params =
@@ -930,6 +964,7 @@
}
RsTypeKind::Slice(t) => self.todo.push(t),
RsTypeKind::Option(t) => self.todo.push(t),
+ RsTypeKind::BridgeType { .. } => {}
RsTypeKind::Other { type_args, .. } => self.todo.extend(type_args.iter().rev()),
};
Some(curr)
diff --git a/rs_bindings_from_cc/importers/cxx_record.cc b/rs_bindings_from_cc/importers/cxx_record.cc
index a6a6c01..3f6d0c4 100644
--- a/rs_bindings_from_cc/importers/cxx_record.cc
+++ b/rs_bindings_from_cc/importers/cxx_record.cc
@@ -122,36 +122,36 @@
llvm::report_fatal_error("Unrecognized clang::TagKind");
}
-std::optional<BridgingTypeInfo> GetBridgingTypeInfo(
+std::optional<BridgeTypeInfo> GetBridgeTypeInfo(
const clang::RecordDecl* record_decl) {
- constexpr absl::string_view kBridgingTypeTag = "crubit_bridging_type";
- constexpr absl::string_view kBridgingTypeRustToCppConverterTag =
- "crubit_bridging_type_rust_to_cpp_converter";
- constexpr absl::string_view kBridgingTypeCppToRustConverterTag =
- "crubit_bridging_type_cpp_to_rust_converter";
- CHECK_OK(RequireSingleStringArgIfExists(record_decl, kBridgingTypeTag));
+ constexpr absl::string_view kBridgeTypeTag = "crubit_bridge_type";
+ constexpr absl::string_view kBridgeTypeRustToCppConverterTag =
+ "crubit_bridge_type_rust_to_cpp_converter";
+ constexpr absl::string_view kBridgeTypeCppToRustConverterTag =
+ "crubit_bridge_type_cpp_to_rust_converter";
+ CHECK_OK(RequireSingleStringArgIfExists(record_decl, kBridgeTypeTag));
CHECK_OK(RequireSingleStringArgIfExists(record_decl,
- kBridgingTypeRustToCppConverterTag));
+ kBridgeTypeRustToCppConverterTag));
CHECK_OK(RequireSingleStringArgIfExists(record_decl,
- kBridgingTypeCppToRustConverterTag));
- auto bridging_type =
- GetAnnotateArgAsStringByAttribute(record_decl, kBridgingTypeTag);
- auto bridging_type_rust_to_cpp_converter = GetAnnotateArgAsStringByAttribute(
- record_decl, kBridgingTypeRustToCppConverterTag);
- auto bridging_type_cpp_to_rust_converter = GetAnnotateArgAsStringByAttribute(
- record_decl, kBridgingTypeCppToRustConverterTag);
+ kBridgeTypeCppToRustConverterTag));
+ auto bridge_type =
+ GetAnnotateArgAsStringByAttribute(record_decl, kBridgeTypeTag);
+ auto bridge_type_rust_to_cpp_converter = GetAnnotateArgAsStringByAttribute(
+ record_decl, kBridgeTypeRustToCppConverterTag);
+ auto bridge_type_cpp_to_rust_converter = GetAnnotateArgAsStringByAttribute(
+ record_decl, kBridgeTypeCppToRustConverterTag);
- if (bridging_type.has_value()) {
- CHECK(bridging_type_rust_to_cpp_converter.has_value())
- << "Missing " << kBridgingTypeRustToCppConverterTag << " for "
- << kBridgingTypeTag << " " << *bridging_type;
- CHECK(bridging_type_cpp_to_rust_converter.has_value())
- << "Missing " << kBridgingTypeCppToRustConverterTag << " for "
- << kBridgingTypeTag << " " << *bridging_type;
- return BridgingTypeInfo{
- .bridging_type = *bridging_type,
- .rust_to_cpp_converter = *bridging_type_rust_to_cpp_converter,
- .cpp_to_rust_converter = *bridging_type_cpp_to_rust_converter};
+ if (bridge_type.has_value()) {
+ CHECK(bridge_type_rust_to_cpp_converter.has_value())
+ << "Missing " << kBridgeTypeRustToCppConverterTag << " for "
+ << kBridgeTypeTag << " " << *bridge_type;
+ CHECK(bridge_type_cpp_to_rust_converter.has_value())
+ << "Missing " << kBridgeTypeCppToRustConverterTag << " for "
+ << kBridgeTypeTag << " " << *bridge_type;
+ return BridgeTypeInfo{
+ .bridge_type = *bridge_type,
+ .rust_to_cpp_converter = *bridge_type_rust_to_cpp_converter,
+ .cpp_to_rust_converter = *bridge_type_cpp_to_rust_converter};
}
return std::nullopt;
}
@@ -374,7 +374,7 @@
.defining_target = std::move(defining_target),
.unknown_attr = std::move(unknown_attr),
.doc_comment = std::move(doc_comment),
- .bridging_type_info = GetBridgingTypeInfo(record_decl),
+ .bridge_type_info = GetBridgeTypeInfo(record_decl),
.source_loc = ictx_.ConvertSourceLocation(source_loc),
.unambiguous_public_bases = GetUnambiguousPublicBases(*record_decl),
.fields = ImportFields(record_decl),
diff --git a/rs_bindings_from_cc/ir.cc b/rs_bindings_from_cc/ir.cc
index 499030c..82ef44c 100644
--- a/rs_bindings_from_cc/ir.cc
+++ b/rs_bindings_from_cc/ir.cc
@@ -442,9 +442,9 @@
};
}
-llvm::json::Value BridgingTypeInfo::ToJson() const {
+llvm::json::Value BridgeTypeInfo::ToJson() const {
return llvm::json::Object{
- {"bridging_type", bridging_type},
+ {"bridge_type", bridge_type},
{"rust_to_cpp_converter", rust_to_cpp_converter},
{"cpp_to_rust_converter", cpp_to_rust_converter},
};
@@ -466,7 +466,7 @@
{"defining_target", defining_target},
{"unknown_attr", unknown_attr},
{"doc_comment", doc_comment},
- {"bridging_type_info", bridging_type_info},
+ {"bridge_type_info", bridge_type_info},
{"source_loc", source_loc},
{"unambiguous_public_bases", unambiguous_public_bases},
{"fields", fields},
diff --git a/rs_bindings_from_cc/ir.h b/rs_bindings_from_cc/ir.h
index 407fe08..2bf525c 100644
--- a/rs_bindings_from_cc/ir.h
+++ b/rs_bindings_from_cc/ir.h
@@ -551,10 +551,10 @@
int64_t alignment;
};
-struct BridgingTypeInfo {
+struct BridgeTypeInfo {
llvm::json::Value ToJson() const;
- std::string bridging_type;
+ std::string bridge_type;
std::string rust_to_cpp_converter;
std::string cpp_to_rust_converter;
};
@@ -575,7 +575,7 @@
std::optional<BazelLabel> defining_target;
std::optional<std::string> unknown_attr;
std::optional<std::string> doc_comment;
- std::optional<BridgingTypeInfo> bridging_type_info;
+ std::optional<BridgeTypeInfo> bridge_type_info;
std::string source_loc;
std::vector<BaseClass> unambiguous_public_bases;
std::vector<Field> fields;
diff --git a/rs_bindings_from_cc/ir.rs b/rs_bindings_from_cc/ir.rs
index f537840..63e48a3 100644
--- a/rs_bindings_from_cc/ir.rs
+++ b/rs_bindings_from_cc/ir.rs
@@ -630,8 +630,8 @@
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
-pub struct BridgingTypeInfo {
- pub bridging_type: Rc<str>,
+pub struct BridgeTypeInfo {
+ pub bridge_type: Rc<str>,
pub rust_to_cpp_converter: Rc<str>,
pub cpp_to_rust_converter: Rc<str>,
}
@@ -654,7 +654,7 @@
/// default-closed and do not expose functions with unknown attributes.
pub unknown_attr: Option<Rc<str>>,
pub doc_comment: Option<Rc<str>>,
- pub bridging_type_info: Option<BridgingTypeInfo>,
+ pub bridge_type_info: Option<BridgeTypeInfo>,
pub source_loc: Rc<str>,
pub unambiguous_public_bases: Vec<BaseClass>,
pub fields: Vec<Field>,
diff --git a/rs_bindings_from_cc/ir_from_cc_test.rs b/rs_bindings_from_cc/ir_from_cc_test.rs
index 71c34ab..525c2e1 100644
--- a/rs_bindings_from_cc/ir_from_cc_test.rs
+++ b/rs_bindings_from_cc/ir_from_cc_test.rs
@@ -554,13 +554,13 @@
}
#[gtest]
-fn test_struct_with_bridging_type_annotation() {
+fn test_struct_with_bridge_type_annotation() {
let ir = ir_from_cc(
r#"
- struct [[clang::annotate("crubit_bridging_type", "SomeBridgingType"),
- clang::annotate("crubit_bridging_type_rust_to_cpp_converter", "cpp_to_rust_converter"),
- clang::annotate("crubit_bridging_type_cpp_to_rust_converter", "rust_to_cpp_converter")]]
- RecordWithBridgingType {
+ struct [[clang::annotate("crubit_bridge_type", "SomeBridgeType"),
+ clang::annotate("crubit_bridge_type_rust_to_cpp_converter", "cpp_to_rust_converter"),
+ clang::annotate("crubit_bridge_type_cpp_to_rust_converter", "rust_to_cpp_converter")]]
+ RecordWithBridgeType {
int foo;
};"#,
)
@@ -570,9 +570,9 @@
ir,
quote! {
Record {
- rs_name: "RecordWithBridgingType", ...
- bridging_type_info: Some(BridgingTypeInfo {
- bridging_type: "SomeBridgingType",
+ rs_name: "RecordWithBridgeType", ...
+ bridge_type_info: Some(BridgeTypeInfo {
+ bridge_type: "SomeBridgeType",
rust_to_cpp_converter: "cpp_to_rust_converter",
cpp_to_rust_converter: "rust_to_cpp_converter",
}), ...
@@ -2465,7 +2465,7 @@
defining_target: None,
unknown_attr: None,
doc_comment: Some(...),
- bridging_type_info: None,
+ bridge_type_info: None,
source_loc: "Generated from: google3/ir_from_cc_virtual_header.h;l=15",
unambiguous_public_bases: [],
fields: [Field {
diff --git a/rs_bindings_from_cc/test/bridging/BUILD b/rs_bindings_from_cc/test/bridging/BUILD
new file mode 100644
index 0000000..b4c17d0
--- /dev/null
+++ b/rs_bindings_from_cc/test/bridging/BUILD
@@ -0,0 +1,36 @@
+load(
+ "//common:crubit_wrapper_macros_oss.bzl",
+ "crubit_rust_test",
+)
+load(
+ "//rs_bindings_from_cc/bazel_support:additional_rust_srcs_for_crubit_bindings_aspect_hint.bzl",
+ "additional_rust_srcs_for_crubit_bindings",
+)
+
+cc_library(
+ name = "bridging_lib",
+ hdrs = ["bridging_lib.h"],
+ aspect_hints = [
+ "//features:experimental",
+ ":converter",
+ ],
+ deps = [
+ "//support/internal:bindings_support",
+ ],
+)
+
+additional_rust_srcs_for_crubit_bindings(
+ name = "converter",
+ srcs = ["converter.rs"],
+)
+
+crubit_rust_test(
+ name = "test",
+ srcs = ["test.rs"],
+ cc_deps = [
+ ":bridging_lib",
+ ],
+ deps = [
+ "@crate_index//:googletest",
+ ],
+)
diff --git a/rs_bindings_from_cc/test/bridging/bridging_lib.h b/rs_bindings_from_cc/test/bridging/bridging_lib.h
new file mode 100644
index 0000000..820c7bb
--- /dev/null
+++ b/rs_bindings_from_cc/test/bridging/bridging_lib.h
@@ -0,0 +1,45 @@
+// 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
+
+#ifndef THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_BRIDGE_BIRDGE_LIB_H_
+#define THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_BRIDGE_BIRDGE_LIB_H_
+#include <cstddef>
+#include <string>
+#include <utility>
+
+#include "support/internal/attribute_macros.h"
+
+struct CRUBIT_INTERNAL_BRIDGE_SUPPORT("MyRustStruct", "rust_to_cpp_converter",
+ "cpp_to_rust_converter") CppStruct {
+ std::string s;
+
+ explicit CppStruct(std::string s) : s(std::move(s)) {}
+};
+
+inline size_t CalStructSize(CppStruct x) { return x.s.size(); }
+inline CppStruct ReturnHelloWorldStruct() { return CppStruct{"hello world"}; }
+
+inline CppStruct PadADot(CppStruct x) {
+ x.s += ".";
+ return x;
+}
+
+inline CppStruct Concat(CppStruct x, CppStruct y) {
+ x.s += y.s;
+ return x;
+}
+
+inline void ffi_create_my_cpp_struct(const char* s, size_t len, void* output) {
+ *(reinterpret_cast<CppStruct*>(output)) = CppStruct(std::string(s, len));
+}
+
+inline const char* ffi_get_buffer(const void* input) {
+ return reinterpret_cast<const CppStruct*>(input)->s.c_str();
+}
+
+inline size_t ffi_get_buffer_len(const void* input) {
+ return reinterpret_cast<const CppStruct*>(input)->s.size();
+}
+
+#endif // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_BRIDGE_BIRDGE_LIB_H_
diff --git a/rs_bindings_from_cc/test/bridging/converter.rs b/rs_bindings_from_cc/test/bridging/converter.rs
new file mode 100644
index 0000000..7e23c0f
--- /dev/null
+++ b/rs_bindings_from_cc/test/bridging/converter.rs
@@ -0,0 +1,39 @@
+// 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 std;
+use core::mem::MaybeUninit;
+use std::os::raw::c_void;
+use std::slice;
+use std::string::String;
+
+pub struct MyRustStruct {
+ pub s: String,
+}
+
+impl MyRustStruct {
+ pub fn new(s: &str) -> Self {
+ MyRustStruct { s: String::from(s) }
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn rust_to_cpp_converter(x: *const c_void, output: *mut c_void) {
+ unsafe {
+ let x = &*(x as *const MyRustStruct);
+ crate::ffi_create_my_cpp_struct(x.s.as_ptr() as _, x.s.len(), output);
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn cpp_to_rust_converter(input: *const c_void, output: *mut c_void) {
+ unsafe {
+ let output = &mut *(output as *mut MaybeUninit<MyRustStruct>);
+ let buffer = crate::ffi_get_buffer(input);
+ let len = crate::ffi_get_buffer_len(input);
+ output.as_mut_ptr().write(MyRustStruct {
+ s: String::from_utf8_unchecked(slice::from_raw_parts(buffer as _, len).into()),
+ });
+ }
+}
diff --git a/rs_bindings_from_cc/test/bridging/test.rs b/rs_bindings_from_cc/test/bridging/test.rs
new file mode 100644
index 0000000..32208b1
--- /dev/null
+++ b/rs_bindings_from_cc/test/bridging/test.rs
@@ -0,0 +1,30 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+use googletest::prelude::*;
+
+#[gtest]
+fn test_bridge_type_as_function_arg() {
+ let x = bridging_lib::MyRustStruct::new("hello");
+ assert_eq!(bridging_lib::CalStructSize(x), 5);
+}
+
+#[gtest]
+fn test_bridge_type_as_return_value() {
+ assert_eq!(bridging_lib::ReturnHelloWorldStruct().s, String::from("hello world"));
+}
+
+#[gtest]
+fn test_bridge_type_two_args_are_bridged() {
+ let x = bridging_lib::MyRustStruct::new("hello");
+ let y = bridging_lib::MyRustStruct::new(" world");
+ assert_eq!(bridging_lib::Concat(x, y).s, String::from("hello world"));
+}
+
+#[gtest]
+fn test_round_trip() {
+ let x = bridging_lib::MyRustStruct::new("hello");
+ let y = bridging_lib::PadADot(x);
+ assert_eq!(y.s, "hello.");
+}
diff --git a/rs_bindings_from_cc/test/golden/BUILD b/rs_bindings_from_cc/test/golden/BUILD
index 0a2010e..7656692 100644
--- a/rs_bindings_from_cc/test/golden/BUILD
+++ b/rs_bindings_from_cc/test/golden/BUILD
@@ -73,11 +73,18 @@
tags = [tag for tag in (TAGS[name] if name in TAGS else [])],
) for name in TESTS]
+NON_BUILDABLE_TEST = [
+ # The bridge type is not defined in the C++ header so it cannot be built.
+ "bridge_type",
+]
+
+BUILDABLE_TESTS = [name for name in TESTS if name not in NON_BUILDABLE_TEST]
+
[crubit_rust_test(
name = name + "_rs_test",
srcs = ["empty_rs_test.rs"],
cc_deps = ["%s_cc" % name],
-) for name in TESTS]
+) for name in BUILDABLE_TESTS]
cc_library(
name = "namespaces_json",
diff --git a/rs_bindings_from_cc/test/golden/bridge_type.h b/rs_bindings_from_cc/test/golden/bridge_type.h
new file mode 100644
index 0000000..30f328f
--- /dev/null
+++ b/rs_bindings_from_cc/test/golden/bridge_type.h
@@ -0,0 +1,24 @@
+// 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
+
+#ifndef THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_BRIDGE_TYPE_H_
+#define THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_BRIDGE_TYPE_H_
+
+struct [[clang::annotate("crubit_bridge_type", "RustStruct"),
+ clang::annotate("crubit_bridge_type_rust_to_cpp_converter",
+ "rust_to_cpp_converter"),
+ clang::annotate("crubit_bridge_type_cpp_to_rust_converter",
+ "cpp_to_rust_converter")]] CppStruct {
+ ~CppStruct();
+};
+
+CppStruct ReturnCppStruct();
+
+void TakeCppStruct(CppStruct);
+
+void TakeCppStructByPtr(CppStruct*);
+
+CppStruct* ReturnCppStructByPtr();
+
+#endif // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_BRIDGE_TYPE_H_
diff --git a/rs_bindings_from_cc/test/golden/bridge_type_rs_api.rs b/rs_bindings_from_cc/test/golden/bridge_type_rs_api.rs
new file mode 100644
index 0000000..cf145c6
--- /dev/null
+++ b/rs_bindings_from_cc/test/golden/bridge_type_rs_api.rs
@@ -0,0 +1,47 @@
+// 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
+
+// Automatically @generated Rust bindings for the following C++ target:
+// //rs_bindings_from_cc/test/golden:bridge_type_cc
+// Features: experimental, supported
+
+#![rustfmt::skip]
+#![feature(custom_inner_attributes)]
+#![allow(stable_features)]
+#![no_std]
+#![allow(improper_ctypes)]
+#![allow(nonstandard_style)]
+#![allow(dead_code)]
+#![deny(warnings)]
+
+#[inline(always)]
+pub fn ReturnCppStruct() -> RustStruct {
+ unsafe {
+ let mut __return = ::core::mem::MaybeUninit::<RustStruct>::uninit();
+ crate::detail::__rust_thunk___Z15ReturnCppStructv(&mut __return);
+ __return.assume_init()
+ }
+}
+
+#[inline(always)]
+pub fn TakeCppStruct(mut __param_0: RustStruct) {
+ unsafe { crate::detail::__rust_thunk___Z13TakeCppStruct9CppStruct(&mut __param_0) }
+}
+
+// Error while generating bindings for item 'TakeCppStructByPtr':
+// Failed to format type of parameter 0: Bridging types are not supported as pointee/referent types.
+
+// Error while generating bindings for item 'ReturnCppStructByPtr':
+// Failed to format return type: Bridging types are not supported as pointee/referent types.
+
+mod detail {
+ #[allow(unused_imports)]
+ use super::*;
+ extern "C" {
+ pub(crate) fn __rust_thunk___Z15ReturnCppStructv(
+ __return: &mut ::core::mem::MaybeUninit<RustStruct>,
+ );
+ pub(crate) fn __rust_thunk___Z13TakeCppStruct9CppStruct(__param_0: &mut RustStruct);
+ }
+}
diff --git a/rs_bindings_from_cc/test/golden/bridge_type_rs_api_impl.cc b/rs_bindings_from_cc/test/golden/bridge_type_rs_api_impl.cc
new file mode 100644
index 0000000..408451a
--- /dev/null
+++ b/rs_bindings_from_cc/test/golden/bridge_type_rs_api_impl.cc
@@ -0,0 +1,36 @@
+// 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
+
+// Automatically @generated Rust bindings for the following C++ target:
+// //rs_bindings_from_cc/test/golden:bridge_type_cc
+// Features: experimental, supported
+
+#include "support/internal/cxx20_backports.h"
+#include "support/internal/lazy_init.h"
+#include "support/internal/offsetof.h"
+#include "support/internal/sizeof.h"
+
+#include <cstddef>
+#include <memory>
+
+// Public headers of the C++ library being wrapped.
+#include "rs_bindings_from_cc/test/golden/bridge_type.h"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wthread-safety-analysis"
+
+extern "C" void cpp_to_rust_converter(void* cpp_struct, void* rust_struct);
+extern "C" void __rust_thunk___Z15ReturnCppStructv(void* __return) {
+ auto __original_cpp_struct = ReturnCppStruct();
+ cpp_to_rust_converter(&__original_cpp_struct, __return);
+}
+
+extern "C" void rust_to_cpp_converter(void* rust_struct, void* cpp_struct);
+extern "C" void __rust_thunk___Z13TakeCppStruct9CppStruct(void* __param_0) {
+ ::crubit::LazyInit<struct CppStruct> __converted___param_0;
+ rust_to_cpp_converter(__param_0, &__converted___param_0.val);
+ TakeCppStruct(std::move(*&(__converted___param_0.val)));
+}
+
+#pragma clang diagnostic pop
diff --git a/support/internal/BUILD b/support/internal/BUILD
index 06cf9bd..cebffc0 100644
--- a/support/internal/BUILD
+++ b/support/internal/BUILD
@@ -7,6 +7,7 @@
hdrs = [
"attribute_macros.h",
"cxx20_backports.h",
+ "lazy_init.h",
"memswap.h",
"offsetof.h",
"return_value_slot.h",
diff --git a/support/internal/attribute_macros.h b/support/internal/attribute_macros.h
index 2f0bee6..20c36ef 100644
--- a/support/internal/attribute_macros.h
+++ b/support/internal/attribute_macros.h
@@ -71,25 +71,25 @@
#define CRUBIT_INTERNAL_SAME_ABI \
CRUBIT_INTERNAL_ANNOTATE("crubit_internal_same_abi")
-#define CRUBIT_INTERNAL_BRIDGING_TYPE(t) \
- CRUBIT_INTERNAL_ANNOTATE("crubit_bridging_type", t)
+#define CRUBIT_INTERNAL_BRIDGE_TYPE(t) \
+ CRUBIT_INTERNAL_ANNOTATE("crubit_bridge_type", t)
-#define CRUBIT_INTERNAL_BRIDGING_TYPE_RUST_TO_CPP_CONVERTER(t) \
- CRUBIT_INTERNAL_ANNOTATE("crubit_bridging_type_rust_to_cpp_converter", t)
+#define CRUBIT_INTERNAL_BRIDGE_TYPE_RUST_TO_CPP_CONVERTER(t) \
+ CRUBIT_INTERNAL_ANNOTATE("crubit_bridge_type_rust_to_cpp_converter", t)
-#define CRUBIT_INTERNAL_BRIDGING_TYPE_CPP_TO_RUST_CONVERTER(t) \
- CRUBIT_INTERNAL_ANNOTATE("crubit_bridging_type_cpp_to_rust_converter", t)
+#define CRUBIT_INTERNAL_BRIDGE_TYPE_CPP_TO_RUST_CONVERTER(t) \
+ CRUBIT_INTERNAL_ANNOTATE("crubit_bridge_type_cpp_to_rust_converter", t)
-// Declare a Rust type as the bridging type for binding generation.
+// Declare a Rust type as the bridge type for binding generation.
//
// This can be applied to a struct, class, or enum.
//
-// For example, We have the following C++ struct and its Rust bridging
+// For example, We have the following C++ struct and its Rust bridge
// counterpart:
//
// ```c++
// struct
-// CRUBIT_INTERNAL_BRIDGING_SUPPORT("MyRustStruct", "rust_to_cpp",
+// CRUBIT_INTERNAL_BRIDGE_SUPPORT("MyRustStruct", "rust_to_cpp",
// "cpp_to_rust") MyCppStruct {
// std::string name;
// };
@@ -111,11 +111,14 @@
// ```
//
// SAFETY:
-// `ty` must be a fully-qualified valid Rust type name.
-// `rust_to_cpp` and `cpp_to_rust` must be valid function names.
-#define CRUBIT_INTERNAL_BRIDGING_SUPPORT(ty, rust_to_cpp, cpp_to_rust) \
- CRUBIT_INTERNAL_BRIDGING_TYPE(ty) \
- CRUBIT_INTERNAL_BRIDGING_TYPE_RUST_TO_CPP_CONVERTER(rust_to_cpp) \
- CRUBIT_INTERNAL_BRIDGING_TYPE_CPP_TO_RUST_CONVERTER(cpp_to_rust)
+// * `ty` must be a fully-qualified valid Rust type name.
+// * `rust_to_cpp` must be a valid function name, and its signature must be
+// `void rust_to_cpp (void* rust_struct, MyCppStruct* cpp_struct)`.
+// * `cpp_to_rust` must be valid function name and its signature must be
+// `void cpp_to_rust (MyCppStruct* cpp_struct, void* rust_struct)`.
+#define CRUBIT_INTERNAL_BRIDGE_SUPPORT(ty, rust_to_cpp, cpp_to_rust) \
+ CRUBIT_INTERNAL_BRIDGE_TYPE(ty) \
+ CRUBIT_INTERNAL_BRIDGE_TYPE_RUST_TO_CPP_CONVERTER(rust_to_cpp) \
+ CRUBIT_INTERNAL_BRIDGE_TYPE_CPP_TO_RUST_CONVERTER(cpp_to_rust)
#endif // CRUBIT_SUPPORT_INTERNAL_ATTRIBUTES_H_
diff --git a/support/internal/lazy_init.h b/support/internal/lazy_init.h
new file mode 100644
index 0000000..fbd9265
--- /dev/null
+++ b/support/internal/lazy_init.h
@@ -0,0 +1,20 @@
+// 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
+
+#ifndef THIRD_PARTY_CRUBIT_SUPPORT_INTERNAL_LAZY_INIT_H_
+#define THIRD_PARTY_CRUBIT_SUPPORT_INTERNAL_LAZY_INIT_H_
+#include <memory>
+
+namespace crubit {
+
+template <typename T>
+union LazyInit {
+ constexpr LazyInit() {}
+ ~LazyInit() { std::destroy_at(&this->val); }
+ T val;
+};
+
+} // namespace crubit
+
+#endif // THIRD_PARTY_CRUBIT_SUPPORT_INTERNAL_LAZY_INIT_H_