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
1 change: 1 addition & 0 deletions common/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ cc_library(
":status_macros",
":string_view_conversion",
"@abseil-cpp//absl/base:core_headers",
"@abseil-cpp//absl/base:nullability",
"@abseil-cpp//absl/status",
"@abseil-cpp//absl/status:statusor",
"@abseil-cpp//absl/strings",
Expand Down
35 changes: 32 additions & 3 deletions common/annotation_reader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <string>

#include "absl/base/attributes.h"
#include "absl/base/nullability.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
Expand All @@ -23,6 +24,8 @@
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/Expr.h"
#include "clang/AST/TypeBase.h"
#include "clang/Basic/AttrKinds.h"
#include "clang/Basic/LLVM.h"
#include "llvm/ADT/APSInt.h"
#include "llvm/ADT/StringRef.h"
Expand Down Expand Up @@ -167,9 +170,10 @@ static absl::Status CheckAnnotationsConsistent(

// Returns the `clang::Decl` that should be used for reading annotations.
//
// Template declarations technically do not have annotations-- the *templated*
// decl has annotations. So, if we're searching for annotations on a template
// decl, we should search for annotations on the templated decl instead.
// Template declarations technically do not have annotations-- the
// *templated* decl has annotations. So, if we're searching for annotations
// on a template decl, we should search for annotations on the templated
// decl instead.
static const clang::Decl& DeclForAnnotations(const clang::Decl& decl) {
auto* template_decl = clang::dyn_cast<clang::TemplateDecl>(&decl);
if (template_decl == nullptr) {
Expand Down Expand Up @@ -290,4 +294,29 @@ absl::StatusOr<std::optional<std::string>> GetAnnotationWithStringArg(
return std::string(*arg);
}

absl::StatusOr<const clang::AnnotateTypeAttr* absl_nullable>
GetTypeAnnotationSingleDecl(const clang::Type* absl_nonnull type
ABSL_ATTRIBUTE_LIFETIME_BOUND,
absl::string_view annotation_name) {
const clang::Type* current_type = type;
const clang::AnnotateTypeAttr* found_attr = nullptr;
while (const auto* attributed_type =
current_type->getAs<clang::AttributedType>()) {
if (attributed_type->getAttrKind() == clang::attr::AnnotateType) {
const clang::AnnotateTypeAttr* annotateAttr =
clang::cast<clang::AnnotateTypeAttr>(attributed_type->getAttr());
if (annotateAttr->getAnnotation() == llvm::StringRef(annotation_name)) {
if (found_attr != nullptr) {
return absl::InvalidArgumentError(
absl::StrCat("Only one `", annotation_name,
"` annotation may be placed on a type."));
}
found_attr = annotateAttr;
}
}
current_type = attributed_type->getEquivalentType().getTypePtr();
}
return found_attr;
}

} // namespace crubit
10 changes: 10 additions & 0 deletions common/annotation_reader.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include <optional>
#include <string>

#include "absl/base/attributes.h"
#include "absl/base/nullability.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
Expand All @@ -16,6 +18,7 @@
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/Expr.h"
#include "clang/AST/TypeBase.h"

namespace crubit {

Expand Down Expand Up @@ -83,6 +86,13 @@ absl::StatusOr<std::optional<std::string>> GetAnnotationWithStringArg(
absl::StatusOr<bool> HasAnnotationWithoutArgs(
const clang::Decl& decl, absl::string_view annotation_name);

// Returns the `AnnotateTypeAttr` with the given `annotation_name` if it exists.
// If there are multiple annotations with the given name, returns an error.
absl::StatusOr<const clang::AnnotateTypeAttr* absl_nullable>
GetTypeAnnotationSingleDecl(const clang::Type* absl_nonnull type
ABSL_ATTRIBUTE_LIFETIME_BOUND,
absl::string_view annotation_name);

} // namespace crubit

#endif // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_IMPORTERS_ANNOTATION_READER_H_
43 changes: 42 additions & 1 deletion rs_bindings_from_cc/generate_bindings/database/rs_snippet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,7 @@ pub enum RsTypeKind {
crate_path: Rc<CratePath>,
/// If this record is an instantiation of a `UniformReprTemplateType`, this will be set.
uniform_repr_template_type: Option<Rc<UniformReprTemplateType>>,
owned_ptr_type: Option<Rc<str>>,
},
Enum {
enum_: Rc<Enum>,
Expand Down Expand Up @@ -766,6 +767,7 @@ impl RsTypeKind {
record.template_specialization.as_ref(),
is_return_type,
)?,
owned_ptr_type: record.owned_ptr_type.clone(),
record,
crate_path,
})
Expand Down Expand Up @@ -1026,6 +1028,13 @@ impl RsTypeKind {
(missing_features, reasons.into_iter().join(", "))
}

pub fn is_owned_ptr(&self) -> bool {
matches!(
self.unalias(),
RsTypeKind::Pointer { kind: RustPtrKind::CcPtr(PointerTypeKind::Owned), .. }
)
}

/// Returns true if the type can be passed by value through `extern "C"` ABI
/// thunks.
pub fn is_c_abi_compatible_by_value(&self) -> bool {
Expand Down Expand Up @@ -1322,6 +1331,33 @@ impl RsTypeKind {
}
}

/// The same as [`RsTypeKind::to_token_stream`], except that types of
/// `RsTypeKind::Pointer` with kind
/// `RustPtrKind::CcPtr(PointerTypeKind::Owned)` will emit the corresponding
/// owning Rust type rather than a raw pointer.
pub fn to_token_stream_with_owned_ptr_type(&self, db: &dyn BindingsGenerator) -> TokenStream {
// If it's not an owned pointer, just use the default implementation.
let RsTypeKind::Pointer {
pointee, kind: RustPtrKind::CcPtr(PointerTypeKind::Owned), ..
} = self
else {
return self.to_token_stream(db);
};

let RsTypeKind::Record { record, .. } = &**pointee else {
panic!(
"CRUBIT_OWNED_PTR annotated pointers should point to a struct with an associated CRUBIT_OWNED_PTR_TYPE"
)
};

let owned_ptr_type = record.owned_ptr_type.as_ref().expect(
"CRUBIT_OWNED_PTR annotated pointers should point to a struct with an associated CRUBIT_OWNED_PTR_TYPE",
);

let path: syn::Path = syn::parse_str(owned_ptr_type).expect("Couldn't parse path");
quote! { #path }
}

/// Similar to to_token_stream, but replacing RsTypeKind:Record with Self
/// when the underlying Record matches the given one.
pub fn to_token_stream_replacing_by_self(
Expand Down Expand Up @@ -1496,7 +1532,12 @@ impl RsTypeKind {
let record_ident = make_rs_ident(incomplete_record.rs_name.identifier.as_ref());
quote! { #crate_path #record_ident }
}
RsTypeKind::Record { record, crate_path, uniform_repr_template_type } => {
RsTypeKind::Record {
record,
crate_path,
uniform_repr_template_type,
owned_ptr_type: _,
} => {
if let Some(generic_monomorphization) = uniform_repr_template_type {
return generic_monomorphization.to_token_stream(db);
}
Expand Down
28 changes: 21 additions & 7 deletions rs_bindings_from_cc/generate_bindings/generate_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1085,7 +1085,15 @@ fn generate_func_body(
// not generate the thunk at all, but this would be a bit of extra work.
//
// TODO(jeanpierreda): separately handle non-Unpin and non-trivial types.
let mut body = if return_type.is_c_abi_compatible_by_value() {
let mut body = if return_type.is_owned_ptr() {
quote! {
core::mem::transmute(
#crate_root_path::detail::#thunk_ident(
#( #clone_prefixes #thunk_args #clone_suffixes ),*
)
)
}
} else if return_type.is_c_abi_compatible_by_value() {
quote! {
#crate_root_path::detail::#thunk_ident(
#( #clone_prefixes #thunk_args #clone_suffixes ),*
Expand Down Expand Up @@ -1362,10 +1370,10 @@ fn rs_type_kinds_for_func(
PointerTypeKind::Nullable | PointerTypeKind::NonNull => {
*kind = PointerTypeKind::LValueRef;
}
PointerTypeKind::Owned => panic!("owned `this` pointers are not supported"),
}
}
}

errors.consume_error(
db.rs_type_kind_with_lifetime_elision(
param_type,
Expand Down Expand Up @@ -1821,6 +1829,7 @@ pub fn generate_function(
cc_details,
..Default::default()
};

Ok(Some(GeneratedFunction {
snippets: Rc::new(generated_item),
id: Rc::new(function_id),
Expand Down Expand Up @@ -1896,6 +1905,7 @@ fn function_signature(
}
_ => None,
};

for (i, (ident, type_)) in param_idents.iter().zip(param_types.iter()).enumerate() {
// If we are generating bindings for a derived record, parameter types should be
// kept the same because `Self` will refer to the derived record type.
Expand All @@ -1922,10 +1932,10 @@ fn function_signature(
if should_replace_by_self {
type_.to_token_stream_replacing_by_self(db, Some(impl_record))
} else {
type_.to_token_stream(db)
type_.to_token_stream_with_owned_ptr_type(db)
}
} else {
type_.to_token_stream(db)
type_.to_token_stream_with_owned_ptr_type(db)
};
*features |= Feature::impl_trait_in_assoc_type;
api_params.push(quote! {#ident: impl ::ctor::Ctor<Output=#quoted_type_or_self, Error=::ctor::Infallible>});
Expand All @@ -1936,10 +1946,10 @@ fn function_signature(
if should_replace_by_self {
type_.to_token_stream_replacing_by_self(db, Some(impl_record))
} else {
type_.to_token_stream(db)
type_.to_token_stream_with_owned_ptr_type(db)
}
} else {
type_.to_token_stream(db)
type_.to_token_stream_with_owned_ptr_type(db)
};
if type_.is_crubit_abi_bridge_type() {
let crubit_abi_type = db
Expand All @@ -1950,6 +1960,9 @@ fn function_signature(

api_params.push(quote! {#ident: #quoted_type_or_self});
thunk_args.push(quote! {::bridge_rust::unstable_encode!(@ #crubit_abi_type_expr_tokens, #crubit_abi_type_tokens, #ident).as_ptr() as *const u8});
} else if type_.is_owned_ptr() {
api_params.push(quote! {#ident: #quoted_type_or_self});
thunk_args.push(quote! {::core::mem::transmute(#ident)});
} else if type_.is_c_abi_compatible_by_value() {
api_params.push(quote! {#ident: #quoted_type_or_self});
thunk_args.push(quote! {#ident});
Expand Down Expand Up @@ -2041,7 +2054,8 @@ fn function_signature(
if matches!(return_type.unalias(), RsTypeKind::Primitive(Primitive::Void)) {
quote! {}
} else {
let ty = quoted_return_type.unwrap_or_else(|| return_type.to_token_stream(db));
let ty = quoted_return_type
.unwrap_or_else(|| return_type.to_token_stream_with_owned_ptr_type(db));
if return_type.is_unpin() {
ty
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,9 @@ pub fn generate_function_thunk_impl(
CcTypeVariant::Pointer(pointer) => match pointer.kind {
PointerTypeKind::RValueRef => Ok(quote! { std::move(*#ident) }),
PointerTypeKind::LValueRef => Ok(quote! { *#ident }),
PointerTypeKind::Nullable | PointerTypeKind::NonNull => Ok(quote! { #ident }),
PointerTypeKind::Nullable
| PointerTypeKind::NonNull
| PointerTypeKind::Owned => Ok(quote! { #ident }),
},
CcTypeVariant::FuncPointer { non_null, .. } => {
if *non_null {
Expand Down
12 changes: 7 additions & 5 deletions rs_bindings_from_cc/generate_bindings/rs_type_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,13 @@ pub fn rs_type_kind_with_lifetime_elision(
// of the pointer. In the future, we may wish to consume this information along
// with a user-provided annotation in order to convert some pointers into either
// references or optional references.
PointerTypeKind::NonNull | PointerTypeKind::Nullable => RsTypeKind::Pointer {
pointee,
kind: RustPtrKind::CcPtr(pointer.kind),
mutability,
},
PointerTypeKind::NonNull | PointerTypeKind::Nullable | PointerTypeKind::Owned => {
RsTypeKind::Pointer {
pointee,
kind: RustPtrKind::CcPtr(pointer.kind),
mutability,
}
}
})
}
CcTypeVariant::FuncPointer { non_null, call_conv, param_and_return_types } => {
Expand Down
19 changes: 18 additions & 1 deletion rs_bindings_from_cc/importer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "rs_bindings_from_cc/recording_diagnostic_consumer.h"
#include "rs_bindings_from_cc/type_map.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Attrs.inc"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclCXX.h"
Expand Down Expand Up @@ -1149,6 +1150,17 @@ absl::StatusOr<CcType> Importer::ConvertUnattributedType(
// Qualifiers are handled separately in ConvertQualType().
std::string type_string = clang::QualType(type, 0).getAsString();

CRUBIT_ASSIGN_OR_RETURN(
const clang::AnnotateTypeAttr* crubit_owned_ptr_attr,
GetTypeAnnotationSingleDecl(type, "crubit_owned_ptr"));

if (crubit_owned_ptr_attr != nullptr && !type->isPointerType()) {
return absl::InvalidArgumentError(
"CRUBIT_OWNED_PTR can only be applied to pointer types.");
}

bool is_owned_ptr = crubit_owned_ptr_attr != nullptr;

assert(!lifetimes || IsSameCanonicalUnqualifiedType(
lifetimes->Type(), clang::QualType(type, 0)));

Expand Down Expand Up @@ -1214,7 +1226,12 @@ absl::StatusOr<CcType> Importer::ConvertUnattributedType(
// IR consumer to error if a lifetime is required. This allows the IR
// consumer to infer a lifetime where-appropriate (e.g. constructors).
if (type->isPointerType()) {
return CcType::PointerTo(std::move(cpp_pointee_type), lifetime, nullable);
if (is_owned_ptr) {
return CcType::OwnedPointerTo(std::move(cpp_pointee_type), lifetime);
} else {
return CcType::PointerTo(std::move(cpp_pointee_type), lifetime,
nullable);
}
} else if (type->isLValueReferenceType()) {
return CcType::LValueReferenceTo(std::move(cpp_pointee_type), lifetime);
} else {
Expand Down
1 change: 1 addition & 0 deletions rs_bindings_from_cc/importers/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ cc_library(
hdrs = ["function.h"],
deps = [
"//common:annotation_reader",
"//common:status_macros",
"//lifetime_annotations",
"//lifetime_annotations:lifetime",
"//lifetime_annotations:lifetime_error",
Expand Down
10 changes: 10 additions & 0 deletions rs_bindings_from_cc/importers/cxx_record.cc
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,15 @@ std::optional<IR::Item> CXXRecordDeclImporter::Import(
bool is_explicit_class_template_instantiation_definition = false;
std::optional<TemplateSpecialization> template_specialization;
std::optional<BridgeType> bridge_type = GetBridgeTypeAnnotation(*record_decl);

absl::StatusOr<std::optional<std::string>> owned_ptr_type =
GetAnnotationWithStringArg(*record_decl, "crubit_owned_ptr_type");
if (!owned_ptr_type.ok()) {
return ictx_.ImportUnsupportedItem(
*record_decl, std::nullopt,
FormattedError::FromStatus(owned_ptr_type.status()));
}

BazelLabel owning_target = ictx_.GetOwningTarget(record_decl);
if (auto* specialization_decl =
clang::dyn_cast<clang::ClassTemplateSpecializationDecl>(
Expand Down Expand Up @@ -650,6 +659,7 @@ std::optional<IR::Item> CXXRecordDeclImporter::Import(
.unknown_attr = std::move(*unknown_attr),
.doc_comment = std::move(doc_comment),
.bridge_type = std::move(bridge_type),
.owned_ptr_type = std::move(*owned_ptr_type),
.source_loc = ictx_.ConvertSourceLocation(source_loc),
.unambiguous_public_bases = GetUnambiguousPublicBases(*record_decl),
.fields = ImportFields(record_decl),
Expand Down
Loading