Fix how structs are handled by the generated thunks.
- Struct values need to be `std::move`d when calling thunks.
- `#![allow(improper_ctypes_definitions)]` is needed.
PiperOrigin-RevId: 490384826
diff --git a/cc_bindings_from_rs/bindings.rs b/cc_bindings_from_rs/bindings.rs
index 38096b6..eb9d59e 100644
--- a/cc_bindings_from_rs/bindings.rs
+++ b/cc_bindings_from_rs/bindings.rs
@@ -58,6 +58,27 @@
let rs_body = quote! {
#top_comment
+
+ // Rust warns about non-`#[repr(C)]` structs being used as parameter types or return
+ // type of `extern "C"` functions (such as thunks that might be present in `rs_body`).
+ // This warning makes sense, because in absence of a guaranteed / well-defined ABI
+ // for this structs, one can't author C/C++ definitions compatible with that ABI.
+ // Unless... the author is `cc_bindings_from_rs` invoked with exactly the same version
+ // and cmdline flags as `rustc`. Given this, we just disable warnings like the one
+ // in the example below:
+ //
+ // warning: `extern` fn uses type `DefaultReprPoint`, which is not FFI-safe
+ // --> .../cc_bindings_from_rs/test/structs/structs_cc_api_impl.rs:25:6
+ // |
+ // 25 | ) -> structs::DefaultReprPoint {
+ // | ^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
+ // |
+ // = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute...
+ // = note: this struct has unspecified layout
+ // = note: `#[warn(improper_ctypes_definitions)]` on by default
+ #![allow(improper_ctypes_definitions)] __NEWLINE__
+ __NEWLINE__
+
#rs_body
};
@@ -107,6 +128,23 @@
}
}
+/// Formats an argument of a thunk. For example:
+/// - most primitive types are passed as-is - e.g. `123`
+/// - structs need to be moved: `std::move(value)`
+/// - in the future additional processing may be needed for other types (this is
+/// speculative so please take these examples with a grain of salt):
+/// - str: utf-8 verification
+/// - &T: calling into `crubit::MutRef::unsafe_get_ptr`
+fn format_cc_thunk_arg<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, value: TokenStream) -> CcSnippet {
+ let mut includes = BTreeSet::new();
+ if ty.is_copy_modulo_regions(tcx, ty::ParamEnv::empty()) {
+ CcSnippet { includes, snippet: value }
+ } else {
+ includes.insert(CcInclude::utility());
+ CcSnippet { includes, snippet: quote! { std::move(#value) } }
+ }
+}
+
/// Formats `ty` into a `CcSnippet` that represents how the type should be
/// spelled in a C++ declaration of an `extern "C"` function.
fn format_ty_for_cc(tcx: TyCtxt, ty: Ty) -> Result<CcSnippet> {
@@ -445,6 +483,12 @@
} else {
let exported_name =
format_cc_ident(symbol_name.name).context("Error formatting exported name")?;
+ let thunk_args = arg_names
+ .clone()
+ .into_iter()
+ .zip(sig.inputs().iter())
+ .map(|(arg, &ty)| format_cc_thunk_arg(tcx, ty, arg).into_tokens(&mut includes))
+ .collect_vec();
quote! {
namespace __crubit_internal {
extern "C" #ret_type #exported_name (
@@ -453,10 +497,7 @@
} // namespace __crubit_internal
inline #ret_type #fn_name (
#( #arg_types #arg_names ),* ) {
- // TODO(b/258232820): Support using `struct`s as an argument of a thunk. The
- // code below won't compile as-is because it unnecessarily invokes a copy
- // constructor. The generated code should go through `std::move(...)`.
- return __crubit_internal :: #exported_name( #( #arg_names ),* );
+ return __crubit_internal :: #exported_name( #( #thunk_args ),* );
}
}
}
@@ -802,8 +843,8 @@
#[cfg(test)]
pub mod tests {
use super::{
- format_def, format_ret_ty_for_cc, format_ty_for_cc, format_ty_for_rs, GeneratedBindings,
- MixedSnippet,
+ format_cc_thunk_arg, format_def, format_ret_ty_for_cc, format_ty_for_cc, format_ty_for_rs,
+ GeneratedBindings, MixedSnippet,
};
use anyhow::Result;
@@ -1646,7 +1687,7 @@
extern "C" std::int32_t ...(S __param_0);
}
inline std::int32_t func(S __param_0) {
- return __crubit_internal::...(__param_0);
+ return __crubit_internal::...(std::move(__param_0));
}
}
);
@@ -2386,12 +2427,51 @@
});
}
+ #[test]
+ fn test_format_cc_thunk_arg() {
+ let testcases = [
+ // ( <Rust type>, (<expected C++ type>, <expected #include>) )
+ ("i32", ("value", "")),
+ ("SomeStruct", ("std::move(value)", "utility")),
+ ];
+ let preamble = quote! {
+ pub struct SomeStruct {
+ pub x: i32,
+ pub y: i32,
+ }
+ };
+ test_ty(&testcases, preamble, |desc, tcx, ty, (expected_snippet, expected_include)| {
+ let (actual_snippet, actual_includes) = {
+ let cc_snippet = format_cc_thunk_arg(tcx, ty, quote! { value });
+ (cc_snippet.snippet.to_string(), cc_snippet.includes)
+ };
+
+ let expected_snippet = expected_snippet.parse::<TokenStream>().unwrap().to_string();
+ assert_eq!(actual_snippet, expected_snippet, "{desc}");
+
+ if expected_include.is_empty() {
+ assert!(actual_includes.is_empty());
+ } else {
+ let expected_header = format_cc_ident(expected_include).unwrap();
+ assert_cc_matches!(
+ format_cc_includes(&actual_includes),
+ quote! { include <#expected_header> }
+ );
+ }
+ });
+ }
+
fn test_ty<TestFn, Expectation>(
testcases: &[(&str, Expectation)],
preamble: TokenStream,
test_fn: TestFn,
) where
- TestFn: Fn(/* testcase_description: */ &str, TyCtxt, Ty, &Expectation) + Sync,
+ TestFn: for<'tcx> Fn(
+ /* testcase_description: */ &str,
+ TyCtxt<'tcx>,
+ Ty<'tcx>,
+ &Expectation,
+ ) + Sync,
Expectation: Sync,
{
for (index, (input, expected)) in testcases.iter().enumerate() {
diff --git a/cc_bindings_from_rs/cc_bindings_from_rs.rs b/cc_bindings_from_rs/cc_bindings_from_rs.rs
index 91b8c86..9fc4a88 100644
--- a/cc_bindings_from_rs/cc_bindings_from_rs.rs
+++ b/cc_bindings_from_rs/cc_bindings_from_rs.rs
@@ -377,6 +377,8 @@
r#"// Automatically @generated C++ bindings for the following Rust crate:
// test_crate
+#![allow(improper_ctypes_definitions)]
+
#[no_mangle]
extern "C" fn __crubit_thunk__RNvCslKXfKXPWofF_10test_crate15public_function() -> () {
test_crate::public_function()
diff --git a/cc_bindings_from_rs/test/structs/structs.rs b/cc_bindings_from_rs/test/structs/structs.rs
index acdf3ca..f7b2b7e 100644
--- a/cc_bindings_from_rs/test/structs/structs.rs
+++ b/cc_bindings_from_rs/test/structs/structs.rs
@@ -5,22 +5,29 @@
//! This crate is used as a test input for `cc_bindings_from_rs` and the
//! generated C++ bindings are then tested via `structs_test.cc`.
-// TODO(b/258232820): Add test coverage for non-`#[repr(C)]` struct. See
-// cl/489571653.
#[repr(C)]
pub struct ReprCPoint {
pub x: i32,
pub y: i32,
}
-// TODO(b/258232820): Remove `#[no_mangle]` and `extern "C"` to extend test
-// coverage to thunks. See cl/489571653.
-#[no_mangle]
-pub extern "C" fn create_repr_c_point_via_free_function(x: i32, y: i32) -> ReprCPoint {
+pub fn create_repr_c_point_via_free_function(x: i32, y: i32) -> ReprCPoint {
ReprCPoint { x, y }
}
-#[no_mangle]
-pub extern "C" fn get_x_of_repr_c_point_via_free_function(p: ReprCPoint) -> i32 {
+pub fn get_x_of_repr_c_point_via_free_function(p: ReprCPoint) -> i32 {
+ p.x
+}
+
+pub struct DefaultReprPoint {
+ pub x: i32,
+ pub y: i32,
+}
+
+pub fn create_default_repr_point_via_free_function(x: i32, y: i32) -> DefaultReprPoint {
+ DefaultReprPoint { x, y }
+}
+
+pub fn get_x_of_default_repr_point_via_free_function(p: DefaultReprPoint) -> i32 {
p.x
}
diff --git a/cc_bindings_from_rs/test/structs/structs_test.cc b/cc_bindings_from_rs/test/structs/structs_test.cc
index 746f891..a7aa0cf 100644
--- a/cc_bindings_from_rs/test/structs/structs_test.cc
+++ b/cc_bindings_from_rs/test/structs/structs_test.cc
@@ -19,5 +19,10 @@
EXPECT_EQ(123, get_x_of_repr_c_point_via_free_function(std::move(p)));
}
+TEST(StructsTest, DefaultReprPointReturnedOrTakenByValue) {
+ DefaultReprPoint p = create_default_repr_point_via_free_function(123, 456);
+ EXPECT_EQ(123, get_x_of_default_repr_point_via_free_function(std::move(p)));
+}
+
} // namespace
} // namespace crubit
diff --git a/common/code_gen_utils.rs b/common/code_gen_utils.rs
index 2c9d878..3405af6 100644
--- a/common/code_gen_utils.rs
+++ b/common/code_gen_utils.rs
@@ -79,6 +79,13 @@
Self::SystemHeader("memory")
}
+ /// Creates a `CcInclude` that represents `#include <utility>` and provides
+ /// C++ functions like `std::move` and C++ types like `std::tuple`.
+ /// See also https://en.cppreference.com/w/cpp/header/utility
+ pub fn utility() -> Self {
+ Self::SystemHeader("utility")
+ }
+
/// Creates a user include: `#include "some/path/to/header.h"`.
pub fn user_header(path: Rc<str>) -> Self {
Self::UserHeader(path)