Add trivial construction flags to the IR.
These can be used in followup CLs to influence a few things:
* Triviality of move is sufficient (though strictly not necessary) for relocatability. In particular, we'll want to infer that a type is trivially relocatable if it's either `is_trivial_abi` **or** has a (public?) trivial move constructor.
* Triviality of a public copy constructor is necessary (and, together with trivial relocatability, sufficient) to derive Copy.
* Existence of copy at all is necessary to implement Clone.
Since these are interrelated, and for convenience, I did them all at once in one CL.
PiperOrigin-RevId: 400951362
diff --git a/rs_bindings_from_cc/ast_visitor.cc b/rs_bindings_from_cc/ast_visitor.cc
index 4ed72bf..131f938 100644
--- a/rs_bindings_from_cc/ast_visitor.cc
+++ b/rs_bindings_from_cc/ast_visitor.cc
@@ -92,11 +92,55 @@
bool AstVisitor::VisitRecordDecl(clang::RecordDecl* record_decl) {
std::vector<Field> fields;
clang::AccessSpecifier default_access = clang::AS_public;
+ // The definition is always rewritten, but default access to `kPublic` in case
+ // it is implicitly defined.
+ SpecialMemberFunc copy_ctor = {
+ .definition = SpecialMemberFunc::Definition::kTrivial,
+ .access = kPublic,
+ };
+ SpecialMemberFunc move_ctor = {
+ .definition = SpecialMemberFunc::Definition::kTrivial,
+ .access = kPublic,
+ };
if (const auto* cxx_record_decl =
clang::dyn_cast<clang::CXXRecordDecl>(record_decl)) {
if (cxx_record_decl->isClass()) {
default_access = clang::AS_private;
}
+
+ if (cxx_record_decl->hasTrivialCopyConstructor()) {
+ copy_ctor.definition = SpecialMemberFunc::Definition::kTrivial;
+ } else if (cxx_record_decl->hasNonTrivialCopyConstructor()) {
+ copy_ctor.definition = SpecialMemberFunc::Definition::kNontrivial;
+ } else {
+ // I don't think the copy ctor can be implicitly deleted, but just in
+ // case...
+ copy_ctor.definition = SpecialMemberFunc::Definition::kDeleted;
+ }
+
+ if (cxx_record_decl->hasTrivialMoveConstructor()) {
+ move_ctor.definition = SpecialMemberFunc::Definition::kTrivial;
+ } else if (cxx_record_decl->hasNonTrivialMoveConstructor()) {
+ move_ctor.definition = SpecialMemberFunc::Definition::kNontrivial;
+ } else {
+ // The move constructor can be **implicitly deleted** (and so not subject
+ // to the below loop over ctors), e.g. by the presence by a copy ctor.
+ move_ctor.definition = SpecialMemberFunc::Definition::kDeleted;
+ }
+
+ for (clang::CXXConstructorDecl* ctor_decl : cxx_record_decl->ctors()) {
+ if (ctor_decl->isCopyConstructor()) {
+ copy_ctor.access = TranslateAccessSpecifier(ctor_decl->getAccess());
+ if (ctor_decl->isDeleted()) {
+ copy_ctor.definition = SpecialMemberFunc::Definition::kDeleted;
+ }
+ } else if (ctor_decl->isMoveConstructor()) {
+ move_ctor.access = TranslateAccessSpecifier(ctor_decl->getAccess());
+ if (ctor_decl->isDeleted()) {
+ move_ctor.definition = SpecialMemberFunc::Definition::kDeleted;
+ }
+ }
+ }
}
const clang::ASTRecordLayout& layout =
record_decl->getASTContext().getASTRecordLayout(record_decl);
@@ -121,6 +165,8 @@
.fields = std::move(fields),
.size = layout.getSize().getQuantity(),
.alignment = layout.getAlignment().getQuantity(),
+ .copy_constructor = copy_ctor,
+ .move_constructor = move_ctor,
.is_trivial_abi = record_decl->canPassInRegisters()});
return true;
}
diff --git a/rs_bindings_from_cc/ast_visitor_test.cc b/rs_bindings_from_cc/ast_visitor_test.cc
index 12665fe..735fd17 100644
--- a/rs_bindings_from_cc/ast_visitor_test.cc
+++ b/rs_bindings_from_cc/ast_visitor_test.cc
@@ -154,6 +154,23 @@
return false;
}
+// Matches a Record with a copy_constructor that matches all given matchers.
+template <typename... Args>
+auto CopyConstructor(const Args&... matchers) {
+ return testing::Field("copy_constructor", &Record::copy_constructor,
+ AllOf(matchers...));
+}
+
+// Matches a Record with a move_constructor that matches all given matchers.
+template <typename... Args>
+auto MoveConstructor(const Args&... matchers) {
+ return testing::Field("move_constructor", &Record::move_constructor,
+ AllOf(matchers...));
+}
+
+// Matches a SpecialMemberFunc that has the given definition.
+MATCHER_P(DefinitionIs, definition, "") { return arg.definition == definition; }
+
// Matches a Field that has the given access specifier.
MATCHER_P(AccessIs, access, "") { return arg.access == access; }
@@ -260,6 +277,121 @@
FieldType(IsInt()), OffsetIs(32)))))));
}
+TEST(AstVisitorTest, TrivialCopyConstructor) {
+ absl::string_view file =
+ "struct Implicit {};\n"
+ "struct Defaulted { Defaulted(const Defaulted&) = default; };\n";
+ IR ir = IrFromCc({file}, {});
+
+ EXPECT_THAT(ir.items, SizeIs(2));
+ EXPECT_THAT(ir.items, Each(VariantWith<Record>(CopyConstructor(DefinitionIs(
+ SpecialMemberFunc::Definition::kTrivial)))));
+}
+
+TEST(AstVisitorTest, NontrivialCopyConstructor) {
+ absl::string_view file = "struct Defined { Defined(const Defined&);};\n";
+ // TODO(b/202113881): "struct MemberImplicit { Defined x; };\n"
+ // TODO(b/202113881): "struct MemberDefaulted { MemberDefaulted(const
+ // MemberDefaulted&) = default; Defined x; };\n"
+ IR ir = IrFromCc({file}, {});
+ EXPECT_THAT(ir.items, SizeIs(1));
+ EXPECT_THAT(ir.items, Each(VariantWith<Record>(CopyConstructor(DefinitionIs(
+ SpecialMemberFunc::Definition::kNontrivial)))));
+}
+
+TEST(AstVisitorTest, DeletedCopyConstructor) {
+ absl::string_view file =
+ "struct Deleted { Deleted(const Deleted&) = delete;};\n"
+ // TODO(b/202113881): "struct DeletedByMember { Deleted x; };\n"
+ "struct DeletedByCtorDef { DeletedByCtorDef(DeletedByCtorDef&&) {} };\n";
+ IR ir = IrFromCc({file}, {});
+
+ EXPECT_THAT(ir.items, SizeIs(2));
+ EXPECT_THAT(ir.items, Each(VariantWith<Record>(CopyConstructor(DefinitionIs(
+ SpecialMemberFunc::Definition::kDeleted)))));
+}
+
+TEST(AstVisitorTest, PublicCopyConstructor) {
+ absl::string_view file =
+ "class Implicit {};\n"
+ "struct Defaulted { Defaulted(const Defaulted&) = default; };\n"
+ "class Section { public: Section(const Section&) = default; };\n";
+ IR ir = IrFromCc({file}, {});
+
+ EXPECT_THAT(ir.items, SizeIs(3));
+ EXPECT_THAT(ir.items,
+ Each(VariantWith<Record>(CopyConstructor(AccessIs(kPublic)))));
+}
+
+TEST(AstVisitorTest, PrivateCopyConstructor) {
+ absl::string_view file =
+ "class Defaulted { Defaulted(const Defaulted&) = default; };\n"
+ "struct Section { private: Section(const Section&) = default; };\n";
+ IR ir = IrFromCc({file}, {});
+
+ EXPECT_THAT(ir.items, SizeIs(2));
+ EXPECT_THAT(ir.items,
+ Each(VariantWith<Record>(CopyConstructor(AccessIs(kPrivate)))));
+}
+
+TEST(AstVisitorTest, TrivialMoveConstructor) {
+ absl::string_view file =
+ "struct Implicit {};\n"
+ "struct Defaulted { Defaulted(Defaulted&&) = default; };\n";
+ IR ir = IrFromCc({file}, {});
+
+ EXPECT_THAT(ir.items, SizeIs(2));
+ EXPECT_THAT(ir.items, Each(VariantWith<Record>(MoveConstructor(DefinitionIs(
+ SpecialMemberFunc::Definition::kTrivial)))));
+}
+
+TEST(AstVisitorTest, NontrivialMoveConstructor) {
+ absl::string_view file = "struct Defined { Defined(Defined&&);};\n";
+ // TODO(b/202113881): "struct MemberImplicit { Defined x; };\n"
+ // TODO(b/202113881): "struct MemberDefaulted { MemberDefaulted(
+ // MemberDefaulted&&) = default; Defined x; };\n"
+ IR ir = IrFromCc({file}, {});
+ EXPECT_THAT(ir.items, SizeIs(1));
+ EXPECT_THAT(ir.items, Each(VariantWith<Record>(MoveConstructor(DefinitionIs(
+ SpecialMemberFunc::Definition::kNontrivial)))));
+}
+
+TEST(AstVisitorTest, DeletedMoveConstructor) {
+ absl::string_view file =
+ "struct Deleted { Deleted(Deleted&&) = delete;};\n"
+ // TODO(b/202113881): "struct DeletedByMember { Deleted x; };\n"
+ "struct SuppressedByCtorDef {"
+ " SuppressedByCtorDef(const SuppressedByCtorDef&) {}};\n";
+ IR ir = IrFromCc({file}, {});
+
+ EXPECT_THAT(ir.items, SizeIs(2));
+ EXPECT_THAT(ir.items, Each(VariantWith<Record>(MoveConstructor(DefinitionIs(
+ SpecialMemberFunc::Definition::kDeleted)))));
+}
+
+TEST(AstVisitorTest, PublicMoveConstructor) {
+ absl::string_view file =
+ "class Implicit {};\n"
+ "struct Defaulted { Defaulted(Defaulted&&) = default; };\n"
+ "class Section { public: Section(Section&&) = default; };\n";
+ IR ir = IrFromCc({file}, {});
+
+ EXPECT_THAT(ir.items, SizeIs(3));
+ EXPECT_THAT(ir.items,
+ Each(VariantWith<Record>(MoveConstructor(AccessIs(kPublic)))));
+}
+
+TEST(AstVisitorTest, PrivateMoveConstructor) {
+ absl::string_view file =
+ "class Defaulted { Defaulted(Defaulted&&) = default; };\n"
+ "struct Section { private: Section(Section&&) = default; };\n";
+ IR ir = IrFromCc({file}, {});
+
+ EXPECT_THAT(ir.items, SizeIs(2));
+ EXPECT_THAT(ir.items,
+ Each(VariantWith<Record>(MoveConstructor(AccessIs(kPrivate)))));
+}
+
TEST(AstVisitorTest, MemberVariableAccessSpecifiers) {
IR ir = IrFromCc({R"(
struct SomeStruct {
diff --git a/rs_bindings_from_cc/ir.cc b/rs_bindings_from_cc/ir.cc
index a85685a..51a3e75 100644
--- a/rs_bindings_from_cc/ir.cc
+++ b/rs_bindings_from_cc/ir.cc
@@ -110,6 +110,30 @@
return result;
}
+static std::string SpecialMemberDefinitionToString(
+ SpecialMemberFunc::Definition def) {
+ switch (def) {
+ case SpecialMemberFunc::Definition::kTrivial:
+ return "Trivial";
+ case SpecialMemberFunc::Definition::kNontrivial:
+ return "Nontrivial";
+ case SpecialMemberFunc::Definition::kDeleted:
+ return "Deleted";
+ }
+}
+
+std::ostream& operator<<(std::ostream& o,
+ const SpecialMemberFunc::Definition& definition) {
+ return o << SpecialMemberDefinitionToString(definition);
+}
+
+nlohmann::json SpecialMemberFunc::ToJson() const {
+ nlohmann::json result;
+ result["definition"] = SpecialMemberDefinitionToString(definition);
+ result["access"] = AccessToString(access);
+ return result;
+}
+
nlohmann::json Record::ToJson() const {
std::vector<nlohmann::json> json_fields;
json_fields.reserve(fields.size());
@@ -122,6 +146,8 @@
record["fields"] = std::move(json_fields);
record["size"] = size;
record["alignment"] = alignment;
+ record["copy_constructor"] = copy_constructor.ToJson();
+ record["move_constructor"] = move_constructor.ToJson();
record["is_trivial_abi"] = is_trivial_abi;
nlohmann::json item;
diff --git a/rs_bindings_from_cc/ir.h b/rs_bindings_from_cc/ir.h
index 08663f8..9654b39 100644
--- a/rs_bindings_from_cc/ir.h
+++ b/rs_bindings_from_cc/ir.h
@@ -208,6 +208,27 @@
return o << f.ToJson();
}
+/// Information about special member functions.
+struct SpecialMemberFunc {
+ enum class Definition : char {
+ kTrivial,
+ kNontrivial,
+ kDeleted,
+ };
+
+ nlohmann::json ToJson() const;
+
+ Definition definition = Definition::kTrivial;
+ AccessSpecifier access = AccessSpecifier::kPublic;
+};
+
+std::ostream& operator<<(std::ostream& o,
+ const SpecialMemberFunc::Definition& definition);
+
+inline std::ostream& operator<<(std::ostream& o, const SpecialMemberFunc& f) {
+ return o << f.ToJson();
+}
+
// A record (struct, class, union).
struct Record {
nlohmann::json ToJson() const;
@@ -218,6 +239,10 @@
int64_t size;
int64_t alignment;
+ // Special member functions.
+ SpecialMemberFunc copy_constructor = {};
+ SpecialMemberFunc move_constructor = {};
+
// Whether this type is passed by value as if it were a trivial type (the same
// as it would be if it were a struct in C).
//
diff --git a/rs_bindings_from_cc/ir_test.cc b/rs_bindings_from_cc/ir_test.cc
index c6b40cf..991edfe 100644
--- a/rs_bindings_from_cc/ir_test.cc
+++ b/rs_bindings_from_cc/ir_test.cc
@@ -108,6 +108,14 @@
],
"size": 12,
"alignment": 4,
+ "copy_constructor": {
+ "definition": "Nontrivial",
+ "access": "Private"
+ },
+ "move_constructor": {
+ "definition": "Deleted",
+ "access": "Protected"
+ },
"is_trivial_abi": true
}}
]
@@ -144,6 +152,16 @@
},
.size = 12,
.alignment = 4,
+ .copy_constructor =
+ SpecialMemberFunc{
+ .definition = SpecialMemberFunc::Definition::
+ kNontrivial,
+ .access = kPrivate},
+ .move_constructor =
+ SpecialMemberFunc{
+ .definition =
+ SpecialMemberFunc::Definition::kDeleted,
+ .access = kProtected},
.is_trivial_abi = true}}};
EXPECT_EQ(ir.ToJson(), expected);
}