[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