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