[clang] [CIR] Require every record member to specify its kind (PR #215174)
Adam Smith via cfe-commits
cfe-commits at lists.llvm.org
Wed Aug 12 09:04:50 PDT 2026
https://github.com/adams381 updated https://github.com/llvm/llvm-project/pull/215174
>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 1/3] [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());
+}
>From 8bda1f2134d0c1e328263a86a962b80317962b0f Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Tue, 11 Aug 2026 08:20:55 -0700
Subject: [PATCH 2/3] [CIR] Leave a bad record member mark to the type parser
parseMemberKind named `data` only so it could reject it, and separately
rejected a second mark on one member. Both bought a tailored message
where the type parser already fails, and neither spelling can be reached
from printed CIR, since a data member is unmarked and only one mark is
ever emitted.
Drop both. The function then has no failure path left, so it returns
void.
Assisted-by: Cursor / claude-opus-5
---
clang/lib/CIR/Dialect/IR/CIRTypes.cpp | 33 +++++--------------
.../CIR/IR/invalid-record-member-kinds.cir | 6 ++--
2 files changed, 12 insertions(+), 27 deletions(-)
diff --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
index 55fbba11ddb50..5c54fad217d27 100644
--- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
@@ -175,30 +175,14 @@ verifyRecordMemberKinds(function_ref<mlir::InFlightDiagnostic()> emitError,
/// 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"};
+/// parser, and anything else that is not a mark fails there. A data member is
+/// spelled without a mark.
+static void parseMemberKind(mlir::AsmParser &parser, RecordMemberKind &kind) {
+ static const llvm::StringRef marks[] = {"pad", "empty"};
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();
+ if (parser.parseOptionalKeyword(&keyword, marks).succeeded())
+ kind = *symbolizeRecordMemberKind(keyword);
}
/// Parse "incomplete" or "{[mark] type, [mark] type, ...}", writing results
@@ -216,8 +200,7 @@ parseRecordBody(mlir::AsmParser &parser, bool &incomplete,
AsmParser::Delimiter::Braces,
[&parser, &members, &memberKinds]() -> mlir::ParseResult {
RecordMemberKind kind;
- if (parseMemberKind(parser, kind).failed())
- return mlir::failure();
+ parseMemberKind(parser, kind);
memberKinds.push_back(kind);
return parser.parseType(members.emplace_back());
});
@@ -479,7 +462,7 @@ Type UnionType::parse(mlir::AsmParser &parser) {
return {};
const llvm::SMLoc paddingLoc = parser.getCurrentLocation();
llvm::StringRef paddingKeyword;
- static const llvm::StringRef marks[] = {"pad", "empty", "data"};
+ static const llvm::StringRef marks[] = {"pad", "empty"};
if (parser.parseOptionalKeyword(&paddingKeyword, marks).succeeded()) {
parser.emitError(paddingLoc, "a union's tail padding takes no kind mark");
return {};
diff --git a/clang/test/CIR/IR/invalid-record-member-kinds.cir b/clang/test/CIR/IR/invalid-record-member-kinds.cir
index 1709298da10ad..000e9c826020f 100644
--- a/clang/test/CIR/IR/invalid-record-member-kinds.cir
+++ b/clang/test/CIR/IR/invalid-record-member-kinds.cir
@@ -8,16 +8,18 @@ module {}
// -----
+// A data member is spelled without a mark, so 'data' is not a mark keyword.
!u8i = !cir.int<u, 8>
-// expected-error @below {{a data member is spelled without a mark}}
+// expected-error @below {{expected non-function type}}
!rec_S = !cir.struct<"S" {data !u8i}>
module {}
// -----
+// Only one mark is consumed, so a second one is left to the type parser.
!u8i = !cir.int<u, 8>
-// expected-error @below {{a record member takes at most one kind mark}}
+// expected-error @below {{expected non-function type}}
!rec_S = !cir.struct<"S" {pad empty !u8i}>
module {}
>From 4c54d8e6c4699c9b9512c7e6f444b3d0d8d941d5 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Wed, 12 Aug 2026 08:58:37 -0700
Subject: [PATCH 3/3] [CIR] Require every record member to specify its kind
The member kind list was optional, and an all-data list was canonicalized
to an absent one, so "nobody computed this" and "everything is data" had
the same spelling. A producer that forgot to specify was assumed
correct.
The list is now required on both record types and the verifier demands
one kind per member. Normalization is no longer needed and is removed.
A producer whose members all hold data says so with getAllDataKinds.
CIRGen has to compute a list too, so record lowering funnels every
appended field through addField and marks inserted padding as pad.
A data member still prints without a mark, and `data` is now accepted on
input so every kind can be written out.
Assisted-by: Cursor / claude-opus-5
---
clang/include/clang/CIR/Dialect/IR/CIRTypes.h | 15 ++-
.../include/clang/CIR/Dialect/IR/CIRTypes.td | 41 ++++---
.../clang/CIR/Dialect/IR/CIRTypesDetails.h | 16 +--
clang/lib/CIR/CodeGen/CIRGenAsm.cpp | 3 +-
clang/lib/CIR/CodeGen/CIRGenBuilder.cpp | 6 +-
clang/lib/CIR/CodeGen/CIRGenBuilder.h | 20 ++--
clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp | 12 +-
clang/lib/CIR/CodeGen/CIRGenBuiltinNVPTX.cpp | 4 +-
clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp | 31 ++++--
.../CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp | 105 ++++++++++++------
clang/lib/CIR/CodeGen/CIRGenTypes.cpp | 6 +-
clang/lib/CIR/CodeGen/CIRGenVTables.cpp | 4 +-
clang/lib/CIR/Dialect/IR/CIRTypes.cpp | 65 +++++------
.../CIR/Dialect/Transforms/CXXABILowering.cpp | 13 ++-
.../Transforms/CallConvLoweringPass.cpp | 3 +-
.../Dialect/Transforms/LoweringPrepare.cpp | 5 +-
.../TargetLowering/LowerItaniumCXXABI.cpp | 7 +-
clang/test/CIR/CodeGen/atomic.c | 2 +
clang/test/CIR/CodeGen/bitfields.c | 2 +-
.../call-conv-lowering-x86_64-atomic-nyi.c | 10 ++
clang/test/CIR/CodeGen/cleanup.cpp | 2 +-
clang/test/CIR/CodeGen/dumb-record.cpp | 4 +-
clang/test/CIR/CodeGen/empty-union.cpp | 2 +-
.../CIR/CodeGen/finegrain-bitfield-access.cpp | 2 +-
clang/test/CIR/CodeGen/member-functions.cpp | 2 +-
clang/test/CIR/CodeGen/no-unique-address.cpp | 35 +++++-
.../test/CIR/CodeGen/paren-list-agg-init.cpp | 8 +-
.../CodeGen/pointer-to-empty-data-member.cpp | 6 +-
.../test/CIR/CodeGen/record-type-metadata.cpp | 4 +-
clang/test/CIR/CodeGen/struct.c | 2 +-
.../CIR/CodeGen/template-specialization.cpp | 2 +-
clang/test/CIR/CodeGen/vtt.cpp | 4 +-
clang/test/CIR/CodeGenCXX/zero_init_bases.cpp | 2 +-
.../test/CIR/CodeGenCoroutines/coro-task.cpp | 18 +--
.../CIR/IR/invalid-record-member-kinds.cir | 21 +++-
clang/test/CIR/IR/struct.cir | 19 +++-
clang/unittests/CIR/PointerLikeTest.cpp | 12 +-
clang/unittests/CIR/RecordMemberKindTest.cpp | 92 +++++++--------
.../unittests/CIR/RecordTypeMetadataTest.cpp | 4 +-
clang/unittests/CIR/UnionTypeSizeTest.cpp | 32 ++++--
40 files changed, 395 insertions(+), 248 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/call-conv-lowering-x86_64-atomic-nyi.c
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.h b/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
index f93e9ad24b349..eeeb125ac320b 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
@@ -23,6 +23,7 @@
#include "clang/CIR/Dialect/IR/CIRAttrs.h"
#include "clang/CIR/Dialect/IR/CIROpsEnums.h"
#include "clang/CIR/Interfaces/CIRTypeInterfaces.h"
+#include "llvm/ADT/SmallVector.h"
namespace llvm {
struct fltSemantics;
@@ -134,8 +135,8 @@ class RecordType : public mlir::Type {
std::string getPrefixedName() const;
void complete(llvm::ArrayRef<mlir::Type> members, bool packed, bool padded,
- mlir::Type padding = {},
- llvm::ArrayRef<RecordMemberKind> memberKinds = {});
+ mlir::Type padding,
+ llvm::ArrayRef<RecordMemberKind> memberKinds);
uint64_t getElementOffset(const mlir::DataLayout &dataLayout,
unsigned idx) const;
bool isLayoutIdentical(const RecordType &other);
@@ -145,12 +146,10 @@ 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);
+/// One `Data` kind per member. Takes the members rather than a count so that
+/// the two cannot drift apart.
+llvm::SmallVector<RecordMemberKind>
+getAllDataKinds(llvm::ArrayRef<mlir::Type> members);
/// 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
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
index 365b96cd8e86f..8b2e98645991d 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
@@ -687,10 +687,9 @@ 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.
+ Every member has a kind, described by `CIR_RecordMemberKind`, saying what
+ it holds. A `pad` or `empty` member is spelled with its mark. A `data`
+ member may be spelled with or without one, and prints without.
Examples:
@@ -713,7 +712,7 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [
"bool":$incomplete,
"bool":$packed,
"bool":$padded,
- OptionalArrayRefParameter<"cir::RecordMemberKind">:$member_kinds,
+ ArrayRefParameter<"cir::RecordMemberKind">:$member_kinds,
"bool":$is_class
);
@@ -732,10 +731,10 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [
"bool":$packed,
"bool":$padded,
"bool":$is_class,
- CArg<"llvm::ArrayRef<cir::RecordMemberKind>", "{}">:$member_kinds
+ "llvm::ArrayRef<cir::RecordMemberKind>":$member_kinds
), [{
return $_get($_ctxt, members, name, /*incomplete=*/false, packed, padded,
- cir::normalizeRecordMemberKinds(member_kinds), is_class);
+ member_kinds, is_class);
}]>,
// Create an identified and incomplete struct/class type.
@@ -755,11 +754,10 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [
"bool":$packed,
"bool":$padded,
"bool":$is_class,
- CArg<"llvm::ArrayRef<cir::RecordMemberKind>", "{}">:$member_kinds
+ "llvm::ArrayRef<cir::RecordMemberKind>":$member_kinds
), [{
return $_get($_ctxt, members, mlir::StringAttr{}, /*incomplete=*/false,
- packed, padded,
- cir::normalizeRecordMemberKinds(member_kinds), is_class);
+ packed, padded, member_kinds, is_class);
}]>
];
@@ -784,7 +782,7 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [
void complete(llvm::ArrayRef<mlir::Type> members, bool packed,
bool isPadded,
- llvm::ArrayRef<cir::RecordMemberKind> memberKinds = {});
+ llvm::ArrayRef<cir::RecordMemberKind> memberKinds);
uint64_t getElementOffset(const mlir::DataLayout &dataLayout,
unsigned idx) const;
@@ -847,7 +845,7 @@ def CIR_UnionType : CIR_Type<"Union", "union", [
Padded unions carry an explicit tail-padding type to ensure the LLVM struct
that models the union has the correct byte size. That slot is separate
- from the per-member marks described by `CIR_RecordMemberKind`, which say
+ from the per-member kinds described by `CIR_RecordMemberKind`, which say
what each variant holds. The parser rejects a mark on that slot.
Examples:
@@ -867,7 +865,7 @@ def CIR_UnionType : CIR_Type<"Union", "union", [
"bool":$incomplete,
"bool":$packed,
OptionalParameter<"mlir::Type">:$padding,
- OptionalArrayRefParameter<"cir::RecordMemberKind">:$member_kinds
+ ArrayRefParameter<"cir::RecordMemberKind">:$member_kinds
);
// StorageClass is defined in C++ for mutability.
@@ -883,11 +881,11 @@ def CIR_UnionType : CIR_Type<"Union", "union", [
"llvm::ArrayRef<mlir::Type>":$members,
"mlir::StringAttr":$name,
"bool":$packed,
- CArg<"mlir::Type", "{}">:$padding,
- CArg<"llvm::ArrayRef<cir::RecordMemberKind>", "{}">:$member_kinds
+ "mlir::Type":$padding,
+ "llvm::ArrayRef<cir::RecordMemberKind>":$member_kinds
), [{
return $_get($_ctxt, members, name, /*incomplete=*/false, packed,
- padding, cir::normalizeRecordMemberKinds(member_kinds));
+ padding, member_kinds);
}]>,
// Create an identified and incomplete union type.
@@ -902,12 +900,11 @@ def CIR_UnionType : CIR_Type<"Union", "union", [
TypeBuilder<(ins
"llvm::ArrayRef<mlir::Type>":$members,
"bool":$packed,
- CArg<"mlir::Type", "{}">:$padding,
- CArg<"llvm::ArrayRef<cir::RecordMemberKind>", "{}">:$member_kinds
+ "mlir::Type":$padding,
+ "llvm::ArrayRef<cir::RecordMemberKind>":$member_kinds
), [{
return $_get($_ctxt, members, mlir::StringAttr{}, /*incomplete=*/false,
- packed, padding,
- cir::normalizeRecordMemberKinds(member_kinds));
+ packed, padding, member_kinds);
}]>
];
@@ -940,8 +937,8 @@ def CIR_UnionType : CIR_Type<"Union", "union", [
llvm::ArrayRef<mlir::Type> members);
void complete(llvm::ArrayRef<mlir::Type> members, bool packed,
- mlir::Type padding = {},
- llvm::ArrayRef<cir::RecordMemberKind> memberKinds = {});
+ mlir::Type padding,
+ llvm::ArrayRef<cir::RecordMemberKind> memberKinds);
uint64_t getElementOffset(const mlir::DataLayout &, unsigned) const {
return 0;
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypesDetails.h b/clang/include/clang/CIR/Dialect/IR/CIRTypesDetails.h
index 123fa059cfeac..7a451698f4b23 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypesDetails.h
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypesDetails.h
@@ -58,8 +58,8 @@ struct StructTypeStorage : public mlir::TypeStorage {
: members(members), name(name), incomplete(incomplete), packed(packed),
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");
+ assert(member_kinds.size() == members.size() &&
+ "every member must say what it holds");
}
KeyTy getAsKey() const {
@@ -99,7 +99,7 @@ struct StructTypeStorage : public mlir::TypeStorage {
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
+ // including the kinds: otherwise it silently keeps the kinds it was given
// the first time.
if (!incomplete)
return mlir::success(
@@ -108,7 +108,7 @@ struct StructTypeStorage : public mlir::TypeStorage {
// 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())
+ if (memberKinds.size() != members.size())
return llvm::failure();
this->members = allocator.copyInto(members);
@@ -154,8 +154,8 @@ struct UnionTypeStorage : public mlir::TypeStorage {
: members(members), name(name), incomplete(incomplete), packed(packed),
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");
+ assert(member_kinds.size() == members.size() &&
+ "every member must say what it holds");
}
KeyTy getAsKey() const {
@@ -193,7 +193,7 @@ struct UnionTypeStorage : public mlir::TypeStorage {
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
+ // including the kinds: otherwise it silently keeps the kinds it was given
// the first time.
if (!incomplete)
return mlir::success(
@@ -202,7 +202,7 @@ struct UnionTypeStorage : public mlir::TypeStorage {
// 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())
+ if (memberKinds.size() != members.size())
return llvm::failure();
this->members = allocator.copyInto(members);
diff --git a/clang/lib/CIR/CodeGen/CIRGenAsm.cpp b/clang/lib/CIR/CodeGen/CIRGenAsm.cpp
index 26f9f5935c2b4..dcc7a247e65fb 100644
--- a/clang/lib/CIR/CodeGen/CIRGenAsm.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenAsm.cpp
@@ -538,7 +538,8 @@ mlir::LogicalResult CIRGenFunction::emitAsmStmt(const AsmStmt &s) {
resultType = resultRegTypes[0];
else if (resultRegTypes.size() > 1)
resultType = builder.getAnonRecordTy(resultRegTypes, /*packed=*/false,
- /*padded=*/false);
+ /*padded=*/false,
+ cir::getAllDataKinds(resultRegTypes));
bool hasSideEffect = s.isVolatile() || s.getNumOutputs() == 0;
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp b/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
index a562c4b7b763f..954f9fc82e115 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
@@ -206,9 +206,11 @@ cir::RecordType clang::CIRGen::CIRGenBuilderTy::getCompleteRecordType(
});
if (name.empty())
- return getAnonRecordTy(members, packed, padded);
+ return getAnonRecordTy(members, packed, padded,
+ cir::getAllDataKinds(members));
- return getCompleteNamedRecordType(members, packed, padded, name);
+ return getCompleteNamedRecordType(members, packed, padded, name,
+ cir::getAllDataKinds(members));
}
mlir::Attribute clang::CIRGen::CIRGenBuilderTy::getConstRecordOrZeroAttr(
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.h b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
index c906e65a132c2..fd3c116974cf3 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuilder.h
+++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
@@ -94,7 +94,8 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
}
if (!ty)
- ty = getAnonRecordTy(members, packed, padded);
+ ty = getAnonRecordTy(members, packed, padded,
+ cir::getAllDataKinds(members));
auto sTy = mlir::cast<cir::RecordType>(ty);
return cir::ConstRecordAttr::get(sTy, arrayAttr);
@@ -154,16 +155,16 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
///
/// If a record already exists and is complete, but the client tries to fetch
/// it with a different set of attributes, this method will crash.
- cir::RecordType getCompleteNamedRecordType(llvm::ArrayRef<mlir::Type> members,
- bool packed, bool padded,
- llvm::StringRef name) {
+ cir::RecordType getCompleteNamedRecordType(
+ llvm::ArrayRef<mlir::Type> members, bool packed, bool padded,
+ llvm::StringRef name, llvm::ArrayRef<cir::RecordMemberKind> memberKinds) {
const auto nameAttr = getStringAttr(name);
assert(!cir::MissingFeatures::astRecordDeclAttr());
// Create or get the struct type (named anonymous struct helper — always
// struct, never class or union at this call site).
auto type = cir::StructType::get(getContext(), members, nameAttr, packed,
- padded, /*is_class=*/false);
+ padded, /*is_class=*/false, memberKinds);
// If we found an existing type, verify that either it is incomplete or
// it matches the requested attributes.
@@ -173,7 +174,7 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
// Complete an incomplete record or ensure the existing complete record
// matches the requested attributes.
- type.complete(members, packed, padded);
+ type.complete(members, packed, padded, memberKinds);
return type;
}
@@ -403,11 +404,12 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
cir::PointerType getUInt8PtrTy() { return typeCache.uInt8PtrTy; }
/// Get a CIR anonymous struct type.
- cir::StructType getAnonRecordTy(llvm::ArrayRef<mlir::Type> members,
- bool packed = false, bool padded = false) {
+ cir::StructType
+ getAnonRecordTy(llvm::ArrayRef<mlir::Type> members, bool packed, bool padded,
+ llvm::ArrayRef<cir::RecordMemberKind> memberKinds) {
assert(!cir::MissingFeatures::astRecordDeclAttr());
return cir::StructType::get(getContext(), members, packed, padded,
- /*is_class=*/false);
+ /*is_class=*/false, memberKinds);
}
//===--------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp
index e2a1c2b94dec4..d0f68ccbf68bf 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp
@@ -76,8 +76,10 @@ static mlir::Value emitLogbBuiltin(CIRGenFunction &cgf, const CallExpr *e,
mlir::Type srcTy = src0.getType();
mlir::Type int32Ty = builder.getSInt32Ty();
- cir::RecordType frExpResTy =
- builder.getAnonRecordTy({srcTy, int32Ty}, false, false);
+ mlir::Type frExpResMembers[] = {srcTy, int32Ty};
+ cir::RecordType frExpResTy = builder.getAnonRecordTy(
+ frExpResMembers, /*packed=*/false, /*padded=*/false,
+ cir::getAllDataKinds(frExpResMembers));
mlir::Value frExpResult = builder.emitIntrinsicCallOp(
loc, "frexp", frExpResTy, mlir::ValueRange{src0});
@@ -174,8 +176,10 @@ CIRGenFunction::emitAMDGPUBuiltinExpr(unsigned builtinId,
mlir::Value z = emitScalarExpr(expr->getArg(2));
auto i1Ty = builder.getUIntNTy(1);
- cir::RecordType resTy = builder.getAnonRecordTy(
- {x.getType(), i1Ty}, /*packed=*/false, /*padded=*/false);
+ mlir::Type resMembers[] = {x.getType(), i1Ty};
+ cir::RecordType resTy =
+ builder.getAnonRecordTy(resMembers, /*packed=*/false, /*padded=*/false,
+ cir::getAllDataKinds(resMembers));
mlir::Value structResult =
cir::LLVMIntrinsicCallOp::create(builder, getLoc(expr->getExprLoc()),
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltinNVPTX.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltinNVPTX.cpp
index 839e768c7fc88..579f5e40acc67 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltinNVPTX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltinNVPTX.cpp
@@ -1042,7 +1042,9 @@ static mlir::Value packArgsIntoNVPTXFormatBuffer(CIRGenFunction &cgf,
// We can directly store the arguments into a struct, and the alignment
// would automatically be correct. That's because vprintf does not
// accept aggregates.
- mlir::Type allocaTy = builder.getAnonRecordTy(argTypes);
+ mlir::Type allocaTy =
+ builder.getAnonRecordTy(argTypes, /*packed=*/false, /*padded=*/false,
+ cir::getAllDataKinds(argTypes));
auto allocaAlign = clang::CharUnits::fromQuantity(
dataLayout.getABITypeAlign(allocaTy).value());
Address allocaAddr =
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp
index 9228367fdd44f..ceb29ae69e52f 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp
@@ -209,7 +209,8 @@ emitEncodeKey(mlir::MLIRContext *context, CIRGenBuilderTy &builder,
llvm::append_range(members,
llvm::SmallVector<mlir::Type>(vecOutputCount, resVector));
cir::StructType resRecord = cir::StructType::get(
- context, members, /*packed=*/false, /*padded=*/false, /*is_class=*/false);
+ context, members, /*packed=*/false,
+ /*padded=*/false, /*is_class=*/false, cir::getAllDataKinds(members));
mlir::Value outputPtr =
builder.createBitcast(outputOperand, cir::PointerType::get(resVector));
@@ -692,7 +693,10 @@ static mlir::Value emitX86Aes(CIRGenBuilderTy &builder, mlir::Location loc,
// Create return struct type and call intrinsic function.
mlir::Type vecType =
mlir::cast<cir::PointerType>(ops[0].getType()).getPointee();
- cir::RecordType rstRecTy = builder.getAnonRecordTy({retType, vecType});
+ mlir::Type rstMembers[] = {retType, vecType};
+ cir::RecordType rstRecTy =
+ builder.getAnonRecordTy(rstMembers, /*packed=*/false, /*padded=*/false,
+ cir::getAllDataKinds(rstMembers));
mlir::Value rstValueRec = builder.emitIntrinsicCallOp(
loc, intrinsicName, rstRecTy, mlir::ValueRange{ops[1], ops[2]});
@@ -749,7 +753,9 @@ static mlir::Value emitX86Aeswide(CIRGenBuilderTy &builder, mlir::Location loc,
builder.createAlignedLoad(loc, vecType, nextInElePtr,
/*align=*/CharUnits::fromQuantity(16));
}
- cir::RecordType rstRecTy = builder.getAnonRecordTy(recTypes);
+ cir::RecordType rstRecTy =
+ builder.getAnonRecordTy(recTypes, /*packed=*/false, /*padded=*/false,
+ cir::getAllDataKinds(recTypes));
mlir::Value rstValueRec =
builder.emitIntrinsicCallOp(loc, intrinsicName, rstRecTy, arguments);
@@ -927,7 +933,8 @@ cir::GetGlobalOp CIRGenFunction::createGetCpuModel(mlir::Location loc) {
// unsigned int __cpu_subtype;
// unsigned int __cpu_features[1];
mlir::Type tys[] = {u32, u32, u32, cir::ArrayType::get(u32, 1)};
- mlir::Type modelTy = builder.getAnonRecordTy(tys, /*incomplete=*/false);
+ mlir::Type modelTy = builder.getAnonRecordTy(
+ tys, /*packed=*/false, /*padded=*/false, cir::getAllDataKinds(tys));
cpuModel =
cgm.createGlobalOp(loc, "__cpu_model", modelTy, /*isConstant=*/false);
cpuModel.setDsoLocal(true);
@@ -1132,7 +1139,10 @@ CIRGenFunction::emitX86BuiltinExpr(unsigned builtinID, const CallExpr *expr) {
mlir::Location loc = getLoc(expr->getExprLoc());
mlir::Type i64Ty = builder.getUInt64Ty();
mlir::Type i32Ty = builder.getUInt32Ty();
- mlir::Type structTy = builder.getAnonRecordTy({i64Ty, i32Ty});
+ mlir::Type members[] = {i64Ty, i32Ty};
+ mlir::Type structTy =
+ builder.getAnonRecordTy(members, /*packed=*/false, /*padded=*/false,
+ cir::getAllDataKinds(members));
mlir::Value result =
builder.emitIntrinsicCallOp(loc, "x86.rdtscp", structTy);
@@ -2494,7 +2504,8 @@ CIRGenFunction::emitX86BuiltinExpr(unsigned builtinID, const CallExpr *expr) {
builder.getUInt32Ty()};
cir::StructType resRecord =
cir::StructType::get(&getMLIRContext(), resultTypes, /*packed=*/false,
- /*padded=*/false, /*is_class=*/false);
+ /*padded=*/false, /*is_class=*/false,
+ cir::getAllDataKinds(resultTypes));
mlir::Value call =
builder.emitIntrinsicCallOp(loc, intrinsicName, resRecord);
@@ -2561,10 +2572,10 @@ CIRGenFunction::emitX86BuiltinExpr(unsigned builtinID, const CallExpr *expr) {
auto resVector = cir::VectorType::get(builder.getBoolTy(), numElts);
- cir::StructType resRecord =
- cir::StructType::get(&getMLIRContext(), {resVector, resVector},
- /*packed=*/false, /*padded=*/false,
- /*is_class=*/false);
+ mlir::Type resMembers[] = {resVector, resVector};
+ cir::StructType resRecord = cir::StructType::get(
+ &getMLIRContext(), resMembers, /*packed=*/false, /*padded=*/false,
+ /*is_class=*/false, cir::getAllDataKinds(resMembers));
mlir::Value call = builder.emitIntrinsicCallOp(
getLoc(expr->getExprLoc()), intrinsicName, resRecord,
diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
index e4476b88ec6f7..a388fa5431ac3 100644
--- a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
@@ -43,16 +43,21 @@ struct CIRRecordLowering final {
CharUnits offset;
enum class InfoKind { VFPtr, Field, Base, VBase } kind;
mlir::Type data;
+ /// What this member holds, recorded on the CIR record type.
+ cir::RecordMemberKind memberKind;
union {
const FieldDecl *fieldDecl;
const CXXRecordDecl *cxxRecordDecl;
};
MemberInfo(CharUnits offset, InfoKind kind, mlir::Type data,
+ cir::RecordMemberKind memberKind,
const FieldDecl *fieldDecl = nullptr)
- : offset{offset}, kind{kind}, data{data}, fieldDecl{fieldDecl} {}
+ : offset{offset}, kind{kind}, data{data}, memberKind{memberKind},
+ fieldDecl{fieldDecl} {}
MemberInfo(CharUnits offset, InfoKind kind, mlir::Type data,
- const CXXRecordDecl *rd)
- : offset{offset}, kind{kind}, data{data}, cxxRecordDecl{rd} {}
+ cir::RecordMemberKind memberKind, const CXXRecordDecl *rd)
+ : offset{offset}, kind{kind}, data{data}, memberKind{memberKind},
+ cxxRecordDecl{rd} {}
// MemberInfos are sorted so we define a < operator.
bool operator<(const MemberInfo &other) const {
return offset < other.offset;
@@ -63,8 +68,9 @@ struct CIRRecordLowering final {
bool packed);
/// Constructs a MemberInfo instance from an offset and mlir::Type.
- MemberInfo makeStorageInfo(CharUnits offset, mlir::Type data) {
- return MemberInfo(offset, MemberInfo::InfoKind::Field, data);
+ MemberInfo makeStorageInfo(CharUnits offset, mlir::Type data,
+ cir::RecordMemberKind memberKind) {
+ return MemberInfo(offset, MemberInfo::InfoKind::Field, data, memberKind);
}
// Layout routines.
@@ -204,10 +210,25 @@ struct CIRRecordLowering final {
assert(!unionPadding && "at most one union tail-padding type");
unionPadding = padTy;
} else {
- fieldTypes.push_back(padTy);
+ addField(padTy, cir::RecordMemberKind::Pad);
}
}
+ void addField(mlir::Type ty, cir::RecordMemberKind memberKind) {
+ fieldTypes.push_back(ty);
+ fieldKinds.push_back(memberKind);
+ }
+
+ void clearFields() {
+ fieldTypes.clear();
+ fieldKinds.clear();
+ }
+
+ llvm::ArrayRef<mlir::Type> getFieldTypes() const { return fieldTypes; }
+ llvm::ArrayRef<cir::RecordMemberKind> getFieldKinds() const {
+ return fieldKinds;
+ }
+
CIRGenTypes &cirGenTypes;
CIRGenBuilderTy &builder;
const ASTContext &astContext;
@@ -216,8 +237,6 @@ struct CIRRecordLowering final {
const ASTRecordLayout &astRecordLayout;
// Helpful intermediate data-structures
std::vector<MemberInfo> members;
- // Output fields, consumed by CIRGenTypes::computeRecordLayout
- llvm::SmallVector<mlir::Type, 16> fieldTypes;
mlir::Type unionPadding;
llvm::DenseMap<const FieldDecl *, CIRGenBitFieldInfo> bitFields;
llvm::DenseMap<const FieldDecl *, unsigned> fieldIdxMap;
@@ -235,6 +254,12 @@ struct CIRRecordLowering final {
unsigned padded : 1;
private:
+ // Output fields, consumed by CIRGenTypes::computeRecordLayout. Private so
+ // that every append goes through addField and fieldKinds stays parallel to
+ // fieldTypes.
+ llvm::SmallVector<mlir::Type, 16> fieldTypes;
+ llvm::SmallVector<cir::RecordMemberKind> fieldKinds;
+
CIRRecordLowering(const CIRRecordLowering &) = delete;
void operator=(const CIRRecordLowering &) = delete;
}; // CIRRecordLowering
@@ -306,7 +331,10 @@ void CIRRecordLowering::lower(bool nonVirtualBaseType) {
// TODO: Verify bitfield clipping
assert(!cir::MissingFeatures::checkBitfieldClipping());
- members.push_back(makeStorageInfo(size, getUIntNType(8)));
+ // The sentinel is popped before fillOutputFields, so its kind never reaches
+ // the type.
+ members.push_back(
+ makeStorageInfo(size, getUIntNType(8), cir::RecordMemberKind::Data));
determinePacked(nonVirtualBaseType);
insertPadding();
members.pop_back();
@@ -319,7 +347,7 @@ void CIRRecordLowering::lower(bool nonVirtualBaseType) {
void CIRRecordLowering::fillOutputFields() {
for (const MemberInfo &member : members) {
if (member.data)
- fieldTypes.push_back(member.data);
+ addField(member.data, member.memberKind);
if (member.kind == MemberInfo::InfoKind::Field) {
if (member.fieldDecl)
fieldIdxMap[member.fieldDecl->getCanonicalDecl()] =
@@ -369,14 +397,14 @@ CIRRecordLowering::accumulateBitFields(RecordDecl::field_iterator field,
// Add the storage member to the record. This must be added to the
// record before the bitfield members so that it gets laid out before
// the bitfields it contains get laid out.
- members.push_back(
- makeStorageInfo(bitsToCharUnits(startBitOffset), type));
+ members.push_back(makeStorageInfo(bitsToCharUnits(startBitOffset), type,
+ cir::RecordMemberKind::Data));
}
// Bitfields get the offset of their storage but come afterward and remain
// there after a stable sort.
members.push_back(MemberInfo(bitsToCharUnits(startBitOffset),
MemberInfo::InfoKind::Field, nullptr,
- *field));
+ cir::RecordMemberKind::Data, *field));
}
return field;
}
@@ -548,11 +576,13 @@ CIRRecordLowering::accumulateBitFields(RecordDecl::field_iterator field,
assert(getSize(type) == accessSize &&
"Unclipped access must be clipped");
}
- members.push_back(makeStorageInfo(beginOffset, type));
+ members.push_back(
+ makeStorageInfo(beginOffset, type, cir::RecordMemberKind::Data));
for (; begin != bestEnd; ++begin)
if (!begin->isZeroLengthBitField())
- members.push_back(MemberInfo(
- beginOffset, MemberInfo::InfoKind::Field, nullptr, *begin));
+ members.push_back(MemberInfo(beginOffset,
+ MemberInfo::InfoKind::Field, nullptr,
+ cir::RecordMemberKind::Data, *begin));
}
// Reset to start a new span.
field = bestEnd;
@@ -601,7 +631,7 @@ void CIRRecordLowering::accumulateFields() {
field->isPotentiallyOverlapping()
? getStorageType(field->getType()->getAsCXXRecordDecl())
: getStorageType(*field),
- *field));
+ cir::RecordMemberKind::Data, *field));
++field;
}
}
@@ -679,7 +709,8 @@ void CIRRecordLowering::insertPadding() {
// Add the padding to the Members list and sort it.
for (const std::pair<CharUnits, CharUnits> &paddingPair : padding)
members.push_back(makeStorageInfo(paddingPair.first,
- getByteArrayType(paddingPair.second)));
+ getByteArrayType(paddingPair.second),
+ cir::RecordMemberKind::Pad));
llvm::stable_sort(members);
}
@@ -726,8 +757,8 @@ CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
baseLowering.lower(/*nonVirtualBaseType=*/true);
std::string baseIdentifier = getRecordTypeName(rd, ".base");
baseTy = builder.getCompleteNamedRecordType(
- baseLowering.fieldTypes, baseLowering.packed, baseLowering.padded,
- baseIdentifier);
+ baseLowering.getFieldTypes(), baseLowering.packed,
+ baseLowering.padded, baseIdentifier, baseLowering.getFieldKinds());
// TODO(cir): add something like addRecordTypeName
// BaseTy and Ty must agree on their packedness for getCIRFieldNo to work
@@ -745,8 +776,8 @@ CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
// signifies that the type is no longer opaque and record layout is complete,
// but we may need to recursively layout rd while laying D out as a base type.
assert(!cir::MissingFeatures::astRecordDeclAttr());
- ty->complete(lowering.fieldTypes, lowering.packed, lowering.padded,
- lowering.unionPadding);
+ ty->complete(lowering.getFieldTypes(), lowering.packed, lowering.padded,
+ lowering.unionPadding, lowering.getFieldKinds());
// Queue ABI metadata for the module-level cir.record_layouts attribute.
if (ty->getName()) {
@@ -851,7 +882,7 @@ void CIRRecordLowering::lowerUnion(bool nonVirtualBaseType) {
}
fieldIdxMap[field->getCanonicalDecl()] = 0;
- fieldTypes.push_back(fieldType);
+ addField(fieldType, cir::RecordMemberKind::Data);
}
// Compute zero-initializable status.
@@ -878,13 +909,13 @@ void CIRRecordLowering::lowerUnion(bool nonVirtualBaseType) {
zeroInitializable = zeroInitializableAsBase = false;
// If we have no candidates for storage, we are JUST padding.
- if (fieldTypes.empty()) {
+ if (getFieldTypes().empty()) {
appendPaddingBytes(layoutSize);
return;
}
mlir::Type storageType =
- cir::UnionType::getUnionStorageType(dataLayout.layout, fieldTypes);
+ cir::UnionType::getUnionStorageType(dataLayout.layout, getFieldTypes());
// If our storage size was bigger than our required size (can happen in the
// case of packed bitfields on Itanium) then just use an I8 array.
@@ -895,11 +926,11 @@ void CIRRecordLowering::lowerUnion(bool nonVirtualBaseType) {
// the storage type and any trailing padding as ordinary fields rather than
// routing padding through the union's single tail-padding slot.
if (nonVirtualBaseType) {
- fieldTypes.clear();
- fieldTypes.push_back(storageType);
+ clearFields();
+ addField(storageType, cir::RecordMemberKind::Data);
CharUnits padding = layoutSize - getSize(storageType);
if (!padding.isZero()) {
- fieldTypes.push_back(getByteArrayType(padding));
+ addField(getByteArrayType(padding), cir::RecordMemberKind::Pad);
padded = true;
}
} else {
@@ -1044,7 +1075,8 @@ void CIRRecordLowering::accumulateBases() {
if (astRecordLayout.isPrimaryBaseVirtual()) {
const CXXRecordDecl *baseDecl = astRecordLayout.getPrimaryBase();
members.push_back(MemberInfo(CharUnits::Zero(), MemberInfo::InfoKind::Base,
- getStorageType(baseDecl), baseDecl));
+ getStorageType(baseDecl),
+ cir::RecordMemberKind::Data, baseDecl));
}
// Accumulate the non-virtual bases.
@@ -1058,7 +1090,8 @@ void CIRRecordLowering::accumulateBases() {
!astContext.getASTRecordLayout(baseDecl).getNonVirtualSize().isZero()) {
members.push_back(MemberInfo(astRecordLayout.getBaseClassOffset(baseDecl),
MemberInfo::InfoKind::Base,
- getStorageType(baseDecl), baseDecl));
+ getStorageType(baseDecl),
+ cir::RecordMemberKind::Data, baseDecl));
}
}
}
@@ -1073,8 +1106,8 @@ void CIRRecordLowering::accumulateVBases() {
// get its own storage location but instead lives inside of that base.
if (isOverlappingVBaseABI() && astContext.isNearlyEmpty(baseDecl) &&
!hasOwnStorage(cxxRecordDecl, baseDecl)) {
- members.push_back(
- MemberInfo(offset, MemberInfo::InfoKind::VBase, nullptr, baseDecl));
+ members.push_back(MemberInfo(offset, MemberInfo::InfoKind::VBase, nullptr,
+ cir::RecordMemberKind::Data, baseDecl));
continue;
}
// If we've got a vtordisp, add it as a storage type.
@@ -1082,16 +1115,18 @@ void CIRRecordLowering::accumulateVBases() {
.find(baseDecl)
->second.hasVtorDisp())
members.push_back(makeStorageInfo(offset - CharUnits::fromQuantity(4),
- getUIntNType(32)));
+ getUIntNType(32),
+ cir::RecordMemberKind::Data));
members.push_back(MemberInfo(offset, MemberInfo::InfoKind::VBase,
- getStorageType(baseDecl), baseDecl));
+ getStorageType(baseDecl),
+ cir::RecordMemberKind::Data, baseDecl));
}
}
void CIRRecordLowering::accumulateVPtrs() {
if (astRecordLayout.hasOwnVFPtr())
members.push_back(MemberInfo(CharUnits::Zero(), MemberInfo::InfoKind::VFPtr,
- getVFPtrType()));
+ getVFPtrType(), cir::RecordMemberKind::Data));
if (astRecordLayout.hasOwnVBPtr())
cirGenTypes.getCGModule().errorNYI(recordDecl->getSourceRange(),
diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
index e5af4eec7720f..b2824d6f64700 100644
--- a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
@@ -659,9 +659,11 @@ mlir::Type CIRGenTypes::convertType(QualType type) {
auto paddingArray =
cir::ArrayType::get(cgm.sInt8Ty, (atomicSize - valueSize) / 8);
mlir::Type elements[] = {resultType, paddingArray};
+ cir::RecordMemberKind kinds[] = {cir::RecordMemberKind::Data,
+ cir::RecordMemberKind::Pad};
resultType = cir::StructType::get(&getMLIRContext(), /*members=*/elements,
- /*packed=*/false, /*padded=*/false,
- /*is_class=*/false);
+ /*packed=*/false, /*padded=*/true,
+ /*is_class=*/false, kinds);
}
break;
diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
index a4742cb239f21..f2d2dbebb957c 100644
--- a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
@@ -69,7 +69,9 @@ cir::RecordType CIRGenVTables::getVTableType(const VTableLayout &layout) {
// FIXME(cir): should VTableLayout be encoded like we do for some
// AST nodes?
- return cgm.getBuilder().getAnonRecordTy(tys, /*incomplete=*/false);
+ return cgm.getBuilder().getAnonRecordTy(tys, /*packed=*/false,
+ /*padded=*/false,
+ cir::getAllDataKinds(tys));
}
/// At this point in the translation unit, does it appear that can we
diff --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
index 5c54fad217d27..183c414080299 100644
--- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
@@ -152,37 +152,36 @@ void CIRDialect::printType(Type type, DialectAsmPrinter &os) const {
// Shared helpers for StructType and UnionType parse/print.
-llvm::ArrayRef<RecordMemberKind>
-cir::normalizeRecordMemberKinds(llvm::ArrayRef<RecordMemberKind> memberKinds) {
- if (llvm::all_of(memberKinds, [](RecordMemberKind kind) {
- return kind == RecordMemberKind::Data;
- }))
- return {};
- return memberKinds;
+llvm::SmallVector<RecordMemberKind>
+cir::getAllDataKinds(llvm::ArrayRef<mlir::Type> members) {
+ return llvm::SmallVector<RecordMemberKind>(members.size(),
+ RecordMemberKind::Data);
}
-/// 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.
+/// An incomplete record has no members, so a kind for one is caught by the
+/// same check.
static mlir::LogicalResult
verifyRecordMemberKinds(function_ref<mlir::InFlightDiagnostic()> emitError,
size_t numMembers,
llvm::ArrayRef<RecordMemberKind> memberKinds) {
- if (!memberKinds.empty() && memberKinds.size() != numMembers)
+ if (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, and anything else that is not a mark fails there. A data member is
-/// spelled without a mark.
-static void parseMemberKind(mlir::AsmParser &parser, RecordMemberKind &kind) {
- static const llvm::StringRef marks[] = {"pad", "empty"};
+/// Consume a member's optional kind mark, returning whether one was there.
+/// Only a mark keyword is consumed, so anything that is not one is left for the
+/// type parser to accept or reject.
+static bool consumeOptionalMemberKindMark(mlir::AsmParser &parser,
+ RecordMemberKind &kind) {
+ static const llvm::StringRef marks[] = {"data", "pad", "empty"};
kind = RecordMemberKind::Data;
llvm::StringRef keyword;
- if (parser.parseOptionalKeyword(&keyword, marks).succeeded())
- kind = *symbolizeRecordMemberKind(keyword);
+ if (parser.parseOptionalKeyword(&keyword, marks).failed())
+ return false;
+ kind = *symbolizeRecordMemberKind(keyword);
+ return true;
}
/// Parse "incomplete" or "{[mark] type, [mark] type, ...}", writing results
@@ -200,7 +199,7 @@ parseRecordBody(mlir::AsmParser &parser, bool &incomplete,
AsmParser::Delimiter::Braces,
[&parser, &members, &memberKinds]() -> mlir::ParseResult {
RecordMemberKind kind;
- parseMemberKind(parser, kind);
+ consumeOptionalMemberKindMark(parser, kind);
memberKinds.push_back(kind);
return parser.parseType(members.emplace_back());
});
@@ -243,8 +242,7 @@ static void printRecordBody(mlir::AsmPrinter &printer, RecordTy self,
for (auto [idx, member] : llvm::enumerate(members)) {
if (idx)
printer << ", ";
- if (idx < memberKinds.size() &&
- memberKinds[idx] != RecordMemberKind::Data)
+ if (memberKinds[idx] != RecordMemberKind::Data)
printer << stringifyRecordMemberKind(memberKinds[idx]) << ' ';
printer.printType(member);
}
@@ -312,7 +310,7 @@ Type StructType::parse(mlir::AsmParser &parser) {
return {};
ArrayRef<mlir::Type> membersRef(members);
- ArrayRef<RecordMemberKind> kindsRef = normalizeRecordMemberKinds(memberKinds);
+ ArrayRef<RecordMemberKind> kindsRef(memberKinds);
mlir::Type type = {};
if (name && incomplete) {
type = StructType::getChecked(eLoc, context, name, is_class);
@@ -391,8 +389,7 @@ void StructType::removeABIConversionNamePrefix() {
void StructType::complete(ArrayRef<Type> members, bool packed, bool padded,
ArrayRef<RecordMemberKind> memberKinds) {
assert(!cir::MissingFeatures::astRecordDeclAttr());
- if (mutate(members, packed, padded, normalizeRecordMemberKinds(memberKinds))
- .failed())
+ if (mutate(members, packed, padded, memberKinds).failed())
llvm_unreachable("failed to complete struct");
}
@@ -461,9 +458,8 @@ Type UnionType::parse(mlir::AsmParser &parser) {
if (parser.parseLBrace().failed())
return {};
const llvm::SMLoc paddingLoc = parser.getCurrentLocation();
- llvm::StringRef paddingKeyword;
- static const llvm::StringRef marks[] = {"pad", "empty"};
- if (parser.parseOptionalKeyword(&paddingKeyword, marks).succeeded()) {
+ RecordMemberKind ignoredKind;
+ if (consumeOptionalMemberKindMark(parser, ignoredKind)) {
parser.emitError(paddingLoc, "a union's tail padding takes no kind mark");
return {};
}
@@ -477,7 +473,7 @@ Type UnionType::parse(mlir::AsmParser &parser) {
return {};
ArrayRef<mlir::Type> membersRef(members);
- ArrayRef<RecordMemberKind> kindsRef = normalizeRecordMemberKinds(memberKinds);
+ ArrayRef<RecordMemberKind> kindsRef(memberKinds);
mlir::Type type = {};
if (name && incomplete) {
type = UnionType::getChecked(eLoc, context, name);
@@ -560,8 +556,7 @@ void UnionType::complete(ArrayRef<Type> members, bool packed,
mlir::Type padding,
ArrayRef<RecordMemberKind> memberKinds) {
assert(!cir::MissingFeatures::astRecordDeclAttr());
- if (mutate(members, packed, padding, normalizeRecordMemberKinds(memberKinds))
- .failed())
+ if (mutate(members, packed, padding, memberKinds).failed())
llvm_unreachable("failed to complete union");
}
@@ -694,12 +689,7 @@ bool cir::allMembersNonData(RecordType recTy) {
// 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 llvm::none_of(recTy.getMemberKinds(), [](RecordMemberKind kind) {
return kind == RecordMemberKind::Data;
});
}
@@ -1269,7 +1259,8 @@ static mlir::Type getMethodLayoutType(mlir::MLIRContext *ctx) {
auto voidPtrTy = cir::PointerType::get(cir::VoidType::get(ctx));
mlir::Type fields[2]{voidPtrTy, voidPtrTy};
return cir::StructType::get(ctx, fields, /*packed=*/false,
- /*padded=*/false, /*is_class=*/false);
+ /*padded=*/false, /*is_class=*/false,
+ cir::getAllDataKinds(fields));
}
llvm::TypeSize
diff --git a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
index 50a51d9618118..e8e7f4d87346c 100644
--- a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
@@ -853,17 +853,21 @@ class CIRABITypeConverter : public mlir::TypeConverter {
// just do a conversion on it.
if (!type.getName()) {
llvm::SmallVector<mlir::Type> converted = convertRecordMemberTypes(type);
+ assert(converted.size() == type.getNumElements() &&
+ "member conversion must be one type in, one type out for the "
+ "kinds to carry over by index");
if (auto u = mlir::dyn_cast<cir::UnionType>(type)) {
mlir::Type loweredPadding;
if (mlir::Type pad = u.getPadding())
loweredPadding = convertType(pad);
return cir::UnionType::get(type.getContext(), converted,
- type.getPacked(), loweredPadding);
+ type.getPacked(), loweredPadding,
+ u.getMemberKinds());
}
auto s = mlir::cast<cir::StructType>(type);
return cir::StructType::get(type.getContext(), converted,
type.getPacked(), type.getPadded(),
- s.getIsClass());
+ s.getIsClass(), s.getMemberKinds());
}
assert(!type.isIncomplete() || type.getMembers().empty());
@@ -902,13 +906,16 @@ class CIRABITypeConverter : public mlir::TypeConverter {
[&recursiveStack]() { recursiveStack.pop_back(); });
SmallVector<mlir::Type> convertedMembers = convertRecordMemberTypes(type);
+ assert(convertedMembers.size() == type.getNumElements() &&
+ "member conversion must be one type in, one type out for the kinds "
+ "to carry over by index");
mlir::Type loweredPadding;
if (auto u = mlir::dyn_cast<cir::UnionType>(type))
if (mlir::Type pad = u.getPadding())
loweredPadding = convertType(pad);
convertedType.complete(convertedMembers, type.getPacked(), type.getPadded(),
- loweredPadding);
+ loweredPadding, type.getMemberKinds());
addConvertedRecordType(convertedType);
return convertedType;
}
diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
index 193c2b6f4a9dc..88bca4eba09b4 100644
--- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
@@ -196,7 +196,8 @@ static mlir::Type abiTypeToCIR(const llvm::abi::Type *ty, MLIRContext *ctx) {
}
// Coercion types are plain register tuples, not the source record.
return cir::StructType::get(ctx, fieldTypes, /*packed=*/false,
- /*padded=*/false, /*is_class=*/false);
+ /*padded=*/false, /*is_class=*/false,
+ cir::getAllDataKinds(fieldTypes));
})
.Default([](const llvm::abi::Type *) -> mlir::Type { return nullptr; });
}
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index 12d544fecbcc9..cae12fc972e0f 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -2431,9 +2431,10 @@ void LoweringPreparePass::buildCUDAModuleCtor() {
// Create the fatbin wrapper struct:
// struct { int magic; int version; void *fatbin; void *unused; };
+ mlir::Type fatbinWrapperMembers[] = {intTy, intTy, voidPtrTy, voidPtrTy};
auto fatbinWrapperType = cir::StructType::get(
- &getContext(), {intTy, intTy, voidPtrTy, voidPtrTy},
- /*packed=*/false, /*padded=*/false, /*is_class=*/false);
+ &getContext(), fatbinWrapperMembers, /*packed=*/false, /*padded=*/false,
+ /*is_class=*/false, cir::getAllDataKinds(fatbinWrapperMembers));
std::string fatbinWrapperName =
addUnderscoredPrefix(cudaPrefix, "_fatbin_wrapper");
GlobalOp fatbinWrapper = GlobalOp::create(
diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
index 6c276a83f18cf..00b9afcef1c9f 100644
--- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
@@ -190,9 +190,10 @@ mlir::Type LowerItaniumCXXABI::lowerMethodType(
// Note that clang CodeGen emits struct{ptrdiff_t, ptrdiff_t} for member
// function pointers. Let's follow this approach.
- return cir::StructType::get(type.getContext(), {ptrdiffCIRTy, ptrdiffCIRTy},
- /*packed=*/false, /*padded=*/false,
- /*is_class=*/false);
+ mlir::Type members[] = {ptrdiffCIRTy, ptrdiffCIRTy};
+ return cir::StructType::get(type.getContext(), members, /*packed=*/false,
+ /*padded=*/false, /*is_class=*/false,
+ cir::getAllDataKinds(members));
}
mlir::TypedAttr LowerItaniumCXXABI::lowerDataMemberConstant(
diff --git a/clang/test/CIR/CodeGen/atomic.c b/clang/test/CIR/CodeGen/atomic.c
index d6b93c387f2bd..e63ec52b8121f 100644
--- a/clang/test/CIR/CodeGen/atomic.c
+++ b/clang/test/CIR/CodeGen/atomic.c
@@ -9,6 +9,8 @@ struct S1 {
short x, y, z;
};
+// CIR: !rec_anon_struct = !cir.struct<padded {!rec_S1, pad !cir.array<!s8i x 2>}>
+
_Atomic int g1;
_Atomic int g2 = 42;
// CIR: cir.global external @g2 = #cir.int<42> : !s32i {alignment = 4 : i64}
diff --git a/clang/test/CIR/CodeGen/bitfields.c b/clang/test/CIR/CodeGen/bitfields.c
index 1abc23fd36f2a..ff7e772875549 100644
--- a/clang/test/CIR/CodeGen/bitfields.c
+++ b/clang/test/CIR/CodeGen/bitfields.c
@@ -12,7 +12,7 @@ typedef struct {
unsigned still_more_bits : 7;
} A;
-// CIR-DAG: !rec_A = !cir.struct<"A" packed padded {!s8i, !s8i, !s8i, !u16i, !cir.array<!u8i x 3>}>
+// CIR-DAG: !rec_A = !cir.struct<"A" packed padded {!s8i, !s8i, !s8i, !u16i, pad !cir.array<!u8i x 3>}>
// CIR-DAG: #bfi_more_bits = #cir.bitfield_info<name = "more_bits", storage_type = !u16i, size = 4, offset = 3, is_signed = false>
// LLVM-DAG: %struct.A = type <{ i8, i8, i8, i16, [3 x i8] }>
// OGCG-DAG: %struct.A = type <{ i8, i8, i8, i16, [3 x i8] }>
diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-atomic-nyi.c b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-atomic-nyi.c
new file mode 100644
index 0000000000000..c6b81c945be2e
--- /dev/null
+++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-atomic-nyi.c
@@ -0,0 +1,10 @@
+// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir \
+// RUN: -clangir-enable-call-conv-lowering -emit-cir %s -o %t.cir 2>&1 \
+// RUN: | FileCheck %s
+
+struct S1 { short x, y, z; };
+
+// The wrapper that inflates the value to the atomic size holds a pad member,
+// and the classifier does not tell padding from data yet.
+// CHECK: error: 'cir.func' op x86_64 calling-convention lowering not yet implemented for type '!cir.struct<padded {{.*}}pad !cir.array<!cir.int<s, 8> x 2>}>'
+void take_atomic(_Atomic struct S1 s) {}
diff --git a/clang/test/CIR/CodeGen/cleanup.cpp b/clang/test/CIR/CodeGen/cleanup.cpp
index ea3d9c55d05dd..d17bc1e1e33b3 100644
--- a/clang/test/CIR/CodeGen/cleanup.cpp
+++ b/clang/test/CIR/CodeGen/cleanup.cpp
@@ -5,7 +5,7 @@ struct Struk {
~Struk();
};
-// CHECK: !rec_Struk = !cir.struct<"Struk" padded {!u8i}>
+// CHECK: !rec_Struk = !cir.struct<"Struk" padded {pad !u8i}>
// CHECK: cir.func{{.*}} @_ZN5StrukD1Ev(!cir.ptr<!rec_Struk> {{.*}})
diff --git a/clang/test/CIR/CodeGen/dumb-record.cpp b/clang/test/CIR/CodeGen/dumb-record.cpp
index c6ed74af61b45..095def36fa585 100644
--- a/clang/test/CIR/CodeGen/dumb-record.cpp
+++ b/clang/test/CIR/CodeGen/dumb-record.cpp
@@ -15,8 +15,8 @@ struct Empty {
} empty;
// CHECK: Layout: <CIRecordLayout
-// CHECK: CIR Type:!cir.struct<"Empty" padded {!cir.int<u, 8>}>
-// CHECK: NonVirtualBaseCIRType:!cir.struct<"Empty" padded {!cir.int<u, 8>}>
+// CHECK: CIR Type:!cir.struct<"Empty" padded {pad !cir.int<u, 8>}>
+// CHECK: NonVirtualBaseCIRType:!cir.struct<"Empty" padded {pad !cir.int<u, 8>}>
// CHECK: IsZeroInitializable:1
// CHECK: BitFields:[
// CHECK: ]>
diff --git a/clang/test/CIR/CodeGen/empty-union.cpp b/clang/test/CIR/CodeGen/empty-union.cpp
index 7922bb853f936..d02a31dbd8963 100644
--- a/clang/test/CIR/CodeGen/empty-union.cpp
+++ b/clang/test/CIR/CodeGen/empty-union.cpp
@@ -86,7 +86,7 @@ Leading leadArr[2];
// CIR-DAG: !rec_Leading = !cir.struct<"Leading" {!rec_Empty, !s32i}>
// CIR-DAG: !rec_Trailing = !cir.struct<"Trailing" {!s32i, !rec_Empty}>
// CIR-DAG: !rec_Middle = !cir.struct<"Middle" {!s32i, !rec_Empty, !s32i}>
-// CIR-DAG: !rec_LeadingOver = !cir.struct<"LeadingOver" padded {!rec_EmptyAligned, !s32i, !cir.array<!u8i x 12>}>
+// CIR-DAG: !rec_LeadingOver = !cir.struct<"LeadingOver" padded {!rec_EmptyAligned, !s32i, pad !cir.array<!u8i x 12>}>
// CIR-DAG: !rec_LeadingZeroBitfield = !cir.struct<"LeadingZeroBitfield" {!rec_OnlyZeroBitfield, !s32i}>
// CIR keeps the union's own named type as the record's field and leaves the
diff --git a/clang/test/CIR/CodeGen/finegrain-bitfield-access.cpp b/clang/test/CIR/CodeGen/finegrain-bitfield-access.cpp
index ac8f3869966ba..a253d6f7ae348 100644
--- a/clang/test/CIR/CodeGen/finegrain-bitfield-access.cpp
+++ b/clang/test/CIR/CodeGen/finegrain-bitfield-access.cpp
@@ -23,7 +23,7 @@ struct S2 {
unsigned long f3:6;
};
-// CIR-DAG: !rec_S2 = !cir.struct<"S2" padded {!u16i, !u16i, !u8i, !cir.array<!u8i x 3>}>
+// CIR-DAG: !rec_S2 = !cir.struct<"S2" padded {!u16i, !u16i, !u8i, pad !cir.array<!u8i x 3>}>
// LLVM-DAG: %struct.S2 = type { i16, i16, i8, [3 x i8] }
// OGCG-DAG: %struct.S2 = type { i16, i16, i8, [3 x i8] }
diff --git a/clang/test/CIR/CodeGen/member-functions.cpp b/clang/test/CIR/CodeGen/member-functions.cpp
index 8849fcbf2f99a..e365f5312e479 100644
--- a/clang/test/CIR/CodeGen/member-functions.cpp
+++ b/clang/test/CIR/CodeGen/member-functions.cpp
@@ -6,7 +6,7 @@ struct C {
void f2(int a, int b);
};
-// CIR: !rec_C = !cir.struct<"C" padded {!u8i}>
+// CIR: !rec_C = !cir.struct<"C" padded {pad !u8i}>
void C::f() {}
diff --git a/clang/test/CIR/CodeGen/no-unique-address.cpp b/clang/test/CIR/CodeGen/no-unique-address.cpp
index b8600a2f5b65d..54d5d92040c15 100644
--- a/clang/test/CIR/CodeGen/no-unique-address.cpp
+++ b/clang/test/CIR/CodeGen/no-unique-address.cpp
@@ -30,7 +30,7 @@ struct Outer {
// Middle's tail padding.
// CIR: !rec_Middle2Ebase = !cir.struct<"Middle.base" packed {!rec_Base, !s8i}>
-// CIR: !rec_Outer = !cir.struct<"Outer" padded {!rec_Middle2Ebase, !s8i,
+// CIR: !rec_Outer = !cir.struct<"Outer" padded {!rec_Middle2Ebase, !s8i, pad !cir.array<!u8i x 2>}>
// CIR-LABEL: cir.func {{.*}} @_ZN5OuterC2ERK6Middlec(
// CIR: %[[THIS:.*]] = cir.load %{{.+}} : !cir.ptr<!cir.ptr<!rec_Outer>>, !cir.ptr<!rec_Outer>
@@ -50,6 +50,9 @@ struct Outer {
// LLVM-DAG: %struct.UnionWithPadding.base = type { i8 }
// LLVM-DAG: %struct.OuterFinalUnionPad = type { %struct.FinalUnionWithPadding.base, i8 }
// LLVM-DAG: %struct.FinalUnionWithPadding.base = type { i8 }
+// LLVM-DAG: %struct.OuterUnionPadAfterStorage = type { %struct.UnionPadAfterStorage.base, i8 }
+// LLVM-DAG: %struct.UnionPadAfterStorage.base = type <{ i32, [3 x i8] }>
+// LLVM-DAG: @oupas = {{(dso_local )?}}global %struct.OuterUnionPadAfterStorage zeroinitializer, align 4
// LLVM-DAG: @ou = {{(dso_local )?}}global %struct.OuterUnion zeroinitializer, align 8
// LLVM-DAG: @of = {{(dso_local )?}}global %struct.OuterFinal zeroinitializer, align 4
// LLVM-DAG: @oup = {{(dso_local )?}}global %struct.OuterUnionPad zeroinitializer, align 2
@@ -67,6 +70,9 @@ struct Outer {
// OGCG-DAG: %union.UnionWithPadding.base = type { i8 }
// OGCG-DAG: %struct.OuterFinalUnionPad = type { %union.FinalUnionWithPadding.base, i8 }
// OGCG-DAG: %union.FinalUnionWithPadding.base = type { i8 }
+// OGCG-DAG: %struct.OuterUnionPadAfterStorage = type { %union.UnionPadAfterStorage.base, i8 }
+// OGCG-DAG: %union.UnionPadAfterStorage.base = type <{ i32, [3 x i8] }>
+// OGCG-DAG: @oupas = {{(dso_local )?}}global %struct.OuterUnionPadAfterStorage zeroinitializer, align 4
// OGCG-DAG: @ou = {{(dso_local )?}}global %struct.OuterUnion zeroinitializer, align 8
// OGCG-DAG: @of = {{(dso_local )?}}global %struct.OuterFinal zeroinitializer, align 4
// OGCG-DAG: @oup = {{(dso_local )?}}global %struct.OuterUnionPad zeroinitializer, align 2
@@ -155,6 +161,29 @@ struct OuterFinalUnionPad {
OuterFinalUnionPad ofup;
+// A union whose data size outgrows its highest-aligned variant, so the base
+// subobject needs padding of its own after the storage type.
+struct TailBig {
+ TailBig();
+
+private:
+ short s;
+ char c[5];
+};
+
+union UnionPadAfterStorage {
+ UnionPadAfterStorage();
+ int i;
+ [[no_unique_address]] TailBig t;
+};
+
+struct OuterUnionPadAfterStorage {
+ [[no_unique_address]] UnionPadAfterStorage u;
+ bool tail;
+};
+
+OuterUnionPadAfterStorage oupas;
+
// CIR-NUA-DAG: !rec_FinalForNUA = !cir.struct<"FinalForNUA" {!s32i, !s8i}>
// CIR-NUA-DAG: !rec_UnionForNUA = !cir.union<"UnionForNUA" {!s32i, !s64i}>
// CIR-NUA-DAG: !rec_OuterFinal = !cir.struct<"OuterFinal" {!rec_FinalForNUA, !s8i}>
@@ -163,10 +192,14 @@ OuterFinalUnionPad ofup;
// CIR-NUA-DAG: !rec_OuterUnionPad = !cir.struct<"OuterUnionPad" {!rec_UnionWithPadding2Ebase, !cir.bool}>
// CIR-NUA-DAG: !rec_FinalUnionWithPadding2Ebase = !cir.struct<"FinalUnionWithPadding.base" {!u8i}>
// CIR-NUA-DAG: !rec_OuterFinalUnionPad = !cir.struct<"OuterFinalUnionPad" {!rec_FinalUnionWithPadding2Ebase, !cir.bool}>
+// CIR-NUA-DAG: !rec_TailBig = !cir.struct<"TailBig" packed padded {!s16i, !cir.array<!s8i x 5>, pad !u8i}>
+// CIR-NUA-DAG: !rec_UnionPadAfterStorage2Ebase = !cir.struct<"UnionPadAfterStorage.base" packed padded {!s32i, pad !cir.array<!u8i x 3>}>
+// CIR-NUA-DAG: !rec_OuterUnionPadAfterStorage = !cir.struct<"OuterUnionPadAfterStorage" {!rec_UnionPadAfterStorage2Ebase, !cir.bool}>
// CIR-NUA-DAG: cir.global external @ou = #cir.zero : !rec_OuterUnion
// CIR-NUA-DAG: cir.global external @of = #cir.zero : !rec_OuterFinal
// CIR-NUA-DAG: cir.global external @oup = #cir.zero : !rec_OuterUnionPad
// CIR-NUA-DAG: cir.global external @ofup = #cir.zero : !rec_OuterFinalUnionPad
+// CIR-NUA-DAG: cir.global external @oupas = #cir.zero : !rec_OuterUnionPadAfterStorage
struct EmptyForNUA {};
diff --git a/clang/test/CIR/CodeGen/paren-list-agg-init.cpp b/clang/test/CIR/CodeGen/paren-list-agg-init.cpp
index 37b0b42a6f7dc..3184919b04768 100644
--- a/clang/test/CIR/CodeGen/paren-list-agg-init.cpp
+++ b/clang/test/CIR/CodeGen/paren-list-agg-init.cpp
@@ -36,7 +36,7 @@ struct B {
};
// LLVM-DAG: [[STRUCT_C:%.*]] = type <{ [[STRUCT_B]], [[STRUCT_A]], i32, [4 x i8] }>
-// CIR-DAG: ![[STRUCT_C:.*]] = !cir.struct<"C" packed padded {![[STRUCT_B]], ![[STRUCT_A]], !s32i, !cir.array<!u8i x 4>}>
+// CIR-DAG: ![[STRUCT_C:.*]] = !cir.struct<"C" packed padded {![[STRUCT_B]], ![[STRUCT_A]], !s32i, pad !cir.array<!u8i x 4>}>
struct C : public B, public A {
int c;
};
@@ -58,7 +58,7 @@ struct E {
~E() {};
};
-// CIR-DAG: ![[STRUCT_F:.*]] = !cir.struct<"F" padded {!u8i}>
+// CIR-DAG: ![[STRUCT_F:.*]] = !cir.struct<"F" padded {pad !u8i}>
struct F {
F (int i = 1);
F (const F &f) = delete;
@@ -67,7 +67,7 @@ struct F {
// LLVMCIR-DAG: [[STRUCT_G:%.*]] = type <{ i32, %struct.F, [3 x i8] }>
// OGCG-DAG: [[STRUCT_G:%.*]] = type <{ i32, [4 x i8] }>
-// CIR-DAG: ![[STRUCT_G:.*]] = !cir.struct<"G" packed padded {!s32i, !rec_F, !cir.array<!u8i x 3>}>
+// CIR-DAG: ![[STRUCT_G:.*]] = !cir.struct<"G" packed padded {!s32i, !rec_F, pad !cir.array<!u8i x 3>}>
struct G {
int a;
F f;
@@ -85,7 +85,7 @@ union U {
namespace gh61145 {
// LLVM-DAG: [[STRUCT_VEC:%.*Vec.*]] = type { i8 }
- // CIR-DAG: ![[STRUCT_VEC:.*]] = !cir.struct<"gh61145::Vec" padded {!u8i}>
+ // CIR-DAG: ![[STRUCT_VEC:.*]] = !cir.struct<"gh61145::Vec" padded {pad !u8i}>
struct Vec {
Vec();
Vec(Vec&&);
diff --git a/clang/test/CIR/CodeGen/pointer-to-empty-data-member.cpp b/clang/test/CIR/CodeGen/pointer-to-empty-data-member.cpp
index 0b493dc306594..7abc62e3c0b5f 100644
--- a/clang/test/CIR/CodeGen/pointer-to-empty-data-member.cpp
+++ b/clang/test/CIR/CodeGen/pointer-to-empty-data-member.cpp
@@ -7,7 +7,7 @@
// RUN: FileCheck --check-prefix=LLVM,OGCG --input-file=%t.ll %s
struct Empty {};
-// CIR-DAG: !rec_Empty = !cir.struct<"Empty" padded {!u8i}>
+// CIR-DAG: !rec_Empty = !cir.struct<"Empty" padded {pad !u8i}>
// LLVMCIR-DAG: %struct.Empty = type { i8 }
struct HasEmpty {
@@ -38,7 +38,7 @@ const HasEmpty2 globalHE2 = {{}, 1};
// Not referenced enough to be emitted in 'after'.
struct EmptyBase{};
-// CIR-BEFORE-DAG: !rec_EmptyBase = !cir.struct<"EmptyBase" padded {!u8i}>
+// CIR-BEFORE-DAG: !rec_EmptyBase = !cir.struct<"EmptyBase" padded {pad !u8i}>
struct Base { int i; };
// CIR-DAG: !rec_Base = !cir.struct<"Base" {!s32i}>
@@ -87,7 +87,7 @@ struct hasNUA {
[[no_unique_address]] EmptyBase eb6;
int i;
};
-// CIR-DAG: !rec_hasNUA = !cir.struct<"hasNUA" padded {!s32i, !cir.array<!u8i x 4>}>
+// CIR-DAG: !rec_hasNUA = !cir.struct<"hasNUA" padded {!s32i, pad !cir.array<!u8i x 4>}>
// LLVM-DAG: %struct.hasNUA = type { i32, [4 x i8] }
const hasNUA nua = {{},{},{},{},{},{}, 1};
diff --git a/clang/test/CIR/CodeGen/record-type-metadata.cpp b/clang/test/CIR/CodeGen/record-type-metadata.cpp
index 46f823bce96b2..5cf7909f860e2 100644
--- a/clang/test/CIR/CodeGen/record-type-metadata.cpp
+++ b/clang/test/CIR/CodeGen/record-type-metadata.cpp
@@ -18,8 +18,8 @@ void takesNTD(NonTrivialDtor n) {}
// Record types should NOT contain ABI metadata keywords.
// CIR-DAG: !rec_Trivial = !cir.struct<"Trivial" {!s32i, !s32i}>
-// CIR-DAG: !rec_Empty = !cir.struct<"Empty" padded {!u8i}>
-// CIR-DAG: !rec_Aligned = !cir.struct<"Aligned" padded {!s32i, !s32i, !cir.array<!u8i x 8>}>
+// CIR-DAG: !rec_Empty = !cir.struct<"Empty" padded {pad !u8i}>
+// CIR-DAG: !rec_Aligned = !cir.struct<"Aligned" padded {!s32i, !s32i, pad !cir.array<!u8i x 8>}>
// CIR-DAG: !rec_NonTrivialDtor = !cir.struct<class "NonTrivialDtor" {!s32i}>
// ABI metadata lives in module-level cir.record_layouts attribute.
diff --git a/clang/test/CIR/CodeGen/struct.c b/clang/test/CIR/CodeGen/struct.c
index c4c38519599bf..7d5ea46ea8bf8 100644
--- a/clang/test/CIR/CodeGen/struct.c
+++ b/clang/test/CIR/CodeGen/struct.c
@@ -12,7 +12,7 @@
// CIR-DAG: !rec_OuterS = !cir.struct<"OuterS" {!rec_InnerS, !s32i}>
// CIR-DAG: !rec_InnerS = !cir.struct<"InnerS" {!s32i, !s8i}>
// CIR-DAG: !rec_PackedS = !cir.struct<"PackedS" packed {!s32i, !s8i}>
-// CIR-DAG: !rec_PackedAndPaddedS = !cir.struct<"PackedAndPaddedS" packed padded {!s32i, !s8i, !u8i}>
+// CIR-DAG: !rec_PackedAndPaddedS = !cir.struct<"PackedAndPaddedS" packed padded {!s32i, !s8i, pad !u8i}>
// CIR-DAG: !rec_NodeS = !cir.struct<"NodeS" {!cir.ptr<!cir.struct<"NodeS">>}>
// CIR-DAG: !rec_RightS = !cir.struct<"RightS" {!cir.ptr<!cir.struct<"LeftS" {!cir.ptr<!cir.struct<"RightS">>}>>}>
// CIR-DAG: !rec_LeftS = !cir.struct<"LeftS" {!cir.ptr<!rec_RightS>}>
diff --git a/clang/test/CIR/CodeGen/template-specialization.cpp b/clang/test/CIR/CodeGen/template-specialization.cpp
index e965e029ebeb2..66f81baf51f60 100644
--- a/clang/test/CIR/CodeGen/template-specialization.cpp
+++ b/clang/test/CIR/CodeGen/template-specialization.cpp
@@ -13,7 +13,7 @@ class Templ<T, int>{};
Templ<int, int> t;
-// CIR: !rec_Templ3Cint2C_int3E = !cir.struct<class "Templ<int, int>" padded {!u8i}>
+// CIR: !rec_Templ3Cint2C_int3E = !cir.struct<class "Templ<int, int>" padded {pad !u8i}>
// CIR: cir.global external @t = #cir.zero : !rec_Templ3Cint2C_int3E
// LLVM: %"class.Templ<int, int>" = type { i8 }
diff --git a/clang/test/CIR/CodeGen/vtt.cpp b/clang/test/CIR/CodeGen/vtt.cpp
index ae21e0620c4dd..59b674747990b 100644
--- a/clang/test/CIR/CodeGen/vtt.cpp
+++ b/clang/test/CIR/CodeGen/vtt.cpp
@@ -49,8 +49,8 @@ void D::y() {}
// CIR-COMMON: !rec_A2Ebase = !cir.struct<"A.base" packed {!cir.vptr, !s32i}>
// CIR-COMMON: !rec_B2Ebase = !cir.struct<"B.base" packed {!cir.vptr, !s32i}>
// CIR-COMMON: !rec_C2Ebase = !cir.struct<"C.base" {!cir.vptr, !s64i}>
-// CIR-COMMON: !rec_A = !cir.struct<class "A" packed padded {!cir.vptr, !s32i, !cir.array<!u8i x 4>}>
-// CIR-COMMON: !rec_B = !cir.struct<class "B" packed padded {!cir.vptr, !s32i, !cir.array<!u8i x 4>, !rec_A2Ebase, !cir.array<!u8i x 4>}>
+// CIR-COMMON: !rec_A = !cir.struct<class "A" packed padded {!cir.vptr, !s32i, pad !cir.array<!u8i x 4>}>
+// CIR-COMMON: !rec_B = !cir.struct<class "B" packed padded {!cir.vptr, !s32i, pad !cir.array<!u8i x 4>, !rec_A2Ebase, pad !cir.array<!u8i x 4>}>
// CIR-COMMON: !rec_C = !cir.struct<class "C" {!cir.vptr, !s64i, !rec_A2Ebase}>
// CIR-COMMON: !rec_D = !cir.struct<class "D" {!rec_B2Ebase, !rec_C2Ebase, !s64i, !rec_A2Ebase}>
diff --git a/clang/test/CIR/CodeGenCXX/zero_init_bases.cpp b/clang/test/CIR/CodeGenCXX/zero_init_bases.cpp
index c41fa0d7be0a1..82bed271c22ab 100644
--- a/clang/test/CIR/CodeGenCXX/zero_init_bases.cpp
+++ b/clang/test/CIR/CodeGenCXX/zero_init_bases.cpp
@@ -25,7 +25,7 @@ struct VirtualInherits : virtual Base1, virtual Base2 {
// CIR: !rec_Base2 = !cir.struct<"Base2" {!cir.float, !cir.float, !cir.float}>
// CIR: !rec_Base1 = !cir.struct<"Base1" {!s32i, !s32i, !s32i}>
// CIR: !rec_Inherits = !cir.struct<"Inherits" {!rec_Base1, !rec_Base2, !s32i, !s32i, !s32i}>
-// CIR: !rec_VirtualInherits = !cir.struct<"VirtualInherits" packed padded {!cir.vptr, !s32i, !s32i, !s32i, !rec_Base1, !rec_Base2, !cir.array<!u8i x 4>}>
+// CIR: !rec_VirtualInherits = !cir.struct<"VirtualInherits" packed padded {!cir.vptr, !s32i, !s32i, !s32i, !rec_Base1, !rec_Base2, pad !cir.array<!u8i x 4>}>
//
// LLVM: %struct.Inherits = type { %struct.Base1, %struct.Base2, i32, i32, i32 }
// LLVM: %struct.Base1 = type { i32, i32, i32 }
diff --git a/clang/test/CIR/CodeGenCoroutines/coro-task.cpp b/clang/test/CIR/CodeGenCoroutines/coro-task.cpp
index d0ba8c153bdbb..e7d140551529d 100644
--- a/clang/test/CIR/CodeGenCoroutines/coro-task.cpp
+++ b/clang/test/CIR/CodeGenCoroutines/coro-task.cpp
@@ -5,15 +5,15 @@
#include "Inputs/coroutine.h"
-// CIR-DAG: ![[VoidTask:.*]] = !cir.struct<"folly::coro::Task<void>" padded {!u8i}>
-// CIR-DAG: ![[IntTask:.*]] = !cir.struct<"folly::coro::Task<int>" padded {!u8i}>
-// CIR-DAG: ![[VoidPromisse:.*]] = !cir.struct<"folly::coro::Task<void>::promise_type" padded {!u8i}>
-// CIR-DAG: ![[IntPromisse:.*]] = !cir.struct<"folly::coro::Task<int>::promise_type" padded {!u8i}>
-// CIR-DAG: ![[StdString:.*]] = !cir.struct<"std::string" padded {!u8i}>
-// CIR-DAG: ![[CoroHandleVoid:.*]] = !cir.struct<"std::coroutine_handle<void>" padded {!u8i}>
-// CIR-DAG: ![[CoroHandlePromiseVoid:rec_.*]] = !cir.struct<"std::coroutine_handle<folly::coro::Task<void>::promise_type>" padded {!u8i}>
-// CIR-DAG: ![[CoroHandlePromiseInt:rec_.*]] = !cir.struct<"std::coroutine_handle<folly::coro::Task<int>::promise_type>" padded {!u8i}>
-// CIR-DAG: ![[SuspendAlways:.*]] = !cir.struct<"std::suspend_always" padded {!u8i}>
+// CIR-DAG: ![[VoidTask:.*]] = !cir.struct<"folly::coro::Task<void>" padded {pad !u8i}>
+// CIR-DAG: ![[IntTask:.*]] = !cir.struct<"folly::coro::Task<int>" padded {pad !u8i}>
+// CIR-DAG: ![[VoidPromisse:.*]] = !cir.struct<"folly::coro::Task<void>::promise_type" padded {pad !u8i}>
+// CIR-DAG: ![[IntPromisse:.*]] = !cir.struct<"folly::coro::Task<int>::promise_type" padded {pad !u8i}>
+// CIR-DAG: ![[StdString:.*]] = !cir.struct<"std::string" padded {pad !u8i}>
+// CIR-DAG: ![[CoroHandleVoid:.*]] = !cir.struct<"std::coroutine_handle<void>" padded {pad !u8i}>
+// CIR-DAG: ![[CoroHandlePromiseVoid:rec_.*]] = !cir.struct<"std::coroutine_handle<folly::coro::Task<void>::promise_type>" padded {pad !u8i}>
+// CIR-DAG: ![[CoroHandlePromiseInt:rec_.*]] = !cir.struct<"std::coroutine_handle<folly::coro::Task<int>::promise_type>" padded {pad !u8i}>
+// CIR-DAG: ![[SuspendAlways:.*]] = !cir.struct<"std::suspend_always" padded {pad !u8i}>
// OGCG-DAG: %[[VoidPromisse:"struct.folly::coro::Task<void>::promise_type"]] = type { i8 }
// OGCG-DAG: %[[VoidTask:"struct.folly::coro::Task"]] = type { i8 }
diff --git a/clang/test/CIR/IR/invalid-record-member-kinds.cir b/clang/test/CIR/IR/invalid-record-member-kinds.cir
index 000e9c826020f..2d9f85d4cec1f 100644
--- a/clang/test/CIR/IR/invalid-record-member-kinds.cir
+++ b/clang/test/CIR/IR/invalid-record-member-kinds.cir
@@ -8,19 +8,19 @@ module {}
// -----
-// A data member is spelled without a mark, so 'data' is not a mark keyword.
+// Only one mark is consumed, so a second one is left to the type parser.
!u8i = !cir.int<u, 8>
// expected-error @below {{expected non-function type}}
-!rec_S = !cir.struct<"S" {data !u8i}>
+!rec_S = !cir.struct<"S" {pad empty !u8i}>
module {}
// -----
-// Only one mark is consumed, so a second one is left to the type parser.
!u8i = !cir.int<u, 8>
-// expected-error @below {{expected non-function type}}
-!rec_S = !cir.struct<"S" {pad empty !u8i}>
+!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 {}
@@ -29,7 +29,16 @@ 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>}>
+!rec_U = !cir.union<"U" {!s32i}, padding = {data !cir.array<!u8i x 4>}>
+
+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 = {empty !cir.array<!u8i x 4>}>
module {}
diff --git a/clang/test/CIR/IR/struct.cir b/clang/test/CIR/IR/struct.cir
index 7321835fc1d0d..3048aaaac59ef 100644
--- a/clang/test/CIR/IR/struct.cir
+++ b/clang/test/CIR/IR/struct.cir
@@ -36,6 +36,9 @@
!rec_P6 = !cir.struct<"P6" {!u32i, empty !cir.array<!u8i x 3>, pad !u8i}>
!rec_P7 = !cir.struct<"P7" packed padded {!u8i, pad !u8i}>
+// 'data' parses and prints without a mark.
+!rec_P8 = !cir.struct<"P8" {data !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}>
@@ -43,10 +46,9 @@
// 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}>
+// CHECK-DAG: !rec_P8 = !cir.struct<"P8" {!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.
+// Records with identical member types, spelled apart by their kinds.
!rec_M1 = !cir.struct<"M1" {!u8i, pad !u8i}>
!rec_M2 = !cir.struct<"M2" {!u8i, empty !u8i}>
!rec_anon_pad = !cir.struct<{!u8i, pad !u8i}>
@@ -126,9 +128,18 @@ module {
%arg20: !rec_PadNode,
%arg21: !rec_P7,
%arg22: !rec_anon_u_empty,
- %arg23: !rec_anon_u_plain) {
+ %arg23: !rec_anon_u_plain,
+ %arg24: !rec_P8) {
+ cir.return
+ }
+
+ // The 'data' spelling must reach the same type as the unmarked one, which an
+ // anonymous record shows because it keys on its whole body.
+ cir.func @anonDataUnifies(%arg0: !cir.struct<{!u8i, pad !u8i}>,
+ %arg1: !cir.struct<{data !u8i, pad !u8i}>) {
cir.return
}
+// CHECK: cir.func @anonDataUnifies(%arg0: ![[ANON:rec_anon_struct[0-9]*]], %arg1: ![[ANON]])
cir.func @structs() {
%0 = cir.alloca "sc" align(8) init : !cir.ptr<!cir.ptr<!cir.struct<"Sc" {!u8i, !u16i, !u32i}>>>
diff --git a/clang/unittests/CIR/PointerLikeTest.cpp b/clang/unittests/CIR/PointerLikeTest.cpp
index 952bf67421470..fda90043f197d 100644
--- a/clang/unittests/CIR/PointerLikeTest.cpp
+++ b/clang/unittests/CIR/PointerLikeTest.cpp
@@ -169,7 +169,9 @@ class CIROpenACCPointerLikeTest : public ::testing::Test {
else
structTy = cir::StructType::get(&context, getUniqueRecordName("S"),
/*is_class=*/false);
- structTy.complete({ty1, ty2}, false, false);
+ mlir::Type members[] = {ty1, ty2};
+ structTy.complete(members, /*packed=*/false, /*padded=*/false,
+ /*padding=*/{}, cir::getAllDataKinds(members));
mlir::Type ptrTy = cir::PointerType::get(structTy);
// Verify that the pointer points to the structure type.
@@ -251,7 +253,9 @@ class CIROpenACCPointerLikeTest : public ::testing::Test {
cir::RecordType structTy =
cir::StructType::get(&context, getUniqueRecordName("S"),
/*is_class=*/false);
- structTy.complete({ptrTy, ptrTy}, false, false);
+ mlir::Type members[] = {ptrTy, ptrTy};
+ structTy.complete(members, /*packed=*/false, /*padded=*/false,
+ /*padding=*/{}, cir::getAllDataKinds(members));
mlir::Type structPptrTy = cir::PointerType::get(structTy);
// Create an alloca for the struct.
@@ -361,6 +365,8 @@ TEST_F(CIROpenACCPointerLikeTest, testPointerToStructMember) {
cir::RecordType structTy =
cir::StructType::get(&context, getUniqueRecordName("S"),
/*is_class=*/false);
- structTy.complete({i32Ty, i32Ty}, false, false);
+ mlir::Type members[] = {i32Ty, i32Ty};
+ structTy.complete(members, /*packed=*/false, /*padded=*/false,
+ /*padding=*/{}, cir::getAllDataKinds(members));
testPointerToMemberType(structTy, mlir::acc::VariableTypeCategory::composite);
}
diff --git a/clang/unittests/CIR/RecordMemberKindTest.cpp b/clang/unittests/CIR/RecordMemberKindTest.cpp
index bb635a9913a99..7094fced8b5b0 100644
--- a/clang/unittests/CIR/RecordMemberKindTest.cpp
+++ b/clang/unittests/CIR/RecordMemberKindTest.cpp
@@ -6,8 +6,7 @@
//
//===----------------------------------------------------------------------===//
//
-// 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.
+// Unit tests for per-member record kinds: ABI emptiness and type identity.
//
//===----------------------------------------------------------------------===//
@@ -20,13 +19,17 @@
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; }) {}
+/// Swallows verifier diagnostics, so a getChecked failure can be asserted
+/// without the error reaching stderr.
+struct ScopedDiagnosticCapture {
+ explicit ScopedDiagnosticCapture(MLIRContext &context)
+ : handler(&context, [this](mlir::Diagnostic &diag) {
+ ++count;
+ lastMessage = diag.str();
+ }) {}
unsigned count = 0;
+ std::string lastMessage;
private:
mlir::ScopedDiagnosticHandler handler;
@@ -65,40 +68,49 @@ TEST_F(RecordMemberKindTest, EmptyForTheABIWhenNoMemberHoldsData) {
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("d1", {u8}, {RecordMemberKind::Data})));
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.
+TEST_F(RecordMemberKindTest, RejectsAKindListThatDoesNotNameEveryMember) {
+ // The assembly syntax cannot express either of these, 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);
+ ScopedDiagnosticCapture 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);
+ EXPECT_EQ(diags.lastMessage, "expected 2 member kinds, got 1");
+
+ // An omitted list is not shorthand for all-data.
+ EXPECT_FALSE(StructType::getChecked(getLoc(), &context, membersRef,
+ /*packed=*/false, /*padded=*/false,
+ /*is_class=*/false,
+ llvm::ArrayRef<RecordMemberKind>{}));
+ EXPECT_EQ(diags.count, 2u);
+ EXPECT_EQ(diags.lastMessage, "expected 2 member kinds, got 0");
+
+ // A union answers to the same check.
+ EXPECT_FALSE(UnionType::getChecked(getLoc(), &context, membersRef,
+ /*packed=*/false, /*padding=*/mlir::Type{},
+ llvm::ArrayRef<RecordMemberKind>{}));
+ EXPECT_EQ(diags.count, 3u);
+ EXPECT_EQ(diags.lastMessage, "expected 2 member kinds, got 0");
}
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);
+ ScopedDiagnosticCapture diags(context);
llvm::ArrayRef<mlir::Type> membersRef(members);
EXPECT_FALSE(UnionType::getChecked(getLoc(), &context, membersRef,
/*packed=*/false, /*padding=*/mlir::Type{},
@@ -111,8 +123,6 @@ TEST_F(RecordMemberKindTest, RejectsPadOnAUnionMember) {
}
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));
}
@@ -127,23 +137,24 @@ TEST_F(RecordMemberKindTest, AUnionsTailPaddingSlotIsNotAMember) {
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);
+ auto holdsData =
+ UnionType::get(&context, membersRef, getName("ud"), /*packed=*/false,
+ /*padding=*/u8, getAllDataKinds(membersRef));
EXPECT_FALSE(allMembersNonData(holdsData));
}
-TEST_F(RecordMemberKindTest, MarksTakePartInAnonymousTypeIdentity) {
+TEST_F(RecordMemberKindTest, KindsTakePartInAnonymousTypeIdentity) {
IntType u8 = getU8();
- auto marksPad = StructType::get(
+ auto kindsPad = StructType::get(
&context, {u8, u8}, /*packed=*/false, /*padded=*/false,
/*is_class=*/false, {RecordMemberKind::Data, RecordMemberKind::Pad});
- auto marksEmpty = StructType::get(
+ auto kindsEmpty = StructType::get(
&context, {u8, u8}, /*packed=*/false, /*padded=*/false,
/*is_class=*/false, {RecordMemberKind::Data, RecordMemberKind::Empty});
- EXPECT_NE(marksPad, marksEmpty);
+ EXPECT_NE(kindsPad, kindsEmpty);
- // Marks are provenance rather than layout.
- EXPECT_TRUE(marksPad.isLayoutIdentical(marksEmpty));
+ // Kinds are provenance rather than layout.
+ EXPECT_TRUE(kindsPad.isLayoutIdentical(kindsEmpty));
llvm::SmallVector<mlir::Type> unionMembers{u8, u8};
llvm::SmallVector<RecordMemberKind> unionEmpty{RecordMemberKind::Data,
@@ -152,18 +163,9 @@ TEST_F(RecordMemberKindTest, MarksTakePartInAnonymousTypeIdentity) {
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());
+ auto unionAllData = UnionType::get(
+ &context, unionMembersRef, /*packed=*/false,
+ /*padding=*/mlir::Type{}, getAllDataKinds(unionMembersRef));
+ EXPECT_NE(unionMarked, unionAllData);
+ EXPECT_TRUE(unionMarked.isLayoutIdentical(unionAllData));
}
diff --git a/clang/unittests/CIR/RecordTypeMetadataTest.cpp b/clang/unittests/CIR/RecordTypeMetadataTest.cpp
index 8118055920886..e78d2e7e15648 100644
--- a/clang/unittests/CIR/RecordTypeMetadataTest.cpp
+++ b/clang/unittests/CIR/RecordTypeMetadataTest.cpp
@@ -63,7 +63,9 @@ TEST_F(RecordLayoutAttrTest, HighAlignment) {
TEST_F(RecordLayoutAttrTest, RecordTypeUnchanged) {
IntType i32 = IntType::get(&context, 32, true);
auto ty = StructType::get(&context, getName("Foo"), /*is_class=*/false);
- ty.complete({i32, i32}, /*packed=*/false, /*padded=*/false);
+ mlir::Type members[] = {i32, i32};
+ ty.complete(members, /*packed=*/false, /*padded=*/false,
+ getAllDataKinds(members));
EXPECT_TRUE(ty.isComplete());
EXPECT_EQ(ty.getMembers().size(), 2u);
}
diff --git a/clang/unittests/CIR/UnionTypeSizeTest.cpp b/clang/unittests/CIR/UnionTypeSizeTest.cpp
index f0a6fa0ad2872..e5debbd2905de 100644
--- a/clang/unittests/CIR/UnionTypeSizeTest.cpp
+++ b/clang/unittests/CIR/UnionTypeSizeTest.cpp
@@ -33,7 +33,9 @@ class UnionTypeSizeTest : public ::testing::Test {
TEST_F(UnionTypeSizeTest, SizeInBitsNotBytes) {
IntType i32 = IntType::get(&context, 32, true);
auto ty = UnionType::get(&context, getName("U"));
- ty.complete({i32}, /*packed=*/false, /*padding=*/mlir::Type{});
+ mlir::Type members[] = {i32};
+ ty.complete(members, /*packed=*/false, /*padding=*/mlir::Type{},
+ getAllDataKinds(members));
OpBuilder builder(&context);
auto loc = builder.getUnknownLoc();
@@ -50,7 +52,9 @@ TEST_F(UnionTypeSizeTest, MultiMemberUnion) {
IntType i32 = IntType::get(&context, 32, true);
IntType i64 = IntType::get(&context, 64, true);
auto ty = UnionType::get(&context, getName("U2"));
- ty.complete({i32, i64}, /*packed=*/false, /*padding=*/mlir::Type{});
+ mlir::Type members[] = {i32, i64};
+ ty.complete(members, /*packed=*/false, /*padding=*/mlir::Type{},
+ getAllDataKinds(members));
OpBuilder builder(&context);
auto loc = builder.getUnknownLoc();
@@ -65,7 +69,8 @@ TEST_F(UnionTypeSizeTest, MultiMemberUnion) {
TEST_F(UnionTypeSizeTest, EmptyUnion) {
auto ty = UnionType::get(&context, getName("Empty"));
- ty.complete({}, /*packed=*/false, /*padding=*/mlir::Type{});
+ ty.complete(/*members=*/{}, /*packed=*/false, /*padding=*/mlir::Type{},
+ /*memberKinds=*/{});
OpBuilder builder(&context);
auto loc = builder.getUnknownLoc();
@@ -80,10 +85,13 @@ TEST_F(UnionTypeSizeTest, EmptyUnion) {
TEST_F(UnionTypeSizeTest, IsLayoutIdenticalNoPadding) {
IntType i32 = IntType::get(&context, 32, true);
+ mlir::Type members[] = {i32};
auto ty1 = UnionType::get(&context, getName("Ua"));
- ty1.complete({i32}, /*packed=*/false, /*padding=*/mlir::Type{});
+ ty1.complete(members, /*packed=*/false, /*padding=*/mlir::Type{},
+ getAllDataKinds(members));
auto ty2 = UnionType::get(&context, getName("Ub"));
- ty2.complete({i32}, /*packed=*/false, /*padding=*/mlir::Type{});
+ ty2.complete(members, /*packed=*/false, /*padding=*/mlir::Type{},
+ getAllDataKinds(members));
EXPECT_TRUE(ty1.isLayoutIdentical(ty2));
}
@@ -91,19 +99,25 @@ TEST_F(UnionTypeSizeTest, IsLayoutIdenticalDifferentPadding) {
IntType i32 = IntType::get(&context, 32, true);
IntType i8 = IntType::get(&context, 8, false);
IntType i16 = IntType::get(&context, 16, false);
+ mlir::Type members[] = {i32};
auto ty1 = UnionType::get(&context, getName("Upad1"));
- ty1.complete({i32}, /*packed=*/false, /*padding=*/i8);
+ ty1.complete(members, /*packed=*/false, /*padding=*/i8,
+ getAllDataKinds(members));
auto ty2 = UnionType::get(&context, getName("Upad2"));
- ty2.complete({i32}, /*packed=*/false, /*padding=*/i16);
+ ty2.complete(members, /*packed=*/false, /*padding=*/i16,
+ getAllDataKinds(members));
EXPECT_FALSE(ty1.isLayoutIdentical(ty2));
}
TEST_F(UnionTypeSizeTest, IsLayoutIdenticalSamePadding) {
IntType i32 = IntType::get(&context, 32, true);
IntType i8 = IntType::get(&context, 8, false);
+ mlir::Type members[] = {i32};
auto ty1 = UnionType::get(&context, getName("Upad3"));
- ty1.complete({i32}, /*packed=*/false, /*padding=*/i8);
+ ty1.complete(members, /*packed=*/false, /*padding=*/i8,
+ getAllDataKinds(members));
auto ty2 = UnionType::get(&context, getName("Upad4"));
- ty2.complete({i32}, /*packed=*/false, /*padding=*/i8);
+ ty2.complete(members, /*packed=*/false, /*padding=*/i8,
+ getAllDataKinds(members));
EXPECT_TRUE(ty1.isLayoutIdentical(ty2));
}
More information about the cfe-commits
mailing list