From 83c106eac3f8aaa53c2136ffb818b42e1a81943c Mon Sep 17 00:00:00 2001 From: Googler Date: Fri, 20 Mar 2026 11:32:10 -0700 Subject: [PATCH] Wrap CRUBIT_THREAD_SAFE types in UnsafeCell For types annotated with CRUBIT_THREAD_SAFE, replace the normal Rust struct fields with a single opaque UnsafeCell<[MaybeUninit; SIZE]> body. This enables interior mutability, allowing non-const C++ methods to be called through shared references. This also prevents standard Rust `#[derive(Copy, Clone)]` generation, and forces an explicit alignment override since the opaque byte array body has alignment 1. PiperOrigin-RevId: 886895197 --- .../database/code_snippet.rs | 56 ++-- .../generate_bindings/database/rs_snippet.rs | 5 + .../generate_struct_and_union.rs | 32 ++- .../generate_struct_and_union_test.rs | 74 ++++++ rs_bindings_from_cc/importers/cxx_record.cc | 13 + rs_bindings_from_cc/ir.cc | 1 + rs_bindings_from_cc/ir.h | 5 + rs_bindings_from_cc/ir.rs | 8 + rs_bindings_from_cc/ir_from_cc_test.rs | 43 +++ rs_bindings_from_cc/test/annotations/BUILD | 27 ++ .../test/annotations/thread_safe.h | 29 +++ .../test/annotations/thread_safe_api_impl.cc | 80 ++++++ .../test/annotations/thread_safe_rs_api.rs | 244 ++++++++++++++++++ .../test/annotations/thread_safe_test.rs | 47 ++++ support/annotations.h | 24 ++ 15 files changed, 660 insertions(+), 28 deletions(-) create mode 100644 rs_bindings_from_cc/test/annotations/thread_safe.h create mode 100644 rs_bindings_from_cc/test/annotations/thread_safe_api_impl.cc create mode 100644 rs_bindings_from_cc/test/annotations/thread_safe_rs_api.rs create mode 100644 rs_bindings_from_cc/test/annotations/thread_safe_test.rs diff --git a/rs_bindings_from_cc/generate_bindings/database/code_snippet.rs b/rs_bindings_from_cc/generate_bindings/database/code_snippet.rs index 93ddc8df1..d68ce975d 100644 --- a/rs_bindings_from_cc/generate_bindings/database/code_snippet.rs +++ b/rs_bindings_from_cc/generate_bindings/database/code_snippet.rs @@ -602,6 +602,8 @@ pub fn generated_items_to_tokens<'db>( owned_type_name, member_methods, lifetime_params, + is_thread_safe, + size, } = record_item.as_ref(); let type_param_tokens = if !lifetime_params.is_empty() { @@ -615,15 +617,37 @@ pub fn generated_items_to_tokens<'db>( quote! { align(#align) } })); - let head_padding = head_padding.map(|n| { - let n = Literal::usize_unsuffixed(n); - // TODO(b/481405536): Do this unconditionally. - if *internally_mutable_unknown_fields { - quote! { __non_field_data: [::core::cell::Cell<::core::mem::MaybeUninit>; #n], } - } else { - quote! { __non_field_data: [::core::mem::MaybeUninit; #n], } + // For thread-safe types, generate an opaque UnsafeCell body instead of + // individual fields. This enables interior mutability, allowing non-const + // C++ methods to be called through shared references (&self). + let struct_body = if *is_thread_safe { + let size_literal = Literal::usize_unsuffixed(*size); + quote! { + __opaque: ::core::cell::UnsafeCell<[::core::mem::MaybeUninit; #size_literal]>, } - }); + } else { + let head_padding = head_padding.map(|n| { + let n = Literal::usize_unsuffixed(n); + // TODO(b/481405536): Do this unconditionally. + if *internally_mutable_unknown_fields { + quote! { __non_field_data: [::core::cell::Cell<::core::mem::MaybeUninit>; #n], } + } else { + quote! { __non_field_data: [::core::mem::MaybeUninit; #n], } + } + }); + let lifetime_markers: Vec = lifetime_params + .iter() + .map(|lt| { + let field_name = format_ident!("__marker_{}", lt.ident); + quote! { #field_name: ::core::marker::PhantomData<& #lt ()> } + }) + .collect(); + quote! { + #head_padding + #( #field_definitions )* + #( #lifetime_markers )* + } + }; let send_impl = match implements_send { true => { @@ -681,14 +705,6 @@ pub fn generated_items_to_tokens<'db>( None }; - let lifetime_markers: Vec = lifetime_params - .iter() - .map(|lt| { - let field_name = format_ident!("__marker_{}", lt.ident); - quote! { #field_name: ::core::marker::PhantomData<& #lt ()> } - }) - .collect(); - quote! { #doc_comment_attr #derive_attr @@ -697,9 +713,7 @@ pub fn generated_items_to_tokens<'db>( #[repr(#(#repr_attrs),*)] #crubit_annotation #visibility #struct_or_union #ident #type_param_tokens { - #head_padding - #( #field_definitions )* - #( #lifetime_markers )* + #struct_body } #send_impl @@ -1015,6 +1029,10 @@ pub struct Record { pub owned_type_name: Option, pub member_methods: Vec, pub lifetime_params: Vec, + /// Whether this type is annotated as thread-safe (CRUBIT_THREAD_SAFE). + pub is_thread_safe: bool, + /// The size of the type in bytes (needed for opaque UnsafeCell wrapping). + pub size: usize, } #[derive(Clone, Debug)] diff --git a/rs_bindings_from_cc/generate_bindings/database/rs_snippet.rs b/rs_bindings_from_cc/generate_bindings/database/rs_snippet.rs index e7d28e1b8..bcac91b33 100644 --- a/rs_bindings_from_cc/generate_bindings/database/rs_snippet.rs +++ b/rs_bindings_from_cc/generate_bindings/database/rs_snippet.rs @@ -244,6 +244,11 @@ pub fn format_generic_params_replacing_by_self<'db, 'a>( // Otherwise, these functions should be moved into a separate module. pub fn should_derive_clone(record: &Record) -> bool { + // Thread-safe types wrap their fields in UnsafeCell<[MaybeUninit; N]>, + // which prevents them from deriving Clone. + if record.is_thread_safe { + return false; + } match record.trait_derives.clone { TraitImplPolarity::Positive => true, TraitImplPolarity::Negative => false, diff --git a/rs_bindings_from_cc/generate_bindings/generate_struct_and_union.rs b/rs_bindings_from_cc/generate_bindings/generate_struct_and_union.rs index 5891751a7..7125ff14c 100644 --- a/rs_bindings_from_cc/generate_bindings/generate_struct_and_union.rs +++ b/rs_bindings_from_cc/generate_bindings/generate_struct_and_union.rs @@ -709,7 +709,9 @@ pub fn generate_record(db: &BindingsGenerator, record: Rc) -> Result 1 { + // Thread-safe types always need explicit alignment because the opaque + // UnsafeCell<[MaybeUninit; N]> body has alignment 1. + align: if (override_alignment || record.is_thread_safe) && record.size_align.alignment > 1 { Some(record.size_align.alignment) } else { None @@ -745,6 +747,8 @@ pub fn generate_record(db: &BindingsGenerator, record: Rc) -> Result) -> Result DeriveAttr { + // Thread-safe types wrap their fields in UnsafeCell<[MaybeUninit; N]>. + // This opaque byte array doesn't support useful standard derives, and Clone/Copy + // are explicitly prevented to support interior mutability anyway. + if record.is_thread_safe { + return DeriveAttr(vec![]); + } let mut derives = vec![]; if should_derive_clone(record) { derives.push(quote! { Clone }); diff --git a/rs_bindings_from_cc/generate_bindings/generate_struct_and_union_test.rs b/rs_bindings_from_cc/generate_bindings/generate_struct_and_union_test.rs index b975659f0..a1b2bf5e4 100644 --- a/rs_bindings_from_cc/generate_bindings/generate_struct_and_union_test.rs +++ b/rs_bindings_from_cc/generate_bindings/generate_struct_and_union_test.rs @@ -1943,3 +1943,77 @@ fn test_display() -> Result<()> { ); Ok(()) } + +#[gtest] +fn test_thread_safe_annotation_generates_send_sync() -> Result<()> { + let ir = ir_from_cc( + r#" + struct [[clang::annotate("crubit_thread_safe")]] ThreadSafeStruct final { + int field; + }; + "#, + )?; + + let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api; + + // Thread-safe types should get `unsafe impl Send` and `unsafe impl Sync`. + assert_rs_matches!(rs_api, quote! { unsafe impl Send for ThreadSafeStruct {} }); + assert_rs_matches!(rs_api, quote! { unsafe impl Sync for ThreadSafeStruct {} }); + + Ok(()) +} + +#[gtest] +fn test_thread_safe_annotation_generates_unsafe_cell_body() -> Result<()> { + let ir = ir_from_cc( + r#" + struct [[clang::annotate("crubit_thread_safe")]] ThreadSafeStruct final { + int field; + }; + "#, + )?; + + let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api; + + // Thread-safe types should have an opaque UnsafeCell body instead of individual fields. + assert_rs_matches!( + rs_api, + quote! { + pub struct ThreadSafeStruct { + __opaque: ::core::cell::UnsafeCell<[::core::mem::MaybeUninit; 4]>, + } + } + ); + + // Individual fields should NOT appear. + assert_rs_not_matches!(rs_api, quote! { pub field }); + + Ok(()) +} + +#[gtest] +fn test_non_thread_safe_struct_has_negative_send_sync() -> Result<()> { + let ir = ir_from_cc( + r#" + struct RegularStruct final { + int field; + }; + "#, + )?; + + let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api; + + // Non-thread-safe types should NOT have `unsafe impl Send/Sync`. + assert_rs_not_matches!(rs_api, quote! { unsafe impl Send for RegularStruct {} }); + assert_rs_not_matches!(rs_api, quote! { unsafe impl Sync for RegularStruct {} }); + + // They should have negative impls instead. + assert_rs_matches!(rs_api, quote! { impl !Send for RegularStruct {} }); + assert_rs_matches!(rs_api, quote! { impl !Sync for RegularStruct {} }); + + // And should have normal fields, not UnsafeCell wrapping. + assert_rs_matches!(rs_api, quote! { pub field: ::ffi_11::c_int }); + assert_rs_not_matches!(rs_api, quote! { __opaque }); + + Ok(()) +} diff --git a/rs_bindings_from_cc/importers/cxx_record.cc b/rs_bindings_from_cc/importers/cxx_record.cc index 7b59e4646..83adfc721 100644 --- a/rs_bindings_from_cc/importers/cxx_record.cc +++ b/rs_bindings_from_cc/importers/cxx_record.cc @@ -1121,12 +1121,24 @@ std::optional CXXRecordDeclImporter::Import( const clang::TypedefNameDecl* anon_typedef = record_decl->getTypedefNameForAnonDecl(); + absl::StatusOr is_thread_safe = + HasAnnotationWithoutArgs(*record_decl, "crubit_thread_safe"); + if (!is_thread_safe.ok()) { + return unsupported( + FormattedError::FromStatus(std::move(is_thread_safe).status())); + } + absl::StatusOr trait_derives = GetTraitDerives(*record_decl); if (!trait_derives.ok()) { return unsupported( FormattedError::FromStatus(std::move(trait_derives).status())); } + if (*is_thread_safe) { + trait_derives->send = true; + trait_derives->sync = true; + } + absl::StatusOr safety_annotation = GetSafetyAnnotation(*record_decl); if (!safety_annotation.ok()) { @@ -1203,6 +1215,7 @@ std::optional CXXRecordDeclImporter::Import( .enclosing_item_id = std::move(enclosing_item_id), .overloads_operator_delete = MayOverloadOperatorDelete(*record_decl), .detected_formatter = *detected_formatter, + .is_thread_safe = *is_thread_safe, .lifetime_inputs = std::move(lifetime_inputs), }; diff --git a/rs_bindings_from_cc/ir.cc b/rs_bindings_from_cc/ir.cc index 8c19f3934..5e5a71c6b 100644 --- a/rs_bindings_from_cc/ir.cc +++ b/rs_bindings_from_cc/ir.cc @@ -720,6 +720,7 @@ llvm::json::Value Record::ToJson() const { {"must_bind", must_bind}, {"overloads_operator_delete", overloads_operator_delete}, {"detected_formatter", detected_formatter}, + {"is_thread_safe", is_thread_safe}, }; if (!lifetime_inputs.empty()) { diff --git a/rs_bindings_from_cc/ir.h b/rs_bindings_from_cc/ir.h index 2bddc13bb..f24b4919a 100644 --- a/rs_bindings_from_cc/ir.h +++ b/rs_bindings_from_cc/ir.h @@ -819,6 +819,11 @@ struct Record { bool overloads_operator_delete = false; bool detected_formatter = false; + // Whether this type is annotated as thread-safe (CRUBIT_THREAD_SAFE). + // Thread-safe types implement Send+Sync and wrap their internals in + // UnsafeCell, allowing non-const C++ methods to be called via &self. + bool is_thread_safe = false; + // Lifetime variable names bound by this record. std::vector lifetime_inputs; diff --git a/rs_bindings_from_cc/ir.rs b/rs_bindings_from_cc/ir.rs index d23bf13fc..602723bf9 100644 --- a/rs_bindings_from_cc/ir.rs +++ b/rs_bindings_from_cc/ir.rs @@ -1214,6 +1214,9 @@ pub struct Record { /// string is used. #[serde(default)] pub deprecated: Option>, + /// Whether this type is annotated as thread-safe (CRUBIT_THREAD_SAFE). + #[serde(default)] + pub is_thread_safe: bool, } impl GenericItem for Record { @@ -1299,6 +1302,11 @@ impl Record { } pub fn should_derive_copy(&self) -> bool { + // Thread-safe types wrap their fields in UnsafeCell<[MaybeUninit; N]>, + // which prevents them from deriving Copy. + if self.is_thread_safe { + return false; + } match self.trait_derives.copy { TraitImplPolarity::Positive => true, TraitImplPolarity::Negative => false, diff --git a/rs_bindings_from_cc/ir_from_cc_test.rs b/rs_bindings_from_cc/ir_from_cc_test.rs index 6e82b8492..bb67614f3 100644 --- a/rs_bindings_from_cc/ir_from_cc_test.rs +++ b/rs_bindings_from_cc/ir_from_cc_test.rs @@ -926,6 +926,49 @@ fn test_conflicting_unsafe_annotation() { ); } +#[gtest] +fn test_struct_with_thread_safe_annotation() { + let ir = ir_from_cc( + r#" + struct [[clang::annotate("crubit_thread_safe")]] + ThreadSafeType { + int foo; + };"#, + ) + .unwrap(); + + assert_ir_matches!( + ir, + quote! { + Record { + rs_name: "ThreadSafeType", ... + is_thread_safe: true, ... + } + } + ); +} + +#[gtest] +fn test_struct_without_thread_safe_annotation() { + let ir = ir_from_cc( + r#" + struct NotThreadSafe { + int foo; + };"#, + ) + .unwrap(); + + assert_ir_matches!( + ir, + quote! { + Record { + rs_name: "NotThreadSafe", ... + is_thread_safe: false, ... + } + } + ); +} + #[gtest] fn test_struct_with_unnamed_struct_and_union_members() { // This test input causes `field_decl->getName()` to return an empty string. diff --git a/rs_bindings_from_cc/test/annotations/BUILD b/rs_bindings_from_cc/test/annotations/BUILD index f8a938e1c..efbaad68c 100644 --- a/rs_bindings_from_cc/test/annotations/BUILD +++ b/rs_bindings_from_cc/test/annotations/BUILD @@ -159,3 +159,30 @@ crubit_rust_test( "@crate_index//:googletest", ], ) + +crubit_test_cc_library( + name = "thread_safe", + hdrs = ["thread_safe.h"], + deps = [ + "//support:annotations", + ], +) + +crubit_rust_test( + name = "thread_safe_test", + srcs = ["thread_safe_test.rs"], + cc_deps = [ + ":thread_safe", + ], + deps = [ + "@crate_index//:googletest", + ], +) + +golden_test( + name = "thread_safe_golden_test", + basename = "thread_safe", + cc_library = "thread_safe", + golden_cc = "thread_safe_api_impl.cc", + golden_rs = "thread_safe_rs_api.rs", +) diff --git a/rs_bindings_from_cc/test/annotations/thread_safe.h b/rs_bindings_from_cc/test/annotations/thread_safe.h new file mode 100644 index 000000000..7cb6eec13 --- /dev/null +++ b/rs_bindings_from_cc/test/annotations/thread_safe.h @@ -0,0 +1,29 @@ +// 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_ANNOTATIONS_THREAD_SAFE_H_ +#define THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_ANNOTATIONS_THREAD_SAFE_H_ + +#include "support/annotations.h" + +namespace crubit::test { + +// A simple thread-safe struct. +struct CRUBIT_THREAD_SAFE ThreadSafeStruct final { + ThreadSafeStruct() : x(0), y(0) {} + ThreadSafeStruct(int x, int y) : x(x), y(y) {} + int Sum() const { return x + y; } + + int x; + int y; +}; + +// A regular (non-thread-safe) struct for comparison. +struct RegularStruct final { + int value; +}; + +} // namespace crubit::test + +#endif // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_ANNOTATIONS_THREAD_SAFE_H_ diff --git a/rs_bindings_from_cc/test/annotations/thread_safe_api_impl.cc b/rs_bindings_from_cc/test/annotations/thread_safe_api_impl.cc new file mode 100644 index 000000000..9b302f31d --- /dev/null +++ b/rs_bindings_from_cc/test/annotations/thread_safe_api_impl.cc @@ -0,0 +1,80 @@ +// 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/annotations:thread_safe +// Features: supported, types + +#include "support/internal/cxx20_backports.h" +#include "support/internal/offsetof.h" +#include "support/internal/sizeof.h" + +#include +#include + +// Public headers of the C++ library being wrapped. +#include "rs_bindings_from_cc/test/annotations/thread_safe.h" + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wthread-safety-analysis" + +static_assert(CRUBIT_SIZEOF(struct crubit::test::ThreadSafeStruct) == 8); +static_assert(alignof(struct crubit::test::ThreadSafeStruct) == 4); +static_assert(CRUBIT_OFFSET_OF(x, struct crubit::test::ThreadSafeStruct) == 0); +static_assert(CRUBIT_OFFSET_OF(y, struct crubit::test::ThreadSafeStruct) == 4); + +extern "C" void __rust_thunk___ZN6crubit4test16ThreadSafeStructC1ERKS1_( + struct crubit::test::ThreadSafeStruct* __this, + struct crubit::test::ThreadSafeStruct const* __param_0) { + crubit::construct_at(__this, *__param_0); +} + +extern "C" void __rust_thunk___ZN6crubit4test16ThreadSafeStructC1EOS1_( + struct crubit::test::ThreadSafeStruct* __this, + struct crubit::test::ThreadSafeStruct* __param_0) { + crubit::construct_at(__this, std::move(*__param_0)); +} + +extern "C" struct crubit::test::ThreadSafeStruct* +__rust_thunk___ZN6crubit4test16ThreadSafeStructaSERKS1_( + struct crubit::test::ThreadSafeStruct* __this, + struct crubit::test::ThreadSafeStruct const* __param_0) { + return std::addressof(__this->operator=(*__param_0)); +} + +extern "C" struct crubit::test::ThreadSafeStruct* +__rust_thunk___ZN6crubit4test16ThreadSafeStructaSEOS1_( + struct crubit::test::ThreadSafeStruct* __this, + struct crubit::test::ThreadSafeStruct* __param_0) { + return std::addressof(__this->operator=(std::move(*__param_0))); +} + +extern "C" void __rust_thunk___ZN6crubit4test16ThreadSafeStructC1Ev( + struct crubit::test::ThreadSafeStruct* __this) { + crubit::construct_at(__this); +} + +extern "C" void __rust_thunk___ZN6crubit4test16ThreadSafeStructC1Eii( + struct crubit::test::ThreadSafeStruct* __this, int x, int y) { + crubit::construct_at(__this, x, y); +} + +extern "C" int __rust_thunk___ZNK6crubit4test16ThreadSafeStruct3SumEv( + struct crubit::test::ThreadSafeStruct const* __this) { + return __this->Sum(); +} + +static_assert((int (crubit::test::ThreadSafeStruct::*)() const) & + ::crubit::test::ThreadSafeStruct::Sum); + +static_assert(CRUBIT_SIZEOF(struct crubit::test::RegularStruct) == 4); +static_assert(alignof(struct crubit::test::RegularStruct) == 4); +static_assert(CRUBIT_OFFSET_OF(value, struct crubit::test::RegularStruct) == 0); + +extern "C" void __rust_thunk___ZN6crubit4test13RegularStructC1Ev( + struct crubit::test::RegularStruct* __this) { + crubit::construct_at(__this); +} + +#pragma clang diagnostic pop diff --git a/rs_bindings_from_cc/test/annotations/thread_safe_rs_api.rs b/rs_bindings_from_cc/test/annotations/thread_safe_rs_api.rs new file mode 100644 index 000000000..cb8901b88 --- /dev/null +++ b/rs_bindings_from_cc/test/annotations/thread_safe_rs_api.rs @@ -0,0 +1,244 @@ +// 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/annotations:thread_safe +// Features: supported, types + +#![rustfmt::skip] +#![feature(custom_inner_attributes, negative_impls)] +#![allow(stable_features)] +#![allow(improper_ctypes)] +#![allow(nonstandard_style)] +#![allow(unused)] +#![deny(warnings)] + +pub mod crubit { + pub mod test { + /// A simple thread-safe struct. + /// + /// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=13 + #[repr(C, align(4))] + ///CRUBIT_ANNOTATE: cpp_type=crubit :: test :: ThreadSafeStruct + pub struct ThreadSafeStruct { + __opaque: ::core::cell::UnsafeCell<[::core::mem::MaybeUninit; 8]>, + } + unsafe impl Send for ThreadSafeStruct {} + unsafe impl Sync for ThreadSafeStruct {} + unsafe impl ::cxx::ExternType for ThreadSafeStruct { + type Id = ::cxx::type_id!("crubit :: test :: ThreadSafeStruct"); + type Kind = ::cxx::kind::Trivial; + } + impl ThreadSafeStruct { + /// # Safety + /// + /// The caller must ensure that the following unsafe arguments are not misused by the function: + /// * `__this`: raw pointer + /// + /// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=16 + #[inline(always)] + pub unsafe fn Sum(__this: *const Self) -> ::ffi_11::c_int { + crate::detail::__rust_thunk___ZNK6crubit4test16ThreadSafeStruct3SumEv(__this) + } + } + + /// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=13 + impl Clone for ThreadSafeStruct { + #[inline(always)] + fn clone(&self) -> Self { + let mut tmp = ::core::mem::MaybeUninit::::zeroed(); + unsafe { + crate::detail::__rust_thunk___ZN6crubit4test16ThreadSafeStructC1ERKS1_( + &raw mut tmp as *mut _, + self, + ); + tmp.assume_init() + } + } + fn clone_from(&mut self, other: &Self) { + use ::ctor::UnpinAssign; + self.unpin_assign(other); + } + } + + /// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=13 + impl From<::ctor::RvalueReference<'_, Self>> for ThreadSafeStruct { + #[inline(always)] + fn from(args: ::ctor::RvalueReference<'_, Self>) -> Self { + let mut __param_0 = args; + let mut tmp = ::core::mem::MaybeUninit::::zeroed(); + unsafe { + crate::detail::__rust_thunk___ZN6crubit4test16ThreadSafeStructC1EOS1_( + &raw mut tmp as *mut _, + __param_0, + ); + tmp.assume_init() + } + } + } + impl ::ctor::CtorNew<::ctor::RvalueReference<'_, Self>> for ThreadSafeStruct { + type CtorType = Self; + type Error = ::ctor::Infallible; + #[inline(always)] + fn ctor_new(args: ::ctor::RvalueReference<'_, Self>) -> Self::CtorType { + >>::from(args) + } + } + + /// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=13 + impl ::ctor::UnpinAssign<&Self> for ThreadSafeStruct { + #[inline(always)] + fn unpin_assign(&mut self, __param_0: &Self) { + unsafe { + crate::detail::__rust_thunk___ZN6crubit4test16ThreadSafeStructaSERKS1_( + self, __param_0, + ); + } + } + } + + /// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=13 + impl ::ctor::UnpinAssign<::ctor::RvalueReference<'_, Self>> for ThreadSafeStruct { + #[inline(always)] + fn unpin_assign(&mut self, __param_0: ::ctor::RvalueReference<'_, Self>) { + unsafe { + crate::detail::__rust_thunk___ZN6crubit4test16ThreadSafeStructaSEOS1_( + self, __param_0, + ); + } + } + } + + /// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=14 + impl Default for ThreadSafeStruct { + #[inline(always)] + fn default() -> Self { + let mut tmp = ::core::mem::MaybeUninit::::zeroed(); + unsafe { + crate::detail::__rust_thunk___ZN6crubit4test16ThreadSafeStructC1Ev( + &raw mut tmp as *mut _, + ); + tmp.assume_init() + } + } + } + + /// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=15 + impl From<(::ffi_11::c_int, ::ffi_11::c_int)> for ThreadSafeStruct { + #[inline(always)] + fn from(args: (::ffi_11::c_int, ::ffi_11::c_int)) -> Self { + let (mut x, mut y) = args; + let mut tmp = ::core::mem::MaybeUninit::::zeroed(); + unsafe { + crate::detail::__rust_thunk___ZN6crubit4test16ThreadSafeStructC1Eii( + &raw mut tmp as *mut _, + x, + y, + ); + tmp.assume_init() + } + } + } + impl ::ctor::CtorNew<(::ffi_11::c_int, ::ffi_11::c_int)> for ThreadSafeStruct { + type CtorType = Self; + type Error = ::ctor::Infallible; + #[inline(always)] + fn ctor_new(args: (::ffi_11::c_int, ::ffi_11::c_int)) -> Self::CtorType { + >::from(args) + } + } + + /// A regular (non-thread-safe) struct for comparison. + /// + /// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=23 + #[derive(Clone, Copy, ::ctor::MoveAndAssignViaCopy)] + #[repr(C)] + ///CRUBIT_ANNOTATE: cpp_type=crubit :: test :: RegularStruct + pub struct RegularStruct { + pub value: ::ffi_11::c_int, + } + impl !Send for RegularStruct {} + impl !Sync for RegularStruct {} + unsafe impl ::cxx::ExternType for RegularStruct { + type Id = ::cxx::type_id!("crubit :: test :: RegularStruct"); + type Kind = ::cxx::kind::Trivial; + } + + /// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=23 + impl Default for RegularStruct { + #[inline(always)] + fn default() -> Self { + let mut tmp = ::core::mem::MaybeUninit::::zeroed(); + unsafe { + crate::detail::__rust_thunk___ZN6crubit4test13RegularStructC1Ev( + &raw mut tmp as *mut _, + ); + tmp.assume_init() + } + } + } + } +} + +// namespace crubit::test + +// Generated from: nowhere/llvm/src/libcxx/include/__type_traits/integral_constant.h;l=21 +// error: struct `std::integral_constant` could not be bound +// template instantiation is not yet supported + +// Generated from: nowhere/llvm/src/libcxx/include/__type_traits/integral_constant.h;l=21 +// error: struct `std::integral_constant` could not be bound +// template instantiation is not yet supported + +mod detail { + #[allow(unused_imports)] + use super::*; + unsafe extern "C" { + pub(crate) unsafe fn __rust_thunk___ZN6crubit4test16ThreadSafeStructC1ERKS1_( + __this: *mut ::core::ffi::c_void, + __param_0: &crate::crubit::test::ThreadSafeStruct, + ); + pub(crate) unsafe fn __rust_thunk___ZN6crubit4test16ThreadSafeStructC1EOS1_( + __this: *mut ::core::ffi::c_void, + __param_0: ::ctor::RvalueReference<'_, crate::crubit::test::ThreadSafeStruct>, + ); + pub(crate) unsafe fn __rust_thunk___ZN6crubit4test16ThreadSafeStructaSERKS1_< + '__return_lifetime, + >( + __this: &mut crate::crubit::test::ThreadSafeStruct, + __param_0: &crate::crubit::test::ThreadSafeStruct, + ) -> &'__return_lifetime mut crate::crubit::test::ThreadSafeStruct; + pub(crate) unsafe fn __rust_thunk___ZN6crubit4test16ThreadSafeStructaSEOS1_< + '__return_lifetime, + >( + __this: &mut crate::crubit::test::ThreadSafeStruct, + __param_0: ::ctor::RvalueReference<'_, crate::crubit::test::ThreadSafeStruct>, + ) -> &'__return_lifetime mut crate::crubit::test::ThreadSafeStruct; + pub(crate) unsafe fn __rust_thunk___ZN6crubit4test16ThreadSafeStructC1Ev( + __this: *mut ::core::ffi::c_void, + ); + pub(crate) unsafe fn __rust_thunk___ZN6crubit4test16ThreadSafeStructC1Eii( + __this: *mut ::core::ffi::c_void, + x: ::ffi_11::c_int, + y: ::ffi_11::c_int, + ); + pub(crate) unsafe fn __rust_thunk___ZNK6crubit4test16ThreadSafeStruct3SumEv( + __this: *const crate::crubit::test::ThreadSafeStruct, + ) -> ::ffi_11::c_int; + pub(crate) unsafe fn __rust_thunk___ZN6crubit4test13RegularStructC1Ev( + __this: *mut ::core::ffi::c_void, + ); + } +} + +const _: () = { + assert!(::core::mem::size_of::() == 8); + assert!(::core::mem::align_of::() == 4); + static_assertions::assert_not_impl_any!(crate::crubit::test::ThreadSafeStruct: Copy,Drop); + assert!(::core::mem::size_of::() == 4); + assert!(::core::mem::align_of::() == 4); + static_assertions::assert_impl_all!(crate::crubit::test::RegularStruct: Copy,Clone); + static_assertions::assert_not_impl_any!(crate::crubit::test::RegularStruct: Drop); + assert!(::core::mem::offset_of!(crate::crubit::test::RegularStruct, value) == 0); +}; diff --git a/rs_bindings_from_cc/test/annotations/thread_safe_test.rs b/rs_bindings_from_cc/test/annotations/thread_safe_test.rs new file mode 100644 index 000000000..293155976 --- /dev/null +++ b/rs_bindings_from_cc/test/annotations/thread_safe_test.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 + +use googletest::gtest; +use googletest::prelude::*; +use thread_safe::crubit::test::ThreadSafeStruct; + +/// Verify that the thread-safe type implements Send. +#[gtest] +fn test_thread_safe_is_send() { + fn assert_send() {} + assert_send::(); +} + +/// Verify that the thread-safe type implements Sync. +#[gtest] +fn test_thread_safe_is_sync() { + fn assert_sync() {} + assert_sync::(); +} + +/// Verify that the generated struct has the expected size. +#[gtest] +fn test_thread_safe_struct_has_correct_size() { + assert_eq!(std::mem::size_of::(), 8); +} + +/// Verify that we can construct a ThreadSafeStruct and call its Sum() method. +#[gtest] +fn test_thread_safe_struct_sum() { + let s = ThreadSafeStruct::from((3, 4)); + assert_that!(unsafe { ThreadSafeStruct::Sum(&s as *const _) }, eq(7)); +} + +/// Verify default construction initializes to zero. +#[gtest] +fn test_thread_safe_struct_default() { + let s = ThreadSafeStruct::default(); + assert_that!(unsafe { ThreadSafeStruct::Sum(&s as *const _) }, eq(0)); +} + +/// Verify that a non-thread-safe type has the expected size. +#[gtest] +fn test_regular_struct_is_not_send_or_sync() { + assert_eq!(std::mem::size_of::(), 4); +} diff --git a/support/annotations.h b/support/annotations.h index 522164bb8..8067c4bcc 100644 --- a/support/annotations.h +++ b/support/annotations.h @@ -331,6 +331,30 @@ #define CRUBIT_OWNED_POINTEE(name) \ CRUBIT_INTERNAL_ANNOTATE("crubit_owned_pointee", name) +// Marks a type as thread-safe for Rust interop. +// +// Types annotated with `CRUBIT_THREAD_SAFE` will: +// * Implement `Send + Sync` in Rust +// * Have their internal representation wrapped in `UnsafeCell`, allowing +// non-const C++ methods to be called via shared references (`&self`) +// +// This annotation is appropriate for types that internally synchronize +// access (e.g., types with mutexes, atomics, or other synchronization +// primitives). +// +// Example: +// ```c++ +// class CRUBIT_THREAD_SAFE ThreadSafeCounter { +// public: +// void Increment(); // Can be called via &self in Rust +// int Get() const; // Can also be called via &self +// private: +// std::atomic count_; +// mutable std::mutex mu_; +// }; +// ``` +#define CRUBIT_THREAD_SAFE CRUBIT_INTERNAL_ANNOTATE("crubit_thread_safe") + // Overrides the `Display` binding detection for a type to true or false. // // If detected: binds to Rust's `Display` trait, preferring `AbslStringify` over