[llvm-branch-commits] [clang] [CIR] Derive record padding from the member marks (PR #215176)

Adam Smith via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Sun Aug 9 21:16:23 PDT 2026


https://github.com/adams381 created https://github.com/llvm/llvm-project/pull/215176

Now that every record member carries a kind, the record-level `padded` bool is redundant: a record is padded exactly when some member is marked pad.  Drop the parameter and answer `getPadded()` from the marks.

That also fixes `computeStructDataSize`, which had read the bool as "the last member is tail padding" and so dropped a real member when padding sat between two data members.  It now drops the trailing run of pad members instead.

Depends on [#215175](https://github.com/llvm/llvm-project/pull/215175).

Assisted-by: Cursor / claude-opus-5


>From d2e6940c6feb082090307e0d24586d453f47262f Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Sun, 9 Aug 2026 19:27:31 -0700
Subject: [PATCH] [CIR] Derive record padding from the member marks

Now that every record member carries a kind, the record-level `padded`
bool is redundant: a record is padded exactly when some member is marked
pad.  Drop the parameter and answer `getPadded()` from the marks.

That also fixes `computeStructDataSize`, which had read the bool as "the
last member is tail padding" and so dropped a real member when padding
sat between two data members.  It now drops the trailing run of pad
members instead.

Assisted-by: Cursor / claude-opus-5
---
 .../include/clang/CIR/Dialect/IR/CIRAttrs.td  |   4 +-
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |   8 +-
 clang/include/clang/CIR/Dialect/IR/CIRTypes.h |   4 +-
 .../include/clang/CIR/Dialect/IR/CIRTypes.td  |  24 ++--
 .../clang/CIR/Dialect/IR/CIRTypesDetails.h    |  31 ++--
 clang/lib/CIR/CodeGen/CIRGenAsm.cpp           |   3 +-
 clang/lib/CIR/CodeGen/CIRGenBuilder.cpp       |  27 +---
 clang/lib/CIR/CodeGen/CIRGenBuilder.h         |  39 ++---
 clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp |   6 +-
 clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp    |   9 +-
 clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp  |   2 -
 clang/lib/CIR/CodeGen/CIRGenModule.cpp        |   3 +-
 clang/lib/CIR/CodeGen/CIRGenRecordLayout.h    |   4 +-
 .../CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp |  18 +--
 clang/lib/CIR/CodeGen/CIRGenTypes.cpp         |   3 +-
 clang/lib/CIR/CodeGen/CIRGenVTables.cpp       |   2 +-
 clang/lib/CIR/Dialect/IR/CIRTypes.cpp         |  92 ++++++------
 .../CIR/Dialect/Transforms/CXXABILowering.cpp |   8 +-
 .../Transforms/CallConvLoweringPass.cpp       |   2 +-
 .../Dialect/Transforms/LoweringPrepare.cpp    |   6 +-
 .../TargetLowering/LowerItaniumCXXABI.cpp     |   3 +-
 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  |   2 +-
 .../test/CIR/CodeGen/paren-list-agg-init.cpp  |   8 +-
 .../CodeGen/pointer-to-empty-data-member.cpp  |   6 +-
 clang/test/CIR/CodeGen/record-member-kinds.c  |   2 +-
 .../test/CIR/CodeGen/record-member-kinds.cpp  |  14 +-
 .../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 +--
 clang/test/CIR/IR/array-ctor.cir              |   2 +-
 clang/test/CIR/IR/array-dtor.cir              |   2 +-
 clang/test/CIR/IR/construct-catch-param.cir   |   2 +-
 clang/test/CIR/IR/func-attrs.cir              |   2 +-
 clang/test/CIR/IR/global-init.cir             |   6 +-
 clang/test/CIR/IR/invalid-array-structor.cir  |  32 ++---
 .../CIR/IR/invalid-construct-catch-param.cir  |  14 +-
 clang/test/CIR/IR/invalid-delete-array.cir    |   2 +-
 clang/test/CIR/IR/invalid-loop-cleanup.cir    |   8 +-
 clang/test/CIR/IR/loop-cleanup.cir            |   2 +-
 clang/test/CIR/IR/struct.cir                  |  19 +--
 .../CIR/Lowering/copy-skip-tail-padding.cir   | 136 ++++++++++++++++++
 .../abi-lowering/x86_64-aggregate-nyi.cir     |  12 +-
 .../CIR/Transforms/cxx-abi-lowering-attrs.cir |   2 +-
 ...h-abi-lowering-construct-catch-invalid.cir |   6 +-
 .../eh-abi-lowering-construct-catch.cir       |   2 +-
 .../Transforms/flatten-cleanup-scope-eh.cir   |   2 +-
 .../CIR/Transforms/flatten-loop-cleanup.cir   |   2 +-
 clang/test/CIR/Transforms/mem2reg.cir         |  27 ++++
 clang/unittests/CIR/PointerLikeTest.cpp       |   6 +-
 clang/unittests/CIR/RecordMemberKindTest.cpp  |  61 ++++++--
 .../unittests/CIR/RecordTypeMetadataTest.cpp  |   2 +-
 60 files changed, 430 insertions(+), 295 deletions(-)
 create mode 100644 clang/test/CIR/Lowering/copy-skip-tail-padding.cir

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index 9a726a3619ffc..e1c432fb2d382 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -1563,8 +1563,8 @@ 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,
-                                                   pad !cir.array<!u8i x 2>}>
+        !rec_S = !cir.struct<"S" packed {!u64i, !u16i,
+                                         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 513d765fff23d..9cc32b66fb245 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -3751,8 +3751,8 @@ 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,
-                               pad !cir.array<!u8i x 2>} #cir.record.decl.ast>
+    !record_type = !cir.struct<"S" packed {!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>
 
@@ -3842,8 +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,
-                                          pad !cir.array<!u8i x 2>}>
+    !cir.struct<"S" packed {!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/include/clang/CIR/Dialect/IR/CIRTypes.h b/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
index f93e9ad24b349..ca2376fff43db 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
@@ -133,7 +133,9 @@ class RecordType : public mlir::Type {
   std::string getKindAsStr() const;
   std::string getPrefixedName() const;
 
-  void complete(llvm::ArrayRef<mlir::Type> members, bool packed, bool padded,
+  /// \p padding is union-only.  A struct carries its padding as a member
+  /// marked pad.
+  void complete(llvm::ArrayRef<mlir::Type> members, bool packed,
                 mlir::Type padding = {},
                 llvm::ArrayRef<RecordMemberKind> memberKinds = {});
   uint64_t getElementOffset(const mlir::DataLayout &dataLayout,
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
index 365b96cd8e86f..412b5e953e6e3 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
@@ -700,7 +700,6 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [
         !rec_incomplete = !cir.struct<"incomplete" incomplete>
         !anonymous    = !cir.struct<{!u8i}>
         !rec_packed   = !cir.struct<"p1" packed {!u8i, !u8i}>
-        !rec_padded   = !cir.struct<"p2" padded {!u8i, !u8i}>
         !rec_pad      = !cir.struct<"p3" {!u8i, pad !cir.array<!u8i x 3>}>
         !rec_empty    = !cir.struct<"e" {empty !u8i}>
         !recursive    = !cir.struct<"Node" {!cir.ptr<!cir.struct<"Node">>}>
@@ -712,7 +711,6 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [
     OptionalParameter<"mlir::StringAttr">:$name,
     "bool":$incomplete,
     "bool":$packed,
-    "bool":$padded,
     OptionalArrayRefParameter<"cir::RecordMemberKind">:$member_kinds,
     "bool":$is_class
   );
@@ -730,11 +728,10 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [
       "llvm::ArrayRef<mlir::Type>":$members,
       "mlir::StringAttr":$name,
       "bool":$packed,
-      "bool":$padded,
       "bool":$is_class,
       CArg<"llvm::ArrayRef<cir::RecordMemberKind>", "{}">:$member_kinds
     ), [{
-      return $_get($_ctxt, members, name, /*incomplete=*/false, packed, padded,
+      return $_get($_ctxt, members, name, /*incomplete=*/false, packed,
                    cir::normalizeRecordMemberKinds(member_kinds), is_class);
     }]>,
 
@@ -744,7 +741,7 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [
       "bool":$is_class
     ), [{
       return $_get($_ctxt, /*members=*/llvm::ArrayRef<mlir::Type>{}, name,
-                   /*incomplete=*/true, /*packed=*/false, /*padded=*/false,
+                   /*incomplete=*/true, /*packed=*/false,
                    /*member_kinds=*/llvm::ArrayRef<cir::RecordMemberKind>{},
                    is_class);
     }]>,
@@ -753,13 +750,12 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [
     TypeBuilder<(ins
       "llvm::ArrayRef<mlir::Type>":$members,
       "bool":$packed,
-      "bool":$padded,
       "bool":$is_class,
       CArg<"llvm::ArrayRef<cir::RecordMemberKind>", "{}">:$member_kinds
     ), [{
       return $_get($_ctxt, members, mlir::StringAttr{}, /*incomplete=*/false,
-                   packed, padded,
-                   cir::normalizeRecordMemberKinds(member_kinds), is_class);
+                   packed, cir::normalizeRecordMemberKinds(member_kinds),
+                   is_class);
     }]>
   ];
 
@@ -783,9 +779,11 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [
     }
 
     void complete(llvm::ArrayRef<mlir::Type> members, bool packed,
-                  bool isPadded,
                   llvm::ArrayRef<cir::RecordMemberKind> memberKinds = {});
 
+    /// Whether any member is marked pad.
+    bool getPadded() const;
+
     uint64_t getElementOffset(const mlir::DataLayout &dataLayout,
                               unsigned idx) const;
 
@@ -809,7 +807,9 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [
 
     bool isSized() const { return isComplete(); }
 
-    /// Returns the data size (excluding tail padding) for this struct type.
+    /// Returns the data size for this struct type.  Tail padding is the
+    /// trailing run of pad members, so interior padding and a trailing empty
+    /// member stay inside the data size.
     unsigned computeStructDataSize(const mlir::DataLayout &dataLayout) const;
 
   private:
@@ -918,9 +918,7 @@ def CIR_UnionType : CIR_Type<"Union", "union", [
     bool isComplete() const { return !isIncomplete(); }
     bool isIncomplete() const;
 
-    /// Returns true when this union carries a tail-padding type.
-    /// Derived from getPadding(): a union is padded iff it has a non-null
-    /// padding type.
+    /// Whether this union carries a tail-padding type.
     bool getPadded() const;
 
     size_t getNumElements() const { return getMembers().size(); }
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypesDetails.h b/clang/include/clang/CIR/Dialect/IR/CIRTypesDetails.h
index 123fa059cfeac..6357a4270821c 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypesDetails.h
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypesDetails.h
@@ -32,68 +32,64 @@ struct StructTypeStorage : public mlir::TypeStorage {
     mlir::StringAttr name;
     bool incomplete;
     bool packed;
-    bool padded;
     llvm::ArrayRef<RecordMemberKind> member_kinds;
     bool is_class;
 
     KeyTy(llvm::ArrayRef<mlir::Type> members, mlir::StringAttr name,
-          bool incomplete, bool packed, bool padded,
+          bool incomplete, bool packed,
           llvm::ArrayRef<RecordMemberKind> member_kinds, bool is_class)
         : members(members), name(name), incomplete(incomplete), packed(packed),
-          padded(padded), member_kinds(member_kinds), is_class(is_class) {}
+          member_kinds(member_kinds), is_class(is_class) {}
   };
 
   llvm::ArrayRef<mlir::Type> members;
   mlir::StringAttr name;
   bool incomplete;
   bool packed;
-  bool padded;
   llvm::ArrayRef<RecordMemberKind> member_kinds;
   bool is_class;
 
   StructTypeStorage(llvm::ArrayRef<mlir::Type> members, mlir::StringAttr name,
-                    bool incomplete, bool packed, bool padded,
+                    bool incomplete, bool packed,
                     llvm::ArrayRef<RecordMemberKind> member_kinds,
                     bool is_class)
       : members(members), name(name), incomplete(incomplete), packed(packed),
-        padded(padded), member_kinds(member_kinds), is_class(is_class) {
+        member_kinds(member_kinds), is_class(is_class) {
     assert((name || !incomplete) && "Incomplete records must have a name");
     assert((member_kinds.empty() || member_kinds.size() == members.size()) &&
            "member kind list must cover every member");
   }
 
   KeyTy getAsKey() const {
-    return KeyTy(members, name, incomplete, packed, padded, member_kinds,
-                 is_class);
+    return KeyTy(members, name, incomplete, packed, member_kinds, is_class);
   }
 
   bool operator==(const KeyTy &key) const {
     if (name)
       return (name == key.name) && (is_class == key.is_class);
-    return std::tie(members, name, incomplete, packed, padded, member_kinds,
+    return std::tie(members, name, incomplete, packed, member_kinds,
                     is_class) == std::tie(key.members, key.name, key.incomplete,
-                                          key.packed, key.padded,
-                                          key.member_kinds, key.is_class);
+                                          key.packed, key.member_kinds,
+                                          key.is_class);
   }
 
   static llvm::hash_code hashKey(const KeyTy &key) {
     if (key.name)
       return llvm::hash_combine(key.name, key.is_class);
     return llvm::hash_combine(key.members, key.incomplete, key.packed,
-                              key.padded, key.member_kinds, key.is_class);
+                              key.member_kinds, key.is_class);
   }
 
   static StructTypeStorage *construct(mlir::TypeStorageAllocator &allocator,
                                       const KeyTy &key) {
     return new (allocator.allocate<StructTypeStorage>()) StructTypeStorage(
         allocator.copyInto(key.members), key.name, key.incomplete, key.packed,
-        key.padded, allocator.copyInto(key.member_kinds), key.is_class);
+        allocator.copyInto(key.member_kinds), key.is_class);
   }
 
   /// Mutates the members and attributes of an identified struct/class.
   llvm::LogicalResult mutate(mlir::TypeStorageAllocator &allocator,
                              llvm::ArrayRef<mlir::Type> members, bool packed,
-                             bool padded,
                              llvm::ArrayRef<RecordMemberKind> memberKinds) {
     if (!name)
       return llvm::failure();
@@ -102,9 +98,9 @@ struct StructTypeStorage : public mlir::TypeStorage {
     // including the marks: otherwise it silently keeps the marks it was given
     // the first time.
     if (!incomplete)
-      return mlir::success(
-          (this->members == members) && (this->packed == packed) &&
-          (this->padded == padded) && (this->member_kinds == memberKinds));
+      return mlir::success((this->members == members) &&
+                           (this->packed == packed) &&
+                           (this->member_kinds == memberKinds));
 
     // mutate is the one entrance verify() never sees, so check the length here
     // rather than leave it to an assert.
@@ -113,7 +109,6 @@ struct StructTypeStorage : public mlir::TypeStorage {
 
     this->members = allocator.copyInto(members);
     this->packed = packed;
-    this->padded = padded;
     this->member_kinds = allocator.copyInto(memberKinds);
     incomplete = false;
     return llvm::success();
diff --git a/clang/lib/CIR/CodeGen/CIRGenAsm.cpp b/clang/lib/CIR/CodeGen/CIRGenAsm.cpp
index 26f9f5935c2b4..79deede56e424 100644
--- a/clang/lib/CIR/CodeGen/CIRGenAsm.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenAsm.cpp
@@ -537,8 +537,7 @@ mlir::LogicalResult CIRGenFunction::emitAsmStmt(const AsmStmt &s) {
   if (resultRegTypes.size() == 1)
     resultType = resultRegTypes[0];
   else if (resultRegTypes.size() > 1)
-    resultType = builder.getAnonRecordTy(resultRegTypes, /*packed=*/false,
-                                         /*padded=*/false);
+    resultType = builder.getAnonRecordTy(resultRegTypes, /*packed=*/false);
 
   bool hasSideEffect = s.isVolatile() || s.getNumOutputs() == 0;
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp b/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
index d339c35fbffa0..eac10f47bbbae 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
@@ -195,33 +195,8 @@ uint64_t CIRGenBuilderTy::computeOffsetFromGlobalViewIndices(
   return offset;
 }
 
-cir::RecordType clang::CIRGen::CIRGenBuilderTy::getCompleteRecordType(
-    mlir::ArrayAttr fields, bool packed, bool padded, llvm::StringRef name) {
-  assert(!cir::MissingFeatures::astRecordDeclAttr());
-  llvm::SmallVector<mlir::Type> members;
-  members.reserve(fields.size());
-  llvm::transform(fields, std::back_inserter(members),
-                  [](mlir::Attribute attr) {
-                    return mlir::cast<mlir::TypedAttr>(attr).getType();
-                  });
-
-  if (name.empty())
-    return getAnonRecordTy(members, packed, padded);
-
-  return getCompleteNamedRecordType(members, packed, padded, name,
-                                    /*memberKinds=*/{});
-}
-
 mlir::Attribute clang::CIRGen::CIRGenBuilderTy::getConstRecordOrZeroAttr(
-    mlir::ArrayAttr arrayAttr, bool packed, bool padded, mlir::Type type) {
-  auto recordTy = mlir::cast_or_null<cir::RecordType>(type);
-
-  // Record type not specified: create anon record type from members.
-  if (!recordTy) {
-    recordTy = getCompleteRecordType(arrayAttr, packed, padded);
-  }
-
-  // Return zero or anonymous constant record.
+    mlir::ArrayAttr arrayAttr, cir::RecordType recordTy) {
   const bool isZero = llvm::all_of(
       arrayAttr, [&](mlir::Attribute a) { return isNullValue(a); });
   if (isZero)
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.h b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
index f414f67c76fbe..b241b4f029dc6 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuilder.h
+++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
@@ -79,25 +79,16 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
   }
 
   mlir::Attribute getConstRecordOrZeroAttr(mlir::ArrayAttr arrayAttr,
-                                           bool packed = false,
-                                           bool padded = false,
-                                           mlir::Type type = {});
-
-  cir::ConstRecordAttr getAnonConstRecord(mlir::ArrayAttr arrayAttr,
-                                          bool packed = false,
-                                          bool padded = false,
-                                          mlir::Type ty = {}) {
-    llvm::SmallVector<mlir::Type, 4> members;
+                                           cir::RecordType recordTy);
+
+  cir::ConstRecordAttr getAnonConstRecord(mlir::ArrayAttr arrayAttr) {
+    llvm::SmallVector<mlir::Type> members;
     for (auto &f : arrayAttr) {
       auto ta = mlir::cast<mlir::TypedAttr>(f);
       members.push_back(ta.getType());
     }
 
-    if (!ty)
-      ty = getAnonRecordTy(members, packed, padded);
-
-    auto sTy = mlir::cast<cir::RecordType>(ty);
-    return cir::ConstRecordAttr::get(sTy, arrayAttr);
+    return cir::ConstRecordAttr::get(getAnonRecordTy(members), arrayAttr);
   }
 
   cir::TypeInfoAttr getTypeInfo(mlir::ArrayAttr fieldsAttr) {
@@ -155,26 +146,20 @@ 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, llvm::ArrayRef<cir::RecordMemberKind> memberKinds) {
+      llvm::ArrayRef<mlir::Type> members, bool packed, 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).
+    // Always a struct at this call site, never a class or a union.
     auto type = cir::StructType::get(getContext(), members, nameAttr, packed,
-                                     padded, /*is_class=*/false, memberKinds);
+                                     /*is_class=*/false, memberKinds);
 
-    type.complete(members, packed, padded, memberKinds);
+    type.complete(members, packed, memberKinds);
 
     return type;
   }
 
-  cir::RecordType getCompleteRecordType(mlir::ArrayAttr fields,
-                                        bool packed = false,
-                                        bool padded = false,
-                                        llvm::StringRef name = "");
-
   /// Get an incomplete CIR record type. If we have a complete record
   /// declaration, we may create an incomplete type and then add the
   /// members, so \p rd here may be complete.
@@ -396,9 +381,9 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
 
   /// Get a CIR anonymous struct type.
   cir::StructType getAnonRecordTy(llvm::ArrayRef<mlir::Type> members,
-                                  bool packed = false, bool padded = false) {
+                                  bool packed = false) {
     assert(!cir::MissingFeatures::astRecordDeclAttr());
-    return cir::StructType::get(getContext(), members, packed, padded,
+    return cir::StructType::get(getContext(), members, packed,
                                 /*is_class=*/false);
   }
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp
index e2a1c2b94dec4..fd38b35bdb726 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp
@@ -76,8 +76,7 @@ static mlir::Value emitLogbBuiltin(CIRGenFunction &cgf, const CallExpr *e,
   mlir::Type srcTy = src0.getType();
   mlir::Type int32Ty = builder.getSInt32Ty();
 
-  cir::RecordType frExpResTy =
-      builder.getAnonRecordTy({srcTy, int32Ty}, false, false);
+  cir::RecordType frExpResTy = builder.getAnonRecordTy({srcTy, int32Ty});
 
   mlir::Value frExpResult = builder.emitIntrinsicCallOp(
       loc, "frexp", frExpResTy, mlir::ValueRange{src0});
@@ -174,8 +173,7 @@ CIRGenFunction::emitAMDGPUBuiltinExpr(unsigned builtinId,
     mlir::Value z = emitScalarExpr(expr->getArg(2));
 
     auto i1Ty = builder.getUIntNTy(1);
-    cir::RecordType resTy = builder.getAnonRecordTy(
-        {x.getType(), i1Ty}, /*packed=*/false, /*padded=*/false);
+    cir::RecordType resTy = builder.getAnonRecordTy({x.getType(), i1Ty});
 
     mlir::Value structResult =
         cir::LLVMIntrinsicCallOp::create(builder, getLoc(expr->getExprLoc()),
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp
index 9228367fdd44f..a4b5651878836 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp
@@ -209,7 +209,7 @@ emitEncodeKey(mlir::MLIRContext *context, CIRGenBuilderTy &builder,
   llvm::append_range(members,
                      llvm::SmallVector<mlir::Type>(vecOutputCount, resVector));
   cir::StructType resRecord = cir::StructType::get(
-      context, members, /*packed=*/false, /*padded=*/false, /*is_class=*/false);
+      context, members, /*packed=*/false, /*is_class=*/false);
 
   mlir::Value outputPtr =
       builder.createBitcast(outputOperand, cir::PointerType::get(resVector));
@@ -927,7 +927,7 @@ cir::GetGlobalOp CIRGenFunction::createGetCpuModel(mlir::Location loc) {
     // unsigned int __cpu_subtype;
     // unsigned int __cpu_features[1];
     mlir::Type tys[] = {u32, u32, u32, cir::ArrayType::get(u32, 1)};
-    mlir::Type modelTy = builder.getAnonRecordTy(tys, /*incomplete=*/false);
+    mlir::Type modelTy = builder.getAnonRecordTy(tys, /*packed=*/false);
     cpuModel =
         cgm.createGlobalOp(loc, "__cpu_model", modelTy, /*isConstant=*/false);
     cpuModel.setDsoLocal(true);
@@ -2494,7 +2494,7 @@ CIRGenFunction::emitX86BuiltinExpr(unsigned builtinID, const CallExpr *expr) {
                                                     builder.getUInt32Ty()};
     cir::StructType resRecord =
         cir::StructType::get(&getMLIRContext(), resultTypes, /*packed=*/false,
-                             /*padded=*/false, /*is_class=*/false);
+                             /*is_class=*/false);
 
     mlir::Value call =
         builder.emitIntrinsicCallOp(loc, intrinsicName, resRecord);
@@ -2563,8 +2563,7 @@ CIRGenFunction::emitX86BuiltinExpr(unsigned builtinID, const CallExpr *expr) {
 
     cir::StructType resRecord =
         cir::StructType::get(&getMLIRContext(), {resVector, resVector},
-                             /*packed=*/false, /*padded=*/false,
-                             /*is_class=*/false);
+                             /*packed=*/false, /*is_class=*/false);
 
     mlir::Value call = builder.emitIntrinsicCallOp(
         getLoc(expr->getExprLoc()), intrinsicName, resRecord,
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
index 45868fba962aa..28df97d7b774b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
@@ -400,8 +400,6 @@ mlir::Attribute buildRecordHelper(ConstantEmitter &emitter,
   }
 
   return builder.getConstRecordOrZeroAttr(builder.getArrayAttr(elements),
-                                          /*packed=*/recordTy.getPacked(),
-                                          /*padded=*/recordTy.getPadded(),
                                           recordTy);
 }
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index ae9cad0b7c30f..0b80c0a74e0ef 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -1098,8 +1098,7 @@ static mlir::Attribute getNewInitValue(CIRGenModule &cgm, cir::GlobalOp newGlob,
   if (auto oldRecord = mlir::dyn_cast<cir::ConstRecordAttr>(oldInit)) {
     mlir::ArrayAttr newMembers = getNewInitElements(oldRecord.getMembers());
     auto recordTy = mlir::cast<cir::RecordType>(oldRecord.getType());
-    return cgm.getBuilder().getConstRecordOrZeroAttr(
-        newMembers, recordTy.getPacked(), recordTy.getPadded(), recordTy);
+    return cgm.getBuilder().getConstRecordOrZeroAttr(newMembers, recordTy);
   }
 
   // This may be unreachable in practice, but keep it as errorNYI while CIR
diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h b/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h
index c4968f3024d99..992dfa95e40cf 100644
--- a/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h
+++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h
@@ -45,8 +45,8 @@ namespace clang::CIRGen {
 /// represented by one !u16i value, and the array provides padding to align the
 /// struct to a 4-byte alignment.
 ///
-///   !rec_S = !cir.record<struct "S" padded {!s8i, !s8i, !s8i, !u16i,
-///   pad !cir.array<!u8i x 3>}>
+///   !rec_S = !cir.struct<"S" packed {!s8i, !s8i, !s8i, !u16i,
+///                                   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 8258f1bf1aa33..8c670b9a3242f 100644
--- a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
@@ -224,7 +224,6 @@ struct CIRRecordLowering final {
     if (size.isZero())
       return;
     mlir::Type padTy = getByteArrayType(size);
-    padded = true;
     if (recordDecl->isUnion()) {
       assert(!unionPadding && "at most one union tail-padding type");
       unionPadding = padTy;
@@ -270,8 +269,6 @@ struct CIRRecordLowering final {
   unsigned zeroInitializableAsBase : 1;
   LLVM_PREFERRED_TYPE(bool)
   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.
@@ -299,7 +296,7 @@ CIRRecordLowering::CIRRecordLowering(CIRGenTypes &cirGenTypes,
           cirGenTypes.getASTContext().getASTRecordLayout(recordDecl)},
       dataLayout{cirGenTypes.getCGModule().getModule()},
       zeroInitializable{true}, zeroInitializableAsBase{true}, packed{packed},
-      padded{false}, droppedFieldHoldingData{false} {}
+      droppedFieldHoldingData{false} {}
 
 void CIRRecordLowering::setBitFieldInfo(const FieldDecl *fd,
                                         CharUnits startOffset,
@@ -767,7 +764,6 @@ void CIRRecordLowering::insertPadding() {
   }
   if (padding.empty())
     return;
-  padded = true;
   // Add the padding to the Members list and sort it.
   for (const std::pair<CharUnits, CharUnits> &paddingPair : padding)
     members.push_back(makeStorageInfo(paddingPair.first,
@@ -831,8 +827,8 @@ CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
       baseLowering.lower(/*nonVirtualBaseType=*/true);
       std::string baseIdentifier = getRecordTypeName(rd, ".base");
       baseTy = builder.getCompleteNamedRecordType(
-          baseLowering.getFieldTypes(), baseLowering.packed,
-          baseLowering.padded, baseIdentifier, baseLowering.getFieldKinds());
+          baseLowering.getFieldTypes(), baseLowering.packed, baseIdentifier,
+          baseLowering.getFieldKinds());
       // TODO(cir): add something like addRecordTypeName
 
       // BaseTy and Ty must agree on their packedness for getCIRFieldNo to work
@@ -856,8 +852,8 @@ CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
   // signifies that the type is no longer opaque and record layout is complete,
   // but we may need to recursively layout rd while laying D out as a base type.
   assert(!cir::MissingFeatures::astRecordDeclAttr());
-  ty->complete(lowering.getFieldTypes(), lowering.packed, lowering.padded,
-               lowering.unionPadding, lowering.getFieldKinds());
+  ty->complete(lowering.getFieldTypes(), lowering.packed, 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
@@ -1025,10 +1021,8 @@ void CIRRecordLowering::lowerUnion(bool nonVirtualBaseType) {
     clearFields();
     addField(storageType, storageKind);
     CharUnits padding = layoutSize - getSize(storageType);
-    if (!padding.isZero()) {
+    if (!padding.isZero())
       addField(getByteArrayType(padding), cir::RecordMemberKind::Pad);
-      padded = true;
-    }
   } else {
     // Else we just add padding normally.
     appendPaddingBytes(layoutSize - getSize(storageType));
diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
index e5af4eec7720f..8d8b258d815e5 100644
--- a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
@@ -660,8 +660,7 @@ mlir::Type CIRGenTypes::convertType(QualType type) {
           cir::ArrayType::get(cgm.sInt8Ty, (atomicSize - valueSize) / 8);
       mlir::Type elements[] = {resultType, paddingArray};
       resultType = cir::StructType::get(&getMLIRContext(), /*members=*/elements,
-                                        /*packed=*/false, /*padded=*/false,
-                                        /*is_class=*/false);
+                                        /*packed=*/false, /*is_class=*/false);
     }
 
     break;
diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
index a4742cb239f21..bd7d8da7c34a4 100644
--- a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
@@ -69,7 +69,7 @@ cir::RecordType CIRGenVTables::getVTableType(const VTableLayout &layout) {
 
   // FIXME(cir): should VTableLayout be encoded like we do for some
   // AST nodes?
-  return cgm.getBuilder().getAnonRecordTy(tys, /*incomplete=*/false);
+  return cgm.getBuilder().getAnonRecordTy(tys, /*packed=*/false);
 }
 
 /// At this point in the translation unit, does it appear that can we
diff --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
index 55fbba11ddb50..302943bfb3a9d 100644
--- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
@@ -224,16 +224,15 @@ parseRecordBody(mlir::AsmParser &parser, bool &incomplete,
 }
 
 /// Print a complete CIR record body:
-///   '<' ['class '] [name] ['packed '] ['padded '] body '>'
-/// where body is "incomplete" or "{[mark] members[, padding = {type}]}".
+///   '<' ['class '] [name] ['packed '] body '>'
+/// where body is "incomplete" or "{[mark] members}[, padding = {type}]".
 /// RecordTy must be a mutable MLIR type (StructType or UnionType).
 template <typename RecordTy>
-static void printRecordBody(mlir::AsmPrinter &printer, RecordTy self,
-                            mlir::StringAttr name, bool hasClassPrefix,
-                            bool isPacked, bool isPadded, bool isIncomplete,
-                            llvm::ArrayRef<mlir::Type> members,
-                            mlir::Type padding,
-                            llvm::ArrayRef<RecordMemberKind> memberKinds) {
+static void
+printRecordBody(mlir::AsmPrinter &printer, RecordTy self, mlir::StringAttr name,
+                bool hasClassPrefix, bool isPacked, bool isIncomplete,
+                llvm::ArrayRef<mlir::Type> members, mlir::Type padding,
+                llvm::ArrayRef<RecordMemberKind> memberKinds) {
   printer << '<';
   if (hasClassPrefix)
     printer << "class ";
@@ -251,8 +250,6 @@ static void printRecordBody(mlir::AsmPrinter &printer, RecordTy self,
     printer << ' ';
   if (isPacked)
     printer << "packed ";
-  if (isPadded)
-    printer << "padded ";
   if (isIncomplete) {
     printer << "incomplete";
   } else {
@@ -281,7 +278,6 @@ Type StructType::parse(mlir::AsmParser &parser) {
   const llvm::SMLoc loc = parser.getCurrentLocation();
   const mlir::Location eLoc = parser.getEncodedSourceLoc(loc);
   bool packed = false;
-  bool padded = false;
   mlir::MLIRContext *context = parser.getContext();
 
   if (parser.parseLess())
@@ -316,9 +312,6 @@ Type StructType::parse(mlir::AsmParser &parser) {
   if (parser.parseOptionalKeyword("packed").succeeded())
     packed = true;
 
-  if (parser.parseOptionalKeyword("padded").succeeded())
-    padded = true;
-
   bool incomplete = true;
   llvm::SmallVector<mlir::Type> members;
   llvm::SmallVector<RecordMemberKind> memberKinds;
@@ -334,18 +327,18 @@ Type StructType::parse(mlir::AsmParser &parser) {
   if (name && incomplete) {
     type = StructType::getChecked(eLoc, context, name, is_class);
   } else if (!name && !incomplete) {
-    type = StructType::getChecked(eLoc, context, membersRef, packed, padded,
-                                  is_class, kindsRef);
+    type = StructType::getChecked(eLoc, context, membersRef, packed, is_class,
+                                  kindsRef);
     if (!type)
       return {};
   } else if (!incomplete) {
     type = StructType::getChecked(eLoc, context, membersRef, name, packed,
-                                  padded, is_class, kindsRef);
+                                  is_class, kindsRef);
     if (!type)
       return {};
     if (auto structTy = mlir::dyn_cast<StructType>(type))
       if (structTy.isIncomplete())
-        structTy.complete(membersRef, packed, padded, kindsRef);
+        structTy.complete(membersRef, packed, kindsRef);
     assert(!cir::MissingFeatures::astRecordDeclAttr());
   } else {
     parser.emitError(loc, "anonymous records must be complete");
@@ -357,16 +350,14 @@ Type StructType::parse(mlir::AsmParser &parser) {
 
 void StructType::print(mlir::AsmPrinter &printer) const {
   printRecordBody(printer, *this, getName(), isClass(), getPacked(),
-                  getPadded(), isIncomplete(), getMembers(), /*padding=*/{},
+                  isIncomplete(), getMembers(), /*padding=*/{},
                   getMemberKinds());
 }
 
-mlir::LogicalResult
-StructType::verify(function_ref<mlir::InFlightDiagnostic()> emitError,
-                   llvm::ArrayRef<mlir::Type> members, mlir::StringAttr name,
-                   bool incomplete, bool packed, bool padded,
-                   llvm::ArrayRef<RecordMemberKind> member_kinds,
-                   bool is_class) {
+mlir::LogicalResult StructType::verify(
+    function_ref<mlir::InFlightDiagnostic()> emitError,
+    llvm::ArrayRef<mlir::Type> members, mlir::StringAttr name, bool incomplete,
+    bool packed, llvm::ArrayRef<RecordMemberKind> member_kinds, bool is_class) {
   if (name && name.getValue().empty())
     return emitError() << "identified records cannot have an empty name";
   return verifyRecordMemberKinds(emitError, members.size(), member_kinds);
@@ -381,12 +372,15 @@ mlir::StringAttr StructType::getName() const { return getImpl()->name; }
 bool StructType::isIncomplete() const { return getImpl()->incomplete; }
 bool StructType::getIncomplete() const { return getImpl()->incomplete; }
 bool StructType::getPacked() const { return getImpl()->packed; }
-bool StructType::getPadded() const { return getImpl()->padded; }
 llvm::ArrayRef<RecordMemberKind> StructType::getMemberKinds() const {
   return getImpl()->member_kinds;
 }
 bool StructType::getIsClass() const { return getImpl()->is_class; }
 
+bool StructType::getPadded() const {
+  return llvm::is_contained(getMemberKinds(), RecordMemberKind::Pad);
+}
+
 bool StructType::isABIConvertedRecord() const {
   return getName() && getName().getValue().starts_with(abi_conversion_prefix);
 }
@@ -405,11 +399,10 @@ void StructType::removeABIConversionNamePrefix() {
         recordName.getType());
 }
 
-void StructType::complete(ArrayRef<Type> members, bool packed, bool padded,
+void StructType::complete(ArrayRef<Type> members, bool packed,
                           ArrayRef<RecordMemberKind> memberKinds) {
   assert(!cir::MissingFeatures::astRecordDeclAttr());
-  if (mutate(members, packed, padded, normalizeRecordMemberKinds(memberKinds))
-          .failed())
+  if (mutate(members, packed, normalizeRecordMemberKinds(memberKinds)).failed())
     llvm_unreachable("failed to complete struct");
 }
 
@@ -522,8 +515,8 @@ Type UnionType::parse(mlir::AsmParser &parser) {
 
 void UnionType::print(mlir::AsmPrinter &printer) const {
   printRecordBody(printer, *this, getName(), /*hasClassPrefix=*/false,
-                  getPacked(), /*isPadded=*/false, isIncomplete(), getMembers(),
-                  getPadding(), getMemberKinds());
+                  getPacked(), isIncomplete(), getMembers(), getPadding(),
+                  getMemberKinds());
 }
 
 mlir::LogicalResult
@@ -660,14 +653,13 @@ std::string RecordType::getKindAsStr() const {
 std::string RecordType::getPrefixedName() const {
   return getKindAsStr() + "." + getName().getValue().str();
 }
-void RecordType::complete(ArrayRef<Type> members, bool packed, bool padded,
+void RecordType::complete(ArrayRef<Type> members, bool packed,
                           mlir::Type padding,
                           ArrayRef<RecordMemberKind> memberKinds) {
-  if (auto s = mlir::dyn_cast<StructType>(*this))
-    return s.complete(members, packed, padded, memberKinds);
-  // Unions derive padded from padding; assert the caller is consistent.
-  assert((!padded || padding) &&
-         "padded=true requires a non-null padding type");
+  if (auto s = mlir::dyn_cast<StructType>(*this)) {
+    assert(!padding && "only a union takes a separate padding slot");
+    return s.complete(members, packed, memberKinds);
+  }
   return mlir::cast<UnionType>(*this).complete(members, packed, padding,
                                                memberKinds);
 }
@@ -899,17 +891,21 @@ unsigned
 StructType::computeStructDataSize(const mlir::DataLayout &dataLayout) const {
   assert(isComplete() && "Cannot get layout of incomplete records");
 
-  // Compute the data size (excluding tail padding) for this record type. For
-  // padded records, the last member is the tail padding array added by
-  // CIRGenRecordLayoutBuilder::appendPaddingBytes, so we exclude it. For
-  // non-padded records, data size equals the full struct size without
-  // alignment.
-  auto members = getMembers();
-  unsigned numMembers =
-      getPadded() && members.size() > 1 ? members.size() - 1 : members.size();
+  // Tail padding is the trailing run of pad members.  A record with no mark
+  // list has nothing marked pad, so nothing drops.  An empty member stays
+  // inside the data size: it is storage the source declared, which a derived
+  // class may not reuse.
+  llvm::ArrayRef<mlir::Type> members = getMembers();
+  llvm::ArrayRef<RecordMemberKind> kinds = getMemberKinds();
+  assert((kinds.empty() || kinds.size() == members.size()) &&
+         "the two drop_back calls below must stay in step");
+  while (!kinds.empty() && kinds.back() == RecordMemberKind::Pad) {
+    kinds = kinds.drop_back();
+    members = members.drop_back();
+  }
+
   unsigned recordSize = 0;
-  for (unsigned i = 0; i < numMembers; ++i) {
-    mlir::Type ty = members[i];
+  for (mlir::Type ty : members) {
     const uint64_t tyAlign =
         (getPacked() ? 1 : dataLayout.getTypeABIAlignment(ty));
     recordSize = llvm::alignTo(recordSize, tyAlign);
@@ -1286,7 +1282,7 @@ static mlir::Type getMethodLayoutType(mlir::MLIRContext *ctx) {
   auto voidPtrTy = cir::PointerType::get(cir::VoidType::get(ctx));
   mlir::Type fields[2]{voidPtrTy, voidPtrTy};
   return cir::StructType::get(ctx, fields, /*packed=*/false,
-                              /*padded=*/false, /*is_class=*/false);
+                              /*is_class=*/false);
 }
 
 llvm::TypeSize
diff --git a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
index 110a391006b20..2f721b3ede3c8 100644
--- a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
@@ -865,8 +865,8 @@ class CIRABITypeConverter : public mlir::TypeConverter {
       }
       auto s = mlir::cast<cir::StructType>(type);
       return cir::StructType::get(type.getContext(), converted,
-                                  type.getPacked(), type.getPadded(),
-                                  s.getIsClass(), s.getMemberKinds());
+                                  type.getPacked(), s.getIsClass(),
+                                  s.getMemberKinds());
     }
 
     assert(!type.isIncomplete() || type.getMembers().empty());
@@ -910,8 +910,8 @@ class CIRABITypeConverter : public mlir::TypeConverter {
     if (auto u = mlir::dyn_cast<cir::UnionType>(type))
       if (mlir::Type pad = u.getPadding())
         loweredPadding = convertType(pad);
-    convertedType.complete(convertedMembers, type.getPacked(), type.getPadded(),
-                           loweredPadding, type.getMemberKinds());
+    convertedType.complete(convertedMembers, type.getPacked(), loweredPadding,
+                           type.getMemberKinds());
     addConvertedRecordType(convertedType);
     return convertedType;
   }
diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
index 193c2b6f4a9dc..993761014090c 100644
--- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
@@ -196,7 +196,7 @@ static mlir::Type abiTypeToCIR(const llvm::abi::Type *ty, MLIRContext *ctx) {
         }
         // Coercion types are plain register tuples, not the source record.
         return cir::StructType::get(ctx, fieldTypes, /*packed=*/false,
-                                    /*padded=*/false, /*is_class=*/false);
+                                    /*is_class=*/false);
       })
       .Default([](const llvm::abi::Type *) -> mlir::Type { return nullptr; });
 }
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index 12d544fecbcc9..8164a93d3633c 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -2431,9 +2431,9 @@ void LoweringPreparePass::buildCUDAModuleCtor() {
 
   // Create the fatbin wrapper struct:
   //    struct { int magic; int version; void *fatbin; void *unused; };
-  auto fatbinWrapperType = cir::StructType::get(
-      &getContext(), {intTy, intTy, voidPtrTy, voidPtrTy},
-      /*packed=*/false, /*padded=*/false, /*is_class=*/false);
+  auto fatbinWrapperType =
+      cir::StructType::get(&getContext(), {intTy, intTy, voidPtrTy, voidPtrTy},
+                           /*packed=*/false, /*is_class=*/false);
   std::string fatbinWrapperName =
       addUnderscoredPrefix(cudaPrefix, "_fatbin_wrapper");
   GlobalOp fatbinWrapper = GlobalOp::create(
diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
index 6c276a83f18cf..78c1634b37b95 100644
--- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
@@ -191,8 +191,7 @@ mlir::Type LowerItaniumCXXABI::lowerMethodType(
   // Note that clang CodeGen emits struct{ptrdiff_t, ptrdiff_t} for member
   // function pointers. Let's follow this approach.
   return cir::StructType::get(type.getContext(), {ptrdiffCIRTy, ptrdiffCIRTy},
-                              /*packed=*/false, /*padded=*/false,
-                              /*is_class=*/false);
+                              /*packed=*/false, /*is_class=*/false);
 }
 
 mlir::TypedAttr LowerItaniumCXXABI::lowerDataMemberConstant(
diff --git a/clang/test/CIR/CodeGen/bitfields.c b/clang/test/CIR/CodeGen/bitfields.c
index ff7e772875549..eca1dea121065 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, pad !cir.array<!u8i x 3>}>
+// CIR-DAG:  !rec_A = !cir.struct<"A" packed {!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 d17bc1e1e33b3..f02517d55388d 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 {pad !u8i}>
+// CHECK: !rec_Struk = !cir.struct<"Struk" {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 095def36fa585..9d26f49110c2e 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 {pad !cir.int<u, 8>}>
-// CHECK:  NonVirtualBaseCIRType:!cir.struct<"Empty" padded {pad !cir.int<u, 8>}>
+// CHECK:  CIR Type:!cir.struct<"Empty" {pad !cir.int<u, 8>}>
+// CHECK:  NonVirtualBaseCIRType:!cir.struct<"Empty" {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 d02a31dbd8963..83204c4183eec 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, pad !cir.array<!u8i x 12>}>
+// CIR-DAG: !rec_LeadingOver = !cir.struct<"LeadingOver" {!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 a253d6f7ae348..04fb2008b7fb6 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, pad !cir.array<!u8i x 3>}>
+// CIR-DAG: !rec_S2 = !cir.struct<"S2" {!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 e365f5312e479..4fc39390b6eb9 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 {pad !u8i}>
+// CIR: !rec_C = !cir.struct<"C" {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 18dc0a9ad1735..682a595759e06 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, pad
+// CIR: !rec_Outer = !cir.struct<"Outer" {!rec_Middle2Ebase, !s8i, pad !cir.array<!u8i x 2>}>
 
 // CIR-LABEL: cir.func {{.*}} @_ZN5OuterC2ERK6Middlec(
 // CIR:         %[[THIS:.*]] = cir.load %{{.+}} : !cir.ptr<!cir.ptr<!rec_Outer>>, !cir.ptr<!rec_Outer>
diff --git a/clang/test/CIR/CodeGen/paren-list-agg-init.cpp b/clang/test/CIR/CodeGen/paren-list-agg-init.cpp
index d5d9dc1040d21..ebd55cd91d9cc 100644
--- a/clang/test/CIR/CodeGen/paren-list-agg-init.cpp
+++ b/clang/test/CIR/CodeGen/paren-list-agg-init.cpp
@@ -36,7 +36,7 @@ struct B {
 };
 
 // LLVM-DAG: [[STRUCT_C:%.*]] = type <{ [[STRUCT_B]], [[STRUCT_A]], i32, [4 x i8] }>
-// CIR-DAG: ![[STRUCT_C:.*]] = !cir.struct<"C" packed padded {![[STRUCT_B]], ![[STRUCT_A]], !s32i, pad !cir.array<!u8i x 4>}>
+// CIR-DAG: ![[STRUCT_C:.*]] = !cir.struct<"C" packed {![[STRUCT_B]], ![[STRUCT_A]], !s32i, pad !cir.array<!u8i x 4>}>
 struct C : public B, public A {
   int c;
 };
@@ -58,7 +58,7 @@ struct E {
   ~E() {};
 };
 
-// CIR-DAG: ![[STRUCT_F:.*]] = !cir.struct<"F" padded {pad !u8i}>
+// CIR-DAG: ![[STRUCT_F:.*]] = !cir.struct<"F" {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, pad !cir.array<!u8i x 3>}>
+// CIR-DAG: ![[STRUCT_G:.*]] = !cir.struct<"G" packed {!s32i, !rec_F, pad !cir.array<!u8i x 3>}>
 struct G {
   int a;
   F f;
@@ -85,7 +85,7 @@ union U {
 
 namespace gh61145 {
   // LLVM-DAG: [[STRUCT_VEC:%.*Vec.*]] = type { i8 }
-  // CIR-DAG: ![[STRUCT_VEC:.*]] = !cir.struct<"gh61145::Vec" padded {pad !u8i}>
+  // CIR-DAG: ![[STRUCT_VEC:.*]] = !cir.struct<"gh61145::Vec" {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 29023355def72..e54ecbbd2c191 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 {pad !u8i}>
+// CIR-DAG: !rec_Empty = !cir.struct<"Empty" {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 {pad !u8i}>
+// CIR-BEFORE-DAG: !rec_EmptyBase = !cir.struct<"EmptyBase" {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, pad !cir.array<!u8i x 4>}>
+// CIR-DAG: !rec_hasNUA = !cir.struct<"hasNUA" {!s32i, pad !cir.array<!u8i x 4>}>
 // LLVM-DAG: %struct.hasNUA = type { i32, [4 x i8] }
 
 const hasNUA nua = {{},{},{},{},{},{}, 1};
diff --git a/clang/test/CIR/CodeGen/record-member-kinds.c b/clang/test/CIR/CodeGen/record-member-kinds.c
index aa44b58ef8754..3d71dc4e427af 100644
--- a/clang/test/CIR/CodeGen/record-member-kinds.c
+++ b/clang/test/CIR/CodeGen/record-member-kinds.c
@@ -72,7 +72,7 @@ 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>}>
+// CIR-DAG: !rec_AlignedTail = !cir.struct<"AlignedTail" {!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.
diff --git a/clang/test/CIR/CodeGen/record-member-kinds.cpp b/clang/test/CIR/CodeGen/record-member-kinds.cpp
index 297428b7bd36d..e414d946553b9 100644
--- a/clang/test/CIR/CodeGen/record-member-kinds.cpp
+++ b/clang/test/CIR/CodeGen/record-member-kinds.cpp
@@ -3,7 +3,7 @@
 // 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}>
+// CIR-DAG: !rec_Empty = !cir.struct<"Empty" {pad !u8i}>
 // LLVM-DAG: %struct.Empty = type { i8 }
 
 // A C++ empty member is not empty for the ABI without [[no_unique_address]].
@@ -44,7 +44,7 @@ struct DerivesBitFieldBase : BitFieldBase { int i; };
 
 // 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>}>
+// CIR-DAG: !rec_HasBitFieldVBase = !cir.struct<"HasBitFieldVBase" packed {!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>}>
@@ -59,7 +59,7 @@ 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>}>
+// CIR-DAG: !rec_AlignasTail = !cir.struct<"AlignasTail" {!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.
@@ -80,10 +80,10 @@ struct UnnamedFirst { int : 16; int a : 8; };
 // 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>}>
+// CIR-DAG: !rec_SpanMixed = !cir.struct<"SpanMixed" {!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>}>
+// CIR-DAG: !rec_SpanEmptyFirst = !cir.struct<"SpanEmptyFirst" {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}>
@@ -99,13 +99,13 @@ 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>}>
+// CIR-DAG: !rec_HasNearlyEmptyVBase = !cir.struct<"HasNearlyEmptyVBase" packed {!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_Clipped = !cir.struct<"Clipped" packed {!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; };
diff --git a/clang/test/CIR/CodeGen/record-type-metadata.cpp b/clang/test/CIR/CodeGen/record-type-metadata.cpp
index 5cf7909f860e2..bac5e6337fa78 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 {pad !u8i}>
-// CIR-DAG: !rec_Aligned = !cir.struct<"Aligned" padded {!s32i, !s32i, pad !cir.array<!u8i x 8>}>
+// CIR-DAG: !rec_Empty = !cir.struct<"Empty" {pad !u8i}>
+// CIR-DAG: !rec_Aligned = !cir.struct<"Aligned" {!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 7d5ea46ea8bf8..344946cd4a7d4 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, pad !u8i}>
+// CIR-DAG: !rec_PackedAndPaddedS = !cir.struct<"PackedAndPaddedS" packed {!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 66f81baf51f60..06fc834a4dcdf 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 {pad !u8i}>
+// CIR: !rec_Templ3Cint2C_int3E = !cir.struct<class "Templ<int, int>" {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 59b674747990b..fd8b666ab83a9 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, 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_A = !cir.struct<class "A" packed {!cir.vptr, !s32i, pad !cir.array<!u8i x 4>}>
+// CIR-COMMON: !rec_B = !cir.struct<class "B" packed {!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 82bed271c22ab..f1bfbfb3e97c7 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, pad !cir.array<!u8i x 4>}>
+// CIR: !rec_VirtualInherits = !cir.struct<"VirtualInherits" packed {!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 e7d140551529d..2cf9c8bdf3e43 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 {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}>
+// CIR-DAG: ![[VoidTask:.*]] = !cir.struct<"folly::coro::Task<void>" {pad !u8i}>
+// CIR-DAG: ![[IntTask:.*]] = !cir.struct<"folly::coro::Task<int>" {pad !u8i}>
+// CIR-DAG: ![[VoidPromisse:.*]] = !cir.struct<"folly::coro::Task<void>::promise_type" {pad !u8i}>
+// CIR-DAG: ![[IntPromisse:.*]] = !cir.struct<"folly::coro::Task<int>::promise_type" {pad !u8i}>
+// CIR-DAG: ![[StdString:.*]] = !cir.struct<"std::string" {pad !u8i}>
+// CIR-DAG: ![[CoroHandleVoid:.*]] = !cir.struct<"std::coroutine_handle<void>" {pad !u8i}>
+// CIR-DAG: ![[CoroHandlePromiseVoid:rec_.*]]  = !cir.struct<"std::coroutine_handle<folly::coro::Task<void>::promise_type>" {pad !u8i}>
+// CIR-DAG: ![[CoroHandlePromiseInt:rec_.*]] = !cir.struct<"std::coroutine_handle<folly::coro::Task<int>::promise_type>" {pad !u8i}>
+// CIR-DAG: ![[SuspendAlways:.*]] = !cir.struct<"std::suspend_always" {pad !u8i}>
 
 // OGCG-DAG: %[[VoidPromisse:"struct.folly::coro::Task<void>::promise_type"]] = type { i8 }
 // OGCG-DAG: %[[VoidTask:"struct.folly::coro::Task"]] = type { i8 }
diff --git a/clang/test/CIR/IR/array-ctor.cir b/clang/test/CIR/IR/array-ctor.cir
index d93111c12cb26..45d4d8fdb4a21 100644
--- a/clang/test/CIR/IR/array-ctor.cir
+++ b/clang/test/CIR/IR/array-ctor.cir
@@ -3,7 +3,7 @@
 
 !u8i = !cir.int<u, 8>
 !u64i = !cir.int<u, 64>
-!rec_S = !cir.struct<"S" padded {!u8i}>
+!rec_S = !cir.struct<"S" {pad !u8i}>
 
 module {
   cir.func private @_ZN1SC1Ev(!cir.ptr<!rec_S>)
diff --git a/clang/test/CIR/IR/array-dtor.cir b/clang/test/CIR/IR/array-dtor.cir
index f1db38cd85f19..ac884f5899d2e 100644
--- a/clang/test/CIR/IR/array-dtor.cir
+++ b/clang/test/CIR/IR/array-dtor.cir
@@ -1,7 +1,7 @@
 // RUN: cir-opt %s --verify-roundtrip | FileCheck %s
 
 !u8i = !cir.int<u, 8>
-!rec_S = !cir.struct<"S" padded {!u8i}>
+!rec_S = !cir.struct<"S" {pad !u8i}>
 
 module {
   cir.func private @_ZN1SD1Ev(!cir.ptr<!rec_S>)
diff --git a/clang/test/CIR/IR/construct-catch-param.cir b/clang/test/CIR/IR/construct-catch-param.cir
index 11c8af5e1b1c1..b72eee2e8d028 100644
--- a/clang/test/CIR/IR/construct-catch-param.cir
+++ b/clang/test/CIR/IR/construct-catch-param.cir
@@ -3,7 +3,7 @@
 !s8i = !cir.int<s, 8>
 !u8i = !cir.int<u, 8>
 !void = !cir.void
-!rec_E = !cir.struct<"E" padded {!u8i}>
+!rec_E = !cir.struct<"E" {pad !u8i}>
 
 module {
 
diff --git a/clang/test/CIR/IR/func-attrs.cir b/clang/test/CIR/IR/func-attrs.cir
index 351e731231751..05ee5696781f6 100644
--- a/clang/test/CIR/IR/func-attrs.cir
+++ b/clang/test/CIR/IR/func-attrs.cir
@@ -5,7 +5,7 @@
 !s8i = !cir.int<s, 8>
 !u8i = !cir.int<u, 8>
 !void = !cir.void
-!rec_Struct = !cir.struct<"Struct" padded {!u8i}>
+!rec_Struct = !cir.struct<"Struct" {pad !u8i}>
 !rec_anon_struct = !cir.struct<{!s64i, !s64i}>
 
 cir.func no_inline dso_local @Func1(%arg0: !s32i {llvm.noundef}, %arg1: !cir.float {llvm.noundef}) -> (!cir.float {llvm.noundef}) {
diff --git a/clang/test/CIR/IR/global-init.cir b/clang/test/CIR/IR/global-init.cir
index e1311ed026768..3e0387ed52594 100644
--- a/clang/test/CIR/IR/global-init.cir
+++ b/clang/test/CIR/IR/global-init.cir
@@ -2,9 +2,9 @@
 
 !u8i = !cir.int<u, 8>
 
-!rec_NeedsCtor = !cir.struct<"NeedsCtor" padded {!u8i}>
-!rec_NeedsDtor = !cir.struct<"NeedsDtor" padded {!u8i}>
-!rec_NeedsCtorDtor = !cir.struct<"NeedsCtorDtor" padded {!u8i}>
+!rec_NeedsCtor = !cir.struct<"NeedsCtor" {pad !u8i}>
+!rec_NeedsDtor = !cir.struct<"NeedsDtor" {pad !u8i}>
+!rec_NeedsCtorDtor = !cir.struct<"NeedsCtorDtor" {pad !u8i}>
 
 module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
   cir.func private @_ZN9NeedsCtorC1Ev(!cir.ptr<!rec_NeedsCtor>)
diff --git a/clang/test/CIR/IR/invalid-array-structor.cir b/clang/test/CIR/IR/invalid-array-structor.cir
index 874fc54f1c0a8..aad53dc5e5ed3 100644
--- a/clang/test/CIR/IR/invalid-array-structor.cir
+++ b/clang/test/CIR/IR/invalid-array-structor.cir
@@ -3,7 +3,7 @@
 
 !u8i = !cir.int<u, 8>
 !u64i = !cir.int<u, 64>
-!rec_S = !cir.struct<"S" padded {!u8i}>
+!rec_S = !cir.struct<"S" {pad !u8i}>
 
 module {
   cir.func dso_local @bad_static_array_ctor_with_num_elements(%p: !cir.ptr<!cir.array<!rec_S x 10>>, %n: !u64i) {
@@ -19,7 +19,7 @@ module {
 
 !u8i = !cir.int<u, 8>
 !u64i = !cir.int<u, 64>
-!rec_S = !cir.struct<"S" padded {!u8i}>
+!rec_S = !cir.struct<"S" {pad !u8i}>
 
 module {
   cir.func private @_ZN1SC1Ev(!cir.ptr<!rec_S>)
@@ -38,7 +38,7 @@ module {
 // -----
 
 !u8i = !cir.int<u, 8>
-!rec_S = !cir.struct<"S" padded {!u8i}>
+!rec_S = !cir.struct<"S" {pad !u8i}>
 
 module {
   cir.func private @construct_int(!cir.ptr<!u8i>)
@@ -58,8 +58,8 @@ module {
 
 !u8i = !cir.int<u, 8>
 !u64i = !cir.int<u, 64>
-!rec_S = !cir.struct<"S" padded {!u8i}>
-!rec_T = !cir.struct<"T" padded {!u8i}>
+!rec_S = !cir.struct<"S" {pad !u8i}>
+!rec_T = !cir.struct<"T" {pad !u8i}>
 
 module {
   cir.func private @_ZN1SC1Ev(!cir.ptr<!rec_S>)
@@ -78,8 +78,8 @@ module {
 
 !u8i = !cir.int<u, 8>
 !u64i = !cir.int<u, 64>
-!rec_S = !cir.struct<"S" padded {!u8i}>
-!rec_T = !cir.struct<"T" padded {!u8i}>
+!rec_S = !cir.struct<"S" {pad !u8i}>
+!rec_T = !cir.struct<"T" {pad !u8i}>
 
 module {
   cir.func private @_ZN1SC1Ev(!cir.ptr<!rec_S>)
@@ -99,7 +99,7 @@ module {
 
 !u8i = !cir.int<u, 8>
 !u64i = !cir.int<u, 64>
-!rec_S = !cir.struct<"S" padded {!u8i}>
+!rec_S = !cir.struct<"S" {pad !u8i}>
 
 module {
   cir.func dso_local @bad_static_array_dtor_with_num_elements(%p: !cir.ptr<!cir.array<!rec_S x 10>>, %n: !u64i) {
@@ -114,7 +114,7 @@ module {
 // -----
 
 !u8i = !cir.int<u, 8>
-!rec_S = !cir.struct<"S" padded {!u8i}>
+!rec_S = !cir.struct<"S" {pad !u8i}>
 
 module {
   cir.func private @_ZN1SD1Ev(!cir.ptr<!rec_S>)
@@ -131,7 +131,7 @@ module {
 // -----
 
 !u8i = !cir.int<u, 8>
-!rec_S = !cir.struct<"S" padded {!u8i}>
+!rec_S = !cir.struct<"S" {pad !u8i}>
 
 module {
   cir.func private @destroy_int(!cir.ptr<!u8i>)
@@ -150,8 +150,8 @@ module {
 // -----
 
 !u8i = !cir.int<u, 8>
-!rec_S = !cir.struct<"S" padded {!u8i}>
-!rec_T = !cir.struct<"T" padded {!u8i}>
+!rec_S = !cir.struct<"S" {pad !u8i}>
+!rec_T = !cir.struct<"T" {pad !u8i}>
 
 module {
   cir.func private @_ZN1SD1Ev(!cir.ptr<!rec_S>)
@@ -169,8 +169,8 @@ module {
 
 !u8i = !cir.int<u, 8>
 !u64i = !cir.int<u, 64>
-!rec_S = !cir.struct<"S" padded {!u8i}>
-!rec_T = !cir.struct<"T" padded {!u8i}>
+!rec_S = !cir.struct<"S" {pad !u8i}>
+!rec_T = !cir.struct<"T" {pad !u8i}>
 
 module {
   cir.func private @_ZN1SD1Ev(!cir.ptr<!rec_S>)
@@ -189,8 +189,8 @@ module {
 
 !u8i = !cir.int<u, 8>
 !u64i = !cir.int<u, 64>
-!rec_S = !cir.struct<"S" padded {!u8i}>
-!rec_T = !cir.struct<"T" padded {!u8i}>
+!rec_S = !cir.struct<"S" {pad !u8i}>
+!rec_T = !cir.struct<"T" {pad !u8i}>
 
 module {
   cir.func private @_ZN1SD1Ev(!cir.ptr<!rec_S>)
diff --git a/clang/test/CIR/IR/invalid-construct-catch-param.cir b/clang/test/CIR/IR/invalid-construct-catch-param.cir
index 7a750c222df1c..86c4c6d6612aa 100644
--- a/clang/test/CIR/IR/invalid-construct-catch-param.cir
+++ b/clang/test/CIR/IR/invalid-construct-catch-param.cir
@@ -3,7 +3,7 @@
 !u8i = !cir.int<u, 8>
 !s32i = !cir.int<s, 32>
 !void = !cir.void
-!rec_E = !cir.struct<"E" padded {!u8i}>
+!rec_E = !cir.struct<"E" {pad !u8i}>
 
 module {
 
@@ -34,7 +34,7 @@ cir.func @copy_fn_missing() {
 
 !u8i = !cir.int<u, 8>
 !void = !cir.void
-!rec_E = !cir.struct<"E" padded {!u8i}>
+!rec_E = !cir.struct<"E" {pad !u8i}>
 
 module {
 
@@ -68,7 +68,7 @@ cir.func @copy_fn_missing_thunk_attr() {
 
 !u8i = !cir.int<u, 8>
 !void = !cir.void
-!rec_E = !cir.struct<"E" padded {!u8i}>
+!rec_E = !cir.struct<"E" {pad !u8i}>
 
 module {
 
@@ -104,7 +104,7 @@ cir.func @copy_fn_wrong_arity() {
 !u8i = !cir.int<u, 8>
 !s32i = !cir.int<s, 32>
 !void = !cir.void
-!rec_E = !cir.struct<"E" padded {!u8i}>
+!rec_E = !cir.struct<"E" {pad !u8i}>
 
 module {
 
@@ -141,7 +141,7 @@ cir.func @copy_fn_non_void_return() {
 !u8i = !cir.int<u, 8>
 !s32i = !cir.int<s, 32>
 !void = !cir.void
-!rec_E = !cir.struct<"E" padded {!u8i}>
+!rec_E = !cir.struct<"E" {pad !u8i}>
 
 module {
 
@@ -179,7 +179,7 @@ cir.func @copy_fn_first_arg_mismatch() {
 !u8i = !cir.int<u, 8>
 !s32i = !cir.int<s, 32>
 !void = !cir.void
-!rec_E = !cir.struct<"E" padded {!u8i}>
+!rec_E = !cir.struct<"E" {pad !u8i}>
 
 module {
 
@@ -216,7 +216,7 @@ cir.func @copy_fn_second_arg_mismatch() {
 
 !u8i = !cir.int<u, 8>
 !void = !cir.void
-!rec_E = !cir.struct<"E" padded {!u8i}>
+!rec_E = !cir.struct<"E" {pad !u8i}>
 
 module {
 
diff --git a/clang/test/CIR/IR/invalid-delete-array.cir b/clang/test/CIR/IR/invalid-delete-array.cir
index 2263c95de6e03..7d6ce8be0951f 100644
--- a/clang/test/CIR/IR/invalid-delete-array.cir
+++ b/clang/test/CIR/IR/invalid-delete-array.cir
@@ -2,7 +2,7 @@
 
 !void = !cir.void
 !u8i = !cir.int<u, 8>
-!rec_S = !cir.struct<"S" padded {!u8i}>
+!rec_S = !cir.struct<"S" {pad !u8i}>
 
 module {
   cir.func private @_ZdaPvm(!cir.ptr<!void>)
diff --git a/clang/test/CIR/IR/invalid-loop-cleanup.cir b/clang/test/CIR/IR/invalid-loop-cleanup.cir
index f48ac44cd1541..ea07513bde788 100644
--- a/clang/test/CIR/IR/invalid-loop-cleanup.cir
+++ b/clang/test/CIR/IR/invalid-loop-cleanup.cir
@@ -1,7 +1,7 @@
 // RUN: cir-opt %s -verify-diagnostics -split-input-file
 
 !u8i = !cir.int<u, 8>
-!rec_S = !cir.struct<"S" padded {!u8i}>
+!rec_S = !cir.struct<"S" {pad !u8i}>
 
 cir.func private @ctor(!cir.ptr<!rec_S>)
 cir.func private @operatorBool(!cir.ptr<!rec_S>) -> !cir.bool
@@ -28,7 +28,7 @@ cir.func @while_cleanup_missing_kind() {
 // -----
 
 !u8i = !cir.int<u, 8>
-!rec_S = !cir.struct<"S" padded {!u8i}>
+!rec_S = !cir.struct<"S" {pad !u8i}>
 
 cir.func private @ctor(!cir.ptr<!rec_S>)
 cir.func private @operatorBool(!cir.ptr<!rec_S>) -> !cir.bool
@@ -57,7 +57,7 @@ cir.func @for_cleanup_missing_kind() {
 // -----
 
 !u8i = !cir.int<u, 8>
-!rec_S = !cir.struct<"S" padded {!u8i}>
+!rec_S = !cir.struct<"S" {pad !u8i}>
 
 cir.func private @ctor(!cir.ptr<!rec_S>)
 cir.func private @operatorBool(!cir.ptr<!rec_S>) -> !cir.bool
@@ -83,7 +83,7 @@ cir.func @while_cleanup_eh() {
 // -----
 
 !u8i = !cir.int<u, 8>
-!rec_S = !cir.struct<"S" padded {!u8i}>
+!rec_S = !cir.struct<"S" {pad !u8i}>
 
 cir.func private @ctor(!cir.ptr<!rec_S>)
 cir.func private @operatorBool(!cir.ptr<!rec_S>) -> !cir.bool
diff --git a/clang/test/CIR/IR/loop-cleanup.cir b/clang/test/CIR/IR/loop-cleanup.cir
index ee995f8ca3262..84267a33105f7 100644
--- a/clang/test/CIR/IR/loop-cleanup.cir
+++ b/clang/test/CIR/IR/loop-cleanup.cir
@@ -2,7 +2,7 @@
 
 !u8i = !cir.int<u, 8>
 #true = #cir.bool<true> : !cir.bool
-!rec_S = !cir.struct<"S" padded {!u8i}>
+!rec_S = !cir.struct<"S" {pad !u8i}>
 
 module {
   cir.func private @ctor(!cir.ptr<!rec_S>)
diff --git a/clang/test/CIR/IR/struct.cir b/clang/test/CIR/IR/struct.cir
index 7321835fc1d0d..0348827bd8ee5 100644
--- a/clang/test/CIR/IR/struct.cir
+++ b/clang/test/CIR/IR/struct.cir
@@ -25,24 +25,26 @@
 // CHECK-DAG: !rec_S1 = !cir.struct<"S1" {!s32i, !s32i}>
 // CHECK-DAG: !rec_Sc = !cir.struct<"Sc" {!u8i, !u16i, !u32i}>
 
-// Packed and padded structs
+// A packed struct, and a packed and unpacked pair with the same members
 !rec_P1 = !cir.struct<"P1" packed {!s32i, !s32i}>
-!rec_P2 = !cir.struct<"P2" padded {!u8i, !u16i, !u32i}>
-!rec_P3 = !cir.struct<"P3" packed padded {!u8i, !u16i, !u32i}>
+!rec_P2 = !cir.struct<"P2" {!u8i, pad !u8i, !u16i, !u32i}>
+!rec_P3 = !cir.struct<"P3" packed {!u8i, pad !u8i, !u16i, !u32i}>
 
 // Members marked pad or empty
 !rec_P4 = !cir.struct<"P4" {pad !u8i}>
 !rec_P5 = !cir.struct<"P5" {empty !u8i}>
 !rec_P6 = !cir.struct<"P6" {!u32i, empty !cir.array<!u8i x 3>, pad !u8i}>
-!rec_P7 = !cir.struct<"P7" packed padded {!u8i, pad !u8i}>
+!rec_P7 = !cir.struct<"P7" packed {!u8i, pad !u8i}>
+!rec_P8 = !cir.struct<"P8" {!u16i, pad !u8i, pad !u8i}>
 
 // CHECK-DAG: !rec_P1 = !cir.struct<"P1" packed {!s32i, !s32i}>
-// CHECK-DAG: !rec_P2 = !cir.struct<"P2" padded {!u8i, !u16i, !u32i}>
-// CHECK-DAG: !rec_P3 = !cir.struct<"P3" packed padded {!u8i, !u16i, !u32i}>
+// CHECK-DAG: !rec_P2 = !cir.struct<"P2" {!u8i, pad !u8i, !u16i, !u32i}>
+// CHECK-DAG: !rec_P3 = !cir.struct<"P3" packed {!u8i, pad !u8i, !u16i, !u32i}>
 // CHECK-DAG: !rec_P4 = !cir.struct<"P4" {pad !u8i}>
 // CHECK-DAG: !rec_P5 = !cir.struct<"P5" {empty !u8i}>
 // CHECK-DAG: !rec_P6 = !cir.struct<"P6" {!u32i, empty !cir.array<!u8i x 3>, pad !u8i}>
-// CHECK-DAG: !rec_P7 = !cir.struct<"P7" packed padded {!u8i, pad !u8i}>
+// CHECK-DAG: !rec_P7 = !cir.struct<"P7" packed {!u8i, pad !u8i}>
+// CHECK-DAG: !rec_P8 = !cir.struct<"P8" {!u16i, pad !u8i, pad !u8i}>
 
 // Records with identical member types, spelled apart by their marks.  The
 // anonymous pair must stay two distinct types, since an anonymous record keys
@@ -126,7 +128,8 @@ module  {
                      %arg20: !rec_PadNode,
                      %arg21: !rec_P7,
                      %arg22: !rec_anon_u_empty,
-                     %arg23: !rec_anon_u_plain) {
+                     %arg23: !rec_anon_u_plain,
+                     %arg24: !rec_P8) {
     cir.return
   }
 
diff --git a/clang/test/CIR/Lowering/copy-skip-tail-padding.cir b/clang/test/CIR/Lowering/copy-skip-tail-padding.cir
new file mode 100644
index 0000000000000..492ae95ac7a25
--- /dev/null
+++ b/clang/test/CIR/Lowering/copy-skip-tail-padding.cir
@@ -0,0 +1,136 @@
+// RUN: cir-opt %s --cir-to-llvm -o %t.mlir
+// RUN: FileCheck --input-file=%t.mlir %s
+
+!s8i = !cir.int<s, 8>
+!u8i = !cir.int<u, 8>
+!u16i = !cir.int<u, 16>
+!s32i = !cir.int<s, 32>
+
+!rec_Plain = !cir.struct<"Plain" {!s32i, !s32i}>
+!rec_Trail = !cir.struct<"Trail" {!s32i, !u8i, pad !cir.array<!u8i x 3>}>
+!rec_MultiPad = !cir.struct<"MultiPad" {!u16i, pad !u8i, pad !u8i}>
+!rec_Interior = !cir.struct<"Interior" {!u8i, pad !cir.array<!u8i x 3>, !s32i}>
+!rec_TailEmpty = !cir.struct<"TailEmpty" {!u8i, empty !u8i}>
+!rec_Packed = !cir.struct<"Packed" packed {!u8i, !s32i, pad !u8i}>
+!rec_AllPad = !cir.struct<"AllPad" {pad !u8i}>
+!rec_InteriorPacked =
+    !cir.struct<"InteriorPacked" packed {!u8i, pad !cir.array<!u8i x 3>, !s32i}>
+!rec_Nest = !cir.struct<"Nest" {!rec_Trail, !u8i, pad !cir.array<!u8i x 3>}>
+!rec_AlignasTail =
+    !cir.struct<"AlignasTail" {!s8i, pad !cir.array<!u8i x 7>, !s32i,
+                               pad !cir.array<!u8i x 4>}>
+!rec_SpanMixed =
+    !cir.struct<"SpanMixed" {!u8i, pad !cir.array<!u8i x 3>, empty !u8i,
+                             pad !cir.array<!u8i x 3>}>
+
+module attributes {cir.triple = "x86_64-unknown-linux-gnu",
+                   dlti.dl_spec = #dlti.dl_spec<>} {
+  // An unmarked record has no tail padding to skip.
+  // CHECK-LABEL: llvm.func @plain
+  // CHECK:         %[[N:.*]] = llvm.mlir.constant(8 : i64) : i64
+  // CHECK:         "llvm.intr.memcpy"(%{{.+}}, %{{.+}}, %[[N]])
+  cir.func @plain(%arg0: !cir.ptr<!rec_Plain>, %arg1: !cir.ptr<!rec_Plain>) {
+    cir.copy %arg0 to %arg1 skip_tail_padding : !cir.ptr<!rec_Plain>
+    cir.return
+  }
+
+  // CHECK-LABEL: llvm.func @trail
+  // CHECK:         %[[N:.*]] = llvm.mlir.constant(5 : i64) : i64
+  // CHECK:         "llvm.intr.memcpy"(%{{.+}}, %{{.+}}, %[[N]])
+  cir.func @trail(%arg0: !cir.ptr<!rec_Trail>, %arg1: !cir.ptr<!rec_Trail>) {
+    cir.copy %arg0 to %arg1 skip_tail_padding : !cir.ptr<!rec_Trail>
+    cir.return
+  }
+
+  // Every member of the trailing run drops, not only the last one.
+  // CHECK-LABEL: llvm.func @multi_pad
+  // CHECK:         %[[N:.*]] = llvm.mlir.constant(2 : i64) : i64
+  // CHECK:         "llvm.intr.memcpy"(%{{.+}}, %{{.+}}, %[[N]])
+  cir.func @multi_pad(%arg0: !cir.ptr<!rec_MultiPad>,
+                      %arg1: !cir.ptr<!rec_MultiPad>) {
+    cir.copy %arg0 to %arg1 skip_tail_padding : !cir.ptr<!rec_MultiPad>
+    cir.return
+  }
+
+  // Padding before a data member is not tail padding, so nothing drops.
+  // CHECK-LABEL: llvm.func @interior_pad
+  // CHECK:         %[[N:.*]] = llvm.mlir.constant(8 : i64) : i64
+  // CHECK:         "llvm.intr.memcpy"(%{{.+}}, %{{.+}}, %[[N]])
+  cir.func @interior_pad(%arg0: !cir.ptr<!rec_Interior>,
+                         %arg1: !cir.ptr<!rec_Interior>) {
+    cir.copy %arg0 to %arg1 skip_tail_padding : !cir.ptr<!rec_Interior>
+    cir.return
+  }
+
+  // An empty member is declared storage a derived class may not reuse, so it
+  // stays inside the data size.
+  // CHECK-LABEL: llvm.func @tail_empty
+  // CHECK:         %[[N:.*]] = llvm.mlir.constant(2 : i64) : i64
+  // CHECK:         "llvm.intr.memcpy"(%{{.+}}, %{{.+}}, %[[N]])
+  cir.func @tail_empty(%arg0: !cir.ptr<!rec_TailEmpty>,
+                       %arg1: !cir.ptr<!rec_TailEmpty>) {
+    cir.copy %arg0 to %arg1 skip_tail_padding : !cir.ptr<!rec_TailEmpty>
+    cir.return
+  }
+
+  // A packed record sums its members at alignment one.
+  // CHECK-LABEL: llvm.func @packed
+  // CHECK:         %[[N:.*]] = llvm.mlir.constant(5 : i64) : i64
+  // CHECK:         "llvm.intr.memcpy"(%{{.+}}, %{{.+}}, %[[N]])
+  cir.func @packed(%arg0: !cir.ptr<!rec_Packed>, %arg1: !cir.ptr<!rec_Packed>) {
+    cir.copy %arg0 to %arg1 skip_tail_padding : !cir.ptr<!rec_Packed>
+    cir.return
+  }
+
+  // An empty C++ class lays out as one padding byte and has no data at all.
+  // CHECK-LABEL: llvm.func @all_pad
+  // CHECK:         %[[N:.*]] = llvm.mlir.constant(0 : i64) : i64
+  // CHECK:         "llvm.intr.memcpy"(%{{.+}}, %{{.+}}, %[[N]])
+  cir.func @all_pad(%arg0: !cir.ptr<!rec_AllPad>,
+                    %arg1: !cir.ptr<!rec_AllPad>) {
+    cir.copy %arg0 to %arg1 skip_tail_padding : !cir.ptr<!rec_AllPad>
+    cir.return
+  }
+
+  // Interior padding again, packed so that no realignment refills the hole.
+  // Dropping the pad here would change the answer.
+  // CHECK-LABEL: llvm.func @interior_pad_packed
+  // CHECK:         %[[N:.*]] = llvm.mlir.constant(8 : i64) : i64
+  // CHECK:         "llvm.intr.memcpy"(%{{.+}}, %{{.+}}, %[[N]])
+  cir.func @interior_pad_packed(%arg0: !cir.ptr<!rec_InteriorPacked>,
+                                %arg1: !cir.ptr<!rec_InteriorPacked>) {
+    cir.copy %arg0 to %arg1 skip_tail_padding : !cir.ptr<!rec_InteriorPacked>
+    cir.return
+  }
+
+  // Only the outermost object's tail padding is skippable, so a record member
+  // contributes its full size.
+  // CHECK-LABEL: llvm.func @nested
+  // CHECK:         %[[N:.*]] = llvm.mlir.constant(9 : i64) : i64
+  // CHECK:         "llvm.intr.memcpy"(%{{.+}}, %{{.+}}, %[[N]])
+  cir.func @nested(%arg0: !cir.ptr<!rec_Nest>, %arg1: !cir.ptr<!rec_Nest>) {
+    cir.copy %arg0 to %arg1 skip_tail_padding : !cir.ptr<!rec_Nest>
+    cir.return
+  }
+
+  // Interior and trailing padding together: the run stops at the data member.
+  // CHECK-LABEL: llvm.func @alignas_tail
+  // CHECK:         %[[N:.*]] = llvm.mlir.constant(12 : i64) : i64
+  // CHECK:         "llvm.intr.memcpy"(%{{.+}}, %{{.+}}, %[[N]])
+  cir.func @alignas_tail(%arg0: !cir.ptr<!rec_AlignasTail>,
+                         %arg1: !cir.ptr<!rec_AlignasTail>) {
+    cir.copy %arg0 to %arg1 skip_tail_padding : !cir.ptr<!rec_AlignasTail>
+    cir.return
+  }
+
+  // The run stops at the empty member: the pad after it drops, the pad
+  // before it does not.
+  // CHECK-LABEL: llvm.func @span_mixed
+  // CHECK:         %[[N:.*]] = llvm.mlir.constant(5 : i64) : i64
+  // CHECK:         "llvm.intr.memcpy"(%{{.+}}, %{{.+}}, %[[N]])
+  cir.func @span_mixed(%arg0: !cir.ptr<!rec_SpanMixed>,
+                       %arg1: !cir.ptr<!rec_SpanMixed>) {
+    cir.copy %arg0 to %arg1 skip_tail_padding : !cir.ptr<!rec_SpanMixed>
+    cir.return
+  }
+}
diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir
index 83d36ce22b970..e51c96030877e 100644
--- a/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir
@@ -13,8 +13,8 @@
 !rec_SWrapsOverAligned = !cir.struct<"SWrapsOverAligned" {!cir.double, !rec_UOverAligned}>
 !rec_UEmptyLarge = !cir.union<"UEmptyLarge" {}, padding = {!cir.array<!u8i x 32>}>
 !rec_P = !cir.struct<"P" packed {!s8i, !s32i}>
-!rec_Ov = !cir.struct<"Ov" padded {!s32i, !cir.array<!u8i x 12>}>
-!rec_E = !cir.struct<"E" padded {!u8i}>
+!rec_Ov = !cir.struct<"Ov" {!s32i, pad !cir.array<!u8i x 12>}>
+!rec_E = !cir.struct<"E" {pad !u8i}>
 !rec_FF = !cir.struct<"FF" {!cir.float, !cir.float}>
 !rec_RetFF = !cir.struct<"RetFF" {!cir.float, !cir.float}>
 
@@ -111,15 +111,15 @@ module attributes {
     cir.return
   }
 
-  // CHECK: not yet implemented for type '!cir.struct<"Ov" padded
+  // CHECK: not yet implemented for type '!cir.struct<"Ov" {!cir.int<s, 32>, pad !cir.array<!cir.int<u, 8> x 12>}>'
 
-  // An empty C++ class is laid out as a single padded byte, so it is rejected
-  // by the padded check; its Ignore classification is deferred.
+  // An empty C++ class is laid out as a single padding byte, so it is
+  // rejected as padded; its Ignore classification is deferred.
   cir.func @take_empty(%arg0: !rec_E) {
     cir.return
   }
 
-  // CHECK: not yet implemented for type '!cir.struct<"E" padded
+  // CHECK: not yet implemented for type '!cir.struct<"E" {pad !cir.int<u, 8>}>'
 
   // An all-float struct classifies to an SSE vector coerce this bridge does
   // not represent, so it is reported NYI rather than passed unchanged.
diff --git a/clang/test/CIR/Transforms/cxx-abi-lowering-attrs.cir b/clang/test/CIR/Transforms/cxx-abi-lowering-attrs.cir
index 2faf2acf421d4..8a295d64ee44b 100644
--- a/clang/test/CIR/Transforms/cxx-abi-lowering-attrs.cir
+++ b/clang/test/CIR/Transforms/cxx-abi-lowering-attrs.cir
@@ -3,7 +3,7 @@
 
 !u8i = !cir.int<u, 8>
 !s32i = !cir.int<s, 32>
-!rec_Other = !cir.struct<"Other" padded {!u8i}>
+!rec_Other = !cir.struct<"Other" {pad !u8i}>
 !rec_Struct = !cir.struct<"Struct" {!cir.data_member<!s32i in !rec_Other>}>
 // CHECK: !rec_Struct = !cir.struct<"Struct" {!s64i}>
 
diff --git a/clang/test/CIR/Transforms/eh-abi-lowering-construct-catch-invalid.cir b/clang/test/CIR/Transforms/eh-abi-lowering-construct-catch-invalid.cir
index d2e1bbfc7077b..660b59a3ee17d 100644
--- a/clang/test/CIR/Transforms/eh-abi-lowering-construct-catch-invalid.cir
+++ b/clang/test/CIR/Transforms/eh-abi-lowering-construct-catch-invalid.cir
@@ -7,7 +7,7 @@
 !s32i = !cir.int<s, 32>
 !u8i = !cir.int<u, 8>
 !void = !cir.void
-!rec_E = !cir.struct<"E" padded {!u8i}>
+!rec_E = !cir.struct<"E" {pad !u8i}>
 
 module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
 
@@ -65,7 +65,7 @@ cir.func @bad_kind() {
 !s32i = !cir.int<s, 32>
 !u8i = !cir.int<u, 8>
 !void = !cir.void
-!rec_E = !cir.struct<"E" padded {!u8i}>
+!rec_E = !cir.struct<"E" {pad !u8i}>
 
 module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
 
@@ -118,7 +118,7 @@ cir.func @copy_fn_no_body() {
 !s32i = !cir.int<s, 32>
 !u8i = !cir.int<u, 8>
 !void = !cir.void
-!rec_E = !cir.struct<"E" padded {!u8i}>
+!rec_E = !cir.struct<"E" {pad !u8i}>
 
 module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
 
diff --git a/clang/test/CIR/Transforms/eh-abi-lowering-construct-catch.cir b/clang/test/CIR/Transforms/eh-abi-lowering-construct-catch.cir
index cffd1892845f8..b20de9b0c6324 100644
--- a/clang/test/CIR/Transforms/eh-abi-lowering-construct-catch.cir
+++ b/clang/test/CIR/Transforms/eh-abi-lowering-construct-catch.cir
@@ -22,7 +22,7 @@
 !u32i = !cir.int<u, 32>
 !u8i = !cir.int<u, 8>
 !void = !cir.void
-!rec_E = !cir.struct<"E" padded {!u8i}>
+!rec_E = !cir.struct<"E" {pad !u8i}>
 !rec_anon_struct = !cir.struct<{!cir.ptr<!u8i>, !cir.ptr<!u8i>}>
 
 module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
diff --git a/clang/test/CIR/Transforms/flatten-cleanup-scope-eh.cir b/clang/test/CIR/Transforms/flatten-cleanup-scope-eh.cir
index 8df7fb042bb52..d0372410aa38e 100644
--- a/clang/test/CIR/Transforms/flatten-cleanup-scope-eh.cir
+++ b/clang/test/CIR/Transforms/flatten-cleanup-scope-eh.cir
@@ -4,7 +4,7 @@
 !s32i = !cir.int<s, 32>
 !u8i = !cir.int<u, 8>
 !rec_SomeClass = !cir.struct<"SomeClass" {!s32i}>
-!rec_NonTrivial = !cir.struct<"NonTrivial" padded {!u8i}>
+!rec_NonTrivial = !cir.struct<"NonTrivial" {pad !u8i}>
 #false = #cir.bool<false> : !cir.bool
 #true = #cir.bool<true> : !cir.bool
 
diff --git a/clang/test/CIR/Transforms/flatten-loop-cleanup.cir b/clang/test/CIR/Transforms/flatten-loop-cleanup.cir
index 40adb54633520..0db66822ad4aa 100644
--- a/clang/test/CIR/Transforms/flatten-loop-cleanup.cir
+++ b/clang/test/CIR/Transforms/flatten-loop-cleanup.cir
@@ -3,7 +3,7 @@
 
 !u8i = !cir.int<u, 8>
 !s32i = !cir.int<s, 32>
-!rec_S = !cir.struct<"S" padded {!u8i}>
+!rec_S = !cir.struct<"S" {pad !u8i}>
 
 module {
   cir.func private @ctor(!cir.ptr<!rec_S>)
diff --git a/clang/test/CIR/Transforms/mem2reg.cir b/clang/test/CIR/Transforms/mem2reg.cir
index 84f83595f1c6c..8df057f70c29f 100644
--- a/clang/test/CIR/Transforms/mem2reg.cir
+++ b/clang/test/CIR/Transforms/mem2reg.cir
@@ -1,6 +1,10 @@
 // RUN: cir-opt %s -cir-flatten-cfg -mem2reg -o - | FileCheck %s
 
 !s32i = !cir.int<s, 32>
+!u8i = !cir.int<u, 8>
+!u16i = !cir.int<u, 16>
+!rec_Whole = !cir.struct<"Whole" {!u16i, !u16i}>
+!rec_AllPad = !cir.struct<"AllPad" {pad !u8i}>
 
 module {
   // Promote CIR stack slots through the generic mem2reg pass after CFG flattening.
@@ -67,4 +71,27 @@ module {
     %2 = cir.load %0 : !cir.ptr<!s32i>, !s32i
     cir.return %2 : !s32i
   }
+
+  // A skip_tail_padding copy that covers the whole slot still promotes.
+  // CHECK-LABEL: cir.func @promote_copy_covering_slot
+  cir.func @promote_copy_covering_slot(%src: !cir.ptr<!rec_Whole>) -> !rec_Whole {
+    // CHECK-NOT: cir.alloca
+    // CHECK-NOT: cir.copy
+    %slot = cir.alloca "s" align(2) init : !cir.ptr<!rec_Whole>
+    cir.copy %src to %slot skip_tail_padding : !cir.ptr<!rec_Whole>
+    %v = cir.load %slot : !cir.ptr<!rec_Whole>, !rec_Whole
+    cir.return %v : !rec_Whole
+  }
+
+  // An all-pad record has a data size of zero, so the copy leaves the slot's
+  // byte untouched and the slot cannot be promoted.
+  // CHECK-LABEL: cir.func @do_not_promote_partial_copy
+  cir.func @do_not_promote_partial_copy(%src: !cir.ptr<!rec_AllPad>) -> !rec_AllPad {
+    // CHECK: %[[SLOT:.*]] = cir.alloca "s" align(1) init : !cir.ptr<!rec_AllPad>
+    // CHECK: cir.copy %{{.+}} to %[[SLOT]] skip_tail_padding : !cir.ptr<!rec_AllPad>
+    %slot = cir.alloca "s" align(1) init : !cir.ptr<!rec_AllPad>
+    cir.copy %src to %slot skip_tail_padding : !cir.ptr<!rec_AllPad>
+    %v = cir.load %slot : !cir.ptr<!rec_AllPad>, !rec_AllPad
+    cir.return %v : !rec_AllPad
+  }
 }
diff --git a/clang/unittests/CIR/PointerLikeTest.cpp b/clang/unittests/CIR/PointerLikeTest.cpp
index 952bf67421470..35491e4c5c79d 100644
--- a/clang/unittests/CIR/PointerLikeTest.cpp
+++ b/clang/unittests/CIR/PointerLikeTest.cpp
@@ -169,7 +169,7 @@ class CIROpenACCPointerLikeTest : public ::testing::Test {
     else
       structTy = cir::StructType::get(&context, getUniqueRecordName("S"),
                                       /*is_class=*/false);
-    structTy.complete({ty1, ty2}, false, false);
+    structTy.complete({ty1, ty2}, false);
     mlir::Type ptrTy = cir::PointerType::get(structTy);
 
     // Verify that the pointer points to the structure type.
@@ -251,7 +251,7 @@ class CIROpenACCPointerLikeTest : public ::testing::Test {
     cir::RecordType structTy =
         cir::StructType::get(&context, getUniqueRecordName("S"),
                              /*is_class=*/false);
-    structTy.complete({ptrTy, ptrTy}, false, false);
+    structTy.complete({ptrTy, ptrTy}, false);
     mlir::Type structPptrTy = cir::PointerType::get(structTy);
 
     // Create an alloca for the struct.
@@ -361,6 +361,6 @@ TEST_F(CIROpenACCPointerLikeTest, testPointerToStructMember) {
   cir::RecordType structTy =
       cir::StructType::get(&context, getUniqueRecordName("S"),
                            /*is_class=*/false);
-  structTy.complete({i32Ty, i32Ty}, false, false);
+  structTy.complete({i32Ty, i32Ty}, false);
   testPointerToMemberType(structTy, mlir::acc::VariableTypeCategory::composite);
 }
diff --git a/clang/unittests/CIR/RecordMemberKindTest.cpp b/clang/unittests/CIR/RecordMemberKindTest.cpp
index bb635a9913a99..00199b1ffcd97 100644
--- a/clang/unittests/CIR/RecordMemberKindTest.cpp
+++ b/clang/unittests/CIR/RecordMemberKindTest.cpp
@@ -50,7 +50,7 @@ class RecordMemberKindTest : public ::testing::Test {
                         llvm::ArrayRef<mlir::Type> members,
                         llvm::ArrayRef<RecordMemberKind> kinds) {
     auto ty = StructType::get(&context, getName(name), /*is_class=*/false);
-    ty.complete(members, /*packed=*/false, /*isPadded=*/false, kinds);
+    ty.complete(members, /*packed=*/false, kinds);
     return ty;
   }
 };
@@ -76,6 +76,39 @@ TEST_F(RecordMemberKindTest, EmptyForTheABIWhenNoMemberHoldsData) {
   EXPECT_FALSE(allMembersNonData(makeStruct("unmarked", {u8}, {})));
 }
 
+TEST_F(RecordMemberKindTest, PaddedFollowsThePadMarks) {
+  IntType u8 = getU8();
+  EXPECT_FALSE(makeStruct("d", {u8}, {RecordMemberKind::Data}).getPadded());
+  EXPECT_FALSE(makeStruct("e", {u8}, {RecordMemberKind::Empty}).getPadded());
+  EXPECT_TRUE(makeStruct("p", {u8}, {RecordMemberKind::Pad}).getPadded());
+  // Interior padding counts too, not just a trailing run.
+  EXPECT_TRUE(makeStruct("dpd", {u8, u8, u8},
+                         {RecordMemberKind::Data, RecordMemberKind::Pad,
+                          RecordMemberKind::Data})
+                  .getPadded());
+  // An incomplete struct has no members to read a mark from.
+  EXPECT_FALSE(StructType::get(&context, getName("inc"), /*is_class=*/false)
+                   .getPadded());
+}
+
+TEST_F(RecordMemberKindTest, AUnionsPaddingComesFromItsPaddingSlot) {
+  // A union keeps answering from its padding slot, not from the marks.
+  IntType u8 = getU8();
+  llvm::SmallVector<mlir::Type> members{u8};
+  llvm::ArrayRef<mlir::Type> membersRef(members);
+  EXPECT_FALSE(UnionType::get(&context, membersRef, getName("bare"),
+                              /*packed=*/false, /*padding=*/mlir::Type{})
+                   .getPadded());
+  EXPECT_TRUE(UnionType::get(&context, membersRef, getName("padded"),
+                             /*packed=*/false, /*padding=*/u8)
+                  .getPadded());
+  // An empty mark on a member is not padding.
+  EXPECT_FALSE(UnionType::get(&context, membersRef, getName("marked"),
+                              /*packed=*/false, /*padding=*/mlir::Type{},
+                              {RecordMemberKind::Empty})
+                   .getPadded());
+}
+
 TEST_F(RecordMemberKindTest, RejectsAMarkListThatDoesNotCoverEveryMember) {
   // The assembly syntax cannot express this, since it builds one kind per
   // member, but a C++ caller can.
@@ -86,8 +119,8 @@ TEST_F(RecordMemberKindTest, RejectsAMarkListThatDoesNotCoverEveryMember) {
   llvm::ArrayRef<mlir::Type> membersRef(members);
   llvm::ArrayRef<RecordMemberKind> kindsRef(tooFew);
   EXPECT_FALSE(StructType::getChecked(getLoc(), &context, membersRef,
-                                      /*packed=*/false, /*padded=*/false,
-                                      /*is_class=*/false, kindsRef));
+                                      /*packed=*/false, /*is_class=*/false,
+                                      kindsRef));
   EXPECT_EQ(diags.count, 1u);
 }
 
@@ -134,12 +167,12 @@ TEST_F(RecordMemberKindTest, AUnionsTailPaddingSlotIsNotAMember) {
 
 TEST_F(RecordMemberKindTest, MarksTakePartInAnonymousTypeIdentity) {
   IntType u8 = getU8();
-  auto marksPad = StructType::get(
-      &context, {u8, u8}, /*packed=*/false, /*padded=*/false,
-      /*is_class=*/false, {RecordMemberKind::Data, RecordMemberKind::Pad});
-  auto marksEmpty = StructType::get(
-      &context, {u8, u8}, /*packed=*/false, /*padded=*/false,
-      /*is_class=*/false, {RecordMemberKind::Data, RecordMemberKind::Empty});
+  auto marksPad =
+      StructType::get(&context, {u8, u8}, /*packed=*/false, /*is_class=*/false,
+                      {RecordMemberKind::Data, RecordMemberKind::Pad});
+  auto marksEmpty =
+      StructType::get(&context, {u8, u8}, /*packed=*/false, /*is_class=*/false,
+                      {RecordMemberKind::Data, RecordMemberKind::Empty});
   EXPECT_NE(marksPad, marksEmpty);
 
   // Marks are provenance rather than layout.
@@ -159,11 +192,11 @@ TEST_F(RecordMemberKindTest, MarksTakePartInAnonymousTypeIdentity) {
 
 TEST_F(RecordMemberKindTest, AnAllDataMarkListIsDropped) {
   IntType u8 = getU8();
-  auto allData = StructType::get(
-      &context, {u8, u8}, /*packed=*/false, /*padded=*/false,
-      /*is_class=*/false, {RecordMemberKind::Data, RecordMemberKind::Data});
-  auto noList = StructType::get(&context, {u8, u8}, /*packed=*/false,
-                                /*padded=*/false, /*is_class=*/false);
+  auto allData =
+      StructType::get(&context, {u8, u8}, /*packed=*/false, /*is_class=*/false,
+                      {RecordMemberKind::Data, RecordMemberKind::Data});
+  auto noList =
+      StructType::get(&context, {u8, u8}, /*packed=*/false, /*is_class=*/false);
   EXPECT_EQ(allData, noList);
   EXPECT_TRUE(allData.getMemberKinds().empty());
 }
diff --git a/clang/unittests/CIR/RecordTypeMetadataTest.cpp b/clang/unittests/CIR/RecordTypeMetadataTest.cpp
index 8118055920886..083a85560fec6 100644
--- a/clang/unittests/CIR/RecordTypeMetadataTest.cpp
+++ b/clang/unittests/CIR/RecordTypeMetadataTest.cpp
@@ -63,7 +63,7 @@ TEST_F(RecordLayoutAttrTest, HighAlignment) {
 TEST_F(RecordLayoutAttrTest, RecordTypeUnchanged) {
   IntType i32 = IntType::get(&context, 32, true);
   auto ty = StructType::get(&context, getName("Foo"), /*is_class=*/false);
-  ty.complete({i32, i32}, /*packed=*/false, /*padded=*/false);
+  ty.complete({i32, i32}, /*packed=*/false);
   EXPECT_TRUE(ty.isComplete());
   EXPECT_EQ(ty.getMembers().size(), 2u);
 }



More information about the llvm-branch-commits mailing list