https://github.com/adams381 created https://github.com/llvm/llvm-project/pull/215174
A struct's `padded` bool only says that padding exists somewhere in the record. It cannot say which member, and it cannot tell compiler-inserted padding from storage the source declared that holds no ABI data, such as an unnamed bit-field unit. Those two need to differ, because padding is reusable tail padding and declared storage is not, so they give different data sizes. Give each member a mark instead: unmarked for source data, `pad`, or `empty`. A record is then empty for the ABI when no member holds data, which `allMembersNonData` reads off the type. This is the first of three PRs, and nothing populates the marks yet, so `padded` stays for now. Retiring it before CIRGen fills the marks in would make every struct claim it has no padding, and the x86_64 classifier would start counting padding arrays as data with no diagnostic. The CIRGen PR comes next, then the bool removal PR. Assisted-by: Cursor / claude-opus-5 >From 963e51ba1b0e371ceef43111d1388b3bf02b5812 Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Sun, 9 Aug 2026 09:35:09 -0700 Subject: [PATCH] [CIR] Let a record type mark what each member holds A struct's `padded` bool only says that padding exists somewhere in the record. It cannot say which member, and it cannot tell compiler-inserted padding from storage the source declared that holds no ABI data, such as an unnamed bit-field unit. Those two need to differ, because padding is reusable tail padding and declared storage is not, so they give different data sizes. Give each member a mark instead: unmarked for source data, `pad`, or `empty`. A record is then empty for the ABI when no member holds data, which `allMembersNonData` reads off the type. This is the first of three PRs, and nothing populates the marks yet, so `padded` stays for now. Retiring it before CIRGen fills the marks in would make every struct claim it has no padding, and the x86_64 classifier would start counting padding arrays as data with no diagnostic. The CIRGen PR comes next, then the bool removal PR. Assisted-by: Cursor / claude-opus-5 --- clang/include/clang/CIR/Dialect/IR/CIRTypes.h | 17 +- .../include/clang/CIR/Dialect/IR/CIRTypes.td | 87 +++++++-- .../clang/CIR/Dialect/IR/CIRTypesDetails.h | 97 +++++++--- clang/lib/CIR/Dialect/IR/CIRTypes.cpp | 178 +++++++++++++++--- .../CIR/IR/invalid-record-member-kinds.cir | 41 ++++ clang/test/CIR/IR/struct.cir | 59 +++++- clang/unittests/CIR/CMakeLists.txt | 1 + clang/unittests/CIR/RecordMemberKindTest.cpp | 169 +++++++++++++++++ 8 files changed, 570 insertions(+), 79 deletions(-) create mode 100644 clang/test/CIR/IR/invalid-record-member-kinds.cir create mode 100644 clang/unittests/CIR/RecordMemberKindTest.cpp diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.h b/clang/include/clang/CIR/Dialect/IR/CIRTypes.h index f72d10d236612..f93e9ad24b349 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.h +++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.h @@ -122,6 +122,7 @@ class RecordType : public mlir::Type { bool isComplete() const { return !isIncomplete(); } bool getPacked() const; bool getPadded() const; + llvm::ArrayRef<RecordMemberKind> getMemberKinds() const; bool isClass() const; bool isStruct() const; @@ -133,7 +134,8 @@ class RecordType : public mlir::Type { std::string getPrefixedName() const; void complete(llvm::ArrayRef<mlir::Type> members, bool packed, bool padded, - mlir::Type padding = {}); + mlir::Type padding = {}, + llvm::ArrayRef<RecordMemberKind> memberKinds = {}); uint64_t getElementOffset(const mlir::DataLayout &dataLayout, unsigned idx) const; bool isLayoutIdentical(const RecordType &other); @@ -143,6 +145,19 @@ class RecordType : public mlir::Type { void removeABIConversionNamePrefix(); }; +/// Drop a member-kind list that marks nothing, so that a record whose members +/// all hold data has exactly one spelling. Two storage keys that print +/// identically would otherwise give two unequal types no reader could tell +/// apart. +llvm::ArrayRef<RecordMemberKind> +normalizeRecordMemberKinds(llvm::ArrayRef<RecordMemberKind> memberKinds); + +/// Whether no member of \p recTy holds data, which makes the record empty for +/// the ABI. Vacuously true for a complete record with no members, and false +/// for an incomplete one, whose members are not known yet. A union's +/// tail-padding slot is not a member and does not count. +bool allMembersNonData(RecordType recTy); + } // namespace cir #endif // CLANG_CIR_DIALECT_IR_CIRTYPES_H diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td index 29afaa6d41f4b..365b96cd8e86f 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td @@ -621,6 +621,37 @@ def CIR_VoidType : CIR_Type<"Void", "void"> { }]; } +//===----------------------------------------------------------------------===// +// RecordMemberKind +// +// What a record member holds, for members that do not hold source data. +//===----------------------------------------------------------------------===// + +def CIR_RecordMemberKind : CIR_I32EnumAttr< + "RecordMemberKind", "what a record member holds", [ + I32EnumAttrCase<"Data", 0, "data">, + I32EnumAttrCase<"Pad", 1, "pad">, + I32EnumAttrCase<"Empty", 2, "empty"> +]> { + let description = [{ + Distinguishes a record member that holds source data from one that does + not. `pad` is storage the compiler inserted to place a later member at its + required offset, and is reusable tail padding when it trails the record. + `empty` is storage the source declared that carries no data for argument + passing: an unnamed bit-field unit, or a field of a record that is empty for + the ABI. Everything else, including a vtable pointer, a base subobject, and + a bit-field unit with a named occupant, is `data`. + + A record is empty for the ABI when no member is `data`, which is vacuously + true for a record with no members. The distinction between `pad` and + `empty` is load-bearing beyond that: only `pad` is reusable, so a record + whose trailing member is an unnamed bit-field unit keeps that unit in its + data size. + }]; + + let genSpecializedAttr = 0; +} + //===----------------------------------------------------------------------===// // StructType // @@ -656,6 +687,11 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [ plain struct declarations. Both are semantically identical; the keyword preserves the original source spelling. + A member may carry a `pad` or `empty` mark, described by + `CIR_RecordMemberKind`, saying that it holds no source data. An unmarked + member holds data, and a record whose members are all unmarked carries no + mark list at all. + Examples: ``` @@ -665,6 +701,8 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [ !anonymous = !cir.struct<{!u8i}> !rec_packed = !cir.struct<"p1" packed {!u8i, !u8i}> !rec_padded = !cir.struct<"p2" padded {!u8i, !u8i}> + !rec_pad = !cir.struct<"p3" {!u8i, pad !cir.array<!u8i x 3>}> + !rec_empty = !cir.struct<"e" {empty !u8i}> !recursive = !cir.struct<"Node" {!cir.ptr<!cir.struct<"Node">>}> ``` }]; @@ -675,6 +713,7 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [ "bool":$incomplete, "bool":$packed, "bool":$padded, + OptionalArrayRefParameter<"cir::RecordMemberKind">:$member_kinds, "bool":$is_class ); @@ -692,10 +731,11 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [ "mlir::StringAttr":$name, "bool":$packed, "bool":$padded, - "bool":$is_class + "bool":$is_class, + CArg<"llvm::ArrayRef<cir::RecordMemberKind>", "{}">:$member_kinds ), [{ return $_get($_ctxt, members, name, /*incomplete=*/false, packed, padded, - is_class); + cir::normalizeRecordMemberKinds(member_kinds), is_class); }]>, // Create an identified and incomplete struct/class type. @@ -704,8 +744,9 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [ "bool":$is_class ), [{ return $_get($_ctxt, /*members=*/llvm::ArrayRef<mlir::Type>{}, name, - /*incomplete=*/true, /*packed=*/false, - /*padded=*/false, is_class); + /*incomplete=*/true, /*packed=*/false, /*padded=*/false, + /*member_kinds=*/llvm::ArrayRef<cir::RecordMemberKind>{}, + is_class); }]>, // Create an anonymous struct/class type (always complete). @@ -713,10 +754,12 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [ "llvm::ArrayRef<mlir::Type>":$members, "bool":$packed, "bool":$padded, - "bool":$is_class + "bool":$is_class, + CArg<"llvm::ArrayRef<cir::RecordMemberKind>", "{}">:$member_kinds ), [{ return $_get($_ctxt, members, mlir::StringAttr{}, /*incomplete=*/false, - packed, padded, is_class); + packed, padded, + cir::normalizeRecordMemberKinds(member_kinds), is_class); }]> ]; @@ -740,11 +783,14 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [ } void complete(llvm::ArrayRef<mlir::Type> members, bool packed, - bool isPadded); + bool isPadded, + llvm::ArrayRef<cir::RecordMemberKind> memberKinds = {}); uint64_t getElementOffset(const mlir::DataLayout &dataLayout, unsigned idx) const; + /// Marks are provenance rather than layout, so two records that differ + /// only in how their members were produced are layout-identical. bool isLayoutIdentical(const StructType &other); // Checks the name of this record to check if it is a 'after' (or during) @@ -800,7 +846,9 @@ def CIR_UnionType : CIR_Type<"Union", "union", [ - Anonymous: no name and a known body. Padded unions carry an explicit tail-padding type to ensure the LLVM struct - that models the union has the correct byte size. + that models the union has the correct byte size. That slot is separate + from the per-member marks described by `CIR_RecordMemberKind`, which say + what each variant holds. The parser rejects a mark on that slot. Examples: @@ -809,6 +857,7 @@ def CIR_UnionType : CIR_Type<"Union", "union", [ !u_incomplete = !cir.union<"U" incomplete> !u_anonymous = !cir.union<{!s32i, !u8i}> !u_padded = !cir.union<"U" {!s32i, !u8i}, padding = {!u8i}> + !u_empty = !cir.union<"U" {empty !u8i}> ``` }]; @@ -817,7 +866,8 @@ def CIR_UnionType : CIR_Type<"Union", "union", [ OptionalParameter<"mlir::StringAttr">:$name, "bool":$incomplete, "bool":$packed, - OptionalParameter<"mlir::Type">:$padding + OptionalParameter<"mlir::Type">:$padding, + OptionalArrayRefParameter<"cir::RecordMemberKind">:$member_kinds ); // StorageClass is defined in C++ for mutability. @@ -833,27 +883,31 @@ def CIR_UnionType : CIR_Type<"Union", "union", [ "llvm::ArrayRef<mlir::Type>":$members, "mlir::StringAttr":$name, "bool":$packed, - CArg<"mlir::Type", "{}">:$padding + CArg<"mlir::Type", "{}">:$padding, + CArg<"llvm::ArrayRef<cir::RecordMemberKind>", "{}">:$member_kinds ), [{ return $_get($_ctxt, members, name, /*incomplete=*/false, packed, - padding); + padding, cir::normalizeRecordMemberKinds(member_kinds)); }]>, // Create an identified and incomplete union type. TypeBuilder<(ins "mlir::StringAttr":$name), [{ return $_get($_ctxt, /*members=*/llvm::ArrayRef<mlir::Type>{}, name, /*incomplete=*/true, /*packed=*/false, - /*padding=*/mlir::Type{}); + /*padding=*/mlir::Type{}, + /*member_kinds=*/llvm::ArrayRef<cir::RecordMemberKind>{}); }]>, // Create an anonymous union type (always complete). TypeBuilder<(ins "llvm::ArrayRef<mlir::Type>":$members, "bool":$packed, - CArg<"mlir::Type", "{}">:$padding + CArg<"mlir::Type", "{}">:$padding, + CArg<"llvm::ArrayRef<cir::RecordMemberKind>", "{}">:$member_kinds ), [{ return $_get($_ctxt, members, mlir::StringAttr{}, /*incomplete=*/false, - packed, padding); + packed, padding, + cir::normalizeRecordMemberKinds(member_kinds)); }]> ]; @@ -886,12 +940,15 @@ def CIR_UnionType : CIR_Type<"Union", "union", [ llvm::ArrayRef<mlir::Type> members); void complete(llvm::ArrayRef<mlir::Type> members, bool packed, - mlir::Type padding = {}); + mlir::Type padding = {}, + llvm::ArrayRef<cir::RecordMemberKind> memberKinds = {}); uint64_t getElementOffset(const mlir::DataLayout &, unsigned) const { return 0; } + /// Marks are provenance rather than layout, so two unions that differ only + /// in how their members were produced are layout-identical. bool isLayoutIdentical(const UnionType &other); // Checks the name of this record to check if it is a 'after' (or during) diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypesDetails.h b/clang/include/clang/CIR/Dialect/IR/CIRTypesDetails.h index e94e1d81ff4c6..123fa059cfeac 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRTypesDetails.h +++ b/clang/include/clang/CIR/Dialect/IR/CIRTypesDetails.h @@ -33,12 +33,14 @@ struct StructTypeStorage : public mlir::TypeStorage { bool incomplete; bool packed; bool padded; + llvm::ArrayRef<RecordMemberKind> member_kinds; bool is_class; KeyTy(llvm::ArrayRef<mlir::Type> members, mlir::StringAttr name, - bool incomplete, bool packed, bool padded, bool is_class) + bool incomplete, bool packed, bool padded, + llvm::ArrayRef<RecordMemberKind> member_kinds, bool is_class) : members(members), name(name), incomplete(incomplete), packed(packed), - padded(padded), is_class(is_class) {} + padded(padded), member_kinds(member_kinds), is_class(is_class) {} }; llvm::ArrayRef<mlir::Type> members; @@ -46,56 +48,73 @@ struct StructTypeStorage : public mlir::TypeStorage { bool incomplete; bool packed; bool padded; + llvm::ArrayRef<RecordMemberKind> member_kinds; bool is_class; StructTypeStorage(llvm::ArrayRef<mlir::Type> members, mlir::StringAttr name, - bool incomplete, bool packed, bool padded, bool is_class) + bool incomplete, bool packed, bool padded, + llvm::ArrayRef<RecordMemberKind> member_kinds, + bool is_class) : members(members), name(name), incomplete(incomplete), packed(packed), - padded(padded), is_class(is_class) { + padded(padded), member_kinds(member_kinds), is_class(is_class) { assert((name || !incomplete) && "Incomplete records must have a name"); + assert((member_kinds.empty() || member_kinds.size() == members.size()) && + "member kind list must cover every member"); } KeyTy getAsKey() const { - return KeyTy(members, name, incomplete, packed, padded, is_class); + return KeyTy(members, name, incomplete, packed, padded, member_kinds, + is_class); } bool operator==(const KeyTy &key) const { if (name) return (name == key.name) && (is_class == key.is_class); - return std::tie(members, name, incomplete, packed, padded, is_class) == - std::tie(key.members, key.name, key.incomplete, key.packed, - key.padded, key.is_class); + return std::tie(members, name, incomplete, packed, padded, member_kinds, + is_class) == std::tie(key.members, key.name, key.incomplete, + key.packed, key.padded, + key.member_kinds, key.is_class); } static llvm::hash_code hashKey(const KeyTy &key) { if (key.name) return llvm::hash_combine(key.name, key.is_class); return llvm::hash_combine(key.members, key.incomplete, key.packed, - key.padded, key.is_class); + key.padded, key.member_kinds, key.is_class); } static StructTypeStorage *construct(mlir::TypeStorageAllocator &allocator, const KeyTy &key) { - return new (allocator.allocate<StructTypeStorage>()) - StructTypeStorage(allocator.copyInto(key.members), key.name, - key.incomplete, key.packed, key.padded, key.is_class); + return new (allocator.allocate<StructTypeStorage>()) StructTypeStorage( + allocator.copyInto(key.members), key.name, key.incomplete, key.packed, + key.padded, allocator.copyInto(key.member_kinds), key.is_class); } /// Mutates the members and attributes of an identified struct/class. llvm::LogicalResult mutate(mlir::TypeStorageAllocator &allocator, llvm::ArrayRef<mlir::Type> members, bool packed, - bool padded) { + bool padded, + llvm::ArrayRef<RecordMemberKind> memberKinds) { if (!name) return llvm::failure(); + // A second completion must agree with the first in every parameter, + // including the marks: otherwise it silently keeps the marks it was given + // the first time. if (!incomplete) - return mlir::success((this->members == members) && - (this->packed == packed) && - (this->padded == padded)); + return mlir::success( + (this->members == members) && (this->packed == packed) && + (this->padded == padded) && (this->member_kinds == memberKinds)); + + // mutate is the one entrance verify() never sees, so check the length here + // rather than leave it to an assert. + if (!memberKinds.empty() && memberKinds.size() != members.size()) + return llvm::failure(); this->members = allocator.copyInto(members); this->packed = packed; this->padded = padded; + this->member_kinds = allocator.copyInto(memberKinds); incomplete = false; return llvm::success(); } @@ -113,11 +132,13 @@ struct UnionTypeStorage : public mlir::TypeStorage { bool incomplete; bool packed; mlir::Type padding; + llvm::ArrayRef<RecordMemberKind> member_kinds; KeyTy(llvm::ArrayRef<mlir::Type> members, mlir::StringAttr name, - bool incomplete, bool packed, mlir::Type padding) + bool incomplete, bool packed, mlir::Type padding, + llvm::ArrayRef<RecordMemberKind> member_kinds) : members(members), name(name), incomplete(incomplete), packed(packed), - padding(padding) {} + padding(padding), member_kinds(member_kinds) {} }; llvm::ArrayRef<mlir::Type> members; @@ -125,55 +146,69 @@ struct UnionTypeStorage : public mlir::TypeStorage { bool incomplete; bool packed; mlir::Type padding; + llvm::ArrayRef<RecordMemberKind> member_kinds; UnionTypeStorage(llvm::ArrayRef<mlir::Type> members, mlir::StringAttr name, - bool incomplete, bool packed, mlir::Type padding) + bool incomplete, bool packed, mlir::Type padding, + llvm::ArrayRef<RecordMemberKind> member_kinds) : members(members), name(name), incomplete(incomplete), packed(packed), - padding(padding) { + padding(padding), member_kinds(member_kinds) { assert((name || !incomplete) && "Incomplete records must have a name"); + assert((member_kinds.empty() || member_kinds.size() == members.size()) && + "member kind list must cover every member"); } KeyTy getAsKey() const { - return KeyTy(members, name, incomplete, packed, padding); + return KeyTy(members, name, incomplete, packed, padding, member_kinds); } bool operator==(const KeyTy &key) const { if (name) return name == key.name; - return std::tie(members, name, incomplete, packed, padding) == + return std::tie(members, name, incomplete, packed, padding, member_kinds) == std::tie(key.members, key.name, key.incomplete, key.packed, - key.padding); + key.padding, key.member_kinds); } static llvm::hash_code hashKey(const KeyTy &key) { if (key.name) return llvm::hash_combine(key.name); return llvm::hash_combine(key.members, key.incomplete, key.packed, - key.padding); + key.padding, key.member_kinds); } static UnionTypeStorage *construct(mlir::TypeStorageAllocator &allocator, const KeyTy &key) { - return new (allocator.allocate<UnionTypeStorage>()) - UnionTypeStorage(allocator.copyInto(key.members), key.name, - key.incomplete, key.packed, key.padding); + return new (allocator.allocate<UnionTypeStorage>()) UnionTypeStorage( + allocator.copyInto(key.members), key.name, key.incomplete, key.packed, + key.padding, allocator.copyInto(key.member_kinds)); } /// Mutates the members and attributes of an identified union. llvm::LogicalResult mutate(mlir::TypeStorageAllocator &allocator, llvm::ArrayRef<mlir::Type> members, bool packed, - mlir::Type padding) { + mlir::Type padding, + llvm::ArrayRef<RecordMemberKind> memberKinds) { if (!name) return llvm::failure(); + // A second completion must agree with the first in every parameter, + // including the marks: otherwise it silently keeps the marks it was given + // the first time. if (!incomplete) - return mlir::success((this->members == members) && - (this->packed == packed) && - (this->padding == padding)); + return mlir::success( + (this->members == members) && (this->packed == packed) && + (this->padding == padding) && (this->member_kinds == memberKinds)); + + // mutate is the one entrance verify() never sees, so check the length here + // rather than leave it to an assert. + if (!memberKinds.empty() && memberKinds.size() != members.size()) + return llvm::failure(); this->members = allocator.copyInto(members); this->packed = packed; this->padding = padding; + this->member_kinds = allocator.copyInto(memberKinds); incomplete = false; return llvm::success(); } diff --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp index af1bbdcd64fea..55fbba11ddb50 100644 --- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp @@ -152,31 +152,88 @@ void CIRDialect::printType(Type type, DialectAsmPrinter &os) const { // Shared helpers for StructType and UnionType parse/print. -/// Parse "incomplete" or "{type, type, ...}", writing results into -/// \p incomplete and \p members. Returns failure if member parsing fails. +llvm::ArrayRef<RecordMemberKind> +cir::normalizeRecordMemberKinds(llvm::ArrayRef<RecordMemberKind> memberKinds) { + if (llvm::all_of(memberKinds, [](RecordMemberKind kind) { + return kind == RecordMemberKind::Data; + })) + return {}; + return memberKinds; +} + +/// A mark list either is absent or names every member. An incomplete record +/// has no members, so a mark on one is caught by the same length check. +static mlir::LogicalResult +verifyRecordMemberKinds(function_ref<mlir::InFlightDiagnostic()> emitError, + size_t numMembers, + llvm::ArrayRef<RecordMemberKind> memberKinds) { + if (!memberKinds.empty() && memberKinds.size() != numMembers) + return emitError() << "expected " << numMembers << " member kinds, got " + << memberKinds.size(); + return mlir::success(); +} + +/// Parse the optional mark that precedes a member type. Only a mark keyword is +/// consumed, so a member spelled as a bare builtin type still reaches the type +/// parser. A data member is spelled without a mark, and accepting 'data' would +/// give a record two spellings that print the same way, so it is named here +/// only to reject it. +static mlir::ParseResult parseMemberKind(mlir::AsmParser &parser, + RecordMemberKind &kind) { + static const llvm::StringRef marks[] = {"pad", "empty", "data"}; + kind = RecordMemberKind::Data; + llvm::StringRef keyword; + const llvm::SMLoc loc = parser.getCurrentLocation(); + if (parser.parseOptionalKeyword(&keyword, marks).failed()) + return mlir::success(); + + if (keyword == "data") { + parser.emitError(loc, "a data member is spelled without a mark"); + return mlir::failure(); + } + kind = *symbolizeRecordMemberKind(keyword); + + const llvm::SMLoc secondLoc = parser.getCurrentLocation(); + if (parser.parseOptionalKeyword(&keyword, marks).succeeded()) { + parser.emitError(secondLoc, "a record member takes at most one kind mark"); + return mlir::failure(); + } + return mlir::success(); +} + +/// Parse "incomplete" or "{[mark] type, [mark] type, ...}", writing results +/// into \p incomplete, \p members and \p memberKinds. Returns failure if +/// member parsing fails. static mlir::ParseResult parseRecordBody(mlir::AsmParser &parser, bool &incomplete, - llvm::SmallVector<mlir::Type> &members) { + llvm::SmallVector<mlir::Type> &members, + llvm::SmallVectorImpl<RecordMemberKind> &memberKinds) { assert(incomplete && "caller must pre-initialize incomplete to true"); if (parser.parseOptionalKeyword("incomplete").succeeded()) return mlir::success(); incomplete = false; return parser.parseCommaSeparatedList( - AsmParser::Delimiter::Braces, [&parser, &members]() { + AsmParser::Delimiter::Braces, + [&parser, &members, &memberKinds]() -> mlir::ParseResult { + RecordMemberKind kind; + if (parseMemberKind(parser, kind).failed()) + return mlir::failure(); + memberKinds.push_back(kind); return parser.parseType(members.emplace_back()); }); } /// Print a complete CIR record body: /// '<' ['class '] [name] ['packed '] ['padded '] body '>' -/// where body is "incomplete" or "{members[, padding = {type}]}". +/// where body is "incomplete" or "{[mark] members[, padding = {type}]}". /// RecordTy must be a mutable MLIR type (StructType or UnionType). template <typename RecordTy> static void printRecordBody(mlir::AsmPrinter &printer, RecordTy self, mlir::StringAttr name, bool hasClassPrefix, bool isPacked, bool isPadded, bool isIncomplete, llvm::ArrayRef<mlir::Type> members, - mlir::Type padding = {}) { + mlir::Type padding, + llvm::ArrayRef<RecordMemberKind> memberKinds) { printer << '<'; if (hasClassPrefix) printer << "class "; @@ -200,7 +257,14 @@ static void printRecordBody(mlir::AsmPrinter &printer, RecordTy self, printer << "incomplete"; } else { printer << "{"; - llvm::interleaveComma(members, printer); + for (auto [idx, member] : llvm::enumerate(members)) { + if (idx) + printer << ", "; + if (idx < memberKinds.size() && + memberKinds[idx] != RecordMemberKind::Data) + printer << stringifyRecordMemberKind(memberKinds[idx]) << ' '; + printer.printType(member); + } printer << "}"; if (padding) { printer << ", padding = {"; @@ -257,29 +321,31 @@ Type StructType::parse(mlir::AsmParser &parser) { bool incomplete = true; llvm::SmallVector<mlir::Type> members; - if (parseRecordBody(parser, incomplete, members).failed()) + llvm::SmallVector<RecordMemberKind> memberKinds; + if (parseRecordBody(parser, incomplete, members, memberKinds).failed()) return {}; if (parser.parseGreater()) return {}; ArrayRef<mlir::Type> membersRef(members); + ArrayRef<RecordMemberKind> kindsRef = normalizeRecordMemberKinds(memberKinds); mlir::Type type = {}; if (name && incomplete) { type = StructType::getChecked(eLoc, context, name, is_class); } else if (!name && !incomplete) { type = StructType::getChecked(eLoc, context, membersRef, packed, padded, - is_class); + is_class, kindsRef); if (!type) return {}; } else if (!incomplete) { type = StructType::getChecked(eLoc, context, membersRef, name, packed, - padded, is_class); + padded, is_class, kindsRef); if (!type) return {}; if (auto structTy = mlir::dyn_cast<StructType>(type)) if (structTy.isIncomplete()) - structTy.complete(membersRef, packed, padded); + structTy.complete(membersRef, packed, padded, kindsRef); assert(!cir::MissingFeatures::astRecordDeclAttr()); } else { parser.emitError(loc, "anonymous records must be complete"); @@ -291,16 +357,19 @@ Type StructType::parse(mlir::AsmParser &parser) { void StructType::print(mlir::AsmPrinter &printer) const { printRecordBody(printer, *this, getName(), isClass(), getPacked(), - getPadded(), isIncomplete(), getMembers()); + getPadded(), isIncomplete(), getMembers(), /*padding=*/{}, + getMemberKinds()); } mlir::LogicalResult StructType::verify(function_ref<mlir::InFlightDiagnostic()> emitError, llvm::ArrayRef<mlir::Type> members, mlir::StringAttr name, - bool incomplete, bool packed, bool padded, bool is_class) { + bool incomplete, bool packed, bool padded, + llvm::ArrayRef<RecordMemberKind> member_kinds, + bool is_class) { if (name && name.getValue().empty()) return emitError() << "identified records cannot have an empty name"; - return mlir::success(); + return verifyRecordMemberKinds(emitError, members.size(), member_kinds); } // Accessors are hand-written because genStorageClass = 0 suppresses generated @@ -313,6 +382,9 @@ bool StructType::isIncomplete() const { return getImpl()->incomplete; } bool StructType::getIncomplete() const { return getImpl()->incomplete; } bool StructType::getPacked() const { return getImpl()->packed; } bool StructType::getPadded() const { return getImpl()->padded; } +llvm::ArrayRef<RecordMemberKind> StructType::getMemberKinds() const { + return getImpl()->member_kinds; +} bool StructType::getIsClass() const { return getImpl()->is_class; } bool StructType::isABIConvertedRecord() const { @@ -333,9 +405,11 @@ void StructType::removeABIConversionNamePrefix() { recordName.getType()); } -void StructType::complete(ArrayRef<Type> members, bool packed, bool padded) { +void StructType::complete(ArrayRef<Type> members, bool packed, bool padded, + ArrayRef<RecordMemberKind> memberKinds) { assert(!cir::MissingFeatures::astRecordDeclAttr()); - if (mutate(members, packed, padded).failed()) + if (mutate(members, packed, padded, normalizeRecordMemberKinds(memberKinds)) + .failed()) llvm_unreachable("failed to complete struct"); } @@ -390,10 +464,12 @@ Type UnionType::parse(mlir::AsmParser &parser) { bool incomplete = true; llvm::SmallVector<mlir::Type> members; - if (parseRecordBody(parser, incomplete, members).failed()) + llvm::SmallVector<RecordMemberKind> memberKinds; + if (parseRecordBody(parser, incomplete, members, memberKinds).failed()) return {}; - // Optional tail-padding slot: ", padding = { <type> }". + // Optional tail-padding slot: ", padding = { <type> }". It is not a variant + // and so takes no mark. if (!incomplete && parser.parseOptionalComma().succeeded()) { if (parser.parseKeyword("padding").failed()) return {}; @@ -401,6 +477,13 @@ Type UnionType::parse(mlir::AsmParser &parser) { return {}; if (parser.parseLBrace().failed()) return {}; + const llvm::SMLoc paddingLoc = parser.getCurrentLocation(); + llvm::StringRef paddingKeyword; + static const llvm::StringRef marks[] = {"pad", "empty", "data"}; + if (parser.parseOptionalKeyword(&paddingKeyword, marks).succeeded()) { + parser.emitError(paddingLoc, "a union's tail padding takes no kind mark"); + return {}; + } if (parser.parseType(padding).failed()) return {}; if (parser.parseRBrace().failed()) @@ -411,21 +494,23 @@ Type UnionType::parse(mlir::AsmParser &parser) { return {}; ArrayRef<mlir::Type> membersRef(members); + ArrayRef<RecordMemberKind> kindsRef = normalizeRecordMemberKinds(memberKinds); mlir::Type type = {}; if (name && incomplete) { type = UnionType::getChecked(eLoc, context, name); } else if (!name && !incomplete) { - type = UnionType::getChecked(eLoc, context, membersRef, packed, padding); + type = UnionType::getChecked(eLoc, context, membersRef, packed, padding, + kindsRef); if (!type) return {}; } else if (!incomplete) { - type = - UnionType::getChecked(eLoc, context, membersRef, name, packed, padding); + type = UnionType::getChecked(eLoc, context, membersRef, name, packed, + padding, kindsRef); if (!type) return {}; if (auto unionTy = mlir::dyn_cast<UnionType>(type)) if (unionTy.isIncomplete()) - unionTy.complete(membersRef, packed, padding); + unionTy.complete(membersRef, packed, padding, kindsRef); assert(!cir::MissingFeatures::astRecordDeclAttr()); } else { parser.emitError(loc, "anonymous records must be complete"); @@ -438,16 +523,22 @@ Type UnionType::parse(mlir::AsmParser &parser) { void UnionType::print(mlir::AsmPrinter &printer) const { printRecordBody(printer, *this, getName(), /*hasClassPrefix=*/false, getPacked(), /*isPadded=*/false, isIncomplete(), getMembers(), - getPadding()); + getPadding(), getMemberKinds()); } mlir::LogicalResult UnionType::verify(function_ref<mlir::InFlightDiagnostic()> emitError, llvm::ArrayRef<mlir::Type> members, mlir::StringAttr name, - bool incomplete, bool packed, mlir::Type padding) { + bool incomplete, bool packed, mlir::Type padding, + llvm::ArrayRef<RecordMemberKind> member_kinds) { if (name && name.getValue().empty()) return emitError() << "identified records cannot have an empty name"; - return mlir::success(); + // A union's variants all start at offset zero, so there is no inter-member + // padding for a pad mark to describe. Its tail padding lives in the separate + // padding slot. + if (llvm::is_contained(member_kinds, RecordMemberKind::Pad)) + return emitError() << "a union member cannot be marked pad"; + return verifyRecordMemberKinds(emitError, members.size(), member_kinds); } // Accessors. @@ -460,6 +551,9 @@ bool UnionType::getIncomplete() const { return getImpl()->incomplete; } bool UnionType::getPacked() const { return getImpl()->packed; } bool UnionType::getPadded() const { return getPadding() ? true : false; } mlir::Type UnionType::getPadding() const { return getImpl()->padding; } +llvm::ArrayRef<RecordMemberKind> UnionType::getMemberKinds() const { + return getImpl()->member_kinds; +} bool UnionType::isABIConvertedRecord() const { return getName() && getName().getValue().starts_with(abi_conversion_prefix); @@ -480,9 +574,11 @@ void UnionType::removeABIConversionNamePrefix() { } void UnionType::complete(ArrayRef<Type> members, bool packed, - mlir::Type padding) { + mlir::Type padding, + ArrayRef<RecordMemberKind> memberKinds) { assert(!cir::MissingFeatures::astRecordDeclAttr()); - if (mutate(members, packed, padding).failed()) + if (mutate(members, packed, padding, normalizeRecordMemberKinds(memberKinds)) + .failed()) llvm_unreachable("failed to complete union"); } @@ -541,6 +637,11 @@ bool RecordType::getPadded() const { return s.getPadded(); return mlir::cast<UnionType>(*this).getPadded(); } +llvm::ArrayRef<RecordMemberKind> RecordType::getMemberKinds() const { + if (auto s = mlir::dyn_cast<StructType>(*this)) + return s.getMemberKinds(); + return mlir::cast<UnionType>(*this).getMemberKinds(); +} bool RecordType::isClass() const { if (auto s = mlir::dyn_cast<StructType>(*this)) return s.isClass(); @@ -560,13 +661,15 @@ std::string RecordType::getPrefixedName() const { return getKindAsStr() + "." + getName().getValue().str(); } void RecordType::complete(ArrayRef<Type> members, bool packed, bool padded, - mlir::Type padding) { + mlir::Type padding, + ArrayRef<RecordMemberKind> memberKinds) { if (auto s = mlir::dyn_cast<StructType>(*this)) - return s.complete(members, packed, padded); + return s.complete(members, packed, padded, memberKinds); // Unions derive padded from padding; assert the caller is consistent. assert((!padded || padding) && "padded=true requires a non-null padding type"); - return mlir::cast<UnionType>(*this).complete(members, packed, padding); + return mlir::cast<UnionType>(*this).complete(members, packed, padding, + memberKinds); } uint64_t RecordType::getElementOffset(const mlir::DataLayout &dataLayout, unsigned idx) const { @@ -603,6 +706,21 @@ void RecordType::removeABIConversionNamePrefix() { return mlir::cast<UnionType>(*this).removeABIConversionNamePrefix(); } +bool cir::allMembersNonData(RecordType recTy) { + // An incomplete record has no members yet, which must not read as vacuously + // holding no data. + if (recTy.isIncomplete()) + return false; + if (recTy.getMembers().empty()) + return true; + // An absent list is the canonical spelling for all-data, so a record with + // members and no list holds data in all of them. + llvm::ArrayRef<RecordMemberKind> kinds = recTy.getMemberKinds(); + return !kinds.empty() && llvm::none_of(kinds, [](RecordMemberKind kind) { + return kind == RecordMemberKind::Data; + }); +} + //===----------------------------------------------------------------------===// // Data Layout information for types //===----------------------------------------------------------------------===// diff --git a/clang/test/CIR/IR/invalid-record-member-kinds.cir b/clang/test/CIR/IR/invalid-record-member-kinds.cir new file mode 100644 index 0000000000000..1709298da10ad --- /dev/null +++ b/clang/test/CIR/IR/invalid-record-member-kinds.cir @@ -0,0 +1,41 @@ +// RUN: cir-opt %s -verify-diagnostics -split-input-file + +!u8i = !cir.int<u, 8> +// expected-error @below {{expected non-function type}} +!rec_S = !cir.struct<"S" {bogus !u8i}> + +module {} + +// ----- + +!u8i = !cir.int<u, 8> +// expected-error @below {{a data member is spelled without a mark}} +!rec_S = !cir.struct<"S" {data !u8i}> + +module {} + +// ----- + +!u8i = !cir.int<u, 8> +// expected-error @below {{a record member takes at most one kind mark}} +!rec_S = !cir.struct<"S" {pad empty !u8i}> + +module {} + +// ----- + +!u8i = !cir.int<u, 8> +!s32i = !cir.int<s, 32> +// expected-error @below {{a union's tail padding takes no kind mark}} +!rec_U = !cir.union<"U" {!s32i}, padding = {pad !cir.array<!u8i x 4>}> + +module {} + +// ----- + +!u8i = !cir.int<u, 8> +!s32i = !cir.int<s, 32> +// expected-error @below {{a union member cannot be marked pad}} +!rec_U = !cir.union<"U" {!s32i, pad !u8i}> + +module {} diff --git a/clang/test/CIR/IR/struct.cir b/clang/test/CIR/IR/struct.cir index 783a56e55ed43..7321835fc1d0d 100644 --- a/clang/test/CIR/IR/struct.cir +++ b/clang/test/CIR/IR/struct.cir @@ -19,6 +19,7 @@ !rec_S1 = !cir.struct<"S1" {!s32i, !s32i}> !rec_Sc = !cir.struct<"Sc" {!u8i, !u16i, !u32i}> +// CHECK-DAG: ![[ARR_TY:rec_anon_struct[0-9]*]] = !cir.struct<packed {!s32i, !s32i, !cir.array<!s32i x 8>}> // CHECK-DAG: !cir.struct<{!cir.array<!cir.ptr<!u8i> x 5>}> // CHECK-DAG: !cir.struct<{!cir.ptr<!u8i>, !cir.ptr<!u8i>, !cir.ptr<!u8i>}> // CHECK-DAG: !rec_S1 = !cir.struct<"S1" {!s32i, !s32i}> @@ -29,15 +30,46 @@ !rec_P2 = !cir.struct<"P2" padded {!u8i, !u16i, !u32i}> !rec_P3 = !cir.struct<"P3" packed padded {!u8i, !u16i, !u32i}> +// Members marked pad or empty +!rec_P4 = !cir.struct<"P4" {pad !u8i}> +!rec_P5 = !cir.struct<"P5" {empty !u8i}> +!rec_P6 = !cir.struct<"P6" {!u32i, empty !cir.array<!u8i x 3>, pad !u8i}> +!rec_P7 = !cir.struct<"P7" packed padded {!u8i, pad !u8i}> + // CHECK-DAG: !rec_P1 = !cir.struct<"P1" packed {!s32i, !s32i}> // CHECK-DAG: !rec_P2 = !cir.struct<"P2" padded {!u8i, !u16i, !u32i}> // CHECK-DAG: !rec_P3 = !cir.struct<"P3" packed padded {!u8i, !u16i, !u32i}> +// CHECK-DAG: !rec_P4 = !cir.struct<"P4" {pad !u8i}> +// CHECK-DAG: !rec_P5 = !cir.struct<"P5" {empty !u8i}> +// CHECK-DAG: !rec_P6 = !cir.struct<"P6" {!u32i, empty !cir.array<!u8i x 3>, pad !u8i}> +// CHECK-DAG: !rec_P7 = !cir.struct<"P7" packed padded {!u8i, pad !u8i}> + +// Records with identical member types, spelled apart by their marks. The +// anonymous pair must stay two distinct types, since an anonymous record keys +// on its whole body. +!rec_M1 = !cir.struct<"M1" {!u8i, pad !u8i}> +!rec_M2 = !cir.struct<"M2" {!u8i, empty !u8i}> +!rec_anon_pad = !cir.struct<{!u8i, pad !u8i}> +!rec_anon_empty = !cir.struct<{!u8i, empty !u8i}> + +// CHECK-DAG: !rec_M1 = !cir.struct<"M1" {!u8i, pad !u8i}> +// CHECK-DAG: !rec_M2 = !cir.struct<"M2" {!u8i, empty !u8i}> +// CHECK-DAG: !cir.struct<{!u8i, pad !u8i}> +// CHECK-DAG: !cir.struct<{!u8i, empty !u8i}> !rec_U1 = !cir.union<"U1" {!s32i, !u8i}, padding = {!u8i}> !rec_U2 = !cir.union<"U2" packed {!s32i}, padding = {!cir.array<!u8i x 4>}> +!rec_anon_u_empty = !cir.union<{!s32i, empty !u8i}> +!rec_anon_u_plain = !cir.union<{!s32i, !u8i}> +!rec_U3 = !cir.union<"U3" {empty !u8i}> +!rec_U4 = !cir.union<"U4" {!s32i, empty !u8i}, padding = {!cir.array<!u8i x 4>}> // CHECK-DAG: !rec_U1 = !cir.union<"U1" {!s32i, !u8i}, padding = {!u8i}> // CHECK-DAG: !rec_U2 = !cir.union<"U2" packed {!s32i}, padding = {!cir.array<!u8i x 4>}> +// CHECK-DAG: !cir.union<{!s32i, empty !u8i}> +// CHECK-DAG: !cir.union<{!s32i, !u8i}> +// CHECK-DAG: !rec_U3 = !cir.union<"U3" {empty !u8i}> +// CHECK-DAG: !rec_U4 = !cir.union<"U4" {!s32i, empty !u8i}, padding = {!cir.array<!u8i x 4>}> // Complete a previously incomplete record @@ -45,10 +77,19 @@ !rec_Ac = !cir.struct<class "A" {!u8i, !s32i}> // CHECK-DAG: !rec_A = !cir.struct<class "A" {!u8i, !s32i}> +// Complete a previously incomplete record whose members carry marks. +!rec_B = !cir.struct<class "B" incomplete> +!rec_Bc = !cir.struct<class "B" {!u8i, pad !cir.array<!u8i x 3>, !s32i}> +// CHECK-DAG: !rec_B = !cir.struct<class "B" {!u8i, pad !cir.array<!u8i x 3>, !s32i}> + // Test recursive struct parsing/printing. !rec_Node = !cir.struct<"Node" {!cir.ptr<!cir.struct<"Node">>}> // CHECK-DAG: !cir.struct<"Node" {!cir.ptr<!cir.struct<"Node">>}> +// A mark survives the cyclic-print guard on a self-referential record. +!rec_PadNode = !cir.struct<"PadNode" {!cir.ptr<!cir.struct<"PadNode">>, pad !u8i}> +// CHECK-DAG: !cir.struct<"PadNode" {!cir.ptr<!cir.struct<"PadNode">>, pad !u8i}> + module { @@ -59,7 +100,7 @@ module { // CHECK: cir.global external @p1 = #cir.ptr<null> : !cir.ptr<!rec_S> // CHECK: cir.global external @p2 = #cir.ptr<null> : !cir.ptr<!rec_U> // CHECK: cir.global external @p3 = #cir.ptr<null> : !cir.ptr<!rec_C> -// CHECK: cir.global external @arr = #cir.const_record<{#cir.int<1> : !s32i, #cir.int<2> : !s32i, #cir.zero : !cir.array<!s32i x 8>}> : !rec_anon_struct +// CHECK: cir.global external @arr = #cir.const_record<{#cir.int<1> : !s32i, #cir.int<2> : !s32i, #cir.zero : !cir.array<!s32i x 8>}> : ![[ARR_TY]]{{$}} // Dummy function to use types and force them to be printed. cir.func @useTypes(%arg0: !rec_Node, @@ -71,7 +112,21 @@ module { %arg6: !rec_P2, %arg7: !rec_P3, %arg8: !rec_U1, - %arg9: !rec_U2) { + %arg9: !rec_U2, + %arg10: !rec_P4, + %arg11: !rec_P5, + %arg12: !rec_P6, + %arg13: !rec_M1, + %arg14: !rec_M2, + %arg15: !rec_anon_pad, + %arg16: !rec_anon_empty, + %arg17: !rec_U3, + %arg18: !rec_U4, + %arg19: !rec_Bc, + %arg20: !rec_PadNode, + %arg21: !rec_P7, + %arg22: !rec_anon_u_empty, + %arg23: !rec_anon_u_plain) { cir.return } diff --git a/clang/unittests/CIR/CMakeLists.txt b/clang/unittests/CIR/CMakeLists.txt index f31b8d210f4f7..3779fe69b5649 100644 --- a/clang/unittests/CIR/CMakeLists.txt +++ b/clang/unittests/CIR/CMakeLists.txt @@ -10,6 +10,7 @@ add_distinct_clang_unittest(CIRUnitTests GetFloatingPointTypeTest.cpp IntTypeABIAlignTest.cpp PointerLikeTest.cpp + RecordMemberKindTest.cpp RecordTypeMetadataTest.cpp UnionTypeSizeTest.cpp VectorTypeABIAlignTest.cpp diff --git a/clang/unittests/CIR/RecordMemberKindTest.cpp b/clang/unittests/CIR/RecordMemberKindTest.cpp new file mode 100644 index 0000000000000..bb635a9913a99 --- /dev/null +++ b/clang/unittests/CIR/RecordMemberKindTest.cpp @@ -0,0 +1,169 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Unit tests for per-member record kinds: what they imply about a record's +// emptiness for the ABI, and how they take part in type identity. +// +//===----------------------------------------------------------------------===// + +#include "mlir/IR/Diagnostics.h" +#include "mlir/IR/MLIRContext.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" +#include "gtest/gtest.h" + +using namespace mlir; +using namespace cir; + +/// Swallows verifier diagnostics and counts them, so a getChecked failure can +/// be asserted without the error reaching stderr. +struct ScopedDiagnosticCounter { + explicit ScopedDiagnosticCounter(MLIRContext &context) + : handler(&context, [this](mlir::Diagnostic &) { ++count; }) {} + + unsigned count = 0; + +private: + mlir::ScopedDiagnosticHandler handler; +}; + +class RecordMemberKindTest : public ::testing::Test { +protected: + RecordMemberKindTest() { context.loadDialect<cir::CIRDialect>(); } + + MLIRContext context; + + mlir::Location getLoc() { return mlir::UnknownLoc::get(&context); } + + mlir::StringAttr getName(llvm::StringRef name) { + return mlir::StringAttr::get(&context, name); + } + + IntType getU8() { return IntType::get(&context, 8, false); } + + StructType makeStruct(llvm::StringRef name, + llvm::ArrayRef<mlir::Type> members, + llvm::ArrayRef<RecordMemberKind> kinds) { + auto ty = StructType::get(&context, getName(name), /*is_class=*/false); + ty.complete(members, /*packed=*/false, /*isPadded=*/false, kinds); + return ty; + } +}; + +TEST_F(RecordMemberKindTest, EmptyForTheABIWhenNoMemberHoldsData) { + IntType u8 = getU8(); + // A record with no members is vacuously empty. + EXPECT_TRUE(allMembersNonData(makeStruct("none", {}, {}))); + EXPECT_TRUE( + allMembersNonData(makeStruct("p1", {u8}, {RecordMemberKind::Pad}))); + EXPECT_TRUE( + allMembersNonData(makeStruct("e1", {u8}, {RecordMemberKind::Empty}))); + EXPECT_TRUE(allMembersNonData(makeStruct( + "pe", {u8, u8}, {RecordMemberKind::Pad, RecordMemberKind::Empty}))); + // An all-data list is dropped on completion rather than stored, which is the + // mutate-path half of the canonicalization. + EXPECT_TRUE(makeStruct("d1", {u8}, {RecordMemberKind::Data}) + .getMemberKinds() + .empty()); + EXPECT_FALSE(allMembersNonData(makeStruct( + "dp", {u8, u8}, {RecordMemberKind::Data, RecordMemberKind::Pad}))); + // A record with members and no mark list holds data in all of them. + EXPECT_FALSE(allMembersNonData(makeStruct("unmarked", {u8}, {}))); +} + +TEST_F(RecordMemberKindTest, RejectsAMarkListThatDoesNotCoverEveryMember) { + // The assembly syntax cannot express this, since it builds one kind per + // member, but a C++ caller can. + llvm::SmallVector<mlir::Type> members{getU8(), getU8()}; + llvm::SmallVector<RecordMemberKind> tooFew{RecordMemberKind::Pad}; + + ScopedDiagnosticCounter diags(context); + llvm::ArrayRef<mlir::Type> membersRef(members); + llvm::ArrayRef<RecordMemberKind> kindsRef(tooFew); + EXPECT_FALSE(StructType::getChecked(getLoc(), &context, membersRef, + /*packed=*/false, /*padded=*/false, + /*is_class=*/false, kindsRef)); + EXPECT_EQ(diags.count, 1u); +} + +TEST_F(RecordMemberKindTest, RejectsPadOnAUnionMember) { + // A union's variants all start at offset zero, so there is no inter-member + // padding a pad mark could describe. + llvm::SmallVector<mlir::Type> members{getU8()}; + llvm::SmallVector<RecordMemberKind> pad{RecordMemberKind::Pad}; + llvm::SmallVector<RecordMemberKind> empty{RecordMemberKind::Empty}; + + ScopedDiagnosticCounter diags(context); + llvm::ArrayRef<mlir::Type> membersRef(members); + EXPECT_FALSE(UnionType::getChecked(getLoc(), &context, membersRef, + /*packed=*/false, /*padding=*/mlir::Type{}, + llvm::ArrayRef<RecordMemberKind>(pad))); + EXPECT_EQ(diags.count, 1u); + EXPECT_TRUE(UnionType::getChecked(getLoc(), &context, membersRef, + /*packed=*/false, /*padding=*/mlir::Type{}, + llvm::ArrayRef<RecordMemberKind>(empty))); + EXPECT_EQ(diags.count, 1u); +} + +TEST_F(RecordMemberKindTest, AnIncompleteRecordIsNotEmptyForTheABI) { + // An incomplete record has no members, which must not read as vacuously + // holding no data. + auto ty = StructType::get(&context, getName("I"), /*is_class=*/false); + EXPECT_FALSE(allMembersNonData(ty)); +} + +TEST_F(RecordMemberKindTest, AUnionsTailPaddingSlotIsNotAMember) { + IntType u8 = getU8(); + llvm::SmallVector<mlir::Type> members{u8}; + llvm::SmallVector<RecordMemberKind> empty{RecordMemberKind::Empty}; + llvm::ArrayRef<mlir::Type> membersRef(members); + + auto allEmpty = + UnionType::get(&context, membersRef, getName("ue"), /*packed=*/false, + /*padding=*/u8, llvm::ArrayRef<RecordMemberKind>(empty)); + EXPECT_TRUE(allMembersNonData(allEmpty)); + auto holdsData = UnionType::get(&context, membersRef, getName("ud"), + /*packed=*/false, /*padding=*/u8); + EXPECT_FALSE(allMembersNonData(holdsData)); +} + +TEST_F(RecordMemberKindTest, MarksTakePartInAnonymousTypeIdentity) { + IntType u8 = getU8(); + auto marksPad = StructType::get( + &context, {u8, u8}, /*packed=*/false, /*padded=*/false, + /*is_class=*/false, {RecordMemberKind::Data, RecordMemberKind::Pad}); + auto marksEmpty = StructType::get( + &context, {u8, u8}, /*packed=*/false, /*padded=*/false, + /*is_class=*/false, {RecordMemberKind::Data, RecordMemberKind::Empty}); + EXPECT_NE(marksPad, marksEmpty); + + // Marks are provenance rather than layout. + EXPECT_TRUE(marksPad.isLayoutIdentical(marksEmpty)); + + llvm::SmallVector<mlir::Type> unionMembers{u8, u8}; + llvm::SmallVector<RecordMemberKind> unionEmpty{RecordMemberKind::Data, + RecordMemberKind::Empty}; + llvm::ArrayRef<mlir::Type> unionMembersRef(unionMembers); + auto unionMarked = UnionType::get( + &context, unionMembersRef, /*packed=*/false, /*padding=*/mlir::Type{}, + llvm::ArrayRef<RecordMemberKind>(unionEmpty)); + auto unionPlain = UnionType::get(&context, unionMembersRef, /*packed=*/false); + EXPECT_NE(unionMarked, unionPlain); + EXPECT_TRUE(unionMarked.isLayoutIdentical(unionPlain)); +} + +TEST_F(RecordMemberKindTest, AnAllDataMarkListIsDropped) { + IntType u8 = getU8(); + auto allData = StructType::get( + &context, {u8, u8}, /*packed=*/false, /*padded=*/false, + /*is_class=*/false, {RecordMemberKind::Data, RecordMemberKind::Data}); + auto noList = StructType::get(&context, {u8, u8}, /*packed=*/false, + /*padded=*/false, /*is_class=*/false); + EXPECT_EQ(allData, noList); + EXPECT_TRUE(allData.getMemberKinds().empty()); +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
