[clang] [CIR] Mark record members as data, pad, or empty in CIRGen (PR #215175)
Adam Smith via cfe-commits
cfe-commits at lists.llvm.org
Fri Aug 14 14:07:46 PDT 2026
https://github.com/adams381 updated https://github.com/llvm/llvm-project/pull/215175
>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/4] [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 9d4d0bdd9345c55f676085121dff2062df610736 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Sun, 9 Aug 2026 14:16:48 -0700
Subject: [PATCH 2/4] [CIR] Mark record members as data, pad, or empty in
CIRGen
The per-member marks landed with no producer, so every record still reads as
though all of its members held data. A record type still can't say whether it
carries anything for argument passing. The x86_64 classifier has to know that
before it can drop an empty class from a signature.
CIRGen fills the marks in now. Every member goes through one `addField` that
takes its kind, so a new push site can't quietly inherit a default.
A field's mark comes from `isEmptyFieldForABI`, ported from `isEmptyField` in
`ABIInfoImpl.cpp`. Taking the ABI predicate rather than the layout one is
what gets C right. Given `struct E {}`, a struct holding one `E` is empty for
the ABI in C but not in C++.
An assert on every record checks the marks against `isEmptyRecordForABI`, so
the existing `-fclangir` tests exercise them.
One case is now NYI. A `[[no_unique_address]]` field that
`isEmptyFieldForLayout` drops from the layout can still hold ABI data. With
the field gone there is no member left to mark, so CIRGen says so rather than
emit a record that understates what it holds.
This is the second of three PRs. Nothing reads the marks yet. The `padded`
bool stays, and `computeStructDataSize` still uses it. The next PR will
properly use the marks and remove `padded`.
Assisted-by: Cursor / claude-opus-5
---
.../include/clang/CIR/Dialect/IR/CIRAttrs.td | 2 +-
clang/include/clang/CIR/Dialect/IR/CIROps.td | 5 +-
clang/lib/CIR/CodeGen/CIRGenBuilder.cpp | 3 +-
clang/lib/CIR/CodeGen/CIRGenBuilder.h | 18 +-
clang/lib/CIR/CodeGen/CIRGenRecordLayout.h | 2 +-
.../CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp | 207 ++++++++++++++----
clang/lib/CIR/CodeGen/TargetInfo.cpp | 56 +++++
clang/lib/CIR/CodeGen/TargetInfo.h | 23 ++
.../CIR/Dialect/Transforms/CXXABILowering.cpp | 9 +-
clang/test/CIR/CodeGen/bitfields.c | 2 +-
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 | 4 +-
.../test/CIR/CodeGen/paren-list-agg-init.cpp | 12 +-
.../CodeGen/pointer-to-empty-data-member.cpp | 10 +-
.../CIR/CodeGen/record-member-kinds-nyi.cpp | 14 ++
clang/test/CIR/CodeGen/record-member-kinds.c | 97 ++++++++
.../test/CIR/CodeGen/record-member-kinds.cpp | 141 ++++++++++++
.../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 +-
27 files changed, 554 insertions(+), 95 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/record-member-kinds-nyi.cpp
create mode 100644 clang/test/CIR/CodeGen/record-member-kinds.c
create mode 100644 clang/test/CIR/CodeGen/record-member-kinds.cpp
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index 71585cd83fb66..9a726a3619ffc 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -1564,7 +1564,7 @@ def CIR_BitfieldInfoAttr : CIR_Attr<"BitfieldInfo", "bitfield_info"> {
The CIR representation of the struct `S` might look like:
```
!rec_S = !cir.record<struct "S" packed padded {!u64i, !u16i,
- !cir.array<!u8i x 2>}>
+ pad !cir.array<!u8i x 2>}>
```
And the bitfield info attribute for member `a` would be:
```
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index d0f3c9ee6715f..513d765fff23d 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -3752,7 +3752,7 @@ def CIR_SetBitfieldOp : CIR_Op<"set_bitfield"> {
```
// 'e' is in the storage with the index 1
!record_type = !cir.record<struct "S" packed padded {!u64i, !u16i,
- !cir.array<!u8i x 2>} #cir.record.decl.ast>
+ pad !cir.array<!u8i x 2>} #cir.record.decl.ast>
#bfi_e = #cir.bitfield_info<name = "e", storage_type = !u16i, size = 15,
offset = 0, is_signed = true>
@@ -3842,7 +3842,8 @@ def CIR_GetBitfieldOp : CIR_Op<"get_bitfield"> {
```
// 'e' is in the storage with the index 1
- !cir.record<struct "S" packed padded {!u64i, !u16i, !cir.array<!u8i x 2>}>
+ !cir.record<struct "S" packed padded {!u64i, !u16i,
+ pad !cir.array<!u8i x 2>}>
#bfi_e = #cir.bitfield_info<name = "e", storage_type = !u16i, size = 15,
offset = 0, is_signed = true>
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp b/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
index a562c4b7b763f..d339c35fbffa0 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
@@ -208,7 +208,8 @@ cir::RecordType clang::CIRGen::CIRGenBuilderTy::getCompleteRecordType(
if (name.empty())
return getAnonRecordTy(members, packed, padded);
- return getCompleteNamedRecordType(members, packed, padded, name);
+ return getCompleteNamedRecordType(members, packed, padded, name,
+ /*memberKinds=*/{});
}
mlir::Attribute clang::CIRGen::CIRGenBuilderTy::getConstRecordOrZeroAttr(
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.h b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
index c906e65a132c2..f414f67c76fbe 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuilder.h
+++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
@@ -154,26 +154,18 @@ 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.
- assert(!type.isIncomplete() ||
- (type.getMembers() == members && type.getPacked() == packed &&
- type.getPadded() == padded));
-
- // 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;
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h b/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h
index 9733d18f52ce8..c4968f3024d99 100644
--- a/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h
+++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h
@@ -46,7 +46,7 @@ namespace clang::CIRGen {
/// struct to a 4-byte alignment.
///
/// !rec_S = !cir.record<struct "S" padded {!s8i, !s8i, !s8i, !u16i,
-/// !cir.array<!u8i x 3>}>
+/// pad !cir.array<!u8i x 3>}>
///
/// When generating code to access more_bits, we'll generate something
/// essentially like this:
diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
index e4476b88ec6f7..8258f1bf1aa33 100644
--- a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
@@ -13,6 +13,7 @@
#include "CIRGenBuilder.h"
#include "CIRGenModule.h"
#include "CIRGenTypes.h"
+#include "TargetInfo.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
@@ -43,16 +44,23 @@ 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 so that later
+ /// passes can tell declared storage from compiler-inserted padding. Every
+ /// constructor takes it, so a new member cannot be added without deciding.
+ 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 +71,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.
@@ -83,7 +92,7 @@ struct CIRRecordLowering final {
void accumulateBases();
void accumulateVPtrs();
void accumulateVBases();
- void accumulateFields();
+ void accumulateFields(bool nonVirtualBaseType);
RecordDecl::field_iterator
accumulateBitFields(RecordDecl::field_iterator field,
RecordDecl::field_iterator fieldEnd);
@@ -141,6 +150,22 @@ struct CIRRecordLowering final {
return cirGenTypes.isZeroInitializable(rd);
}
+ /// The mark for a field.
+ cir::RecordMemberKind getFieldMemberKind(const FieldDecl *fd) {
+ return isEmptyFieldForABI(astContext, fd) ? cir::RecordMemberKind::Empty
+ : cir::RecordMemberKind::Data;
+ }
+
+ /// The mark for a base subobject. A base contributes no ABI data when it is
+ /// empty for the ABI, which is not the same as CXXRecordDecl::isEmpty(): a
+ /// base holding only unnamed bit-fields is laid out but carries no data.
+ cir::RecordMemberKind getBaseMemberKind(const CXXRecordDecl *baseDecl) {
+ return isEmptyRecordForABI(astContext,
+ astContext.getCanonicalTagType(baseDecl))
+ ? cir::RecordMemberKind::Empty
+ : cir::RecordMemberKind::Data;
+ }
+
/// Wraps cir::IntType with some implicit arguments.
mlir::Type getUIntNType(uint64_t numBits) {
unsigned alignedBits = llvm::PowerOf2Ceil(numBits);
@@ -204,10 +229,26 @@ struct CIRRecordLowering final {
assert(!unionPadding && "at most one union tail-padding type");
unionPadding = padTy;
} else {
- fieldTypes.push_back(padTy);
+ addField(padTy, cir::RecordMemberKind::Pad);
}
}
+ /// The single entry point for appending an output field.
+ 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 +257,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;
@@ -233,8 +272,19 @@ struct CIRRecordLowering final {
unsigned packed : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned padded : 1;
+ /// Whether a field was dropped whose data no member can represent. Reported
+ /// as NYI. Until that gap is closed the completed type reads as ABI-empty
+ /// when it is not, so the differential asserts below cannot run on it.
+ LLVM_PREFERRED_TYPE(bool)
+ unsigned droppedFieldHoldingData : 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
@@ -249,7 +299,7 @@ CIRRecordLowering::CIRRecordLowering(CIRGenTypes &cirGenTypes,
cirGenTypes.getASTContext().getASTRecordLayout(recordDecl)},
dataLayout{cirGenTypes.getCGModule().getModule()},
zeroInitializable{true}, zeroInitializableAsBase{true}, packed{packed},
- padded{false} {}
+ padded{false}, droppedFieldHoldingData{false} {}
void CIRRecordLowering::setBitFieldInfo(const FieldDecl *fd,
CharUnits startOffset,
@@ -288,7 +338,7 @@ void CIRRecordLowering::lower(bool nonVirtualBaseType) {
CharUnits size = nonVirtualBaseType ? astRecordLayout.getNonVirtualSize()
: astRecordLayout.getSize();
- accumulateFields();
+ accumulateFields(nonVirtualBaseType);
if (cxxRecordDecl) {
accumulateVPtrs();
@@ -306,7 +356,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();
@@ -318,8 +371,11 @@ void CIRRecordLowering::lower(bool nonVirtualBaseType) {
void CIRRecordLowering::fillOutputFields() {
for (const MemberInfo &member : members) {
+ // A bit-field occupant and a primary virtual base without own storage both
+ // carry null data, so the kind must be appended inside this guard or every
+ // later mark shifts by one while the two lengths still agree.
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()] =
@@ -352,6 +408,9 @@ CIRRecordLowering::accumulateBitFields(RecordDecl::field_iterator field,
// used to determine if the ASTRecordLayout is treating these two bitfields
// as contiguous. StartBitOffset is offset of the beginning of the Run.
uint64_t startBitOffset, tail = 0;
+ // Where the current run's storage member sits in members, so that a named
+ // occupant joining the run can promote it to data.
+ size_t runStorageIdx = 0;
for (; field != fieldEnd && field->isBitField(); ++field) {
// Zero-width bitfields end runs.
if (field->isZeroLengthBitField()) {
@@ -368,15 +427,22 @@ CIRRecordLowering::accumulateBitFields(RecordDecl::field_iterator field,
tail = startBitOffset + dataLayout.getTypeAllocSizeInBits(type);
// 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));
+ // the bitfields it contains get laid out. The run is only known one
+ // field at a time here, so the unit starts out holding no data and is
+ // promoted below when a named occupant lands in it.
+ runStorageIdx = members.size();
+ members.push_back(makeStorageInfo(bitsToCharUnits(startBitOffset), type,
+ cir::RecordMemberKind::Empty));
}
+ assert(members[runStorageIdx].offset == bitsToCharUnits(startBitOffset) &&
+ "runStorageIdx must name the current run's storage");
+ if (!field->isUnnamedBitField())
+ members[runStorageIdx].memberKind = 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 +614,20 @@ CIRRecordLowering::accumulateBitFields(RecordDecl::field_iterator field,
assert(getSize(type) == accessSize &&
"Unclipped access must be clipped");
}
- members.push_back(makeStorageInfo(beginOffset, type));
+ // An unnamed bit-field of any width occupies no ABI class, so a unit
+ // made up of nothing but those is declared storage holding no data.
+ const bool hasNamedOccupant = llvm::any_of(
+ llvm::make_range(begin, bestEnd),
+ [](const FieldDecl *fd) { return !fd->isUnnamedBitField(); });
+ members.push_back(makeStorageInfo(beginOffset, type,
+ hasNamedOccupant
+ ? cir::RecordMemberKind::Data
+ : cir::RecordMemberKind::Empty));
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;
@@ -570,7 +645,7 @@ CIRRecordLowering::accumulateBitFields(RecordDecl::field_iterator field,
return field;
}
-void CIRRecordLowering::accumulateFields() {
+void CIRRecordLowering::accumulateFields(bool nonVirtualBaseType) {
for (RecordDecl::field_iterator field = recordDecl->field_begin(),
fieldEnd = recordDecl->field_end();
field != fieldEnd;) {
@@ -591,17 +666,34 @@ void CIRRecordLowering::accumulateFields() {
// problem with taking the address of one of these, so it is in practice
// not a horrifyingly problematic issue.
assert(!cir::MissingFeatures::noUniqueAddressLayout());
+ // Dropping the field leaves no member to mark, so its bytes read as
+ // padding. That is only sound when the field carries no ABI data
+ // either, which isEmptyFieldForLayout does not guarantee. Report the
+ // gap rather than claim an emptiness the record does not have. The base
+ // subobject lowering sees the same field, so only the complete object
+ // reports it.
+ if (!isEmptyFieldForABI(astContext, *field)) {
+ if (!nonVirtualBaseType)
+ cirGenTypes.getCGModule().errorNYI(
+ field->getSourceRange(),
+ "[[no_unique_address]] field that is empty for layout but holds "
+ "data for the ABI");
+ droppedFieldHoldingData = true;
+ }
++field;
} else {
// Use base subobject layout for potentially-overlapping fields,
// as it is done in RecordLayoutBuilder.
+ //
+ // The mark comes from isEmptyFieldForABI, not the isEmptyFieldForLayout
+ // above. Neither predicate subsumes the other.
members.push_back(MemberInfo(
bitsToCharUnits(getFieldBitOffset(*field)),
MemberInfo::InfoKind::Field,
field->isPotentiallyOverlapping()
? getStorageType(field->getType()->getAsCXXRecordDecl())
: getStorageType(*field),
- *field));
+ getFieldMemberKind(*field), *field));
++field;
}
}
@@ -679,7 +771,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);
}
@@ -696,6 +789,18 @@ convertRecordArgPassingKind(RecordArgPassingKind kind) {
llvm_unreachable("unknown RecordArgPassingKind");
}
+/// Whether the member kinds on \p recordTy answer the record's ABI emptiness
+/// the same way the AST predicate does. A lowering that dropped a field
+/// holding data has no member left to carry that data, so it is exempt.
+[[maybe_unused]] static bool
+marksMatchABIEmptiness(const ASTContext &astContext, const RecordDecl *rd,
+ cir::RecordType recordTy, bool droppedFieldHoldingData) {
+ if (droppedFieldHoldingData)
+ return true;
+ return cir::allMembersNonData(recordTy) ==
+ isEmptyRecordForABI(astContext, astContext.getCanonicalTagType(rd));
+}
+
std::unique_ptr<CIRGenRecordLayout>
CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
CIRRecordLowering lowering(*this, rd, /*packed=*/false);
@@ -726,8 +831,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
@@ -738,6 +843,12 @@ CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
// size and so needs no such exemption.)
assert((rd->isUnion() || lowering.packed == baseLowering.packed) &&
"Non-virtual and complete types must agree on packedness");
+ // Emptiness is a property of the decl, so the base subobject must answer
+ // the same way the complete object does. The two are not comparable
+ // mark by mark: they see different sizes and so different tail padding.
+ assert(marksMatchABIEmptiness(astContext, rd, baseTy,
+ baseLowering.droppedFieldHoldingData) &&
+ "base subobject member kinds must reproduce its ABI emptiness");
}
}
@@ -745,8 +856,16 @@ 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());
+
+ // The marks exist so that emptiness can be read off the type, so check that
+ // answer against the AST predicate on every record CIRGen lays out. This
+ // does not check the individual marks, only what they add up to. The marks
+ // themselves are pinned by clang/test/CIR/CodeGen/record-member-kinds.*.
+ assert(marksMatchABIEmptiness(astContext, rd, *ty,
+ lowering.droppedFieldHoldingData) &&
+ "member kinds must reproduce the ABI emptiness of the record");
// Queue ABI metadata for the module-level cir.record_layouts attribute.
if (ty->getName()) {
@@ -851,7 +970,9 @@ void CIRRecordLowering::lowerUnion(bool nonVirtualBaseType) {
}
fieldIdxMap[field->getCanonicalDecl()] = 0;
- fieldTypes.push_back(fieldType);
+ addField(fieldType, isEmptyFieldForABI(astContext, field)
+ ? cir::RecordMemberKind::Empty
+ : cir::RecordMemberKind::Data);
}
// Compute zero-initializable status.
@@ -895,11 +1016,17 @@ 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);
+ // One member stands in for every variant, so it holds data unless no
+ // variant does. Computed before clearFields() drops the variant marks.
+ const cir::RecordMemberKind storageKind =
+ llvm::is_contained(fieldKinds, cir::RecordMemberKind::Data)
+ ? cir::RecordMemberKind::Data
+ : cir::RecordMemberKind::Empty;
+ clearFields();
+ addField(storageType, storageKind);
CharUnits padding = layoutSize - getSize(storageType);
if (!padding.isZero()) {
- fieldTypes.push_back(getByteArrayType(padding));
+ addField(getByteArrayType(padding), cir::RecordMemberKind::Pad);
padded = true;
}
} else {
@@ -1044,7 +1171,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),
+ getBaseMemberKind(baseDecl), baseDecl));
}
// Accumulate the non-virtual bases.
@@ -1058,7 +1186,8 @@ void CIRRecordLowering::accumulateBases() {
!astContext.getASTRecordLayout(baseDecl).getNonVirtualSize().isZero()) {
members.push_back(MemberInfo(astRecordLayout.getBaseClassOffset(baseDecl),
MemberInfo::InfoKind::Base,
- getStorageType(baseDecl), baseDecl));
+ getStorageType(baseDecl),
+ getBaseMemberKind(baseDecl), baseDecl));
}
}
}
@@ -1073,8 +1202,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 +1211,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),
+ getBaseMemberKind(baseDecl), 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/TargetInfo.cpp b/clang/lib/CIR/CodeGen/TargetInfo.cpp
index ba7eeb29dd252..51c9cfc056eee 100644
--- a/clang/lib/CIR/CodeGen/TargetInfo.cpp
+++ b/clang/lib/CIR/CodeGen/TargetInfo.cpp
@@ -45,6 +45,62 @@ bool clang::CIRGen::isEmptyFieldForLayout(const ASTContext &context,
return isEmptyRecordForLayout(context, fd->getType());
}
+bool clang::CIRGen::isEmptyRecordForABI(const ASTContext &context, QualType t) {
+ const auto *rd = t->getAsRecordDecl();
+ if (!rd)
+ return false;
+ if (rd->hasFlexibleArrayMember())
+ return false;
+
+ if (const auto *cxxrd = dyn_cast<CXXRecordDecl>(rd)) {
+ // A vtable pointer is neither a base nor a field, so clang's predicate
+ // calls a polymorphic class empty and leans on its callers rejecting one as
+ // non-trivially-copyable beforehand. This answer is read off the record
+ // type without that precondition, so rule it out here instead.
+ if (cxxrd->isDynamicClass())
+ return false;
+
+ for (const auto &i : cxxrd->bases())
+ if (!isEmptyRecordForABI(context, i.getType()))
+ return false;
+ }
+
+ for (const auto *i : rd->fields())
+ if (!isEmptyFieldForABI(context, i))
+ return false;
+ return true;
+}
+
+bool clang::CIRGen::isEmptyFieldForABI(const ASTContext &context,
+ const FieldDecl *fd) {
+ if (fd->isUnnamedBitField())
+ return true;
+
+ QualType ft = fd->getType();
+
+ // An array of empty records is empty, and a zero-length array always is.
+ bool wasArray = false;
+ while (const ConstantArrayType *at = context.getAsConstantArrayType(ft)) {
+ if (at->isZeroSize())
+ return true;
+ ft = at->getElementType();
+ wasArray = true;
+ }
+
+ const auto *rt = ft->getAsCanonical<RecordType>();
+ if (!rt)
+ return false;
+
+ // A C++ record field is never empty under the Itanium ABI unless
+ // [[no_unique_address]] makes it so, and that exception covers a record
+ // rather than an array of them.
+ if (isa<CXXRecordDecl>(rt->getDecl()) &&
+ (wasArray || !fd->hasAttr<NoUniqueAddressAttr>()))
+ return false;
+
+ return isEmptyRecordForABI(context, ft);
+}
+
namespace {
class AMDGPUABIInfo : public ABIInfo {
diff --git a/clang/lib/CIR/CodeGen/TargetInfo.h b/clang/lib/CIR/CodeGen/TargetInfo.h
index e720a4ad2ec5c..9613185e93127 100644
--- a/clang/lib/CIR/CodeGen/TargetInfo.h
+++ b/clang/lib/CIR/CodeGen/TargetInfo.h
@@ -37,6 +37,29 @@ bool isEmptyFieldForLayout(const ASTContext &context, const FieldDecl *fd);
/// if the [[no_unique_address]] attribute would have made them empty.
bool isEmptyRecordForLayout(const ASTContext &context, QualType t);
+/// isEmptyFieldForABI - Return true if the field is "empty" for argument
+/// passing. An unnamed bit-field of any width qualifies, as does an empty
+/// record, though a C++ one only under [[no_unique_address]] and never through
+/// an array of them. Neither this nor isEmptyFieldForLayout subsumes the
+/// other. An unnamed bit-field of any width is empty here but only at width
+/// zero there, because a narrower one occupies no ABI class even though it
+/// takes up layout space. Conversely a C++ record whose own members are empty
+/// C++ records is empty for layout but not here.
+bool isEmptyFieldForABI(const ASTContext &context, const FieldDecl *fd);
+
+/// isEmptyRecordForABI - Return true if a record contains only empty base
+/// classes and fields, and so contributes no data to argument passing. Unlike
+/// clang's isEmptyRecord, a polymorphic class is never empty here: its vtable
+/// pointer is neither a base nor a field, and this answer is read without the
+/// precondition that a caller has already rejected a non-trivially-copyable
+/// type. Clang's isEmptyRecord takes AllowArrays and AsIfNoUniqueAddr
+/// parameters and targets choose different values (RISC-V passes
+/// AsIfNoUniqueAddr=true, ARM passes AllowArrays=false for return types).
+/// This predicate fixes them at true and false, which is what x86-64 asks for.
+/// A consumer reading the member kinds cannot recover the other answers: a
+/// field that a different setting would call empty is already marked as data.
+bool isEmptyRecordForABI(const ASTContext &context, QualType t);
+
class CIRGenFunction;
class TargetCIRGenInfo {
diff --git a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
index 50a51d9618118..110a391006b20 100644
--- a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
@@ -853,17 +853,20 @@ class CIRABITypeConverter : public mlir::TypeConverter {
// just do a conversion on it.
if (!type.getName()) {
llvm::SmallVector<mlir::Type> converted = convertRecordMemberTypes(type);
+ // Member conversion is one type in, one type out, so the marks 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());
@@ -908,7 +911,7 @@ class CIRABITypeConverter : public mlir::TypeConverter {
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/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/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..18dc0a9ad1735 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-LABEL: cir.func {{.*}} @_ZN5OuterC2ERK6Middlec(
// CIR: %[[THIS:.*]] = cir.load %{{.+}} : !cir.ptr<!cir.ptr<!rec_Outer>>, !cir.ptr<!rec_Outer>
@@ -203,6 +203,6 @@ OuterAllEmpty oae;
// CIR-NUA-DAG: !rec_OuterAllEmpty = !cir.struct<"OuterAllEmpty" {!cir.bool}>
// CIR-NUA-DAG: cir.global external @oae = #cir.zero : !rec_OuterAllEmpty
-// CIR-NUA-DAG: !rec_UnionZeroDataSize = !cir.union<"UnionZeroDataSize" {!rec_EmptyForNUA, !s32i}>
+// CIR-NUA-DAG: !rec_UnionZeroDataSize = !cir.union<"UnionZeroDataSize" {empty !rec_EmptyForNUA, !s32i}>
// CIR-NUA-DAG: !rec_OuterZeroData = !cir.struct<"OuterZeroData" {!rec_UnionZeroDataSize, !cir.bool}>
// CIR-NUA-DAG: cir.global external @ozd = #cir.zero : !rec_OuterZeroData
diff --git a/clang/test/CIR/CodeGen/paren-list-agg-init.cpp b/clang/test/CIR/CodeGen/paren-list-agg-init.cpp
index 37b0b42a6f7dc..d5d9dc1040d21 100644
--- a/clang/test/CIR/CodeGen/paren-list-agg-init.cpp
+++ b/clang/test/CIR/CodeGen/paren-list-agg-init.cpp
@@ -36,13 +36,13 @@ 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;
};
// LLVM-DAG: [[STRUCT_D:%.*]] = type { [[STRUCT_A]], [[STRUCT_A]], i8, [[STRUCT_A]] }
-// CIR-DAG: ![[STRUCT_D:.*]] = !cir.struct<"D" {![[STRUCT_A]], ![[STRUCT_A]], !u8i, ![[STRUCT_A]]}>
+// CIR-DAG: ![[STRUCT_D:.*]] = !cir.struct<"D" {![[STRUCT_A]], ![[STRUCT_A]], empty !u8i, ![[STRUCT_A]]}>
struct D {
A a;
A b = A{2, 2.0};
@@ -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;
@@ -75,7 +75,7 @@ struct G {
// LLVM-DAG: [[UNION_U:%.*]] = type { [[STRUCT_A]] }
// LLVM-DAG: [[STR:@.*]] = private {{.*}}constant [6 x i8] {{.*}}foo18{{.*}}, align 1
-// CIR-DAG: ![[UNION_U:.*]] = !cir.union<"U" {!u8i, ![[STRUCT_A]], !s8i}>
+// CIR-DAG: ![[UNION_U:.*]] = !cir.union<"U" {empty !u8i, ![[STRUCT_A]], !s8i}>
union U {
unsigned : 1;
A a;
@@ -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..29023355def72 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};
@@ -229,7 +229,7 @@ union U6 {
EmptyBase eb2;
int i;
};
-// CIR-BEFORE-DAG: !rec_U6 = !cir.union<"U6" {!rec_EmptyBase, !rec_EmptyBase, !s32i}>
+// CIR-BEFORE-DAG: !rec_U6 = !cir.union<"U6" {empty !rec_EmptyBase, empty !rec_EmptyBase, !s32i}>
int U6::* u6i = &U6::i;
// CIR-BEFORE-DAG: cir.global external @u6i = #cir.data_member<[2]> : !cir.data_member<!s32i in !rec_U6>
// CIR-AFTER-DAG: cir.global external @u6i = #cir.int<0> : !s64i
@@ -251,7 +251,7 @@ union U7 {
[[no_unique_address]]
EmptyBase eb2;
};
-// CIR-BEFORE-DAG: !rec_U7 = !cir.union<"U7" {!s32i, !rec_EmptyBase, !rec_EmptyBase}>
+// CIR-BEFORE-DAG: !rec_U7 = !cir.union<"U7" {!s32i, empty !rec_EmptyBase, empty !rec_EmptyBase}>
int U7::* u7i = &U7::i;
// CIR-BEFORE-DAG: cir.global external @u7i = #cir.data_member<[0]> : !cir.data_member<!s32i in !rec_U7>
// CIR-AFTER-DAG: cir.global external @u7i = #cir.int<0> : !s64i
diff --git a/clang/test/CIR/CodeGen/record-member-kinds-nyi.cpp b/clang/test/CIR/CodeGen/record-member-kinds-nyi.cpp
new file mode 100644
index 0000000000000..142cad54e5555
--- /dev/null
+++ b/clang/test/CIR/CodeGen/record-member-kinds-nyi.cpp
@@ -0,0 +1,14 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -emit-cir -verify %s
+
+struct Empty {};
+
+// Empty for layout, because its own member is an empty record, but not empty
+// for the ABI, because a C++ record member is data without the attribute.
+struct EmptyForLayoutOnly { Empty e; };
+
+struct Wrapper {
+ // expected-error at +1 {{ClangIR code gen Not Yet Implemented: [[no_unique_address]] field that is empty for layout but holds data for the ABI}}
+ [[no_unique_address]] EmptyForLayoutOnly e;
+};
+
+Wrapper w;
diff --git a/clang/test/CIR/CodeGen/record-member-kinds.c b/clang/test/CIR/CodeGen/record-member-kinds.c
new file mode 100644
index 0000000000000..aa44b58ef8754
--- /dev/null
+++ b/clang/test/CIR/CodeGen/record-member-kinds.c
@@ -0,0 +1,97 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o - | FileCheck %s --check-prefix=CIR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o - | FileCheck %s --check-prefix=LLVM
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o - | FileCheck %s --check-prefix=LLVM
+
+struct E {};
+// CIR-DAG: !rec_E = !cir.struct<"E" {}>
+
+// In C, containment of an empty record recurses, so ContainsEmpty is itself
+// empty for the ABI. The same declaration in C++ is not.
+struct ContainsEmpty { struct E e; };
+// CIR-DAG: !rec_ContainsEmpty = !cir.struct<"ContainsEmpty" {empty !rec_E}>
+
+struct ContainsEmptyAndInt { struct E e; int i; };
+// CIR-DAG: !rec_ContainsEmptyAndInt = !cir.struct<"ContainsEmptyAndInt" {empty !rec_E, !s32i}>
+
+struct EmptyArr { struct E e[2]; };
+// CIR-DAG: !rec_EmptyArr = !cir.struct<"EmptyArr" {empty !cir.array<!rec_E x 2>}>
+
+// isEmptyFieldForABI peels every array dimension, not just one.
+struct MultiDimEmpty { struct E e[2][3]; };
+// CIR-DAG: !rec_MultiDimEmpty = !cir.struct<"MultiDimEmpty" {empty !cir.array<!cir.array<!rec_E x 3> x 2>}>
+
+struct ZeroLenArr { int a[0]; };
+// CIR-DAG: !rec_ZeroLenArr = !cir.struct<"ZeroLenArr" {empty !cir.array<!s32i x 0>}>
+
+// A flexible array member is data, and keeps its record non-empty.
+struct Fam { int n; int a[]; };
+// CIR-DAG: !rec_Fam = !cir.struct<"Fam" {!s32i, !cir.array<!s32i x 0>}>
+
+struct UnnamedBitOnly { int : 8; };
+// CIR-DAG: !rec_UnnamedBitOnly = !cir.struct<"UnnamedBitOnly" {empty !u8i}>
+
+struct UnnamedBitThenField { int : 8; int f; };
+// CIR-DAG: !rec_UnnamedBitThenField = !cir.struct<"UnnamedBitThenField" {empty !u8i, !s32i}>
+
+// The discrete ms_struct path allocates a unit per formal type. A unit whose
+// only occupant is unnamed holds no data.
+struct MsOnlyUnnamed { int : 3; } __attribute__((ms_struct));
+// CIR-DAG: !rec_MsOnlyUnnamed = !cir.struct<"MsOnlyUnnamed" {empty !s32i}>
+
+struct MsNamedThenUnnamed { int a : 3; int : 3; } __attribute__((ms_struct));
+// CIR-DAG: !rec_MsNamedThenUnnamed = !cir.struct<"MsNamedThenUnnamed" {!s32i}>
+
+struct MsUnnamedThenNamed { int : 3; int b : 3; } __attribute__((ms_struct));
+// CIR-DAG: !rec_MsUnnamedThenNamed = !cir.struct<"MsUnnamedThenNamed" {!s32i}>
+
+// A differing formal type starts a new unit, so this record carries one unit of
+// each kind.
+struct MsMixed { int a : 3; char : 3; } __attribute__((ms_struct));
+// CIR-DAG: !rec_MsMixed = !cir.struct<"MsMixed" {!s32i, empty !s8i}>
+
+struct MsEmptyFirst { char : 3; int a : 3; } __attribute__((ms_struct));
+// CIR-DAG: !rec_MsEmptyFirst = !cir.struct<"MsEmptyFirst" {empty !s8i, !s32i}>
+
+struct MsEmptyMiddle {
+ int a : 3; char : 3; short b : 3;
+} __attribute__((ms_struct));
+// CIR-DAG: !rec_MsEmptyMiddle = !cir.struct<"MsEmptyMiddle" {!s32i, empty !s8i, !s16i}>
+
+// A zero-width bit-field ends the run here too, so the unit after it is a
+// fresh one that has to be marked on its own.
+struct MsZeroWidthSplit { int a : 3; int : 0; int : 3; } __attribute__((ms_struct));
+// CIR-DAG: !rec_MsZeroWidthSplit = !cir.struct<"MsZeroWidthSplit" {!s32i, empty !s32i}>
+
+struct MsZeroWidthSplit2 { int : 3; int : 0; int b : 3; } __attribute__((ms_struct));
+// CIR-DAG: !rec_MsZeroWidthSplit2 = !cir.struct<"MsZeroWidthSplit2" {empty !s32i, !s32i}>
+
+union UnnamedBitUnion { int : 8; };
+// CIR-DAG: !rec_UnnamedBitUnion = !cir.union<"UnnamedBitUnion" {empty !u8i}>
+
+union ContainsEmptyUnion { struct E e; };
+// CIR-DAG: !rec_ContainsEmptyUnion = !cir.union<"ContainsEmptyUnion" {empty !rec_E}>
+
+struct AlignedTail { char c; int i __attribute__((aligned(8))); };
+// CIR-DAG: !rec_AlignedTail = !cir.struct<"AlignedTail" padded {!s8i, pad !cir.array<!u8i x 7>, !s32i, pad !cir.array<!u8i x 4>}>
+// LLVM-DAG: %struct.AlignedTail = type { i8, [7 x i8], i32, [4 x i8] }
+
+// Name every record so that its CIR type reaches the output.
+void useTypes(struct ContainsEmpty *a, struct ContainsEmptyAndInt *b,
+ struct EmptyArr *c, struct MultiDimEmpty *d,
+ struct ZeroLenArr *e, struct Fam *f, struct UnnamedBitOnly *g,
+ struct UnnamedBitThenField *h, struct MsOnlyUnnamed *i,
+ struct MsNamedThenUnnamed *j, struct MsUnnamedThenNamed *k,
+ struct MsMixed *l, struct MsEmptyFirst *m,
+ struct MsEmptyMiddle *n, struct MsZeroWidthSplit *o,
+ struct MsZeroWidthSplit2 *p, union UnnamedBitUnion *q,
+ union ContainsEmptyUnion *r) {}
+
+struct AlignedTail gAlignedTail;
+
+int getAlignedTailI(void) { return gAlignedTail.i; }
+
+// CIR: cir.func{{.*}} @getAlignedTailI()
+// CIR: %[[G:.*]] = cir.get_global @gAlignedTail : !cir.ptr<!rec_AlignedTail>
+// CIR: %{{.*}} = cir.get_member %[[G]][2] {name = "i"} : !cir.ptr<!rec_AlignedTail> -> !cir.ptr<!s32i>
+// LLVM: define dso_local i32 @getAlignedTailI()
+// LLVM: load i32, ptr getelementptr inbounds nuw (i8, ptr @gAlignedTail, i64 8), align 8
diff --git a/clang/test/CIR/CodeGen/record-member-kinds.cpp b/clang/test/CIR/CodeGen/record-member-kinds.cpp
new file mode 100644
index 0000000000000..297428b7bd36d
--- /dev/null
+++ b/clang/test/CIR/CodeGen/record-member-kinds.cpp
@@ -0,0 +1,141 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -emit-cir %s -o - | FileCheck %s --check-prefix=CIR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -emit-llvm %s -o - | FileCheck %s --check-prefix=LLVM
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -emit-llvm %s -o - | FileCheck %s --check-prefix=LLVM
+
+struct Empty {};
+// CIR-DAG: !rec_Empty = !cir.struct<"Empty" padded {pad !u8i}>
+// LLVM-DAG: %struct.Empty = type { i8 }
+
+// A C++ empty member is not empty for the ABI without [[no_unique_address]].
+struct HoldsEmpty { Empty e; int i; };
+// CIR-DAG: !rec_HoldsEmpty = !cir.struct<"HoldsEmpty" {!rec_Empty, !s32i}>
+
+// A [[no_unique_address]] empty field is elided from a struct's layout, so it
+// never becomes a member. A union keeps its variants, so that is where the
+// mark shows.
+struct NuaEmpty { [[no_unique_address]] Empty e; int i; };
+// CIR-DAG: !rec_NuaEmpty = !cir.struct<"NuaEmpty" {!s32i}>
+
+union NuaEmptyUnion { [[no_unique_address]] Empty e; int i; };
+// CIR-DAG: !rec_NuaEmptyUnion = !cir.union<"NuaEmptyUnion" {empty !rec_Empty, !s32i}>
+
+// A polymorphic class is never empty for the ABI: its vtable pointer is neither
+// a base nor a field.
+struct Poly { virtual ~Poly(); };
+union NuaPolyUnion { [[no_unique_address]] Poly p; int i; };
+// CIR-DAG: !rec_NuaPolyUnion = !cir.union<"NuaPolyUnion" {!rec_Poly, !s32i}>
+
+// Emptiness recurses through base classes, in both directions.
+struct DerivesEmpty : Empty {};
+union NuaDerivedUnion { [[no_unique_address]] DerivesEmpty d; int i; };
+// CIR-DAG: !rec_NuaDerivedUnion = !cir.union<"NuaDerivedUnion" {empty !rec_DerivesEmpty, !s32i}>
+
+struct Pod2 { char c; int i; };
+struct DerivesNonEmpty : Pod2 {};
+union NuaDerivesNonEmptyUnion { [[no_unique_address]] DerivesNonEmpty d; int i; };
+// CIR-DAG: !rec_NuaDerivesNonEmptyUnion = !cir.union<"NuaDerivesNonEmptyUnion" {!rec_DerivesNonEmpty, !s32i}>
+
+// A base holding only unnamed bit-fields is laid out but carries no ABI data,
+// which CXXRecordDecl::isEmpty() does not report.
+struct BitFieldBase { int : 3; };
+// CIR-DAG: !rec_BitFieldBase = !cir.struct<"BitFieldBase" {empty !u8i}>
+struct DerivesBitFieldBase : BitFieldBase { int i; };
+// CIR-DAG: !rec_DerivesBitFieldBase = !cir.struct<"DerivesBitFieldBase" {empty !rec_BitFieldBase, !s32i}>
+
+// A virtual base is marked the same way a non-virtual one is.
+struct HasBitFieldVBase : virtual BitFieldBase { int i; };
+// CIR-DAG: !rec_HasBitFieldVBase = !cir.struct<"HasBitFieldVBase" packed padded {!cir.vptr, !s32i, empty !rec_BitFieldBase, pad !cir.array<!u8i x 3>}>
+
+struct ZeroLenEmptyArr { Empty e[0]; };
+// CIR-DAG: !rec_ZeroLenEmptyArr = !cir.struct<"ZeroLenEmptyArr" {empty !cir.array<!rec_Empty x 0>}>
+
+// A C++ record field is data without the attribute, array or not.
+struct EmptyArr2 { Empty e[2]; };
+// CIR-DAG: !rec_EmptyArr2 = !cir.struct<"EmptyArr2" {!cir.array<!rec_Empty x 2>}>
+
+// The [[no_unique_address]] exception covers a record, not an array of them,
+// so this field stays in the layout and holds data.
+struct NuaEmptyArr { [[no_unique_address]] Empty e[2]; int i; };
+// CIR-DAG: !rec_NuaEmptyArr = !cir.struct<"NuaEmptyArr" {!cir.array<!rec_Empty x 2>, !s32i}>
+
+struct AlignasTail { char c; alignas(8) int i; };
+// CIR-DAG: !rec_AlignasTail = !cir.struct<"AlignasTail" padded {!s8i, pad !cir.array<!u8i x 7>, !s32i, pad !cir.array<!u8i x 4>}>
+// LLVM-DAG: %struct.AlignasTail = type { i8, [7 x i8], i32, [4 x i8] }
+
+// An unnamed bit-field unit is declared storage that holds no ABI data.
+struct OnlyUnnamedBit { int : 24; };
+// CIR-DAG: !rec_OnlyUnnamedBit = !cir.struct<"OnlyUnnamedBit" {empty !cir.array<!u8i x 3>}>
+
+// A unit with a named occupant holds data, whichever order the occupants come
+// in, and however the storage is spelled.
+struct NamedClipped { int i; int j : 24; };
+// CIR-DAG: !rec_NamedClipped = !cir.struct<"NamedClipped" {!s32i, !u32i}>
+
+struct NamedFirst { int a : 8; int : 16; };
+// CIR-DAG: !rec_NamedFirst = !cir.struct<"NamedFirst" {!u32i}>
+
+struct UnnamedFirst { int : 16; int a : 8; };
+// CIR-DAG: !rec_UnnamedFirst = !cir.struct<"UnnamedFirst" {!u32i}>
+
+// A zero-length bit-field separates one span into two units, and a record can
+// carry a data unit and an empty unit at once, in either order.
+struct SpanMixed { int a : 3; int : 0; int : 3; };
+// CIR-DAG: !rec_SpanMixed = !cir.struct<"SpanMixed" padded {!u8i, pad !cir.array<!u8i x 3>, empty !u8i, pad !cir.array<!u8i x 3>}>
+
+struct SpanEmptyFirst { int : 3; int : 0; int b : 3; };
+// CIR-DAG: !rec_SpanEmptyFirst = !cir.struct<"SpanEmptyFirst" padded {empty !u8i, pad !cir.array<!u8i x 3>, !u8i, pad !cir.array<!u8i x 3>}>
+
+union UnnamedBitUnion { int : 8; };
+// CIR-DAG: !rec_UnnamedBitUnion = !cir.union<"UnnamedBitUnion" {empty !u8i}>
+
+union NoMemberUnion {};
+// CIR-DAG: !rec_NoMemberUnion = !cir.union<"NoMemberUnion" {}, padding = {!u8i}>
+
+// Natural alignment already places i, so no member is marked.
+struct Pod { char c; int i; };
+// CIR-DAG: !rec_Pod = !cir.struct<"Pod" {!s8i, !s32i}>
+
+struct NearlyEmptyVBase { virtual ~NearlyEmptyVBase(); };
+// CIR-DAG: !rec_NearlyEmptyVBase = !cir.struct<"NearlyEmptyVBase" {!cir.vptr}>
+
+struct HasNearlyEmptyVBase : virtual NearlyEmptyVBase { int i; };
+// CIR-DAG: !rec_HasNearlyEmptyVBase = !cir.struct<"HasNearlyEmptyVBase" packed padded {!rec_NearlyEmptyVBase, !s32i, pad !cir.array<!u8i x 4>}>
+
+// Both marks appear on one record: the byte array is storage the source
+// declared for its unnamed bit-field, while the byte after it is inserted by
+// the compiler. The storage keeps its mark in the base subobject type.
+struct Clipped { Clipped(const Clipped &); int i; int : 24; };
+// CIR-DAG: !rec_Clipped = !cir.struct<"Clipped" packed padded {!s32i, empty !cir.array<!u8i x 3>, pad !u8i}>
+// CIR-DAG: !rec_Clipped2Ebase = !cir.struct<"Clipped.base" packed {!s32i, empty !cir.array<!u8i x 3>}>
+
+struct DerivedClipped : Clipped { char c; };
+// CIR-DAG: !rec_DerivedClipped = !cir.struct<"DerivedClipped" {!rec_Clipped2Ebase, !s8i}>
+// LLVM-DAG: %struct.Clipped.base = type <{ i32, [3 x i8] }>
+// LLVM-DAG: %struct.DerivedClipped = type { %struct.Clipped.base, i8 }
+
+// Name every record so that its CIR type reaches the output.
+void useTypes(HoldsEmpty *, NuaEmpty *, NuaEmptyUnion *, NuaPolyUnion *,
+ NuaDerivedUnion *, NuaDerivesNonEmptyUnion *, BitFieldBase *,
+ DerivesBitFieldBase *, HasBitFieldVBase *, ZeroLenEmptyArr *,
+ EmptyArr2 *, NuaEmptyArr *, OnlyUnnamedBit *, NamedClipped *,
+ NamedFirst *, UnnamedFirst *, SpanMixed *, SpanEmptyFirst *,
+ UnnamedBitUnion *, NoMemberUnion *, Pod *, NearlyEmptyVBase *,
+ HasNearlyEmptyVBase *, Clipped *) {}
+
+Empty gEmpty;
+AlignasTail gAlignasTail;
+
+int getAlignasTailI() { return gAlignasTail.i; }
+
+// CIR: cir.func{{.*}} @_Z15getAlignasTailIv()
+// CIR: %[[G:.*]] = cir.get_global @gAlignasTail : !cir.ptr<!rec_AlignasTail>
+// CIR: %{{.*}} = cir.get_member %[[G]][2] {name = "i"} : !cir.ptr<!rec_AlignasTail> -> !cir.ptr<!s32i>
+// LLVM: define dso_local noundef i32 @_Z15getAlignasTailIv()
+// LLVM: load i32, ptr getelementptr inbounds nuw (i8, ptr @gAlignasTail, i64 8), align 8
+
+int getDerivedC(DerivedClipped &d) { return d.c; }
+
+// CIR: cir.func{{.*}} @_Z11getDerivedCR14DerivedClipped(
+// CIR: %{{.*}} = cir.get_member %{{.+}}[1] {name = "c"} : !cir.ptr<!rec_DerivedClipped> -> !cir.ptr<!s8i>
+// LLVM: define dso_local noundef i32 @_Z11getDerivedCR14DerivedClipped(ptr noundef nonnull align 4 dereferenceable(8) %{{.+}})
+// LLVM: getelementptr inbounds nuw %struct.DerivedClipped, ptr %{{.+}}, i32 0, i32 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 }
>From 7a2437c2ac35fe636891988af68accaa29df4163 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Thu, 13 Aug 2026 12:54:45 -0700
Subject: [PATCH 3/4] [CIR] Address reviewer feedback
The comments on isEmptyFieldForABI and isEmptyRecordForABI were too verbose. Adapt the comments on classic's isEmptyField and isEmptyRecord.
Add flexible array member tests.
Assisted-by: Cursor / claude-opus-5
---
clang/lib/CIR/CodeGen/TargetInfo.h | 27 ++++++--------------
clang/test/CIR/CodeGen/record-member-kinds.c | 10 +++++++-
2 files changed, 17 insertions(+), 20 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/TargetInfo.h b/clang/lib/CIR/CodeGen/TargetInfo.h
index d7c4d9fc8abac..84680f82f4e4b 100644
--- a/clang/lib/CIR/CodeGen/TargetInfo.h
+++ b/clang/lib/CIR/CodeGen/TargetInfo.h
@@ -37,27 +37,16 @@ bool isEmptyFieldForLayout(const ASTContext &context, const FieldDecl *fd);
/// if the [[no_unique_address]] attribute would have made them empty.
bool isEmptyRecordForLayout(const ASTContext &context, QualType t);
-/// isEmptyFieldForABI - Return true if the field is "empty" for argument
-/// passing. An unnamed bit-field of any width qualifies, as does an empty
-/// record, though a C++ one only under [[no_unique_address]] and never through
-/// an array of them. Neither this nor isEmptyFieldForLayout subsumes the
-/// other. An unnamed bit-field of any width is empty here but only at width
-/// zero there, because a narrower one occupies no ABI class even though it
-/// takes up layout space. Conversely a C++ record whose own members are empty
-/// C++ records is empty for layout but not here.
+/// isEmptyFieldForABI - Return true if the field is "empty", that is, it is an
+/// unnamed bit-field or an (array of) empty record(s). C++ record fields are
+/// never empty unless marked [[no_unique_address]], and that exception applies
+/// only to records, not arrays of records.
bool isEmptyFieldForABI(const ASTContext &context, const FieldDecl *fd);
-/// isEmptyRecordForABI - Return true if a record contains only empty base
-/// classes and fields, and so contributes no data to argument passing. Unlike
-/// clang's isEmptyRecord, a polymorphic class is never empty here: its vtable
-/// pointer is neither a base nor a field, and this answer is read without the
-/// precondition that a caller has already rejected a non-trivially-copyable
-/// type. Clang's isEmptyRecord takes AllowArrays and AsIfNoUniqueAddr
-/// parameters and targets choose different values (RISC-V passes
-/// AsIfNoUniqueAddr=true, ARM passes AllowArrays=false for return types).
-/// This predicate fixes them at true and false, which is what x86-64 asks for.
-/// A consumer reading the member kinds cannot recover the other answers: a
-/// field that a different setting would call empty is already marked as data.
+/// isEmptyRecordForABI - Return true if a structure contains only empty base
+/// classes and fields. Note that a structure with a flexible array member is
+/// not considered empty, and neither is a polymorphic class, whose vtable
+/// pointer is neither a base nor a field.
bool isEmptyRecordForABI(const ASTContext &context, QualType t);
class CIRGenFunction;
diff --git a/clang/test/CIR/CodeGen/record-member-kinds.c b/clang/test/CIR/CodeGen/record-member-kinds.c
index d45592a5ca93b..3c6140d60a7fa 100644
--- a/clang/test/CIR/CodeGen/record-member-kinds.c
+++ b/clang/test/CIR/CodeGen/record-member-kinds.c
@@ -27,6 +27,13 @@ struct ZeroLenArr { int a[0]; };
struct Fam { int n; int a[]; };
// CIR-DAG: !rec_Fam = !cir.struct<"Fam" {data !s32i, data !cir.array<!s32i x 0>}>
+// A trailing array of empty records is empty only when its length is constant.
+struct FamOfEmpty { struct E e; struct E a[]; };
+// CIR-DAG: !rec_FamOfEmpty = !cir.struct<"FamOfEmpty" {empty !rec_E, data !cir.array<!rec_E x 0>}>
+
+struct ZeroLenOfEmpty { struct E e; struct E a[0]; };
+// CIR-DAG: !rec_ZeroLenOfEmpty = !cir.struct<"ZeroLenOfEmpty" {empty !rec_E, empty !cir.array<!rec_E x 0>}>
+
struct UnnamedBitOnly { int : 8; };
// CIR-DAG: !rec_UnnamedBitOnly = !cir.struct<"UnnamedBitOnly" {empty !u8i}>
@@ -78,7 +85,8 @@ struct AlignedTail { char c; int i __attribute__((aligned(8))); };
// Name every record so that its CIR type reaches the output.
void useTypes(struct ContainsEmpty *a, struct ContainsEmptyAndInt *b,
struct EmptyArr *c, struct MultiDimEmpty *d,
- struct ZeroLenArr *e, struct Fam *f, struct UnnamedBitOnly *g,
+ struct ZeroLenArr *e, struct Fam *f, struct FamOfEmpty *f2,
+ struct ZeroLenOfEmpty *f3, struct UnnamedBitOnly *g,
struct UnnamedBitThenField *h, struct MsOnlyUnnamed *i,
struct MsNamedThenUnnamed *j, struct MsUnnamedThenNamed *k,
struct MsMixed *l, struct MsEmptyFirst *m,
>From c6107fbbdc7490bbd2157faeb781471cd92e2e3c Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Fri, 14 Aug 2026 14:05:17 -0700
Subject: [PATCH 4/4] [CIR] Address more reviewer feedback
Remove droppedFieldHoldingData. The differential asserts allow for an error
having already been reported instead, which covers any NYI in record lowering
rather than just the one.
Mark a bit-field access unit by promoting its storage member when a named
occupant is emitted, rather than pre-scanning the unit first. Both bit-field
paths now do it the same way, and the unit's fields are walked once.
Rename runStorageIdx to storageIdx.
Assisted-by: Cursor / claude-opus-5
---
.../CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp | 65 ++++++++-----------
1 file changed, 27 insertions(+), 38 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
index 2660af316e273..8e3d7b5f254c4 100644
--- a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
@@ -269,11 +269,6 @@ struct CIRRecordLowering final {
unsigned packed : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned padded : 1;
- /// Whether a field was dropped whose data no member can represent. Reported
- /// as NYI. Until that gap is closed the completed type reads as ABI-empty
- /// when it is not, so the differential asserts below cannot run on it.
- LLVM_PREFERRED_TYPE(bool)
- unsigned droppedFieldHoldingData : 1;
private:
// Output fields, consumed by CIRGenTypes::computeRecordLayout. Private so
@@ -296,7 +291,7 @@ CIRRecordLowering::CIRRecordLowering(CIRGenTypes &cirGenTypes,
cirGenTypes.getASTContext().getASTRecordLayout(recordDecl)},
dataLayout{cirGenTypes.getCGModule().getModule()},
zeroInitializable{true}, zeroInitializableAsBase{true}, packed{packed},
- padded{false}, droppedFieldHoldingData{false} {}
+ padded{false} {}
void CIRRecordLowering::setBitFieldInfo(const FieldDecl *fd,
CharUnits startOffset,
@@ -404,7 +399,7 @@ CIRRecordLowering::accumulateBitFields(RecordDecl::field_iterator field,
uint64_t startBitOffset, tail = 0;
// Where the current run's storage member sits in members, so that a named
// occupant joining the run can promote it to data.
- size_t runStorageIdx = 0;
+ size_t storageIdx = 0;
for (; field != fieldEnd && field->isBitField(); ++field) {
// Zero-width bitfields end runs.
if (field->isZeroLengthBitField()) {
@@ -424,14 +419,14 @@ CIRRecordLowering::accumulateBitFields(RecordDecl::field_iterator field,
// the bitfields it contains get laid out. The run is only known one
// field at a time here, so the unit starts out holding no data and is
// promoted below when a named occupant lands in it.
- runStorageIdx = members.size();
+ storageIdx = members.size();
members.push_back(makeStorageInfo(bitsToCharUnits(startBitOffset), type,
cir::RecordMemberKind::Empty));
}
- assert(members[runStorageIdx].offset == bitsToCharUnits(startBitOffset) &&
- "runStorageIdx must name the current run's storage");
+ assert(members[storageIdx].offset == bitsToCharUnits(startBitOffset) &&
+ "storageIdx must name the current run's storage");
if (!field->isUnnamedBitField())
- members[runStorageIdx].memberKind = cir::RecordMemberKind::Data;
+ members[storageIdx].memberKind = 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),
@@ -608,20 +603,20 @@ CIRRecordLowering::accumulateBitFields(RecordDecl::field_iterator field,
assert(getSize(type) == accessSize &&
"Unclipped access must be clipped");
}
- // An unnamed bit-field of any width occupies no ABI class, so a unit
- // made up of nothing but those is declared storage holding no data.
- const bool hasNamedOccupant = llvm::any_of(
- llvm::make_range(begin, bestEnd),
- [](const FieldDecl *fd) { return !fd->isUnnamedBitField(); });
- members.push_back(makeStorageInfo(beginOffset, type,
- hasNamedOccupant
- ? cir::RecordMemberKind::Data
- : cir::RecordMemberKind::Empty));
- for (; begin != bestEnd; ++begin)
+ // An unnamed bit-field of any width occupies no ABI class, so the unit
+ // starts out holding no data and is promoted below when a named
+ // occupant lands in it.
+ const size_t storageIdx = members.size();
+ members.push_back(
+ makeStorageInfo(beginOffset, type, cir::RecordMemberKind::Empty));
+ for (; begin != bestEnd; ++begin) {
+ if (!begin->isUnnamedBitField())
+ members[storageIdx].memberKind = cir::RecordMemberKind::Data;
if (!begin->isZeroLengthBitField())
members.push_back(MemberInfo(beginOffset,
MemberInfo::InfoKind::Field, nullptr,
cir::RecordMemberKind::Data, *begin));
+ }
}
// Reset to start a new span.
field = bestEnd;
@@ -666,14 +661,11 @@ void CIRRecordLowering::accumulateFields(bool nonVirtualBaseType) {
// gap rather than claim an emptiness the record does not have. The base
// subobject lowering sees the same field, so only the complete object
// reports it.
- if (!isEmptyFieldForABI(astContext, *field)) {
- if (!nonVirtualBaseType)
- cirGenTypes.getCGModule().errorNYI(
- field->getSourceRange(),
- "[[no_unique_address]] field that is empty for layout but holds "
- "data for the ABI");
- droppedFieldHoldingData = true;
- }
+ if (!nonVirtualBaseType && !isEmptyFieldForABI(astContext, *field))
+ cirGenTypes.getCGModule().errorNYI(
+ field->getSourceRange(),
+ "[[no_unique_address]] field that is empty for layout but holds "
+ "data for the ABI");
++field;
} else {
// Use base subobject layout for potentially-overlapping fields,
@@ -784,13 +776,10 @@ convertRecordArgPassingKind(RecordArgPassingKind kind) {
}
/// Whether the member kinds on \p recordTy answer the record's ABI emptiness
-/// the same way the AST predicate does. A lowering that dropped a field
-/// holding data has no member left to carry that data, so it is exempt.
+/// the same way the AST predicate does.
[[maybe_unused]] static bool
marksMatchABIEmptiness(const ASTContext &astContext, const RecordDecl *rd,
- cir::RecordType recordTy, bool droppedFieldHoldingData) {
- if (droppedFieldHoldingData)
- return true;
+ cir::RecordType recordTy) {
return recordTy.isEmptyForABI() ==
isEmptyRecordForABI(astContext, astContext.getCanonicalTagType(rd));
}
@@ -840,8 +829,8 @@ CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
// Emptiness is a property of the decl, so the base subobject must answer
// the same way the complete object does. The two are not comparable
// mark by mark: they see different sizes and so different tail padding.
- assert(marksMatchABIEmptiness(astContext, rd, baseTy,
- baseLowering.droppedFieldHoldingData) &&
+ assert((marksMatchABIEmptiness(astContext, rd, baseTy) ||
+ cgm.getDiags().hasErrorOccurred()) &&
"base subobject member kinds must reproduce its ABI emptiness");
}
}
@@ -857,8 +846,8 @@ CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
// answer against the AST predicate on every record CIRGen lays out. This
// does not check the individual marks, only what they add up to. The marks
// themselves are pinned by clang/test/CIR/CodeGen/record-member-kinds.*.
- assert(marksMatchABIEmptiness(astContext, rd, *ty,
- lowering.droppedFieldHoldingData) &&
+ assert((marksMatchABIEmptiness(astContext, rd, *ty) ||
+ cgm.getDiags().hasErrorOccurred()) &&
"member kinds must reproduce the ABI emptiness of the record");
// Queue ABI metadata for the module-level cir.record_layouts attribute.
More information about the cfe-commits
mailing list