Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions rs_bindings_from_cc/importers/cxx_record.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1121,12 +1121,24 @@ std::optional<IR::Item> CXXRecordDeclImporter::Import(
const clang::TypedefNameDecl* anon_typedef =
record_decl->getTypedefNameForAnonDecl();

absl::StatusOr<bool> 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<TraitDerives> 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<SafetyAnnotation> safety_annotation =
GetSafetyAnnotation(*record_decl);
if (!safety_annotation.ok()) {
Expand Down Expand Up @@ -1203,6 +1215,7 @@ std::optional<IR::Item> 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),
};

Expand Down
1 change: 1 addition & 0 deletions rs_bindings_from_cc/ir.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
5 changes: 5 additions & 0 deletions rs_bindings_from_cc/ir.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string> lifetime_inputs;

Expand Down
3 changes: 3 additions & 0 deletions rs_bindings_from_cc/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,9 @@ pub struct Record {
/// string is used.
#[serde(default)]
pub deprecated: Option<Rc<str>>,
/// Whether this type is annotated as thread-safe (CRUBIT_THREAD_SAFE).
#[serde(default)]
pub is_thread_safe: bool,
}

impl GenericItem for Record {
Expand Down
43 changes: 43 additions & 0 deletions rs_bindings_from_cc/ir_from_cc_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
27 changes: 27 additions & 0 deletions rs_bindings_from_cc/test/annotations/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)
25 changes: 25 additions & 0 deletions rs_bindings_from_cc/test/annotations/thread_safe.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// 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 {
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_
41 changes: 41 additions & 0 deletions rs_bindings_from_cc/test/annotations/thread_safe_api_impl.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// 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 <cstddef>
#include <memory>

// 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___ZN6crubit4test16ThreadSafeStructC1Ev(
struct crubit::test::ThreadSafeStruct* __this) {
crubit::construct_at(__this);
}

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
117 changes: 117 additions & 0 deletions rs_bindings_from_cc/test/annotations/thread_safe_rs_api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// 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
#[derive(Clone, Copy, ::ctor::MoveAndAssignViaCopy)]
#[repr(C)]
///CRUBIT_ANNOTATE: cpp_type=crubit :: test :: ThreadSafeStruct
pub struct ThreadSafeStruct {
pub x: ::ffi_11::c_int,
pub y: ::ffi_11::c_int,
}
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;
}

/// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=13
impl Default for ThreadSafeStruct {
#[inline(always)]
fn default() -> Self {
let mut tmp = ::core::mem::MaybeUninit::<Self>::zeroed();
unsafe {
crate::detail::__rust_thunk___ZN6crubit4test16ThreadSafeStructC1Ev(
&raw mut tmp as *mut _,
);
tmp.assume_init()
}
}
}

/// A regular (non-thread-safe) struct for comparison.
///
/// Generated from: rs_bindings_from_cc/test/annotations/thread_safe.h;l=19
#[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=19
impl Default for RegularStruct {
#[inline(always)]
fn default() -> Self {
let mut tmp = ::core::mem::MaybeUninit::<Self>::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<bool, false>` 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<bool, true>` 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___ZN6crubit4test16ThreadSafeStructC1Ev(
__this: *mut ::core::ffi::c_void,
);
pub(crate) unsafe fn __rust_thunk___ZN6crubit4test13RegularStructC1Ev(
__this: *mut ::core::ffi::c_void,
);
}
}

const _: () = {
assert!(::core::mem::size_of::<crate::crubit::test::ThreadSafeStruct>() == 8);
assert!(::core::mem::align_of::<crate::crubit::test::ThreadSafeStruct>() == 4);
static_assertions::assert_impl_all!(crate::crubit::test::ThreadSafeStruct: Copy,Clone);
static_assertions::assert_not_impl_any!(crate::crubit::test::ThreadSafeStruct: Drop);
assert!(::core::mem::offset_of!(crate::crubit::test::ThreadSafeStruct, x) == 0);
assert!(::core::mem::offset_of!(crate::crubit::test::ThreadSafeStruct, y) == 4);
assert!(::core::mem::size_of::<crate::crubit::test::RegularStruct>() == 4);
assert!(::core::mem::align_of::<crate::crubit::test::RegularStruct>() == 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);
};
41 changes: 41 additions & 0 deletions rs_bindings_from_cc/test/annotations/thread_safe_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// 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 thread_safe::crubit::test::ThreadSafeStruct;

/// Verify that the thread-safe type implements Send.
/// This is a compile-time check: if ThreadSafeStruct doesn't implement Send,
/// this test will fail to compile.
#[gtest]
fn test_thread_safe_is_send() {
fn assert_send<T: Send>() {}
assert_send::<ThreadSafeStruct>();
}

/// Verify that the thread-safe type implements Sync.
/// This is a compile-time check: if ThreadSafeStruct doesn't implement Sync,
/// this test will fail to compile.
#[gtest]
fn test_thread_safe_is_sync() {
fn assert_sync<T: Sync>() {}
assert_sync::<ThreadSafeStruct>();
}

/// Verify that the generated struct has the expected size (matching the C++ type
/// with two ints).
#[gtest]
fn test_thread_safe_struct_has_correct_size() {
assert_eq!(std::mem::size_of::<ThreadSafeStruct>(), 8);
}

/// Verify that a non-thread-safe type does NOT implement Send or Sync.
/// These are compile-time checks using negative trait bounds.
#[gtest]
fn test_regular_struct_is_not_send_or_sync() {
// RegularStruct should not be Send or Sync (Crubit generates negative impls).
// We verify it exists and has expected size. The Send/Sync negative impls
// are verified by the codegen unit tests.
assert_eq!(std::mem::size_of::<thread_safe::crubit::test::RegularStruct>(), 4);
}
Loading