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 @@ -1108,12 +1108,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 @@ -1190,6 +1202,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),
.deprecated = std::move(deprecated),
};
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 @@ -714,6 +714,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 @@ -814,6 +814,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 @@ -898,6 +898,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
28 changes: 28 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,31 @@ 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 = [
"//support:ctor",
"@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",
)
34 changes: 34 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,34 @@
// 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 <atomic>

#include "support/annotations.h"

namespace crubit::test {

// A simple thread-safe struct.
class CRUBIT_THREAD_SAFE ThreadSafeStruct final {
public:
void Increment() { x_++; }
int Get() const { return x_.load(); }

private:
std::atomic<int> x_{0};
};

// A regular (non-thread-safe) struct for comparison.
struct RegularStruct final {
int value;

int Get() const { return value; }
void Set(int v) { value = v; }
};

} // namespace crubit::test

#endif // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_ANNOTATIONS_THREAD_SAFE_H_
71 changes: 71 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,71 @@
// 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: fmt, 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(class crubit::test::ThreadSafeStruct) == 4);
static_assert(alignof(class crubit::test::ThreadSafeStruct) == 4);

extern "C" void __rust_thunk___ZN6crubit4test16ThreadSafeStructC1Ev(
class crubit::test::ThreadSafeStruct* __this) {
crubit::construct_at(__this);
}

extern "C" void __rust_thunk___ZN6crubit4test16ThreadSafeStruct9IncrementEv(
class crubit::test::ThreadSafeStruct* __this) {
__this->Increment();
}

static_assert((void (::crubit::test::ThreadSafeStruct::*)()) &
::crubit::test::ThreadSafeStruct::Increment);

extern "C" int __rust_thunk___ZNK6crubit4test16ThreadSafeStruct3GetEv(
class crubit::test::ThreadSafeStruct const* __this) {
return __this->Get();
}

static_assert((int (::crubit::test::ThreadSafeStruct::*)() const) &
::crubit::test::ThreadSafeStruct::Get);

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);
}

extern "C" int __rust_thunk___ZNK6crubit4test13RegularStruct3GetEv(
struct crubit::test::RegularStruct const* __this) {
return __this->Get();
}

static_assert((int (::crubit::test::RegularStruct::*)() const) &
::crubit::test::RegularStruct::Get);

extern "C" void __rust_thunk___ZN6crubit4test13RegularStruct3SetEi(
struct crubit::test::RegularStruct* __this, int v) {
__this->Set(v);
}

static_assert((void (::crubit::test::RegularStruct::*)(int)) &
::crubit::test::RegularStruct::Set);

#pragma clang diagnostic pop
Loading