[llvm-branch-commits] [clang] [CIR] Mark record members as data, pad, or empty in CIRGen (PR #215175)
Adam Smith via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sun Aug 9 21:07:50 PDT 2026
https://github.com/adams381 created https://github.com/llvm/llvm-project/pull/215175
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`.
Depends on [#215174](https://github.com/llvm/llvm-project/pull/215174).
Assisted-by: Cursor / claude-opus-5
>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] [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 }
More information about the llvm-branch-commits
mailing list