[clang] [CIR] Let a record type mark what each member holds (PR #215174)
Adam Smith via cfe-commits
cfe-commits at lists.llvm.org
Sun Aug 9 20:58:44 PDT 2026
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 <adams at nvidia.com>
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());
+}
More information about the cfe-commits
mailing list