[clang] [CIR] Require every record member to specify its kind (PR #215174)

Adam Smith via cfe-commits cfe-commits at lists.llvm.org
Wed Aug 12 15:02:36 PDT 2026


https://github.com/adams381 updated https://github.com/llvm/llvm-project/pull/215174

>From 963e51ba1b0e371ceef43111d1388b3bf02b5812 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Sun, 9 Aug 2026 09:35:09 -0700
Subject: [PATCH 1/6] [CIR] Let a record type mark what each member holds

A struct's `padded` bool only says that padding exists somewhere in the record.
It cannot say which member, and it cannot tell compiler-inserted padding from
storage the source declared that holds no ABI data, such as an unnamed
bit-field unit.  Those two need to differ, because padding is reusable tail
padding and declared storage is not, so they give different data sizes.

Give each member a mark instead: unmarked for source data, `pad`, or `empty`.
A record is then empty for the ABI when no member holds data, which
`allMembersNonData` reads off the type.

This is the first of three PRs, and nothing populates the marks yet, so
`padded` stays for now.  Retiring it before CIRGen fills the marks in would
make every struct claim it has no padding, and the x86_64 classifier would
start counting padding arrays as data with no diagnostic.  The CIRGen PR comes
next, then the bool removal PR.

Assisted-by: Cursor / claude-opus-5
---
 clang/include/clang/CIR/Dialect/IR/CIRTypes.h |  17 +-
 .../include/clang/CIR/Dialect/IR/CIRTypes.td  |  87 +++++++--
 .../clang/CIR/Dialect/IR/CIRTypesDetails.h    |  97 +++++++---
 clang/lib/CIR/Dialect/IR/CIRTypes.cpp         | 178 +++++++++++++++---
 .../CIR/IR/invalid-record-member-kinds.cir    |  41 ++++
 clang/test/CIR/IR/struct.cir                  |  59 +++++-
 clang/unittests/CIR/CMakeLists.txt            |   1 +
 clang/unittests/CIR/RecordMemberKindTest.cpp  | 169 +++++++++++++++++
 8 files changed, 570 insertions(+), 79 deletions(-)
 create mode 100644 clang/test/CIR/IR/invalid-record-member-kinds.cir
 create mode 100644 clang/unittests/CIR/RecordMemberKindTest.cpp

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.h b/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
index f72d10d236612..f93e9ad24b349 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
@@ -122,6 +122,7 @@ class RecordType : public mlir::Type {
   bool isComplete() const { return !isIncomplete(); }
   bool getPacked() const;
   bool getPadded() const;
+  llvm::ArrayRef<RecordMemberKind> getMemberKinds() const;
 
   bool isClass() const;
   bool isStruct() const;
@@ -133,7 +134,8 @@ class RecordType : public mlir::Type {
   std::string getPrefixedName() const;
 
   void complete(llvm::ArrayRef<mlir::Type> members, bool packed, bool padded,
-                mlir::Type padding = {});
+                mlir::Type padding = {},
+                llvm::ArrayRef<RecordMemberKind> memberKinds = {});
   uint64_t getElementOffset(const mlir::DataLayout &dataLayout,
                             unsigned idx) const;
   bool isLayoutIdentical(const RecordType &other);
@@ -143,6 +145,19 @@ class RecordType : public mlir::Type {
   void removeABIConversionNamePrefix();
 };
 
+/// Drop a member-kind list that marks nothing, so that a record whose members
+/// all hold data has exactly one spelling.  Two storage keys that print
+/// identically would otherwise give two unequal types no reader could tell
+/// apart.
+llvm::ArrayRef<RecordMemberKind>
+normalizeRecordMemberKinds(llvm::ArrayRef<RecordMemberKind> memberKinds);
+
+/// Whether no member of \p recTy holds data, which makes the record empty for
+/// the ABI.  Vacuously true for a complete record with no members, and false
+/// for an incomplete one, whose members are not known yet.  A union's
+/// tail-padding slot is not a member and does not count.
+bool allMembersNonData(RecordType recTy);
+
 } // namespace cir
 
 #endif // CLANG_CIR_DIALECT_IR_CIRTYPES_H
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
index 29afaa6d41f4b..365b96cd8e86f 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
@@ -621,6 +621,37 @@ def CIR_VoidType : CIR_Type<"Void", "void"> {
   }];
 }
 
+//===----------------------------------------------------------------------===//
+// RecordMemberKind
+//
+// What a record member holds, for members that do not hold source data.
+//===----------------------------------------------------------------------===//
+
+def CIR_RecordMemberKind : CIR_I32EnumAttr<
+    "RecordMemberKind", "what a record member holds", [
+  I32EnumAttrCase<"Data", 0, "data">,
+  I32EnumAttrCase<"Pad", 1, "pad">,
+  I32EnumAttrCase<"Empty", 2, "empty">
+]> {
+  let description = [{
+    Distinguishes a record member that holds source data from one that does
+    not.  `pad` is storage the compiler inserted to place a later member at its
+    required offset, and is reusable tail padding when it trails the record.
+    `empty` is storage the source declared that carries no data for argument
+    passing: an unnamed bit-field unit, or a field of a record that is empty for
+    the ABI.  Everything else, including a vtable pointer, a base subobject, and
+    a bit-field unit with a named occupant, is `data`.
+
+    A record is empty for the ABI when no member is `data`, which is vacuously
+    true for a record with no members.  The distinction between `pad` and
+    `empty` is load-bearing beyond that: only `pad` is reusable, so a record
+    whose trailing member is an unnamed bit-field unit keeps that unit in its
+    data size.
+  }];
+
+  let genSpecializedAttr = 0;
+}
+
 //===----------------------------------------------------------------------===//
 // StructType
 //
@@ -656,6 +687,11 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [
     plain struct declarations.  Both are semantically identical; the keyword
     preserves the original source spelling.
 
+    A member may carry a `pad` or `empty` mark, described by
+    `CIR_RecordMemberKind`, saying that it holds no source data.  An unmarked
+    member holds data, and a record whose members are all unmarked carries no
+    mark list at all.
+
     Examples:
 
     ```
@@ -665,6 +701,8 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [
         !anonymous    = !cir.struct<{!u8i}>
         !rec_packed   = !cir.struct<"p1" packed {!u8i, !u8i}>
         !rec_padded   = !cir.struct<"p2" padded {!u8i, !u8i}>
+        !rec_pad      = !cir.struct<"p3" {!u8i, pad !cir.array<!u8i x 3>}>
+        !rec_empty    = !cir.struct<"e" {empty !u8i}>
         !recursive    = !cir.struct<"Node" {!cir.ptr<!cir.struct<"Node">>}>
     ```
   }];
@@ -675,6 +713,7 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [
     "bool":$incomplete,
     "bool":$packed,
     "bool":$padded,
+    OptionalArrayRefParameter<"cir::RecordMemberKind">:$member_kinds,
     "bool":$is_class
   );
 
@@ -692,10 +731,11 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [
       "mlir::StringAttr":$name,
       "bool":$packed,
       "bool":$padded,
-      "bool":$is_class
+      "bool":$is_class,
+      CArg<"llvm::ArrayRef<cir::RecordMemberKind>", "{}">:$member_kinds
     ), [{
       return $_get($_ctxt, members, name, /*incomplete=*/false, packed, padded,
-                   is_class);
+                   cir::normalizeRecordMemberKinds(member_kinds), is_class);
     }]>,
 
     // Create an identified and incomplete struct/class type.
@@ -704,8 +744,9 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [
       "bool":$is_class
     ), [{
       return $_get($_ctxt, /*members=*/llvm::ArrayRef<mlir::Type>{}, name,
-                   /*incomplete=*/true, /*packed=*/false,
-                   /*padded=*/false, is_class);
+                   /*incomplete=*/true, /*packed=*/false, /*padded=*/false,
+                   /*member_kinds=*/llvm::ArrayRef<cir::RecordMemberKind>{},
+                   is_class);
     }]>,
 
     // Create an anonymous struct/class type (always complete).
@@ -713,10 +754,12 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [
       "llvm::ArrayRef<mlir::Type>":$members,
       "bool":$packed,
       "bool":$padded,
-      "bool":$is_class
+      "bool":$is_class,
+      CArg<"llvm::ArrayRef<cir::RecordMemberKind>", "{}">:$member_kinds
     ), [{
       return $_get($_ctxt, members, mlir::StringAttr{}, /*incomplete=*/false,
-                   packed, padded, is_class);
+                   packed, padded,
+                   cir::normalizeRecordMemberKinds(member_kinds), is_class);
     }]>
   ];
 
@@ -740,11 +783,14 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [
     }
 
     void complete(llvm::ArrayRef<mlir::Type> members, bool packed,
-                  bool isPadded);
+                  bool isPadded,
+                  llvm::ArrayRef<cir::RecordMemberKind> memberKinds = {});
 
     uint64_t getElementOffset(const mlir::DataLayout &dataLayout,
                               unsigned idx) const;
 
+    /// Marks are provenance rather than layout, so two records that differ
+    /// only in how their members were produced are layout-identical.
     bool isLayoutIdentical(const StructType &other);
 
     // Checks the name of this record to check if it is a 'after' (or during)
@@ -800,7 +846,9 @@ def CIR_UnionType : CIR_Type<"Union", "union", [
      - Anonymous: no name and a known body.
 
     Padded unions carry an explicit tail-padding type to ensure the LLVM struct
-    that models the union has the correct byte size.
+    that models the union has the correct byte size.  That slot is separate
+    from the per-member marks described by `CIR_RecordMemberKind`, which say
+    what each variant holds.  The parser rejects a mark on that slot.
 
     Examples:
 
@@ -809,6 +857,7 @@ def CIR_UnionType : CIR_Type<"Union", "union", [
         !u_incomplete = !cir.union<"U" incomplete>
         !u_anonymous  = !cir.union<{!s32i, !u8i}>
         !u_padded     = !cir.union<"U" {!s32i, !u8i}, padding = {!u8i}>
+        !u_empty      = !cir.union<"U" {empty !u8i}>
     ```
   }];
 
@@ -817,7 +866,8 @@ def CIR_UnionType : CIR_Type<"Union", "union", [
     OptionalParameter<"mlir::StringAttr">:$name,
     "bool":$incomplete,
     "bool":$packed,
-    OptionalParameter<"mlir::Type">:$padding
+    OptionalParameter<"mlir::Type">:$padding,
+    OptionalArrayRefParameter<"cir::RecordMemberKind">:$member_kinds
   );
 
   // StorageClass is defined in C++ for mutability.
@@ -833,27 +883,31 @@ def CIR_UnionType : CIR_Type<"Union", "union", [
       "llvm::ArrayRef<mlir::Type>":$members,
       "mlir::StringAttr":$name,
       "bool":$packed,
-      CArg<"mlir::Type", "{}">:$padding
+      CArg<"mlir::Type", "{}">:$padding,
+      CArg<"llvm::ArrayRef<cir::RecordMemberKind>", "{}">:$member_kinds
     ), [{
       return $_get($_ctxt, members, name, /*incomplete=*/false, packed,
-                   padding);
+                   padding, cir::normalizeRecordMemberKinds(member_kinds));
     }]>,
 
     // Create an identified and incomplete union type.
     TypeBuilder<(ins "mlir::StringAttr":$name), [{
       return $_get($_ctxt, /*members=*/llvm::ArrayRef<mlir::Type>{}, name,
                    /*incomplete=*/true, /*packed=*/false,
-                   /*padding=*/mlir::Type{});
+                   /*padding=*/mlir::Type{},
+                   /*member_kinds=*/llvm::ArrayRef<cir::RecordMemberKind>{});
     }]>,
 
     // Create an anonymous union type (always complete).
     TypeBuilder<(ins
       "llvm::ArrayRef<mlir::Type>":$members,
       "bool":$packed,
-      CArg<"mlir::Type", "{}">:$padding
+      CArg<"mlir::Type", "{}">:$padding,
+      CArg<"llvm::ArrayRef<cir::RecordMemberKind>", "{}">:$member_kinds
     ), [{
       return $_get($_ctxt, members, mlir::StringAttr{}, /*incomplete=*/false,
-                   packed, padding);
+                   packed, padding,
+                   cir::normalizeRecordMemberKinds(member_kinds));
     }]>
   ];
 
@@ -886,12 +940,15 @@ def CIR_UnionType : CIR_Type<"Union", "union", [
                                           llvm::ArrayRef<mlir::Type> members);
 
     void complete(llvm::ArrayRef<mlir::Type> members, bool packed,
-                  mlir::Type padding = {});
+                  mlir::Type padding = {},
+                  llvm::ArrayRef<cir::RecordMemberKind> memberKinds = {});
 
     uint64_t getElementOffset(const mlir::DataLayout &, unsigned) const {
       return 0;
     }
 
+    /// Marks are provenance rather than layout, so two unions that differ only
+    /// in how their members were produced are layout-identical.
     bool isLayoutIdentical(const UnionType &other);
 
     // Checks the name of this record to check if it is a 'after' (or during)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypesDetails.h b/clang/include/clang/CIR/Dialect/IR/CIRTypesDetails.h
index e94e1d81ff4c6..123fa059cfeac 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypesDetails.h
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypesDetails.h
@@ -33,12 +33,14 @@ struct StructTypeStorage : public mlir::TypeStorage {
     bool incomplete;
     bool packed;
     bool padded;
+    llvm::ArrayRef<RecordMemberKind> member_kinds;
     bool is_class;
 
     KeyTy(llvm::ArrayRef<mlir::Type> members, mlir::StringAttr name,
-          bool incomplete, bool packed, bool padded, bool is_class)
+          bool incomplete, bool packed, bool padded,
+          llvm::ArrayRef<RecordMemberKind> member_kinds, bool is_class)
         : members(members), name(name), incomplete(incomplete), packed(packed),
-          padded(padded), is_class(is_class) {}
+          padded(padded), member_kinds(member_kinds), is_class(is_class) {}
   };
 
   llvm::ArrayRef<mlir::Type> members;
@@ -46,56 +48,73 @@ struct StructTypeStorage : public mlir::TypeStorage {
   bool incomplete;
   bool packed;
   bool padded;
+  llvm::ArrayRef<RecordMemberKind> member_kinds;
   bool is_class;
 
   StructTypeStorage(llvm::ArrayRef<mlir::Type> members, mlir::StringAttr name,
-                    bool incomplete, bool packed, bool padded, bool is_class)
+                    bool incomplete, bool packed, bool padded,
+                    llvm::ArrayRef<RecordMemberKind> member_kinds,
+                    bool is_class)
       : members(members), name(name), incomplete(incomplete), packed(packed),
-        padded(padded), is_class(is_class) {
+        padded(padded), member_kinds(member_kinds), is_class(is_class) {
     assert((name || !incomplete) && "Incomplete records must have a name");
+    assert((member_kinds.empty() || member_kinds.size() == members.size()) &&
+           "member kind list must cover every member");
   }
 
   KeyTy getAsKey() const {
-    return KeyTy(members, name, incomplete, packed, padded, is_class);
+    return KeyTy(members, name, incomplete, packed, padded, member_kinds,
+                 is_class);
   }
 
   bool operator==(const KeyTy &key) const {
     if (name)
       return (name == key.name) && (is_class == key.is_class);
-    return std::tie(members, name, incomplete, packed, padded, is_class) ==
-           std::tie(key.members, key.name, key.incomplete, key.packed,
-                    key.padded, key.is_class);
+    return std::tie(members, name, incomplete, packed, padded, member_kinds,
+                    is_class) == std::tie(key.members, key.name, key.incomplete,
+                                          key.packed, key.padded,
+                                          key.member_kinds, key.is_class);
   }
 
   static llvm::hash_code hashKey(const KeyTy &key) {
     if (key.name)
       return llvm::hash_combine(key.name, key.is_class);
     return llvm::hash_combine(key.members, key.incomplete, key.packed,
-                              key.padded, key.is_class);
+                              key.padded, key.member_kinds, key.is_class);
   }
 
   static StructTypeStorage *construct(mlir::TypeStorageAllocator &allocator,
                                       const KeyTy &key) {
-    return new (allocator.allocate<StructTypeStorage>())
-        StructTypeStorage(allocator.copyInto(key.members), key.name,
-                          key.incomplete, key.packed, key.padded, key.is_class);
+    return new (allocator.allocate<StructTypeStorage>()) StructTypeStorage(
+        allocator.copyInto(key.members), key.name, key.incomplete, key.packed,
+        key.padded, allocator.copyInto(key.member_kinds), key.is_class);
   }
 
   /// Mutates the members and attributes of an identified struct/class.
   llvm::LogicalResult mutate(mlir::TypeStorageAllocator &allocator,
                              llvm::ArrayRef<mlir::Type> members, bool packed,
-                             bool padded) {
+                             bool padded,
+                             llvm::ArrayRef<RecordMemberKind> memberKinds) {
     if (!name)
       return llvm::failure();
 
+    // A second completion must agree with the first in every parameter,
+    // including the marks: otherwise it silently keeps the marks it was given
+    // the first time.
     if (!incomplete)
-      return mlir::success((this->members == members) &&
-                           (this->packed == packed) &&
-                           (this->padded == padded));
+      return mlir::success(
+          (this->members == members) && (this->packed == packed) &&
+          (this->padded == padded) && (this->member_kinds == memberKinds));
+
+    // mutate is the one entrance verify() never sees, so check the length here
+    // rather than leave it to an assert.
+    if (!memberKinds.empty() && memberKinds.size() != members.size())
+      return llvm::failure();
 
     this->members = allocator.copyInto(members);
     this->packed = packed;
     this->padded = padded;
+    this->member_kinds = allocator.copyInto(memberKinds);
     incomplete = false;
     return llvm::success();
   }
@@ -113,11 +132,13 @@ struct UnionTypeStorage : public mlir::TypeStorage {
     bool incomplete;
     bool packed;
     mlir::Type padding;
+    llvm::ArrayRef<RecordMemberKind> member_kinds;
 
     KeyTy(llvm::ArrayRef<mlir::Type> members, mlir::StringAttr name,
-          bool incomplete, bool packed, mlir::Type padding)
+          bool incomplete, bool packed, mlir::Type padding,
+          llvm::ArrayRef<RecordMemberKind> member_kinds)
         : members(members), name(name), incomplete(incomplete), packed(packed),
-          padding(padding) {}
+          padding(padding), member_kinds(member_kinds) {}
   };
 
   llvm::ArrayRef<mlir::Type> members;
@@ -125,55 +146,69 @@ struct UnionTypeStorage : public mlir::TypeStorage {
   bool incomplete;
   bool packed;
   mlir::Type padding;
+  llvm::ArrayRef<RecordMemberKind> member_kinds;
 
   UnionTypeStorage(llvm::ArrayRef<mlir::Type> members, mlir::StringAttr name,
-                   bool incomplete, bool packed, mlir::Type padding)
+                   bool incomplete, bool packed, mlir::Type padding,
+                   llvm::ArrayRef<RecordMemberKind> member_kinds)
       : members(members), name(name), incomplete(incomplete), packed(packed),
-        padding(padding) {
+        padding(padding), member_kinds(member_kinds) {
     assert((name || !incomplete) && "Incomplete records must have a name");
+    assert((member_kinds.empty() || member_kinds.size() == members.size()) &&
+           "member kind list must cover every member");
   }
 
   KeyTy getAsKey() const {
-    return KeyTy(members, name, incomplete, packed, padding);
+    return KeyTy(members, name, incomplete, packed, padding, member_kinds);
   }
 
   bool operator==(const KeyTy &key) const {
     if (name)
       return name == key.name;
-    return std::tie(members, name, incomplete, packed, padding) ==
+    return std::tie(members, name, incomplete, packed, padding, member_kinds) ==
            std::tie(key.members, key.name, key.incomplete, key.packed,
-                    key.padding);
+                    key.padding, key.member_kinds);
   }
 
   static llvm::hash_code hashKey(const KeyTy &key) {
     if (key.name)
       return llvm::hash_combine(key.name);
     return llvm::hash_combine(key.members, key.incomplete, key.packed,
-                              key.padding);
+                              key.padding, key.member_kinds);
   }
 
   static UnionTypeStorage *construct(mlir::TypeStorageAllocator &allocator,
                                      const KeyTy &key) {
-    return new (allocator.allocate<UnionTypeStorage>())
-        UnionTypeStorage(allocator.copyInto(key.members), key.name,
-                         key.incomplete, key.packed, key.padding);
+    return new (allocator.allocate<UnionTypeStorage>()) UnionTypeStorage(
+        allocator.copyInto(key.members), key.name, key.incomplete, key.packed,
+        key.padding, allocator.copyInto(key.member_kinds));
   }
 
   /// Mutates the members and attributes of an identified union.
   llvm::LogicalResult mutate(mlir::TypeStorageAllocator &allocator,
                              llvm::ArrayRef<mlir::Type> members, bool packed,
-                             mlir::Type padding) {
+                             mlir::Type padding,
+                             llvm::ArrayRef<RecordMemberKind> memberKinds) {
     if (!name)
       return llvm::failure();
 
+    // A second completion must agree with the first in every parameter,
+    // including the marks: otherwise it silently keeps the marks it was given
+    // the first time.
     if (!incomplete)
-      return mlir::success((this->members == members) &&
-                           (this->packed == packed) &&
-                           (this->padding == padding));
+      return mlir::success(
+          (this->members == members) && (this->packed == packed) &&
+          (this->padding == padding) && (this->member_kinds == memberKinds));
+
+    // mutate is the one entrance verify() never sees, so check the length here
+    // rather than leave it to an assert.
+    if (!memberKinds.empty() && memberKinds.size() != members.size())
+      return llvm::failure();
 
     this->members = allocator.copyInto(members);
     this->packed = packed;
     this->padding = padding;
+    this->member_kinds = allocator.copyInto(memberKinds);
     incomplete = false;
     return llvm::success();
   }
diff --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
index af1bbdcd64fea..55fbba11ddb50 100644
--- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
@@ -152,31 +152,88 @@ void CIRDialect::printType(Type type, DialectAsmPrinter &os) const {
 
 // Shared helpers for StructType and UnionType parse/print.
 
-/// Parse "incomplete" or "{type, type, ...}", writing results into
-/// \p incomplete and \p members.  Returns failure if member parsing fails.
+llvm::ArrayRef<RecordMemberKind>
+cir::normalizeRecordMemberKinds(llvm::ArrayRef<RecordMemberKind> memberKinds) {
+  if (llvm::all_of(memberKinds, [](RecordMemberKind kind) {
+        return kind == RecordMemberKind::Data;
+      }))
+    return {};
+  return memberKinds;
+}
+
+/// A mark list either is absent or names every member.  An incomplete record
+/// has no members, so a mark on one is caught by the same length check.
+static mlir::LogicalResult
+verifyRecordMemberKinds(function_ref<mlir::InFlightDiagnostic()> emitError,
+                        size_t numMembers,
+                        llvm::ArrayRef<RecordMemberKind> memberKinds) {
+  if (!memberKinds.empty() && memberKinds.size() != numMembers)
+    return emitError() << "expected " << numMembers << " member kinds, got "
+                       << memberKinds.size();
+  return mlir::success();
+}
+
+/// Parse the optional mark that precedes a member type.  Only a mark keyword is
+/// consumed, so a member spelled as a bare builtin type still reaches the type
+/// parser.  A data member is spelled without a mark, and accepting 'data' would
+/// give a record two spellings that print the same way, so it is named here
+/// only to reject it.
+static mlir::ParseResult parseMemberKind(mlir::AsmParser &parser,
+                                         RecordMemberKind &kind) {
+  static const llvm::StringRef marks[] = {"pad", "empty", "data"};
+  kind = RecordMemberKind::Data;
+  llvm::StringRef keyword;
+  const llvm::SMLoc loc = parser.getCurrentLocation();
+  if (parser.parseOptionalKeyword(&keyword, marks).failed())
+    return mlir::success();
+
+  if (keyword == "data") {
+    parser.emitError(loc, "a data member is spelled without a mark");
+    return mlir::failure();
+  }
+  kind = *symbolizeRecordMemberKind(keyword);
+
+  const llvm::SMLoc secondLoc = parser.getCurrentLocation();
+  if (parser.parseOptionalKeyword(&keyword, marks).succeeded()) {
+    parser.emitError(secondLoc, "a record member takes at most one kind mark");
+    return mlir::failure();
+  }
+  return mlir::success();
+}
+
+/// Parse "incomplete" or "{[mark] type, [mark] type, ...}", writing results
+/// into \p incomplete, \p members and \p memberKinds.  Returns failure if
+/// member parsing fails.
 static mlir::ParseResult
 parseRecordBody(mlir::AsmParser &parser, bool &incomplete,
-                llvm::SmallVector<mlir::Type> &members) {
+                llvm::SmallVector<mlir::Type> &members,
+                llvm::SmallVectorImpl<RecordMemberKind> &memberKinds) {
   assert(incomplete && "caller must pre-initialize incomplete to true");
   if (parser.parseOptionalKeyword("incomplete").succeeded())
     return mlir::success();
   incomplete = false;
   return parser.parseCommaSeparatedList(
-      AsmParser::Delimiter::Braces, [&parser, &members]() {
+      AsmParser::Delimiter::Braces,
+      [&parser, &members, &memberKinds]() -> mlir::ParseResult {
+        RecordMemberKind kind;
+        if (parseMemberKind(parser, kind).failed())
+          return mlir::failure();
+        memberKinds.push_back(kind);
         return parser.parseType(members.emplace_back());
       });
 }
 
 /// Print a complete CIR record body:
 ///   '<' ['class '] [name] ['packed '] ['padded '] body '>'
-/// where body is "incomplete" or "{members[, padding = {type}]}".
+/// where body is "incomplete" or "{[mark] members[, padding = {type}]}".
 /// RecordTy must be a mutable MLIR type (StructType or UnionType).
 template <typename RecordTy>
 static void printRecordBody(mlir::AsmPrinter &printer, RecordTy self,
                             mlir::StringAttr name, bool hasClassPrefix,
                             bool isPacked, bool isPadded, bool isIncomplete,
                             llvm::ArrayRef<mlir::Type> members,
-                            mlir::Type padding = {}) {
+                            mlir::Type padding,
+                            llvm::ArrayRef<RecordMemberKind> memberKinds) {
   printer << '<';
   if (hasClassPrefix)
     printer << "class ";
@@ -200,7 +257,14 @@ static void printRecordBody(mlir::AsmPrinter &printer, RecordTy self,
     printer << "incomplete";
   } else {
     printer << "{";
-    llvm::interleaveComma(members, printer);
+    for (auto [idx, member] : llvm::enumerate(members)) {
+      if (idx)
+        printer << ", ";
+      if (idx < memberKinds.size() &&
+          memberKinds[idx] != RecordMemberKind::Data)
+        printer << stringifyRecordMemberKind(memberKinds[idx]) << ' ';
+      printer.printType(member);
+    }
     printer << "}";
     if (padding) {
       printer << ", padding = {";
@@ -257,29 +321,31 @@ Type StructType::parse(mlir::AsmParser &parser) {
 
   bool incomplete = true;
   llvm::SmallVector<mlir::Type> members;
-  if (parseRecordBody(parser, incomplete, members).failed())
+  llvm::SmallVector<RecordMemberKind> memberKinds;
+  if (parseRecordBody(parser, incomplete, members, memberKinds).failed())
     return {};
 
   if (parser.parseGreater())
     return {};
 
   ArrayRef<mlir::Type> membersRef(members);
+  ArrayRef<RecordMemberKind> kindsRef = normalizeRecordMemberKinds(memberKinds);
   mlir::Type type = {};
   if (name && incomplete) {
     type = StructType::getChecked(eLoc, context, name, is_class);
   } else if (!name && !incomplete) {
     type = StructType::getChecked(eLoc, context, membersRef, packed, padded,
-                                  is_class);
+                                  is_class, kindsRef);
     if (!type)
       return {};
   } else if (!incomplete) {
     type = StructType::getChecked(eLoc, context, membersRef, name, packed,
-                                  padded, is_class);
+                                  padded, is_class, kindsRef);
     if (!type)
       return {};
     if (auto structTy = mlir::dyn_cast<StructType>(type))
       if (structTy.isIncomplete())
-        structTy.complete(membersRef, packed, padded);
+        structTy.complete(membersRef, packed, padded, kindsRef);
     assert(!cir::MissingFeatures::astRecordDeclAttr());
   } else {
     parser.emitError(loc, "anonymous records must be complete");
@@ -291,16 +357,19 @@ Type StructType::parse(mlir::AsmParser &parser) {
 
 void StructType::print(mlir::AsmPrinter &printer) const {
   printRecordBody(printer, *this, getName(), isClass(), getPacked(),
-                  getPadded(), isIncomplete(), getMembers());
+                  getPadded(), isIncomplete(), getMembers(), /*padding=*/{},
+                  getMemberKinds());
 }
 
 mlir::LogicalResult
 StructType::verify(function_ref<mlir::InFlightDiagnostic()> emitError,
                    llvm::ArrayRef<mlir::Type> members, mlir::StringAttr name,
-                   bool incomplete, bool packed, bool padded, bool is_class) {
+                   bool incomplete, bool packed, bool padded,
+                   llvm::ArrayRef<RecordMemberKind> member_kinds,
+                   bool is_class) {
   if (name && name.getValue().empty())
     return emitError() << "identified records cannot have an empty name";
-  return mlir::success();
+  return verifyRecordMemberKinds(emitError, members.size(), member_kinds);
 }
 
 // Accessors are hand-written because genStorageClass = 0 suppresses generated
@@ -313,6 +382,9 @@ bool StructType::isIncomplete() const { return getImpl()->incomplete; }
 bool StructType::getIncomplete() const { return getImpl()->incomplete; }
 bool StructType::getPacked() const { return getImpl()->packed; }
 bool StructType::getPadded() const { return getImpl()->padded; }
+llvm::ArrayRef<RecordMemberKind> StructType::getMemberKinds() const {
+  return getImpl()->member_kinds;
+}
 bool StructType::getIsClass() const { return getImpl()->is_class; }
 
 bool StructType::isABIConvertedRecord() const {
@@ -333,9 +405,11 @@ void StructType::removeABIConversionNamePrefix() {
         recordName.getType());
 }
 
-void StructType::complete(ArrayRef<Type> members, bool packed, bool padded) {
+void StructType::complete(ArrayRef<Type> members, bool packed, bool padded,
+                          ArrayRef<RecordMemberKind> memberKinds) {
   assert(!cir::MissingFeatures::astRecordDeclAttr());
-  if (mutate(members, packed, padded).failed())
+  if (mutate(members, packed, padded, normalizeRecordMemberKinds(memberKinds))
+          .failed())
     llvm_unreachable("failed to complete struct");
 }
 
@@ -390,10 +464,12 @@ Type UnionType::parse(mlir::AsmParser &parser) {
 
   bool incomplete = true;
   llvm::SmallVector<mlir::Type> members;
-  if (parseRecordBody(parser, incomplete, members).failed())
+  llvm::SmallVector<RecordMemberKind> memberKinds;
+  if (parseRecordBody(parser, incomplete, members, memberKinds).failed())
     return {};
 
-  // Optional tail-padding slot: ", padding = { <type> }".
+  // Optional tail-padding slot: ", padding = { <type> }".  It is not a variant
+  // and so takes no mark.
   if (!incomplete && parser.parseOptionalComma().succeeded()) {
     if (parser.parseKeyword("padding").failed())
       return {};
@@ -401,6 +477,13 @@ Type UnionType::parse(mlir::AsmParser &parser) {
       return {};
     if (parser.parseLBrace().failed())
       return {};
+    const llvm::SMLoc paddingLoc = parser.getCurrentLocation();
+    llvm::StringRef paddingKeyword;
+    static const llvm::StringRef marks[] = {"pad", "empty", "data"};
+    if (parser.parseOptionalKeyword(&paddingKeyword, marks).succeeded()) {
+      parser.emitError(paddingLoc, "a union's tail padding takes no kind mark");
+      return {};
+    }
     if (parser.parseType(padding).failed())
       return {};
     if (parser.parseRBrace().failed())
@@ -411,21 +494,23 @@ Type UnionType::parse(mlir::AsmParser &parser) {
     return {};
 
   ArrayRef<mlir::Type> membersRef(members);
+  ArrayRef<RecordMemberKind> kindsRef = normalizeRecordMemberKinds(memberKinds);
   mlir::Type type = {};
   if (name && incomplete) {
     type = UnionType::getChecked(eLoc, context, name);
   } else if (!name && !incomplete) {
-    type = UnionType::getChecked(eLoc, context, membersRef, packed, padding);
+    type = UnionType::getChecked(eLoc, context, membersRef, packed, padding,
+                                 kindsRef);
     if (!type)
       return {};
   } else if (!incomplete) {
-    type =
-        UnionType::getChecked(eLoc, context, membersRef, name, packed, padding);
+    type = UnionType::getChecked(eLoc, context, membersRef, name, packed,
+                                 padding, kindsRef);
     if (!type)
       return {};
     if (auto unionTy = mlir::dyn_cast<UnionType>(type))
       if (unionTy.isIncomplete())
-        unionTy.complete(membersRef, packed, padding);
+        unionTy.complete(membersRef, packed, padding, kindsRef);
     assert(!cir::MissingFeatures::astRecordDeclAttr());
   } else {
     parser.emitError(loc, "anonymous records must be complete");
@@ -438,16 +523,22 @@ Type UnionType::parse(mlir::AsmParser &parser) {
 void UnionType::print(mlir::AsmPrinter &printer) const {
   printRecordBody(printer, *this, getName(), /*hasClassPrefix=*/false,
                   getPacked(), /*isPadded=*/false, isIncomplete(), getMembers(),
-                  getPadding());
+                  getPadding(), getMemberKinds());
 }
 
 mlir::LogicalResult
 UnionType::verify(function_ref<mlir::InFlightDiagnostic()> emitError,
                   llvm::ArrayRef<mlir::Type> members, mlir::StringAttr name,
-                  bool incomplete, bool packed, mlir::Type padding) {
+                  bool incomplete, bool packed, mlir::Type padding,
+                  llvm::ArrayRef<RecordMemberKind> member_kinds) {
   if (name && name.getValue().empty())
     return emitError() << "identified records cannot have an empty name";
-  return mlir::success();
+  // A union's variants all start at offset zero, so there is no inter-member
+  // padding for a pad mark to describe.  Its tail padding lives in the separate
+  // padding slot.
+  if (llvm::is_contained(member_kinds, RecordMemberKind::Pad))
+    return emitError() << "a union member cannot be marked pad";
+  return verifyRecordMemberKinds(emitError, members.size(), member_kinds);
 }
 
 // Accessors.
@@ -460,6 +551,9 @@ bool UnionType::getIncomplete() const { return getImpl()->incomplete; }
 bool UnionType::getPacked() const { return getImpl()->packed; }
 bool UnionType::getPadded() const { return getPadding() ? true : false; }
 mlir::Type UnionType::getPadding() const { return getImpl()->padding; }
+llvm::ArrayRef<RecordMemberKind> UnionType::getMemberKinds() const {
+  return getImpl()->member_kinds;
+}
 
 bool UnionType::isABIConvertedRecord() const {
   return getName() && getName().getValue().starts_with(abi_conversion_prefix);
@@ -480,9 +574,11 @@ void UnionType::removeABIConversionNamePrefix() {
 }
 
 void UnionType::complete(ArrayRef<Type> members, bool packed,
-                         mlir::Type padding) {
+                         mlir::Type padding,
+                         ArrayRef<RecordMemberKind> memberKinds) {
   assert(!cir::MissingFeatures::astRecordDeclAttr());
-  if (mutate(members, packed, padding).failed())
+  if (mutate(members, packed, padding, normalizeRecordMemberKinds(memberKinds))
+          .failed())
     llvm_unreachable("failed to complete union");
 }
 
@@ -541,6 +637,11 @@ bool RecordType::getPadded() const {
     return s.getPadded();
   return mlir::cast<UnionType>(*this).getPadded();
 }
+llvm::ArrayRef<RecordMemberKind> RecordType::getMemberKinds() const {
+  if (auto s = mlir::dyn_cast<StructType>(*this))
+    return s.getMemberKinds();
+  return mlir::cast<UnionType>(*this).getMemberKinds();
+}
 bool RecordType::isClass() const {
   if (auto s = mlir::dyn_cast<StructType>(*this))
     return s.isClass();
@@ -560,13 +661,15 @@ std::string RecordType::getPrefixedName() const {
   return getKindAsStr() + "." + getName().getValue().str();
 }
 void RecordType::complete(ArrayRef<Type> members, bool packed, bool padded,
-                          mlir::Type padding) {
+                          mlir::Type padding,
+                          ArrayRef<RecordMemberKind> memberKinds) {
   if (auto s = mlir::dyn_cast<StructType>(*this))
-    return s.complete(members, packed, padded);
+    return s.complete(members, packed, padded, memberKinds);
   // Unions derive padded from padding; assert the caller is consistent.
   assert((!padded || padding) &&
          "padded=true requires a non-null padding type");
-  return mlir::cast<UnionType>(*this).complete(members, packed, padding);
+  return mlir::cast<UnionType>(*this).complete(members, packed, padding,
+                                               memberKinds);
 }
 uint64_t RecordType::getElementOffset(const mlir::DataLayout &dataLayout,
                                       unsigned idx) const {
@@ -603,6 +706,21 @@ void RecordType::removeABIConversionNamePrefix() {
   return mlir::cast<UnionType>(*this).removeABIConversionNamePrefix();
 }
 
+bool cir::allMembersNonData(RecordType recTy) {
+  // An incomplete record has no members yet, which must not read as vacuously
+  // holding no data.
+  if (recTy.isIncomplete())
+    return false;
+  if (recTy.getMembers().empty())
+    return true;
+  // An absent list is the canonical spelling for all-data, so a record with
+  // members and no list holds data in all of them.
+  llvm::ArrayRef<RecordMemberKind> kinds = recTy.getMemberKinds();
+  return !kinds.empty() && llvm::none_of(kinds, [](RecordMemberKind kind) {
+    return kind == RecordMemberKind::Data;
+  });
+}
+
 //===----------------------------------------------------------------------===//
 // Data Layout information for types
 //===----------------------------------------------------------------------===//
diff --git a/clang/test/CIR/IR/invalid-record-member-kinds.cir b/clang/test/CIR/IR/invalid-record-member-kinds.cir
new file mode 100644
index 0000000000000..1709298da10ad
--- /dev/null
+++ b/clang/test/CIR/IR/invalid-record-member-kinds.cir
@@ -0,0 +1,41 @@
+// RUN: cir-opt %s -verify-diagnostics -split-input-file
+
+!u8i = !cir.int<u, 8>
+// expected-error @below {{expected non-function type}}
+!rec_S = !cir.struct<"S" {bogus !u8i}>
+
+module {}
+
+// -----
+
+!u8i = !cir.int<u, 8>
+// expected-error @below {{a data member is spelled without a mark}}
+!rec_S = !cir.struct<"S" {data !u8i}>
+
+module {}
+
+// -----
+
+!u8i = !cir.int<u, 8>
+// expected-error @below {{a record member takes at most one kind mark}}
+!rec_S = !cir.struct<"S" {pad empty !u8i}>
+
+module {}
+
+// -----
+
+!u8i = !cir.int<u, 8>
+!s32i = !cir.int<s, 32>
+// expected-error @below {{a union's tail padding takes no kind mark}}
+!rec_U = !cir.union<"U" {!s32i}, padding = {pad !cir.array<!u8i x 4>}>
+
+module {}
+
+// -----
+
+!u8i = !cir.int<u, 8>
+!s32i = !cir.int<s, 32>
+// expected-error @below {{a union member cannot be marked pad}}
+!rec_U = !cir.union<"U" {!s32i, pad !u8i}>
+
+module {}
diff --git a/clang/test/CIR/IR/struct.cir b/clang/test/CIR/IR/struct.cir
index 783a56e55ed43..7321835fc1d0d 100644
--- a/clang/test/CIR/IR/struct.cir
+++ b/clang/test/CIR/IR/struct.cir
@@ -19,6 +19,7 @@
 !rec_S1 = !cir.struct<"S1" {!s32i, !s32i}>
 !rec_Sc = !cir.struct<"Sc" {!u8i, !u16i, !u32i}>
 
+// CHECK-DAG: ![[ARR_TY:rec_anon_struct[0-9]*]] = !cir.struct<packed {!s32i, !s32i, !cir.array<!s32i x 8>}>
 // CHECK-DAG: !cir.struct<{!cir.array<!cir.ptr<!u8i> x 5>}>
 // CHECK-DAG: !cir.struct<{!cir.ptr<!u8i>, !cir.ptr<!u8i>, !cir.ptr<!u8i>}>
 // CHECK-DAG: !rec_S1 = !cir.struct<"S1" {!s32i, !s32i}>
@@ -29,15 +30,46 @@
 !rec_P2 = !cir.struct<"P2" padded {!u8i, !u16i, !u32i}>
 !rec_P3 = !cir.struct<"P3" packed padded {!u8i, !u16i, !u32i}>
 
+// Members marked pad or empty
+!rec_P4 = !cir.struct<"P4" {pad !u8i}>
+!rec_P5 = !cir.struct<"P5" {empty !u8i}>
+!rec_P6 = !cir.struct<"P6" {!u32i, empty !cir.array<!u8i x 3>, pad !u8i}>
+!rec_P7 = !cir.struct<"P7" packed padded {!u8i, pad !u8i}>
+
 // CHECK-DAG: !rec_P1 = !cir.struct<"P1" packed {!s32i, !s32i}>
 // CHECK-DAG: !rec_P2 = !cir.struct<"P2" padded {!u8i, !u16i, !u32i}>
 // CHECK-DAG: !rec_P3 = !cir.struct<"P3" packed padded {!u8i, !u16i, !u32i}>
+// CHECK-DAG: !rec_P4 = !cir.struct<"P4" {pad !u8i}>
+// CHECK-DAG: !rec_P5 = !cir.struct<"P5" {empty !u8i}>
+// CHECK-DAG: !rec_P6 = !cir.struct<"P6" {!u32i, empty !cir.array<!u8i x 3>, pad !u8i}>
+// CHECK-DAG: !rec_P7 = !cir.struct<"P7" packed padded {!u8i, pad !u8i}>
+
+// Records with identical member types, spelled apart by their marks.  The
+// anonymous pair must stay two distinct types, since an anonymous record keys
+// on its whole body.
+!rec_M1 = !cir.struct<"M1" {!u8i, pad !u8i}>
+!rec_M2 = !cir.struct<"M2" {!u8i, empty !u8i}>
+!rec_anon_pad = !cir.struct<{!u8i, pad !u8i}>
+!rec_anon_empty = !cir.struct<{!u8i, empty !u8i}>
+
+// CHECK-DAG: !rec_M1 = !cir.struct<"M1" {!u8i, pad !u8i}>
+// CHECK-DAG: !rec_M2 = !cir.struct<"M2" {!u8i, empty !u8i}>
+// CHECK-DAG: !cir.struct<{!u8i, pad !u8i}>
+// CHECK-DAG: !cir.struct<{!u8i, empty !u8i}>
 
 !rec_U1 = !cir.union<"U1" {!s32i, !u8i}, padding = {!u8i}>
 !rec_U2 = !cir.union<"U2" packed {!s32i}, padding = {!cir.array<!u8i x 4>}>
+!rec_anon_u_empty = !cir.union<{!s32i, empty !u8i}>
+!rec_anon_u_plain = !cir.union<{!s32i, !u8i}>
+!rec_U3 = !cir.union<"U3" {empty !u8i}>
+!rec_U4 = !cir.union<"U4" {!s32i, empty !u8i}, padding = {!cir.array<!u8i x 4>}>
 
 // CHECK-DAG: !rec_U1 = !cir.union<"U1" {!s32i, !u8i}, padding = {!u8i}>
 // CHECK-DAG: !rec_U2 = !cir.union<"U2" packed {!s32i}, padding = {!cir.array<!u8i x 4>}>
+// CHECK-DAG: !cir.union<{!s32i, empty !u8i}>
+// CHECK-DAG: !cir.union<{!s32i, !u8i}>
+// CHECK-DAG: !rec_U3 = !cir.union<"U3" {empty !u8i}>
+// CHECK-DAG: !rec_U4 = !cir.union<"U4" {!s32i, empty !u8i}, padding = {!cir.array<!u8i x 4>}>
 
 
 // Complete a previously incomplete record
@@ -45,10 +77,19 @@
 !rec_Ac = !cir.struct<class "A" {!u8i, !s32i}>
 // CHECK-DAG: !rec_A = !cir.struct<class "A" {!u8i, !s32i}>
 
+// Complete a previously incomplete record whose members carry marks.
+!rec_B = !cir.struct<class "B" incomplete>
+!rec_Bc = !cir.struct<class "B" {!u8i, pad !cir.array<!u8i x 3>, !s32i}>
+// CHECK-DAG: !rec_B = !cir.struct<class "B" {!u8i, pad !cir.array<!u8i x 3>, !s32i}>
+
 // Test recursive struct parsing/printing.
 !rec_Node = !cir.struct<"Node" {!cir.ptr<!cir.struct<"Node">>}>
 // CHECK-DAG: !cir.struct<"Node" {!cir.ptr<!cir.struct<"Node">>}>
 
+// A mark survives the cyclic-print guard on a self-referential record.
+!rec_PadNode = !cir.struct<"PadNode" {!cir.ptr<!cir.struct<"PadNode">>, pad !u8i}>
+// CHECK-DAG: !cir.struct<"PadNode" {!cir.ptr<!cir.struct<"PadNode">>, pad !u8i}>
+
 
 
 module  {
@@ -59,7 +100,7 @@ module  {
 // CHECK: cir.global external @p1 = #cir.ptr<null> : !cir.ptr<!rec_S>
 // CHECK: cir.global external @p2 = #cir.ptr<null> : !cir.ptr<!rec_U>
 // CHECK: cir.global external @p3 = #cir.ptr<null> : !cir.ptr<!rec_C>
-// CHECK: cir.global external @arr = #cir.const_record<{#cir.int<1> : !s32i, #cir.int<2> : !s32i, #cir.zero : !cir.array<!s32i x 8>}> : !rec_anon_struct
+// CHECK: cir.global external @arr = #cir.const_record<{#cir.int<1> : !s32i, #cir.int<2> : !s32i, #cir.zero : !cir.array<!s32i x 8>}> : ![[ARR_TY]]{{$}}
 
   // Dummy function to use types and force them to be printed.
   cir.func @useTypes(%arg0: !rec_Node,
@@ -71,7 +112,21 @@ module  {
                      %arg6: !rec_P2,
                      %arg7: !rec_P3,
                      %arg8: !rec_U1,
-                     %arg9: !rec_U2) {
+                     %arg9: !rec_U2,
+                     %arg10: !rec_P4,
+                     %arg11: !rec_P5,
+                     %arg12: !rec_P6,
+                     %arg13: !rec_M1,
+                     %arg14: !rec_M2,
+                     %arg15: !rec_anon_pad,
+                     %arg16: !rec_anon_empty,
+                     %arg17: !rec_U3,
+                     %arg18: !rec_U4,
+                     %arg19: !rec_Bc,
+                     %arg20: !rec_PadNode,
+                     %arg21: !rec_P7,
+                     %arg22: !rec_anon_u_empty,
+                     %arg23: !rec_anon_u_plain) {
     cir.return
   }
 
diff --git a/clang/unittests/CIR/CMakeLists.txt b/clang/unittests/CIR/CMakeLists.txt
index f31b8d210f4f7..3779fe69b5649 100644
--- a/clang/unittests/CIR/CMakeLists.txt
+++ b/clang/unittests/CIR/CMakeLists.txt
@@ -10,6 +10,7 @@ add_distinct_clang_unittest(CIRUnitTests
   GetFloatingPointTypeTest.cpp
   IntTypeABIAlignTest.cpp
   PointerLikeTest.cpp
+  RecordMemberKindTest.cpp
   RecordTypeMetadataTest.cpp
   UnionTypeSizeTest.cpp
   VectorTypeABIAlignTest.cpp
diff --git a/clang/unittests/CIR/RecordMemberKindTest.cpp b/clang/unittests/CIR/RecordMemberKindTest.cpp
new file mode 100644
index 0000000000000..bb635a9913a99
--- /dev/null
+++ b/clang/unittests/CIR/RecordMemberKindTest.cpp
@@ -0,0 +1,169 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Unit tests for per-member record kinds: what they imply about a record's
+// emptiness for the ABI, and how they take part in type identity.
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/IR/Diagnostics.h"
+#include "mlir/IR/MLIRContext.h"
+#include "clang/CIR/Dialect/IR/CIRDialect.h"
+#include "clang/CIR/Dialect/IR/CIRTypes.h"
+#include "gtest/gtest.h"
+
+using namespace mlir;
+using namespace cir;
+
+/// Swallows verifier diagnostics and counts them, so a getChecked failure can
+/// be asserted without the error reaching stderr.
+struct ScopedDiagnosticCounter {
+  explicit ScopedDiagnosticCounter(MLIRContext &context)
+      : handler(&context, [this](mlir::Diagnostic &) { ++count; }) {}
+
+  unsigned count = 0;
+
+private:
+  mlir::ScopedDiagnosticHandler handler;
+};
+
+class RecordMemberKindTest : public ::testing::Test {
+protected:
+  RecordMemberKindTest() { context.loadDialect<cir::CIRDialect>(); }
+
+  MLIRContext context;
+
+  mlir::Location getLoc() { return mlir::UnknownLoc::get(&context); }
+
+  mlir::StringAttr getName(llvm::StringRef name) {
+    return mlir::StringAttr::get(&context, name);
+  }
+
+  IntType getU8() { return IntType::get(&context, 8, false); }
+
+  StructType makeStruct(llvm::StringRef name,
+                        llvm::ArrayRef<mlir::Type> members,
+                        llvm::ArrayRef<RecordMemberKind> kinds) {
+    auto ty = StructType::get(&context, getName(name), /*is_class=*/false);
+    ty.complete(members, /*packed=*/false, /*isPadded=*/false, kinds);
+    return ty;
+  }
+};
+
+TEST_F(RecordMemberKindTest, EmptyForTheABIWhenNoMemberHoldsData) {
+  IntType u8 = getU8();
+  // A record with no members is vacuously empty.
+  EXPECT_TRUE(allMembersNonData(makeStruct("none", {}, {})));
+  EXPECT_TRUE(
+      allMembersNonData(makeStruct("p1", {u8}, {RecordMemberKind::Pad})));
+  EXPECT_TRUE(
+      allMembersNonData(makeStruct("e1", {u8}, {RecordMemberKind::Empty})));
+  EXPECT_TRUE(allMembersNonData(makeStruct(
+      "pe", {u8, u8}, {RecordMemberKind::Pad, RecordMemberKind::Empty})));
+  // An all-data list is dropped on completion rather than stored, which is the
+  // mutate-path half of the canonicalization.
+  EXPECT_TRUE(makeStruct("d1", {u8}, {RecordMemberKind::Data})
+                  .getMemberKinds()
+                  .empty());
+  EXPECT_FALSE(allMembersNonData(makeStruct(
+      "dp", {u8, u8}, {RecordMemberKind::Data, RecordMemberKind::Pad})));
+  // A record with members and no mark list holds data in all of them.
+  EXPECT_FALSE(allMembersNonData(makeStruct("unmarked", {u8}, {})));
+}
+
+TEST_F(RecordMemberKindTest, RejectsAMarkListThatDoesNotCoverEveryMember) {
+  // The assembly syntax cannot express this, since it builds one kind per
+  // member, but a C++ caller can.
+  llvm::SmallVector<mlir::Type> members{getU8(), getU8()};
+  llvm::SmallVector<RecordMemberKind> tooFew{RecordMemberKind::Pad};
+
+  ScopedDiagnosticCounter diags(context);
+  llvm::ArrayRef<mlir::Type> membersRef(members);
+  llvm::ArrayRef<RecordMemberKind> kindsRef(tooFew);
+  EXPECT_FALSE(StructType::getChecked(getLoc(), &context, membersRef,
+                                      /*packed=*/false, /*padded=*/false,
+                                      /*is_class=*/false, kindsRef));
+  EXPECT_EQ(diags.count, 1u);
+}
+
+TEST_F(RecordMemberKindTest, RejectsPadOnAUnionMember) {
+  // A union's variants all start at offset zero, so there is no inter-member
+  // padding a pad mark could describe.
+  llvm::SmallVector<mlir::Type> members{getU8()};
+  llvm::SmallVector<RecordMemberKind> pad{RecordMemberKind::Pad};
+  llvm::SmallVector<RecordMemberKind> empty{RecordMemberKind::Empty};
+
+  ScopedDiagnosticCounter diags(context);
+  llvm::ArrayRef<mlir::Type> membersRef(members);
+  EXPECT_FALSE(UnionType::getChecked(getLoc(), &context, membersRef,
+                                     /*packed=*/false, /*padding=*/mlir::Type{},
+                                     llvm::ArrayRef<RecordMemberKind>(pad)));
+  EXPECT_EQ(diags.count, 1u);
+  EXPECT_TRUE(UnionType::getChecked(getLoc(), &context, membersRef,
+                                    /*packed=*/false, /*padding=*/mlir::Type{},
+                                    llvm::ArrayRef<RecordMemberKind>(empty)));
+  EXPECT_EQ(diags.count, 1u);
+}
+
+TEST_F(RecordMemberKindTest, AnIncompleteRecordIsNotEmptyForTheABI) {
+  // An incomplete record has no members, which must not read as vacuously
+  // holding no data.
+  auto ty = StructType::get(&context, getName("I"), /*is_class=*/false);
+  EXPECT_FALSE(allMembersNonData(ty));
+}
+
+TEST_F(RecordMemberKindTest, AUnionsTailPaddingSlotIsNotAMember) {
+  IntType u8 = getU8();
+  llvm::SmallVector<mlir::Type> members{u8};
+  llvm::SmallVector<RecordMemberKind> empty{RecordMemberKind::Empty};
+  llvm::ArrayRef<mlir::Type> membersRef(members);
+
+  auto allEmpty =
+      UnionType::get(&context, membersRef, getName("ue"), /*packed=*/false,
+                     /*padding=*/u8, llvm::ArrayRef<RecordMemberKind>(empty));
+  EXPECT_TRUE(allMembersNonData(allEmpty));
+  auto holdsData = UnionType::get(&context, membersRef, getName("ud"),
+                                  /*packed=*/false, /*padding=*/u8);
+  EXPECT_FALSE(allMembersNonData(holdsData));
+}
+
+TEST_F(RecordMemberKindTest, MarksTakePartInAnonymousTypeIdentity) {
+  IntType u8 = getU8();
+  auto marksPad = StructType::get(
+      &context, {u8, u8}, /*packed=*/false, /*padded=*/false,
+      /*is_class=*/false, {RecordMemberKind::Data, RecordMemberKind::Pad});
+  auto marksEmpty = StructType::get(
+      &context, {u8, u8}, /*packed=*/false, /*padded=*/false,
+      /*is_class=*/false, {RecordMemberKind::Data, RecordMemberKind::Empty});
+  EXPECT_NE(marksPad, marksEmpty);
+
+  // Marks are provenance rather than layout.
+  EXPECT_TRUE(marksPad.isLayoutIdentical(marksEmpty));
+
+  llvm::SmallVector<mlir::Type> unionMembers{u8, u8};
+  llvm::SmallVector<RecordMemberKind> unionEmpty{RecordMemberKind::Data,
+                                                 RecordMemberKind::Empty};
+  llvm::ArrayRef<mlir::Type> unionMembersRef(unionMembers);
+  auto unionMarked = UnionType::get(
+      &context, unionMembersRef, /*packed=*/false, /*padding=*/mlir::Type{},
+      llvm::ArrayRef<RecordMemberKind>(unionEmpty));
+  auto unionPlain = UnionType::get(&context, unionMembersRef, /*packed=*/false);
+  EXPECT_NE(unionMarked, unionPlain);
+  EXPECT_TRUE(unionMarked.isLayoutIdentical(unionPlain));
+}
+
+TEST_F(RecordMemberKindTest, AnAllDataMarkListIsDropped) {
+  IntType u8 = getU8();
+  auto allData = StructType::get(
+      &context, {u8, u8}, /*packed=*/false, /*padded=*/false,
+      /*is_class=*/false, {RecordMemberKind::Data, RecordMemberKind::Data});
+  auto noList = StructType::get(&context, {u8, u8}, /*packed=*/false,
+                                /*padded=*/false, /*is_class=*/false);
+  EXPECT_EQ(allData, noList);
+  EXPECT_TRUE(allData.getMemberKinds().empty());
+}

>From 8bda1f2134d0c1e328263a86a962b80317962b0f Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Tue, 11 Aug 2026 08:20:55 -0700
Subject: [PATCH 2/6] [CIR] Leave a bad record member mark to the type parser

parseMemberKind named `data` only so it could reject it, and separately
rejected a second mark on one member.  Both bought a tailored message
where the type parser already fails, and neither spelling can be reached
from printed CIR, since a data member is unmarked and only one mark is
ever emitted.

Drop both.  The function then has no failure path left, so it returns
void.

Assisted-by: Cursor / claude-opus-5
---
 clang/lib/CIR/Dialect/IR/CIRTypes.cpp         | 33 +++++--------------
 .../CIR/IR/invalid-record-member-kinds.cir    |  6 ++--
 2 files changed, 12 insertions(+), 27 deletions(-)

diff --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
index 55fbba11ddb50..5c54fad217d27 100644
--- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
@@ -175,30 +175,14 @@ verifyRecordMemberKinds(function_ref<mlir::InFlightDiagnostic()> emitError,
 
 /// Parse the optional mark that precedes a member type.  Only a mark keyword is
 /// consumed, so a member spelled as a bare builtin type still reaches the type
-/// parser.  A data member is spelled without a mark, and accepting 'data' would
-/// give a record two spellings that print the same way, so it is named here
-/// only to reject it.
-static mlir::ParseResult parseMemberKind(mlir::AsmParser &parser,
-                                         RecordMemberKind &kind) {
-  static const llvm::StringRef marks[] = {"pad", "empty", "data"};
+/// parser, and anything else that is not a mark fails there.  A data member is
+/// spelled without a mark.
+static void parseMemberKind(mlir::AsmParser &parser, RecordMemberKind &kind) {
+  static const llvm::StringRef marks[] = {"pad", "empty"};
   kind = RecordMemberKind::Data;
   llvm::StringRef keyword;
-  const llvm::SMLoc loc = parser.getCurrentLocation();
-  if (parser.parseOptionalKeyword(&keyword, marks).failed())
-    return mlir::success();
-
-  if (keyword == "data") {
-    parser.emitError(loc, "a data member is spelled without a mark");
-    return mlir::failure();
-  }
-  kind = *symbolizeRecordMemberKind(keyword);
-
-  const llvm::SMLoc secondLoc = parser.getCurrentLocation();
-  if (parser.parseOptionalKeyword(&keyword, marks).succeeded()) {
-    parser.emitError(secondLoc, "a record member takes at most one kind mark");
-    return mlir::failure();
-  }
-  return mlir::success();
+  if (parser.parseOptionalKeyword(&keyword, marks).succeeded())
+    kind = *symbolizeRecordMemberKind(keyword);
 }
 
 /// Parse "incomplete" or "{[mark] type, [mark] type, ...}", writing results
@@ -216,8 +200,7 @@ parseRecordBody(mlir::AsmParser &parser, bool &incomplete,
       AsmParser::Delimiter::Braces,
       [&parser, &members, &memberKinds]() -> mlir::ParseResult {
         RecordMemberKind kind;
-        if (parseMemberKind(parser, kind).failed())
-          return mlir::failure();
+        parseMemberKind(parser, kind);
         memberKinds.push_back(kind);
         return parser.parseType(members.emplace_back());
       });
@@ -479,7 +462,7 @@ Type UnionType::parse(mlir::AsmParser &parser) {
       return {};
     const llvm::SMLoc paddingLoc = parser.getCurrentLocation();
     llvm::StringRef paddingKeyword;
-    static const llvm::StringRef marks[] = {"pad", "empty", "data"};
+    static const llvm::StringRef marks[] = {"pad", "empty"};
     if (parser.parseOptionalKeyword(&paddingKeyword, marks).succeeded()) {
       parser.emitError(paddingLoc, "a union's tail padding takes no kind mark");
       return {};
diff --git a/clang/test/CIR/IR/invalid-record-member-kinds.cir b/clang/test/CIR/IR/invalid-record-member-kinds.cir
index 1709298da10ad..000e9c826020f 100644
--- a/clang/test/CIR/IR/invalid-record-member-kinds.cir
+++ b/clang/test/CIR/IR/invalid-record-member-kinds.cir
@@ -8,16 +8,18 @@ module {}
 
 // -----
 
+// A data member is spelled without a mark, so 'data' is not a mark keyword.
 !u8i = !cir.int<u, 8>
-// expected-error @below {{a data member is spelled without a mark}}
+// expected-error @below {{expected non-function type}}
 !rec_S = !cir.struct<"S" {data !u8i}>
 
 module {}
 
 // -----
 
+// Only one mark is consumed, so a second one is left to the type parser.
 !u8i = !cir.int<u, 8>
-// expected-error @below {{a record member takes at most one kind mark}}
+// expected-error @below {{expected non-function type}}
 !rec_S = !cir.struct<"S" {pad empty !u8i}>
 
 module {}

>From 4c54d8e6c4699c9b9512c7e6f444b3d0d8d941d5 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Wed, 12 Aug 2026 08:58:37 -0700
Subject: [PATCH 3/6] [CIR] Require every record member to specify its kind

The member kind list was optional, and an all-data list was canonicalized
to an absent one, so "nobody computed this" and "everything is data" had
the same spelling.  A producer that forgot to specify was assumed
correct.

The list is now required on both record types and the verifier demands
one kind per member.  Normalization is no longer needed and is removed.
A producer whose members all hold data says so with getAllDataKinds.
CIRGen has to compute a list too, so record lowering funnels every
appended field through addField and marks inserted padding as pad.

A data member still prints without a mark, and `data` is now accepted on
input so every kind can be written out.

Assisted-by: Cursor / claude-opus-5
---
 clang/include/clang/CIR/Dialect/IR/CIRTypes.h |  15 ++-
 .../include/clang/CIR/Dialect/IR/CIRTypes.td  |  41 ++++---
 .../clang/CIR/Dialect/IR/CIRTypesDetails.h    |  16 +--
 clang/lib/CIR/CodeGen/CIRGenAsm.cpp           |   3 +-
 clang/lib/CIR/CodeGen/CIRGenBuilder.cpp       |   6 +-
 clang/lib/CIR/CodeGen/CIRGenBuilder.h         |  20 ++--
 clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp |  12 +-
 clang/lib/CIR/CodeGen/CIRGenBuiltinNVPTX.cpp  |   4 +-
 clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp    |  31 ++++--
 .../CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp | 105 ++++++++++++------
 clang/lib/CIR/CodeGen/CIRGenTypes.cpp         |   6 +-
 clang/lib/CIR/CodeGen/CIRGenVTables.cpp       |   4 +-
 clang/lib/CIR/Dialect/IR/CIRTypes.cpp         |  65 +++++------
 .../CIR/Dialect/Transforms/CXXABILowering.cpp |  13 ++-
 .../Transforms/CallConvLoweringPass.cpp       |   3 +-
 .../Dialect/Transforms/LoweringPrepare.cpp    |   5 +-
 .../TargetLowering/LowerItaniumCXXABI.cpp     |   7 +-
 clang/test/CIR/CodeGen/atomic.c               |   2 +
 clang/test/CIR/CodeGen/bitfields.c            |   2 +-
 .../call-conv-lowering-x86_64-atomic-nyi.c    |  10 ++
 clang/test/CIR/CodeGen/cleanup.cpp            |   2 +-
 clang/test/CIR/CodeGen/dumb-record.cpp        |   4 +-
 clang/test/CIR/CodeGen/empty-union.cpp        |   2 +-
 .../CIR/CodeGen/finegrain-bitfield-access.cpp |   2 +-
 clang/test/CIR/CodeGen/member-functions.cpp   |   2 +-
 clang/test/CIR/CodeGen/no-unique-address.cpp  |  35 +++++-
 .../test/CIR/CodeGen/paren-list-agg-init.cpp  |   8 +-
 .../CodeGen/pointer-to-empty-data-member.cpp  |   6 +-
 .../test/CIR/CodeGen/record-type-metadata.cpp |   4 +-
 clang/test/CIR/CodeGen/struct.c               |   2 +-
 .../CIR/CodeGen/template-specialization.cpp   |   2 +-
 clang/test/CIR/CodeGen/vtt.cpp                |   4 +-
 clang/test/CIR/CodeGenCXX/zero_init_bases.cpp |   2 +-
 .../test/CIR/CodeGenCoroutines/coro-task.cpp  |  18 +--
 .../CIR/IR/invalid-record-member-kinds.cir    |  21 +++-
 clang/test/CIR/IR/struct.cir                  |  19 +++-
 clang/unittests/CIR/PointerLikeTest.cpp       |  12 +-
 clang/unittests/CIR/RecordMemberKindTest.cpp  |  92 +++++++--------
 .../unittests/CIR/RecordTypeMetadataTest.cpp  |   4 +-
 clang/unittests/CIR/UnionTypeSizeTest.cpp     |  32 ++++--
 40 files changed, 395 insertions(+), 248 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/call-conv-lowering-x86_64-atomic-nyi.c

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

>From 1497707a12f483545d4cad1b384b44fa1141a0ce Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Wed, 12 Aug 2026 11:38:45 -0700
Subject: [PATCH 4/6] [CIR] Move the record kind helpers onto RecordType

Both free functions either took a record or built one of its parameters, so
they belong on the type.

`allMembersNonData` becomes the member `isEmptyForABI` to match
`isEmptyRecordForABI` on the AST side.

`getAllDataKinds` becomes a static on `RecordType`.

Assisted-by: Cursor / claude-opus-5
---
 clang/include/clang/CIR/Dialect/IR/CIRTypes.h | 21 +++++------
 clang/lib/CIR/CodeGen/CIRGenAsm.cpp           |  6 +--
 clang/lib/CIR/CodeGen/CIRGenBuilder.cpp       |  4 +-
 clang/lib/CIR/CodeGen/CIRGenBuilder.h         |  2 +-
 clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp |  4 +-
 clang/lib/CIR/CodeGen/CIRGenBuiltinNVPTX.cpp  |  2 +-
 clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp    | 22 ++++++-----
 clang/lib/CIR/CodeGen/CIRGenVTables.cpp       |  6 +--
 clang/lib/CIR/Dialect/IR/CIRTypes.cpp         | 10 ++---
 .../Transforms/CallConvLoweringPass.cpp       |  7 ++--
 .../Dialect/Transforms/LoweringPrepare.cpp    |  3 +-
 .../TargetLowering/LowerItaniumCXXABI.cpp     |  2 +-
 clang/unittests/CIR/PointerLikeTest.cpp       |  8 ++--
 clang/unittests/CIR/RecordMemberKindTest.cpp  | 37 ++++++++++---------
 .../unittests/CIR/RecordTypeMetadataTest.cpp  |  2 +-
 clang/unittests/CIR/UnionTypeSizeTest.cpp     | 16 ++++----
 16 files changed, 79 insertions(+), 73 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.h b/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
index eeeb125ac320b..c3a513dbbda66 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
@@ -129,6 +129,16 @@ class RecordType : public mlir::Type {
   bool isStruct() const;
   bool isUnion() const { return mlir::isa<UnionType>(*this); }
 
+  /// Whether no member holds data.  Vacuously true for a complete record with
+  /// no members, and false for an incomplete one, whose members are not known
+  /// yet.  A union's tail-padding slot is not a member and does not count.
+  bool isEmptyForABI() const;
+
+  /// One `Data` kind per member.  Takes the member list rather than a count so
+  /// the length cannot drift from the record it describes.
+  static llvm::SmallVector<RecordMemberKind>
+  getAllDataKinds(llvm::ArrayRef<mlir::Type> members);
+
   size_t getNumElements() const { return getMembers().size(); }
   mlir::Type getElementType(size_t idx) const { return getMembers()[idx]; }
   std::string getKindAsStr() const;
@@ -146,17 +156,6 @@ class RecordType : public mlir::Type {
   void removeABIConversionNamePrefix();
 };
 
-/// One `Data` kind per member.  Takes the members rather than a count so that
-/// the two cannot drift apart.
-llvm::SmallVector<RecordMemberKind>
-getAllDataKinds(llvm::ArrayRef<mlir::Type> members);
-
-/// Whether no member of \p recTy holds data, which makes the record empty for
-/// the ABI.  Vacuously true for a complete record with no members, and false
-/// for an incomplete one, whose members are not known yet.  A union's
-/// tail-padding slot is not a member and does not count.
-bool allMembersNonData(RecordType recTy);
-
 } // namespace cir
 
 #endif // CLANG_CIR_DIALECT_IR_CIRTYPES_H
diff --git a/clang/lib/CIR/CodeGen/CIRGenAsm.cpp b/clang/lib/CIR/CodeGen/CIRGenAsm.cpp
index dcc7a247e65fb..9ad09ca95f73a 100644
--- a/clang/lib/CIR/CodeGen/CIRGenAsm.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenAsm.cpp
@@ -537,9 +537,9 @@ 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,
-                                         cir::getAllDataKinds(resultRegTypes));
+    resultType = builder.getAnonRecordTy(
+        resultRegTypes, /*packed=*/false,
+        /*padded=*/false, cir::RecordType::getAllDataKinds(resultRegTypes));
 
   bool hasSideEffect = s.isVolatile() || s.getNumOutputs() == 0;
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp b/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
index 954f9fc82e115..f521fd2a6936e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
@@ -207,10 +207,10 @@ cir::RecordType clang::CIRGen::CIRGenBuilderTy::getCompleteRecordType(
 
   if (name.empty())
     return getAnonRecordTy(members, packed, padded,
-                           cir::getAllDataKinds(members));
+                           cir::RecordType::getAllDataKinds(members));
 
   return getCompleteNamedRecordType(members, packed, padded, name,
-                                    cir::getAllDataKinds(members));
+                                    cir::RecordType::getAllDataKinds(members));
 }
 
 mlir::Attribute clang::CIRGen::CIRGenBuilderTy::getConstRecordOrZeroAttr(
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.h b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
index fd3c116974cf3..f8a152c0e4c30 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuilder.h
+++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
@@ -95,7 +95,7 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
 
     if (!ty)
       ty = getAnonRecordTy(members, packed, padded,
-                           cir::getAllDataKinds(members));
+                           cir::RecordType::getAllDataKinds(members));
 
     auto sTy = mlir::cast<cir::RecordType>(ty);
     return cir::ConstRecordAttr::get(sTy, arrayAttr);
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp
index d0f68ccbf68bf..12abac95172b4 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltinAMDGPU.cpp
@@ -79,7 +79,7 @@ static mlir::Value emitLogbBuiltin(CIRGenFunction &cgf, const CallExpr *e,
   mlir::Type frExpResMembers[] = {srcTy, int32Ty};
   cir::RecordType frExpResTy = builder.getAnonRecordTy(
       frExpResMembers, /*packed=*/false, /*padded=*/false,
-      cir::getAllDataKinds(frExpResMembers));
+      cir::RecordType::getAllDataKinds(frExpResMembers));
 
   mlir::Value frExpResult = builder.emitIntrinsicCallOp(
       loc, "frexp", frExpResTy, mlir::ValueRange{src0});
@@ -179,7 +179,7 @@ CIRGenFunction::emitAMDGPUBuiltinExpr(unsigned builtinId,
     mlir::Type resMembers[] = {x.getType(), i1Ty};
     cir::RecordType resTy =
         builder.getAnonRecordTy(resMembers, /*packed=*/false, /*padded=*/false,
-                                cir::getAllDataKinds(resMembers));
+                                cir::RecordType::getAllDataKinds(resMembers));
 
     mlir::Value structResult =
         cir::LLVMIntrinsicCallOp::create(builder, getLoc(expr->getExprLoc()),
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltinNVPTX.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltinNVPTX.cpp
index 579f5e40acc67..f0ee735e8bc94 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltinNVPTX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltinNVPTX.cpp
@@ -1044,7 +1044,7 @@ static mlir::Value packArgsIntoNVPTXFormatBuffer(CIRGenFunction &cgf,
   // accept aggregates.
   mlir::Type allocaTy =
       builder.getAnonRecordTy(argTypes, /*packed=*/false, /*padded=*/false,
-                              cir::getAllDataKinds(argTypes));
+                              cir::RecordType::getAllDataKinds(argTypes));
   auto allocaAlign = clang::CharUnits::fromQuantity(
       dataLayout.getABITypeAlign(allocaTy).value());
   Address allocaAddr =
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp
index ceb29ae69e52f..a14e71a91df80 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp
@@ -208,9 +208,10 @@ emitEncodeKey(mlir::MLIRContext *context, CIRGenBuilderTy &builder,
   llvm::SmallVector<mlir::Type> members{builder.getUInt32Ty()};
   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, cir::getAllDataKinds(members));
+  cir::StructType resRecord =
+      cir::StructType::get(context, members, /*packed=*/false,
+                           /*padded=*/false, /*is_class=*/false,
+                           cir::RecordType::getAllDataKinds(members));
 
   mlir::Value outputPtr =
       builder.createBitcast(outputOperand, cir::PointerType::get(resVector));
@@ -696,7 +697,7 @@ static mlir::Value emitX86Aes(CIRGenBuilderTy &builder, mlir::Location loc,
   mlir::Type rstMembers[] = {retType, vecType};
   cir::RecordType rstRecTy =
       builder.getAnonRecordTy(rstMembers, /*packed=*/false, /*padded=*/false,
-                              cir::getAllDataKinds(rstMembers));
+                              cir::RecordType::getAllDataKinds(rstMembers));
   mlir::Value rstValueRec = builder.emitIntrinsicCallOp(
       loc, intrinsicName, rstRecTy, mlir::ValueRange{ops[1], ops[2]});
 
@@ -755,7 +756,7 @@ static mlir::Value emitX86Aeswide(CIRGenBuilderTy &builder, mlir::Location loc,
   }
   cir::RecordType rstRecTy =
       builder.getAnonRecordTy(recTypes, /*packed=*/false, /*padded=*/false,
-                              cir::getAllDataKinds(recTypes));
+                              cir::RecordType::getAllDataKinds(recTypes));
   mlir::Value rstValueRec =
       builder.emitIntrinsicCallOp(loc, intrinsicName, rstRecTy, arguments);
 
@@ -933,8 +934,9 @@ 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, /*packed=*/false, /*padded=*/false, cir::getAllDataKinds(tys));
+    mlir::Type modelTy =
+        builder.getAnonRecordTy(tys, /*packed=*/false, /*padded=*/false,
+                                cir::RecordType::getAllDataKinds(tys));
     cpuModel =
         cgm.createGlobalOp(loc, "__cpu_model", modelTy, /*isConstant=*/false);
     cpuModel.setDsoLocal(true);
@@ -1142,7 +1144,7 @@ CIRGenFunction::emitX86BuiltinExpr(unsigned builtinID, const CallExpr *expr) {
     mlir::Type members[] = {i64Ty, i32Ty};
     mlir::Type structTy =
         builder.getAnonRecordTy(members, /*packed=*/false, /*padded=*/false,
-                                cir::getAllDataKinds(members));
+                                cir::RecordType::getAllDataKinds(members));
     mlir::Value result =
         builder.emitIntrinsicCallOp(loc, "x86.rdtscp", structTy);
 
@@ -2505,7 +2507,7 @@ CIRGenFunction::emitX86BuiltinExpr(unsigned builtinID, const CallExpr *expr) {
     cir::StructType resRecord =
         cir::StructType::get(&getMLIRContext(), resultTypes, /*packed=*/false,
                              /*padded=*/false, /*is_class=*/false,
-                             cir::getAllDataKinds(resultTypes));
+                             cir::RecordType::getAllDataKinds(resultTypes));
 
     mlir::Value call =
         builder.emitIntrinsicCallOp(loc, intrinsicName, resRecord);
@@ -2575,7 +2577,7 @@ CIRGenFunction::emitX86BuiltinExpr(unsigned builtinID, const CallExpr *expr) {
     mlir::Type resMembers[] = {resVector, resVector};
     cir::StructType resRecord = cir::StructType::get(
         &getMLIRContext(), resMembers, /*packed=*/false, /*padded=*/false,
-        /*is_class=*/false, cir::getAllDataKinds(resMembers));
+        /*is_class=*/false, cir::RecordType::getAllDataKinds(resMembers));
 
     mlir::Value call = builder.emitIntrinsicCallOp(
         getLoc(expr->getExprLoc()), intrinsicName, resRecord,
diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
index f2d2dbebb957c..1926c2657454b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
@@ -69,9 +69,9 @@ cir::RecordType CIRGenVTables::getVTableType(const VTableLayout &layout) {
 
   // FIXME(cir): should VTableLayout be encoded like we do for some
   // AST nodes?
-  return cgm.getBuilder().getAnonRecordTy(tys, /*packed=*/false,
-                                          /*padded=*/false,
-                                          cir::getAllDataKinds(tys));
+  return cgm.getBuilder().getAnonRecordTy(
+      tys, /*packed=*/false,
+      /*padded=*/false, cir::RecordType::getAllDataKinds(tys));
 }
 
 /// At this point in the translation unit, does it appear that can we
diff --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
index 183c414080299..7bff776c7df95 100644
--- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
@@ -153,7 +153,7 @@ void CIRDialect::printType(Type type, DialectAsmPrinter &os) const {
 // Shared helpers for StructType and UnionType parse/print.
 
 llvm::SmallVector<RecordMemberKind>
-cir::getAllDataKinds(llvm::ArrayRef<mlir::Type> members) {
+RecordType::getAllDataKinds(llvm::ArrayRef<mlir::Type> members) {
   return llvm::SmallVector<RecordMemberKind>(members.size(),
                                              RecordMemberKind::Data);
 }
@@ -684,12 +684,12 @@ void RecordType::removeABIConversionNamePrefix() {
   return mlir::cast<UnionType>(*this).removeABIConversionNamePrefix();
 }
 
-bool cir::allMembersNonData(RecordType recTy) {
+bool RecordType::isEmptyForABI() const {
   // An incomplete record has no members yet, which must not read as vacuously
   // holding no data.
-  if (recTy.isIncomplete())
+  if (isIncomplete())
     return false;
-  return llvm::none_of(recTy.getMemberKinds(), [](RecordMemberKind kind) {
+  return llvm::none_of(getMemberKinds(), [](RecordMemberKind kind) {
     return kind == RecordMemberKind::Data;
   });
 }
@@ -1260,7 +1260,7 @@ static mlir::Type getMethodLayoutType(mlir::MLIRContext *ctx) {
   mlir::Type fields[2]{voidPtrTy, voidPtrTy};
   return cir::StructType::get(ctx, fields, /*packed=*/false,
                               /*padded=*/false, /*is_class=*/false,
-                              cir::getAllDataKinds(fields));
+                              cir::RecordType::getAllDataKinds(fields));
 }
 
 llvm::TypeSize
diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
index 88bca4eba09b4..430f735f881ec 100644
--- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
@@ -195,9 +195,10 @@ static mlir::Type abiTypeToCIR(const llvm::abi::Type *ty, MLIRContext *ctx) {
           fieldTypes.push_back(fieldCIR);
         }
         // Coercion types are plain register tuples, not the source record.
-        return cir::StructType::get(ctx, fieldTypes, /*packed=*/false,
-                                    /*padded=*/false, /*is_class=*/false,
-                                    cir::getAllDataKinds(fieldTypes));
+        return cir::StructType::get(
+            ctx, fieldTypes, /*packed=*/false,
+            /*padded=*/false, /*is_class=*/false,
+            cir::RecordType::getAllDataKinds(fieldTypes));
       })
       .Default([](const llvm::abi::Type *) -> mlir::Type { return nullptr; });
 }
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index cae12fc972e0f..2fed2b1aa6ad9 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -2434,7 +2434,8 @@ void LoweringPreparePass::buildCUDAModuleCtor() {
   mlir::Type fatbinWrapperMembers[] = {intTy, intTy, voidPtrTy, voidPtrTy};
   auto fatbinWrapperType = cir::StructType::get(
       &getContext(), fatbinWrapperMembers, /*packed=*/false, /*padded=*/false,
-      /*is_class=*/false, cir::getAllDataKinds(fatbinWrapperMembers));
+      /*is_class=*/false,
+      cir::RecordType::getAllDataKinds(fatbinWrapperMembers));
   std::string fatbinWrapperName =
       addUnderscoredPrefix(cudaPrefix, "_fatbin_wrapper");
   GlobalOp fatbinWrapper = GlobalOp::create(
diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
index 00b9afcef1c9f..458f87f2b8c07 100644
--- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
@@ -193,7 +193,7 @@ mlir::Type LowerItaniumCXXABI::lowerMethodType(
   mlir::Type members[] = {ptrdiffCIRTy, ptrdiffCIRTy};
   return cir::StructType::get(type.getContext(), members, /*packed=*/false,
                               /*padded=*/false, /*is_class=*/false,
-                              cir::getAllDataKinds(members));
+                              cir::RecordType::getAllDataKinds(members));
 }
 
 mlir::TypedAttr LowerItaniumCXXABI::lowerDataMemberConstant(
diff --git a/clang/unittests/CIR/PointerLikeTest.cpp b/clang/unittests/CIR/PointerLikeTest.cpp
index fda90043f197d..af0faa4a866cb 100644
--- a/clang/unittests/CIR/PointerLikeTest.cpp
+++ b/clang/unittests/CIR/PointerLikeTest.cpp
@@ -171,7 +171,8 @@ class CIROpenACCPointerLikeTest : public ::testing::Test {
                                       /*is_class=*/false);
     mlir::Type members[] = {ty1, ty2};
     structTy.complete(members, /*packed=*/false, /*padded=*/false,
-                      /*padding=*/{}, cir::getAllDataKinds(members));
+                      /*padding=*/{},
+                      cir::RecordType::getAllDataKinds(members));
     mlir::Type ptrTy = cir::PointerType::get(structTy);
 
     // Verify that the pointer points to the structure type.
@@ -255,7 +256,8 @@ class CIROpenACCPointerLikeTest : public ::testing::Test {
                              /*is_class=*/false);
     mlir::Type members[] = {ptrTy, ptrTy};
     structTy.complete(members, /*packed=*/false, /*padded=*/false,
-                      /*padding=*/{}, cir::getAllDataKinds(members));
+                      /*padding=*/{},
+                      cir::RecordType::getAllDataKinds(members));
     mlir::Type structPptrTy = cir::PointerType::get(structTy);
 
     // Create an alloca for the struct.
@@ -367,6 +369,6 @@ TEST_F(CIROpenACCPointerLikeTest, testPointerToStructMember) {
                            /*is_class=*/false);
   mlir::Type members[] = {i32Ty, i32Ty};
   structTy.complete(members, /*packed=*/false, /*padded=*/false,
-                    /*padding=*/{}, cir::getAllDataKinds(members));
+                    /*padding=*/{}, cir::RecordType::getAllDataKinds(members));
   testPointerToMemberType(structTy, mlir::acc::VariableTypeCategory::composite);
 }
diff --git a/clang/unittests/CIR/RecordMemberKindTest.cpp b/clang/unittests/CIR/RecordMemberKindTest.cpp
index 7094fced8b5b0..fd6de7a4296d4 100644
--- a/clang/unittests/CIR/RecordMemberKindTest.cpp
+++ b/clang/unittests/CIR/RecordMemberKindTest.cpp
@@ -49,7 +49,7 @@ class RecordMemberKindTest : public ::testing::Test {
 
   IntType getU8() { return IntType::get(&context, 8, false); }
 
-  StructType makeStruct(llvm::StringRef name,
+  RecordType makeStruct(llvm::StringRef name,
                         llvm::ArrayRef<mlir::Type> members,
                         llvm::ArrayRef<RecordMemberKind> kinds) {
     auto ty = StructType::get(&context, getName(name), /*is_class=*/false);
@@ -61,17 +61,18 @@ class RecordMemberKindTest : public ::testing::Test {
 TEST_F(RecordMemberKindTest, EmptyForTheABIWhenNoMemberHoldsData) {
   IntType u8 = getU8();
   // A record with no members is vacuously empty.
-  EXPECT_TRUE(allMembersNonData(makeStruct("none", {}, {})));
+  EXPECT_TRUE(makeStruct("none", {}, {}).isEmptyForABI());
+  EXPECT_TRUE(makeStruct("p1", {u8}, {RecordMemberKind::Pad}).isEmptyForABI());
   EXPECT_TRUE(
-      allMembersNonData(makeStruct("p1", {u8}, {RecordMemberKind::Pad})));
-  EXPECT_TRUE(
-      allMembersNonData(makeStruct("e1", {u8}, {RecordMemberKind::Empty})));
-  EXPECT_TRUE(allMembersNonData(makeStruct(
-      "pe", {u8, u8}, {RecordMemberKind::Pad, RecordMemberKind::Empty})));
+      makeStruct("e1", {u8}, {RecordMemberKind::Empty}).isEmptyForABI());
+  EXPECT_TRUE(makeStruct("pe", {u8, u8},
+                         {RecordMemberKind::Pad, RecordMemberKind::Empty})
+                  .isEmptyForABI());
   EXPECT_FALSE(
-      allMembersNonData(makeStruct("d1", {u8}, {RecordMemberKind::Data})));
-  EXPECT_FALSE(allMembersNonData(makeStruct(
-      "dp", {u8, u8}, {RecordMemberKind::Data, RecordMemberKind::Pad})));
+      makeStruct("d1", {u8}, {RecordMemberKind::Data}).isEmptyForABI());
+  EXPECT_FALSE(makeStruct("dp", {u8, u8},
+                          {RecordMemberKind::Data, RecordMemberKind::Pad})
+                   .isEmptyForABI());
 }
 
 TEST_F(RecordMemberKindTest, RejectsAKindListThatDoesNotNameEveryMember) {
@@ -123,8 +124,8 @@ TEST_F(RecordMemberKindTest, RejectsPadOnAUnionMember) {
 }
 
 TEST_F(RecordMemberKindTest, AnIncompleteRecordIsNotEmptyForTheABI) {
-  auto ty = StructType::get(&context, getName("I"), /*is_class=*/false);
-  EXPECT_FALSE(allMembersNonData(ty));
+  RecordType ty = StructType::get(&context, getName("I"), /*is_class=*/false);
+  EXPECT_FALSE(ty.isEmptyForABI());
 }
 
 TEST_F(RecordMemberKindTest, AUnionsTailPaddingSlotIsNotAMember) {
@@ -133,14 +134,14 @@ TEST_F(RecordMemberKindTest, AUnionsTailPaddingSlotIsNotAMember) {
   llvm::SmallVector<RecordMemberKind> empty{RecordMemberKind::Empty};
   llvm::ArrayRef<mlir::Type> membersRef(members);
 
-  auto allEmpty =
+  RecordType allEmpty =
       UnionType::get(&context, membersRef, getName("ue"), /*packed=*/false,
                      /*padding=*/u8, llvm::ArrayRef<RecordMemberKind>(empty));
-  EXPECT_TRUE(allMembersNonData(allEmpty));
-  auto holdsData =
+  EXPECT_TRUE(allEmpty.isEmptyForABI());
+  RecordType holdsData =
       UnionType::get(&context, membersRef, getName("ud"), /*packed=*/false,
-                     /*padding=*/u8, getAllDataKinds(membersRef));
-  EXPECT_FALSE(allMembersNonData(holdsData));
+                     /*padding=*/u8, RecordType::getAllDataKinds(membersRef));
+  EXPECT_FALSE(holdsData.isEmptyForABI());
 }
 
 TEST_F(RecordMemberKindTest, KindsTakePartInAnonymousTypeIdentity) {
@@ -165,7 +166,7 @@ TEST_F(RecordMemberKindTest, KindsTakePartInAnonymousTypeIdentity) {
       llvm::ArrayRef<RecordMemberKind>(unionEmpty));
   auto unionAllData = UnionType::get(
       &context, unionMembersRef, /*packed=*/false,
-      /*padding=*/mlir::Type{}, getAllDataKinds(unionMembersRef));
+      /*padding=*/mlir::Type{}, RecordType::getAllDataKinds(unionMembersRef));
   EXPECT_NE(unionMarked, unionAllData);
   EXPECT_TRUE(unionMarked.isLayoutIdentical(unionAllData));
 }
diff --git a/clang/unittests/CIR/RecordTypeMetadataTest.cpp b/clang/unittests/CIR/RecordTypeMetadataTest.cpp
index e78d2e7e15648..b96f7ad36874a 100644
--- a/clang/unittests/CIR/RecordTypeMetadataTest.cpp
+++ b/clang/unittests/CIR/RecordTypeMetadataTest.cpp
@@ -65,7 +65,7 @@ TEST_F(RecordLayoutAttrTest, RecordTypeUnchanged) {
   auto ty = StructType::get(&context, getName("Foo"), /*is_class=*/false);
   mlir::Type members[] = {i32, i32};
   ty.complete(members, /*packed=*/false, /*padded=*/false,
-              getAllDataKinds(members));
+              RecordType::getAllDataKinds(members));
   EXPECT_TRUE(ty.isComplete());
   EXPECT_EQ(ty.getMembers().size(), 2u);
 }
diff --git a/clang/unittests/CIR/UnionTypeSizeTest.cpp b/clang/unittests/CIR/UnionTypeSizeTest.cpp
index e5debbd2905de..ffbf53000a148 100644
--- a/clang/unittests/CIR/UnionTypeSizeTest.cpp
+++ b/clang/unittests/CIR/UnionTypeSizeTest.cpp
@@ -35,7 +35,7 @@ TEST_F(UnionTypeSizeTest, SizeInBitsNotBytes) {
   auto ty = UnionType::get(&context, getName("U"));
   mlir::Type members[] = {i32};
   ty.complete(members, /*packed=*/false, /*padding=*/mlir::Type{},
-              getAllDataKinds(members));
+              RecordType::getAllDataKinds(members));
 
   OpBuilder builder(&context);
   auto loc = builder.getUnknownLoc();
@@ -54,7 +54,7 @@ TEST_F(UnionTypeSizeTest, MultiMemberUnion) {
   auto ty = UnionType::get(&context, getName("U2"));
   mlir::Type members[] = {i32, i64};
   ty.complete(members, /*packed=*/false, /*padding=*/mlir::Type{},
-              getAllDataKinds(members));
+              RecordType::getAllDataKinds(members));
 
   OpBuilder builder(&context);
   auto loc = builder.getUnknownLoc();
@@ -88,10 +88,10 @@ TEST_F(UnionTypeSizeTest, IsLayoutIdenticalNoPadding) {
   mlir::Type members[] = {i32};
   auto ty1 = UnionType::get(&context, getName("Ua"));
   ty1.complete(members, /*packed=*/false, /*padding=*/mlir::Type{},
-               getAllDataKinds(members));
+               RecordType::getAllDataKinds(members));
   auto ty2 = UnionType::get(&context, getName("Ub"));
   ty2.complete(members, /*packed=*/false, /*padding=*/mlir::Type{},
-               getAllDataKinds(members));
+               RecordType::getAllDataKinds(members));
   EXPECT_TRUE(ty1.isLayoutIdentical(ty2));
 }
 
@@ -102,10 +102,10 @@ TEST_F(UnionTypeSizeTest, IsLayoutIdenticalDifferentPadding) {
   mlir::Type members[] = {i32};
   auto ty1 = UnionType::get(&context, getName("Upad1"));
   ty1.complete(members, /*packed=*/false, /*padding=*/i8,
-               getAllDataKinds(members));
+               RecordType::getAllDataKinds(members));
   auto ty2 = UnionType::get(&context, getName("Upad2"));
   ty2.complete(members, /*packed=*/false, /*padding=*/i16,
-               getAllDataKinds(members));
+               RecordType::getAllDataKinds(members));
   EXPECT_FALSE(ty1.isLayoutIdentical(ty2));
 }
 
@@ -115,9 +115,9 @@ TEST_F(UnionTypeSizeTest, IsLayoutIdenticalSamePadding) {
   mlir::Type members[] = {i32};
   auto ty1 = UnionType::get(&context, getName("Upad3"));
   ty1.complete(members, /*packed=*/false, /*padding=*/i8,
-               getAllDataKinds(members));
+               RecordType::getAllDataKinds(members));
   auto ty2 = UnionType::get(&context, getName("Upad4"));
   ty2.complete(members, /*packed=*/false, /*padding=*/i8,
-               getAllDataKinds(members));
+               RecordType::getAllDataKinds(members));
   EXPECT_TRUE(ty1.isLayoutIdentical(ty2));
 }

>From 7ce754e3521f1b96f9b93b7022b8ae1e341b8ebd Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Wed, 12 Aug 2026 12:49:52 -0700
Subject: [PATCH 5/6] [CIR] Spell every record member's kind in the IR

The kind list is a required parameter, but the textual form let a member
drop its mark and mean `data`, so two spellings stood for one type and a
reader had to know that the same type could have multiple spellings.

Every member now carries its mark, printed and parsed.  A member with no
mark is a parse error.  The tail-padding slot of a union still takes no
mark, since it is not a member.

Assisted-by: Cursor / claude-opus-5
---
 .../include/clang/CIR/Dialect/IR/CIRTypes.td  |  23 ++--
 clang/lib/CIR/Dialect/IR/CIRTypes.cpp         |  45 +++----
 clang/test/CIR/CodeGen/atomic.c               |   2 +-
 clang/test/CIR/CodeGen/bitfield-union.c       |   4 +-
 clang/test/CIR/CodeGen/bitfields.c            |  12 +-
 clang/test/CIR/CodeGen/bitfields.cpp          |   4 +-
 clang/test/CIR/CodeGen/bitfields_be.c         |   2 +-
 .../test/CIR/CodeGen/builtin-cpu-is-cputype.c |   2 +-
 .../test/CIR/CodeGen/builtin-cpu-is-subtype.c |   2 +-
 .../test/CIR/CodeGen/builtin-cpu-is-vendor.c  |   2 +-
 clang/test/CIR/CodeGen/class.cpp              |   6 +-
 clang/test/CIR/CodeGen/ctor.cpp               |   2 +-
 .../CodeGen/cxx-abi-lowering-string-array.cpp |   2 +-
 clang/test/CIR/CodeGen/destructors.cpp        |   2 +-
 clang/test/CIR/CodeGen/dumb-record.cpp        |   8 +-
 clang/test/CIR/CodeGen/empty-union.cpp        |  16 +--
 .../CIR/CodeGen/finegrain-bitfield-access.cpp |   6 +-
 clang/test/CIR/CodeGen/flexible-array-init.c  |   4 +-
 clang/test/CIR/CodeGen/forward-decls.cpp      |  20 +--
 clang/test/CIR/CodeGen/inline-cxx-func.cpp    |   2 +-
 clang/test/CIR/CodeGen/mms-bitfields.c        |  12 +-
 clang/test/CIR/CodeGen/multi-vtable.cpp       |  12 +-
 .../CIR/CodeGen/no-proto-fn-ptr-global-init.c |   8 +-
 clang/test/CIR/CodeGen/no-unique-address.cpp  |  32 ++---
 clang/test/CIR/CodeGen/nonzeroinit-struct.cpp |  14 +--
 .../test/CIR/CodeGen/paren-list-agg-init.cpp  |  22 ++--
 .../CodeGen/pointer-to-empty-data-member.cpp  |  30 ++---
 .../test/CIR/CodeGen/pointer-width-32bit.cpp  |   6 +-
 .../test/CIR/CodeGen/record-type-metadata.cpp |   6 +-
 .../CIR/CodeGen/record-with-padded-union.cpp  |   4 +-
 .../CIR/CodeGen/record-zero-init-padding.c    |   8 +-
 clang/test/CIR/CodeGen/struct.c               |  24 ++--
 clang/test/CIR/CodeGen/three-way-cmp.cpp      |   4 +-
 clang/test/CIR/CodeGen/union.c                |   8 +-
 clang/test/CIR/CodeGen/union.cpp              |   2 +-
 .../CIR/CodeGen/unions-with-zero-init.cpp     |  30 ++---
 clang/test/CIR/CodeGen/var_arg.c              |   2 +-
 .../variable-template-specialization.cpp      |   2 +-
 clang/test/CIR/CodeGen/vbase.cpp              |   6 +-
 .../CIR/CodeGen/virtual-function-calls.cpp    |   4 +-
 clang/test/CIR/CodeGen/vtable-emission.cpp    |   2 +-
 .../vtable-nyi-nonconvertible-functype.cpp    |   4 +-
 clang/test/CIR/CodeGen/vtt.cpp                |  22 ++--
 .../X86/avx512vlvp2intersect-builtins.c       |   6 +-
 .../X86/avx512vp2intersect-builtins.c         |   4 +-
 .../test/CIR/CodeGenBuiltins/X86/keylocker.c  |   8 +-
 clang/test/CIR/CodeGenCXX/zero_init_bases.cpp |   8 +-
 clang/test/CIR/IR/array-ctor.cir              |   2 +-
 clang/test/CIR/IR/array-dtor.cir              |   2 +-
 clang/test/CIR/IR/bitfield_info.cir           |   2 +-
 .../CIR/IR/const-record-flexible-array.cir    |   2 +-
 clang/test/CIR/IR/const-record-union.cir      |   2 +-
 clang/test/CIR/IR/construct-catch-param.cir   |   2 +-
 clang/test/CIR/IR/copy.cir                    |   2 +-
 clang/test/CIR/IR/dynamic-cast.cir            |   4 +-
 clang/test/CIR/IR/func-attrs.cir              |   4 +-
 clang/test/CIR/IR/func.cir                    |   2 +-
 clang/test/CIR/IR/global-init.cir             |   6 +-
 clang/test/CIR/IR/invalid-array-structor.cir  |  32 ++---
 clang/test/CIR/IR/invalid-cast.cir            |   4 +-
 clang/test/CIR/IR/invalid-const-record.cir    |  18 +--
 .../CIR/IR/invalid-construct-catch-param.cir  |  14 +--
 clang/test/CIR/IR/invalid-data-member.cir     |  12 +-
 clang/test/CIR/IR/invalid-delete-array.cir    |   2 +-
 clang/test/CIR/IR/invalid-dyn-cast.cir        |   8 +-
 clang/test/CIR/IR/invalid-loop-cleanup.cir    |   8 +-
 .../CIR/IR/invalid-record-member-kinds.cir    |  19 ++-
 clang/test/CIR/IR/invalid-struct.cir          |  12 +-
 clang/test/CIR/IR/invalid-type-info.cir       |   4 +-
 clang/test/CIR/IR/invalid-vtable.cir          |  32 ++---
 clang/test/CIR/IR/loop-cleanup.cir            |   2 +-
 clang/test/CIR/IR/method-attr.cir             |   2 +-
 clang/test/CIR/IR/struct.cir                  | 115 ++++++++----------
 clang/test/CIR/IR/vtable-addrpt.cir           |   4 +-
 clang/test/CIR/IR/vtable-attr.cir             |  10 +-
 clang/test/CIR/IR/vtt-addrpoint.cir           |   8 +-
 .../const-array-bulk-lowering-fallbacks.cir   |  12 +-
 .../CIR/Lowering/const-array-of-pointers.cir  |   2 +-
 clang/test/CIR/Lowering/struct.cir            |   2 +-
 clang/test/CIR/Lowering/vtt-addrpoint.cir     |   8 +-
 .../byval-sret-arg-attr-lowering.cir          |   2 +-
 .../abi-lowering/coerce-int-to-record.cir     |   2 +-
 .../coerce-record-return-larger.cir           |   4 +-
 .../abi-lowering/coerce-record-to-int.cir     |   2 +-
 .../coerce-record-to-record-via-memory.cir    |   4 +-
 .../abi-lowering/direct-flatten.cir           |  12 +-
 .../abi-lowering/expand-struct-arg.cir        |   6 +-
 .../abi-lowering/indirect-byval.cir           |   2 +-
 .../indirect-call-test-target.cir             |   2 +-
 .../Transforms/abi-lowering/indirect-call.cir |   4 +-
 .../abi-lowering/indirect-return-sret.cir     |   2 +-
 .../abi-lowering/x86_64-aggregate-nyi.cir     |  26 ++--
 .../Transforms/abi-lowering/x86_64-bitint.cir |  10 +-
 .../abi-lowering/x86_64-get-global.cir        |   4 +-
 .../abi-lowering/x86_64-int-nyi.cir           |   2 +-
 .../abi-lowering/x86_64-struct-direct.cir     |  14 +--
 .../abi-lowering/x86_64-struct-indirect.cir   |   6 +-
 .../x86_64-union-coerce-shapes.cir            |   8 +-
 .../Transforms/abi-lowering/x86_64-union.cir  |  26 ++--
 .../abi-lowering/x86_64-variadic-call.cir     |   6 +-
 .../abi-lowering/x86_64-variadic-nyi.cir      |  16 +--
 .../Transforms/canonicalize-cleanup-scope.cir |   2 +-
 .../CIR/Transforms/cxx-abi-lowering-attrs.cir |   6 +-
 ...h-abi-lowering-construct-catch-invalid.cir |  12 +-
 .../eh-abi-lowering-construct-catch.cir       |   4 +-
 .../Transforms/eh-abi-lowering-itanium.cir    |   4 +-
 .../Transforms/flatten-cleanup-scope-eh.cir   |   4 +-
 .../flatten-cleanup-scope-multi-exit.cir      |   2 +-
 .../flatten-cleanup-scope-simple.cir          |   2 +-
 .../CIR/Transforms/flatten-loop-cleanup.cir   |   2 +-
 .../CIR/Transforms/flatten-preserve-attrs.cir |   2 +-
 .../flatten-throwing-in-cleanup.cir           |   2 +-
 clang/test/CIR/Transforms/flatten-try-op.cir  |   4 +-
 .../CIR/Transforms/pure-ptr-arithmetic.cir    |   6 +-
 clang/unittests/CIR/CallOpTest.cpp            |   2 +-
 115 files changed, 514 insertions(+), 516 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
index 8b2e98645991d..975d2a4304c42 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
@@ -688,21 +688,20 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [
     preserves the original source spelling.
 
     Every member has a kind, described by `CIR_RecordMemberKind`, saying what
-    it holds.  A `pad` or `empty` member is spelled with its mark.  A `data`
-    member may be spelled with or without one, and prints without.
+    it holds, and every member is spelled with its mark.
 
     Examples:
 
     ```
-        !rec_complete = !cir.struct<"complete" {!u8i}>
-        !rec_class    = !cir.struct<class "MyClass" {!s32i}>
+        !rec_complete = !cir.struct<"complete" {data !u8i}>
+        !rec_class    = !cir.struct<class "MyClass" {data !s32i}>
         !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>}>
+        !anonymous    = !cir.struct<{data !u8i}>
+        !rec_packed   = !cir.struct<"p1" packed {data !u8i, data !u8i}>
+        !rec_padded   = !cir.struct<"p2" padded {data !u8i, data !u8i}>
+        !rec_pad      = !cir.struct<"p3" {data !u8i, pad !cir.array<!u8i x 3>}>
         !rec_empty    = !cir.struct<"e" {empty !u8i}>
-        !recursive    = !cir.struct<"Node" {!cir.ptr<!cir.struct<"Node">>}>
+        !recursive    = !cir.struct<"Node" {data !cir.ptr<!cir.struct<"Node">>}>
     ```
   }];
 
@@ -851,10 +850,10 @@ def CIR_UnionType : CIR_Type<"Union", "union", [
     Examples:
 
     ```
-        !u_complete   = !cir.union<"U" {!s32i, !u8i}>
+        !u_complete   = !cir.union<"U" {data !s32i, data !u8i}>
         !u_incomplete = !cir.union<"U" incomplete>
-        !u_anonymous  = !cir.union<{!s32i, !u8i}>
-        !u_padded     = !cir.union<"U" {!s32i, !u8i}, padding = {!u8i}>
+        !u_anonymous  = !cir.union<{data !s32i, data !u8i}>
+        !u_padded     = !cir.union<"U" {data !s32i, data !u8i}, padding = {!u8i}>
         !u_empty      = !cir.union<"U" {empty !u8i}>
     ```
   }];
diff --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
index 7bff776c7df95..997bc29b48ab4 100644
--- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
@@ -170,23 +170,25 @@ verifyRecordMemberKinds(function_ref<mlir::InFlightDiagnostic()> emitError,
   return mlir::success();
 }
 
-/// Consume a member's optional kind mark, returning whether one was there.
-/// Only a mark keyword is consumed, so anything that is not one is left for the
-/// type parser to accept or reject.
-static bool consumeOptionalMemberKindMark(mlir::AsmParser &parser,
-                                          RecordMemberKind &kind) {
-  static const llvm::StringRef marks[] = {"data", "pad", "empty"};
-  kind = RecordMemberKind::Data;
+/// The keywords that spell a member kind.  A union's tail-padding slot probes
+/// for one of these to reject it, since that slot is not a member.
+static const llvm::StringRef memberKindMarks[] = {"data", "pad", "empty"};
+
+static std::optional<RecordMemberKind>
+parseMemberKind(mlir::AsmParser &parser) {
   llvm::StringRef keyword;
-  if (parser.parseOptionalKeyword(&keyword, marks).failed())
-    return false;
-  kind = *symbolizeRecordMemberKind(keyword);
-  return true;
+  const llvm::SMLoc loc = parser.getCurrentLocation();
+  if (parser.parseKeyword(&keyword).failed())
+    return std::nullopt;
+  std::optional<RecordMemberKind> kind = symbolizeRecordMemberKind(keyword);
+  if (!kind)
+    parser.emitError(loc, "expected a record member kind");
+  return kind;
 }
 
-/// Parse "incomplete" or "{[mark] type, [mark] type, ...}", writing results
-/// into \p incomplete, \p members and \p memberKinds.  Returns failure if
-/// member parsing fails.
+/// Parse "incomplete" or "{mark type, mark type, ...}", writing results into
+/// \p incomplete, \p members and \p memberKinds.  Returns failure if member
+/// parsing fails.
 static mlir::ParseResult
 parseRecordBody(mlir::AsmParser &parser, bool &incomplete,
                 llvm::SmallVector<mlir::Type> &members,
@@ -198,9 +200,10 @@ parseRecordBody(mlir::AsmParser &parser, bool &incomplete,
   return parser.parseCommaSeparatedList(
       AsmParser::Delimiter::Braces,
       [&parser, &members, &memberKinds]() -> mlir::ParseResult {
-        RecordMemberKind kind;
-        consumeOptionalMemberKindMark(parser, kind);
-        memberKinds.push_back(kind);
+        std::optional<RecordMemberKind> kind = parseMemberKind(parser);
+        if (!kind)
+          return mlir::failure();
+        memberKinds.push_back(*kind);
         return parser.parseType(members.emplace_back());
       });
 }
@@ -242,8 +245,7 @@ static void printRecordBody(mlir::AsmPrinter &printer, RecordTy self,
     for (auto [idx, member] : llvm::enumerate(members)) {
       if (idx)
         printer << ", ";
-      if (memberKinds[idx] != RecordMemberKind::Data)
-        printer << stringifyRecordMemberKind(memberKinds[idx]) << ' ';
+      printer << stringifyRecordMemberKind(memberKinds[idx]) << ' ';
       printer.printType(member);
     }
     printer << "}";
@@ -458,8 +460,9 @@ Type UnionType::parse(mlir::AsmParser &parser) {
     if (parser.parseLBrace().failed())
       return {};
     const llvm::SMLoc paddingLoc = parser.getCurrentLocation();
-    RecordMemberKind ignoredKind;
-    if (consumeOptionalMemberKindMark(parser, ignoredKind)) {
+    llvm::StringRef paddingKeyword;
+    if (parser.parseOptionalKeyword(&paddingKeyword, memberKindMarks)
+            .succeeded()) {
       parser.emitError(paddingLoc, "a union's tail padding takes no kind mark");
       return {};
     }
diff --git a/clang/test/CIR/CodeGen/atomic.c b/clang/test/CIR/CodeGen/atomic.c
index e63ec52b8121f..4c2baef5906a4 100644
--- a/clang/test/CIR/CodeGen/atomic.c
+++ b/clang/test/CIR/CodeGen/atomic.c
@@ -9,7 +9,7 @@ struct S1 {
   short x, y, z;
 };
 
-// CIR: !rec_anon_struct = !cir.struct<padded {!rec_S1, pad !cir.array<!s8i x 2>}>
+// CIR: !rec_anon_struct = !cir.struct<padded {data !rec_S1, pad !cir.array<!s8i x 2>}>
 
 _Atomic int g1;
 _Atomic int g2 = 42;
diff --git a/clang/test/CIR/CodeGen/bitfield-union.c b/clang/test/CIR/CodeGen/bitfield-union.c
index 50175a40968fa..2c00bb8371f21 100644
--- a/clang/test/CIR/CodeGen/bitfield-union.c
+++ b/clang/test/CIR/CodeGen/bitfield-union.c
@@ -11,7 +11,7 @@ typedef union {
   int z : 8;
 } demo;
 
-// CIR:  !rec_demo = !cir.union<"demo" {!s32i, !u8i, !u8i}>
+// CIR:  !rec_demo = !cir.union<"demo" {data !s32i, data !u8i, data !u8i}>
 // LLVM: %union.demo = type { i32 }
 // OGCG: %union.demo = type { i32 }
 
@@ -22,7 +22,7 @@ typedef union {
   int z : 2;
 } zero_bit;
 
-// CIR:  !rec_zero_bit = !cir.union<"zero_bit" {!s32i, !u8i, !u8i}>
+// CIR:  !rec_zero_bit = !cir.union<"zero_bit" {data !s32i, data !u8i, data !u8i}>
 // LLVM: %union.zero_bit = type { i32 }
 // OGCG: %union.zero_bit = type { i32 }
 
diff --git a/clang/test/CIR/CodeGen/bitfields.c b/clang/test/CIR/CodeGen/bitfields.c
index ff7e772875549..387023e8deea7 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 padded {data !s8i, data !s8i, data !s8i, data !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] }>
@@ -23,7 +23,7 @@ typedef struct {
   int c;
 } D;
 
-// CIR-DAG:  !rec_D = !cir.struct<"D" {!u16i, !s32i}>
+// CIR-DAG:  !rec_D = !cir.struct<"D" {data !u16i, data !s32i}>
 // LLVM-DAG: %struct.D = type { i16, i32 }
 // OGCG-DAG: %struct.D = type { i16, i32 }
 
@@ -36,7 +36,7 @@ typedef struct {
   unsigned f; // type other than int above, not a bitfield
 } S;
 // CIR-DAG:  #bfi_c = #cir.bitfield_info<name = "c", storage_type = !u64i, size = 17, offset = 32, is_signed = true>
-// CIR-DAG:  !rec_S = !cir.struct<"S" {!u64i, !u16i, !u32i}>
+// CIR-DAG:  !rec_S = !cir.struct<"S" {data !u64i, data !u16i, data !u32i}>
 // LLVM-DAG: %struct.S = type { i64, i16, i32 }
 // OGCG-DAG: %struct.S = type { i64, i16, i32 }
 
@@ -45,7 +45,7 @@ typedef struct {
   unsigned b;
 } T;
 
-// CIR-DAG:  !rec_T = !cir.struct<"T" {!u8i, !u32i}>
+// CIR-DAG:  !rec_T = !cir.struct<"T" {data !u8i, data !u32i}>
 // LLVM-DAG: %struct.T = type { i8, i32 }
 // OGCG-DAG: %struct.T = type { i8, i32 }
 
@@ -67,7 +67,7 @@ typedef struct {
     int l: 14;
 } U;
 
-// CIR-DAG:  !rec_U = !cir.struct<"U" packed {!s8i, !s8i, !s8i, !u8i, !u64i}>
+// CIR-DAG:  !rec_U = !cir.struct<"U" packed {data !s8i, data !s8i, data !s8i, data !u8i, data !u64i}>
 // LLVM-DAG: %struct.U = type <{ i8, i8, i8, i8, i64 }>
 // OGCG-DAG: %struct.U = type <{ i8, i8, i8, i8, i64 }>
 
@@ -77,7 +77,7 @@ typedef struct{
     int c: 30;
 } Clip;
 
-// CIR-DAG: !rec_Clip = !cir.struct<"Clip" {!cir.array<!u8i x 3>, !s8i, !u32i}>
+// CIR-DAG: !rec_Clip = !cir.struct<"Clip" {data !cir.array<!u8i x 3>, data !s8i, data !u32i}>
 // LLVM-DAG: %struct.Clip = type { [3 x i8], i8, i32 }
 // OGCG-DAG: %struct.Clip = type { [3 x i8], i8, i32 }
 
diff --git a/clang/test/CIR/CodeGen/bitfields.cpp b/clang/test/CIR/CodeGen/bitfields.cpp
index 2117d2848a531..1974a4ee7d914 100644
--- a/clang/test/CIR/CodeGen/bitfields.cpp
+++ b/clang/test/CIR/CodeGen/bitfields.cpp
@@ -13,7 +13,7 @@ typedef struct {
   int e : 15;
   unsigned f; // type other than int above, not a bitfield
 } S;
-// CIR-DAG:  !rec_S = !cir.struct<"S" {!u64i, !u16i, !u32i}>
+// CIR-DAG:  !rec_S = !cir.struct<"S" {data !u64i, data !u16i, data !u32i}>
 // CIR-DAG:  #bfi_c = #cir.bitfield_info<name = "c", storage_type = !u64i, size = 17, offset = 32, is_signed = true>
 // LLVM-DAG: %struct.S = type { i64, i16, i32 }
 // OGCG-DAG: %struct.S = type { i64, i16, i32 }
@@ -23,7 +23,7 @@ typedef struct {
   unsigned b;
 } T;
 
-// CIR-DAG:  !rec_T = !cir.struct<"T" {!u8i, !u32i}>
+// CIR-DAG:  !rec_T = !cir.struct<"T" {data !u8i, data !u32i}>
 // LLVM-DAG: %struct.T = type { i8, i32 }
 // OGCG-DAG: %struct.T = type { i8, i32 }
 
diff --git a/clang/test/CIR/CodeGen/bitfields_be.c b/clang/test/CIR/CodeGen/bitfields_be.c
index 5c707ef65e03d..74fa00d3b1f22 100644
--- a/clang/test/CIR/CodeGen/bitfields_be.c
+++ b/clang/test/CIR/CodeGen/bitfields_be.c
@@ -11,7 +11,7 @@ typedef struct {
     int c : 17;
 } S;
 
-// CIR:  !rec_S = !cir.struct<"S" {!u32i}>
+// CIR:  !rec_S = !cir.struct<"S" {data !u32i}>
 // LLVM: %struct.S = type { i32 }
 // OGCG: %struct.S = type { i32 }
 void def() {
diff --git a/clang/test/CIR/CodeGen/builtin-cpu-is-cputype.c b/clang/test/CIR/CodeGen/builtin-cpu-is-cputype.c
index c020fc3b141bf..9968f691515fd 100644
--- a/clang/test/CIR/CodeGen/builtin-cpu-is-cputype.c
+++ b/clang/test/CIR/CodeGen/builtin-cpu-is-cputype.c
@@ -6,7 +6,7 @@
 // in llvm/include/llvm/TargetParser/X86TargetParser.def.
 extern void a(const char *);
 
-// CIR: ![[MODEL_TY:.*]] = !cir.struct<{!u32i, !u32i, !u32i, !cir.array<!u32i x 1>}>
+// CIR: ![[MODEL_TY:.*]] = !cir.struct<{data !u32i, data !u32i, data !u32i, data !cir.array<!u32i x 1>}>
 // CIR: cir.global "private" external dso_local @__cpu_model : ![[MODEL_TY]]
 // LLVM: @__cpu_model = external dso_local global { i32, i32, i32, [1 x i32] }
 
diff --git a/clang/test/CIR/CodeGen/builtin-cpu-is-subtype.c b/clang/test/CIR/CodeGen/builtin-cpu-is-subtype.c
index 65be1e9ebac68..886bda7838d27 100644
--- a/clang/test/CIR/CodeGen/builtin-cpu-is-subtype.c
+++ b/clang/test/CIR/CodeGen/builtin-cpu-is-subtype.c
@@ -7,7 +7,7 @@
 // subtype, llvm/include/llvm/TargetParser/X86TargetParser.def.
 extern void a(const char *);
 
-// CIR: ![[MODEL_TY:.*]] = !cir.struct<{!u32i, !u32i, !u32i, !cir.array<!u32i x 1>}>
+// CIR: ![[MODEL_TY:.*]] = !cir.struct<{data !u32i, data !u32i, data !u32i, data !cir.array<!u32i x 1>}>
 // CIR: cir.global "private" external dso_local @__cpu_model : ![[MODEL_TY]]
 // LLVM: @__cpu_model = external dso_local global { i32, i32, i32, [1 x i32] }
 
diff --git a/clang/test/CIR/CodeGen/builtin-cpu-is-vendor.c b/clang/test/CIR/CodeGen/builtin-cpu-is-vendor.c
index b754f8dc6c1b1..59551a3f804e5 100644
--- a/clang/test/CIR/CodeGen/builtin-cpu-is-vendor.c
+++ b/clang/test/CIR/CodeGen/builtin-cpu-is-vendor.c
@@ -7,7 +7,7 @@
 // X86TargetParser.def.
 extern void a(const char *);
 
-// CIR: ![[MODEL_TY:.*]] = !cir.struct<{!u32i, !u32i, !u32i, !cir.array<!u32i x 1>}>
+// CIR: ![[MODEL_TY:.*]] = !cir.struct<{data !u32i, data !u32i, data !u32i, data !cir.array<!u32i x 1>}>
 // CIR: cir.global "private" external dso_local @__cpu_model : ![[MODEL_TY]]
 // LLVM: @__cpu_model = external dso_local global { i32, i32, i32, [1 x i32] }
 
diff --git a/clang/test/CIR/CodeGen/class.cpp b/clang/test/CIR/CodeGen/class.cpp
index 6baf9e4a02e36..267983a015723 100644
--- a/clang/test/CIR/CodeGen/class.cpp
+++ b/clang/test/CIR/CodeGen/class.cpp
@@ -6,9 +6,9 @@
 // RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
 
 // CIR: !rec_IncompleteC = !cir.struct<class "IncompleteC" incomplete>
-// CIR: !rec_Base = !cir.struct<class "Base" {!s32i}>
-// CIR: !rec_CompleteC = !cir.struct<class "CompleteC" {!s32i, !s8i}>
-// CIR: !rec_Derived = !cir.struct<class "Derived" {!rec_Base, !s32i}>
+// CIR: !rec_Base = !cir.struct<class "Base" {data !s32i}>
+// CIR: !rec_CompleteC = !cir.struct<class "CompleteC" {data !s32i, data !s8i}>
+// CIR: !rec_Derived = !cir.struct<class "Derived" {data !rec_Base, data !s32i}>
 
 // Note: LLVM and OGCG do not emit the type for incomplete classes.
 
diff --git a/clang/test/CIR/CodeGen/ctor.cpp b/clang/test/CIR/CodeGen/ctor.cpp
index b47de42b87935..1187c632b9bee 100644
--- a/clang/test/CIR/CodeGen/ctor.cpp
+++ b/clang/test/CIR/CodeGen/ctor.cpp
@@ -10,7 +10,7 @@ void baz() {
   Struk s;
 }
 
-// CHECK: !rec_Struk = !cir.struct<"Struk" {!s32i}>
+// CHECK: !rec_Struk = !cir.struct<"Struk" {data !s32i}>
 
 // Note: In the absence of the '-mconstructor-aliases' option, we emit two
 //       constructors here. The handling of constructor aliases is currently
diff --git a/clang/test/CIR/CodeGen/cxx-abi-lowering-string-array.cpp b/clang/test/CIR/CodeGen/cxx-abi-lowering-string-array.cpp
index 5e99bc10b97e9..8844e1094e26d 100644
--- a/clang/test/CIR/CodeGen/cxx-abi-lowering-string-array.cpp
+++ b/clang/test/CIR/CodeGen/cxx-abi-lowering-string-array.cpp
@@ -20,7 +20,7 @@ const S g = { &B::y, "abc" };
 
 const S *get() { return &g; }
 
-// CIR: !rec_S = !cir.struct<"S" {!s64i, !cir.array<!s8i x 32>}>
+// CIR: !rec_S = !cir.struct<"S" {data !s64i, data !cir.array<!s8i x 32>}>
 // CIR: cir.global {{.*}} @_ZL1g = #cir.const_record<{
 // CIR-SAME: #cir.int<4> : !s64i,
 // CIR-SAME: #cir.const_array<"abc" : !cir.array<!s8i x 3>, trailing_zeros> : !cir.array<!s8i x 32>
diff --git a/clang/test/CIR/CodeGen/destructors.cpp b/clang/test/CIR/CodeGen/destructors.cpp
index 3370e808f895b..44da623de766e 100644
--- a/clang/test/CIR/CodeGen/destructors.cpp
+++ b/clang/test/CIR/CodeGen/destructors.cpp
@@ -16,7 +16,7 @@ out_of_line_destructor::~out_of_line_destructor() {
     some_function();
 }
 
-// CIR: !rec_out_of_line_destructor = !cir.struct<"out_of_line_destructor" {!s32i}>
+// CIR: !rec_out_of_line_destructor = !cir.struct<"out_of_line_destructor" {data !s32i}>
 
 // CIR: cir.func {{.*}} @_ZN22out_of_line_destructorD2Ev(%{{.+}}: !cir.ptr<!rec_out_of_line_destructor>
 // CIR:   cir.call @_Z13some_functionv() nothrow : () -> () 
diff --git a/clang/test/CIR/CodeGen/dumb-record.cpp b/clang/test/CIR/CodeGen/dumb-record.cpp
index 095def36fa585..75d442f28aee2 100644
--- a/clang/test/CIR/CodeGen/dumb-record.cpp
+++ b/clang/test/CIR/CodeGen/dumb-record.cpp
@@ -5,8 +5,8 @@ struct SimpleStruct {
   float b;
 } simple;
 // CHECK: Layout: <CIRecordLayout
-// CHECK: CIR Type:!cir.struct<"SimpleStruct" {!cir.int<s, 32>, !cir.float}>
-// CHECK: NonVirtualBaseCIRType:!cir.struct<"SimpleStruct" {!cir.int<s, 32>, !cir.float}>
+// CHECK: CIR Type:!cir.struct<"SimpleStruct" {data !cir.int<s, 32>, data !cir.float}>
+// CHECK: NonVirtualBaseCIRType:!cir.struct<"SimpleStruct" {data !cir.int<s, 32>, data !cir.float}>
 // CHECK: IsZeroInitializable:1
 // CHECK:   BitFields:[
 // CHECK: ]>
@@ -30,8 +30,8 @@ struct BitfieldsInOrder {
 } bitfield_order;
 
 // CHECK: Layout: <CIRecordLayout
-// CHECK:  CIR Type:!cir.struct<"BitfieldsInOrder" {!cir.int<s, 8>, !cir.int<u, 8>, !cir.int<u, 32>}>
-// CHECK:  NonVirtualBaseCIRType:!cir.struct<"BitfieldsInOrder" {!cir.int<s, 8>, !cir.int<u, 8>, !cir.int<u, 32>}>
+// CHECK:  CIR Type:!cir.struct<"BitfieldsInOrder" {data !cir.int<s, 8>, data !cir.int<u, 8>, data !cir.int<u, 32>}>
+// CHECK:  NonVirtualBaseCIRType:!cir.struct<"BitfieldsInOrder" {data !cir.int<s, 8>, data !cir.int<u, 8>, data !cir.int<u, 32>}>
 // CHECK:  IsZeroInitializable:1
 // CHECK:  BitFields:[
 // CHECK-NEXT:   <CIRBitFieldInfo name:bit offset:0 size:8 isSigned:0 storageSize:8 storageOffset:1 volatileOffset:0 volatileStorageSize:0 volatileStorageOffset:0>
diff --git a/clang/test/CIR/CodeGen/empty-union.cpp b/clang/test/CIR/CodeGen/empty-union.cpp
index d02a31dbd8963..c543b25253625 100644
--- a/clang/test/CIR/CodeGen/empty-union.cpp
+++ b/clang/test/CIR/CodeGen/empty-union.cpp
@@ -32,8 +32,8 @@ struct WrapEmpty {
   int s;
 };
 WrapEmpty w;
-// CIR-DAG: !rec_OuterWithEmpty = !cir.union<"OuterWithEmpty" {!rec_Empty, !s32i}>
-// CIR-DAG: !rec_WrapEmpty = !cir.struct<"WrapEmpty" {!rec_OuterWithEmpty, !s32i}>
+// CIR-DAG: !rec_OuterWithEmpty = !cir.union<"OuterWithEmpty" {data !rec_Empty, data !s32i}>
+// CIR-DAG: !rec_WrapEmpty = !cir.struct<"WrapEmpty" {data !rec_OuterWithEmpty, data !s32i}>
 // CIR-DAG: cir.global external @w = #cir.zero : !rec_WrapEmpty {alignment = 4 : i64}
 // LLVM-DAG: %struct.WrapEmpty = type { %union.OuterWithEmpty, i32 }
 // LLVM-DAG: %union.OuterWithEmpty = type { i32 }
@@ -44,7 +44,7 @@ WrapEmpty w;
 union OnlyEmpty {
   Empty e;
 };
-// CIR-DAG: !rec_OnlyEmpty = !cir.union<"OnlyEmpty" {!rec_Empty}>
+// CIR-DAG: !rec_OnlyEmpty = !cir.union<"OnlyEmpty" {data !rec_Empty}>
 // LLVM-DAG: %union.OnlyEmpty = type { %union.Empty }
 
 // A storage-less union still occupies its own bytes inside a record, so the
@@ -83,11 +83,11 @@ LeadingOver leadOver;
 LeadingZeroBitfield leadZero;
 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_LeadingZeroBitfield = !cir.struct<"LeadingZeroBitfield" {!rec_OnlyZeroBitfield, !s32i}>
+// CIR-DAG: !rec_Leading = !cir.struct<"Leading" {data !rec_Empty, data !s32i}>
+// CIR-DAG: !rec_Trailing = !cir.struct<"Trailing" {data !s32i, data !rec_Empty}>
+// CIR-DAG: !rec_Middle = !cir.struct<"Middle" {data !s32i, data !rec_Empty, data !s32i}>
+// CIR-DAG: !rec_LeadingOver = !cir.struct<"LeadingOver" padded {data !rec_EmptyAligned, data !s32i, pad !cir.array<!u8i x 12>}>
+// CIR-DAG: !rec_LeadingZeroBitfield = !cir.struct<"LeadingZeroBitfield" {data !rec_OnlyZeroBitfield, data !s32i}>
 
 // CIR keeps the union's own named type as the record's field and leaves the
 // bytes after it to the LLVM struct layout.  Classic covers the union together
diff --git a/clang/test/CIR/CodeGen/finegrain-bitfield-access.cpp b/clang/test/CIR/CodeGen/finegrain-bitfield-access.cpp
index a253d6f7ae348..01512d910b12b 100644
--- a/clang/test/CIR/CodeGen/finegrain-bitfield-access.cpp
+++ b/clang/test/CIR/CodeGen/finegrain-bitfield-access.cpp
@@ -13,7 +13,7 @@ struct S1 {
   unsigned f5:8;
 };
 
-// CIR-DAG: !rec_S1 = !cir.struct<"S1" {!u8i, !u8i, !u16i}>
+// CIR-DAG: !rec_S1 = !cir.struct<"S1" {data !u8i, data !u8i, data !u16i}>
 // LLVM-DAG: %struct.S1 = type { i8, i8, i16 }
 // OGCG-DAG: %struct.S1 = type { i8, i8, i16 }
 
@@ -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" padded {data !u16i, data !u16i, data !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] }
 
@@ -33,7 +33,7 @@ struct S3 {
   unsigned long f3:32;
 };
 
-// CIR-DAG: !rec_S3 = !cir.struct<"S3" {!u32i, !u32i}>
+// CIR-DAG: !rec_S3 = !cir.struct<"S3" {data !u32i, data !u32i}>
 // LLVM-DAG: %struct.S3 = type { i32, i32 }
 // OGCG-DAG: %struct.S3 = type { i32, i32 }
 
diff --git a/clang/test/CIR/CodeGen/flexible-array-init.c b/clang/test/CIR/CodeGen/flexible-array-init.c
index 5e5c007a3de85..a0b062d218b5a 100644
--- a/clang/test/CIR/CodeGen/flexible-array-init.c
+++ b/clang/test/CIR/CodeGen/flexible-array-init.c
@@ -5,8 +5,8 @@
 // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
 // RUN: FileCheck --check-prefix=LLVM,OGCG --input-file=%t.ll %s
 
-// CIR: !rec_S = !cir.struct<"S" {!s32i, !cir.array<!s8i x 0>}>
-// CIR: !rec_T = !cir.struct<"T" {!cir.ptr<!s32i>, !cir.array<!s32i x 0>}>
+// CIR: !rec_S = !cir.struct<"S" {data !s32i, data !cir.array<!s8i x 0>}>
+// CIR: !rec_T = !cir.struct<"T" {data !cir.ptr<!s32i>, data !cir.array<!s32i x 0>}>
 
 // 's1' lowers via the bulk constant-record path (LowerToLLVM.cpp ~line 2585):
 // every member can be lowered to a constant attribute.
diff --git a/clang/test/CIR/CodeGen/forward-decls.cpp b/clang/test/CIR/CodeGen/forward-decls.cpp
index 78cd16dcdddfa..ef5fe16f9566f 100644
--- a/clang/test/CIR/CodeGen/forward-decls.cpp
+++ b/clang/test/CIR/CodeGen/forward-decls.cpp
@@ -24,7 +24,7 @@ void testIncompleteStruct(struct IncompleteStruct *s) {};
 // Foward declaration of the struct is followed by usage, then definition.
 // This means it will initially be created as incomplete, then completed.
 
-// CHECK2: ![[COMPLETE:.+]] = !cir.struct<"ForwardDeclaredStruct" {!s32i}>
+// CHECK2: ![[COMPLETE:.+]] = !cir.struct<"ForwardDeclaredStruct" {data !s32i}>
 // CHECK2: testForwardDeclaredStruct(%arg0: !cir.ptr<![[COMPLETE]]>
 struct ForwardDeclaredStruct;
 void testForwardDeclaredStruct(struct ForwardDeclaredStruct *fds) {};
@@ -42,7 +42,7 @@ struct ForwardDeclaredStruct {
 // Struct is initially forward declared since the self-reference is generated
 // first. Then, once the type is fully generated, it is completed.
 
-// CHECK3: ![[STRUCT:.+]] = !cir.struct<"RecursiveStruct" {!s32i, !cir.ptr<!cir.struct<"RecursiveStruct">>}>
+// CHECK3: ![[STRUCT:.+]] = !cir.struct<"RecursiveStruct" {data !s32i, data !cir.ptr<!cir.struct<"RecursiveStruct">>}>
 struct RecursiveStruct {
   int value;
   struct RecursiveStruct *next;
@@ -67,8 +67,8 @@ void testRecursiveStruct(struct RecursiveStruct *arg) {
 // in recursive type, each struct is expanded until there are no more recursive
 // types, or all the recursive types are self references.
 
-// CHECK4: ![[B:.+]] = !cir.struct<"StructNodeB" {!s32i, !cir.ptr<!cir.struct<"StructNodeA" {!s32i, !cir.ptr<!cir.struct<"StructNodeB">>}
-// CHECK4: ![[A:.+]] = !cir.struct<"StructNodeA" {!s32i, !cir.ptr<![[B]]>}>
+// CHECK4: ![[B:.+]] = !cir.struct<"StructNodeB" {data !s32i, data !cir.ptr<!cir.struct<"StructNodeA" {data !s32i, data !cir.ptr<!cir.struct<"StructNodeB">>}
+// CHECK4: ![[A:.+]] = !cir.struct<"StructNodeA" {data !s32i, data !cir.ptr<![[B]]>}>
 struct StructNodeB;
 struct StructNodeA {
   int value;
@@ -96,12 +96,12 @@ void testIndirectSelfReference(struct StructNodeA arg) {
 // RUN: FileCheck --check-prefix=CHECK5 --input-file=%t/complex_struct.cir %s
 
 // A sizeable complex struct just to double check that stuff is working.
-// CHECK5: !cir.struct<"anon.0" {!cir.ptr<!cir.struct<"A" {!cir.struct<"anon.0">, !cir.struct<"B" {!cir.ptr<!cir.struct<"B">>, !cir.struct<"C" {!cir.ptr<!cir.struct<"A">>, !cir.ptr<!cir.struct<"B">>, !cir.ptr<!cir.struct<"C">>}>, !cir.union<"anon.1" {!cir.ptr<!cir.struct<"A">>, !cir.struct<"anon.2" {!cir.ptr<!cir.struct<"B">>}>}>}>}>>}>
-// CHECK5: !cir.struct<"C" {!cir.ptr<!cir.struct<"A" {!rec_anon2E0, !cir.struct<"B" {!cir.ptr<!cir.struct<"B">>, !cir.struct<"C">, !cir.union<"anon.1" {!cir.ptr<!cir.struct<"A">>, !cir.struct<"anon.2" {!cir.ptr<!cir.struct<"B">>}>}>}>}>>, !cir.ptr<!cir.struct<"B" {!cir.ptr<!cir.struct<"B">>, !cir.struct<"C">, !cir.union<"anon.1" {!cir.ptr<!cir.struct<"A" {!rec_anon2E0, !cir.struct<"B">}>>, !cir.struct<"anon.2" {!cir.ptr<!cir.struct<"B">>}>}>}>>, !cir.ptr<!cir.struct<"C">>}>
-// CHECK5: !cir.struct<"anon.2" {!cir.ptr<!cir.struct<"B" {!cir.ptr<!cir.struct<"B">>, !rec_C, !cir.union<"anon.1" {!cir.ptr<!cir.struct<"A" {!rec_anon2E0, !cir.struct<"B">}>>, !cir.struct<"anon.2">}>}>>}>
-// CHECK5: !cir.union<"anon.1" {!cir.ptr<!cir.struct<"A" {!rec_anon2E0, !cir.struct<"B" {!cir.ptr<!cir.struct<"B">>, !rec_C, !cir.union<"anon.1">}>}>>, !rec_anon2E2}>
-// CHECK5: !cir.struct<"B" {!cir.ptr<!cir.struct<"B">>, !rec_C, !rec_anon2E1}>
-// CHECK5: !cir.struct<"A" {!rec_anon2E0, !rec_B}>
+// CHECK5: !cir.struct<"anon.0" {data !cir.ptr<!cir.struct<"A" {data !cir.struct<"anon.0">, data !cir.struct<"B" {data !cir.ptr<!cir.struct<"B">>, data !cir.struct<"C" {data !cir.ptr<!cir.struct<"A">>, data !cir.ptr<!cir.struct<"B">>, data !cir.ptr<!cir.struct<"C">>}>, data !cir.union<"anon.1" {data !cir.ptr<!cir.struct<"A">>, data !cir.struct<"anon.2" {data !cir.ptr<!cir.struct<"B">>}>}>}>}>>}>
+// CHECK5: !cir.struct<"C" {data !cir.ptr<!cir.struct<"A" {data !rec_anon2E0, data !cir.struct<"B" {data !cir.ptr<!cir.struct<"B">>, data !cir.struct<"C">, data !cir.union<"anon.1" {data !cir.ptr<!cir.struct<"A">>, data !cir.struct<"anon.2" {data !cir.ptr<!cir.struct<"B">>}>}>}>}>>, data !cir.ptr<!cir.struct<"B" {data !cir.ptr<!cir.struct<"B">>, data !cir.struct<"C">, data !cir.union<"anon.1" {data !cir.ptr<!cir.struct<"A" {data !rec_anon2E0, data !cir.struct<"B">}>>, data !cir.struct<"anon.2" {data !cir.ptr<!cir.struct<"B">>}>}>}>>, data !cir.ptr<!cir.struct<"C">>}>
+// CHECK5: !cir.struct<"anon.2" {data !cir.ptr<!cir.struct<"B" {data !cir.ptr<!cir.struct<"B">>, data !rec_C, data !cir.union<"anon.1" {data !cir.ptr<!cir.struct<"A" {data !rec_anon2E0, data !cir.struct<"B">}>>, data !cir.struct<"anon.2">}>}>>}>
+// CHECK5: !cir.union<"anon.1" {data !cir.ptr<!cir.struct<"A" {data !rec_anon2E0, data !cir.struct<"B" {data !cir.ptr<!cir.struct<"B">>, data !rec_C, data !cir.union<"anon.1">}>}>>, data !rec_anon2E2}>
+// CHECK5: !cir.struct<"B" {data !cir.ptr<!cir.struct<"B">>, data !rec_C, data !rec_anon2E1}>
+// CHECK5: !cir.struct<"A" {data !rec_anon2E0, data !rec_B}>
 struct A {
   struct {
     struct A *a1;
diff --git a/clang/test/CIR/CodeGen/inline-cxx-func.cpp b/clang/test/CIR/CodeGen/inline-cxx-func.cpp
index ea216ad9b8422..cc5aeba74da89 100644
--- a/clang/test/CIR/CodeGen/inline-cxx-func.cpp
+++ b/clang/test/CIR/CodeGen/inline-cxx-func.cpp
@@ -13,7 +13,7 @@ struct S {
   }
 };
 
-// CIR: !rec_S = !cir.struct<"S" {!s32i}>
+// CIR: !rec_S = !cir.struct<"S" {data !s32i}>
 // LLVM: %struct.S = type { i32 }
 // OGCG: %struct.S = type { i32 }
 
diff --git a/clang/test/CIR/CodeGen/mms-bitfields.c b/clang/test/CIR/CodeGen/mms-bitfields.c
index a6f97aed8ed84..861164f4beeba 100644
--- a/clang/test/CIR/CodeGen/mms-bitfields.c
+++ b/clang/test/CIR/CodeGen/mms-bitfields.c
@@ -10,7 +10,7 @@ struct s1 {
   long long f64 : 30;
 } s1;
 
-// CIR-DAG: !rec_s1 = !cir.struct<"s1" {!s32i, !s64i}>
+// CIR-DAG: !rec_s1 = !cir.struct<"s1" {data !s32i, data !s64i}>
 // LLVM-DAG: %struct.s1 = type { i32, i64 }
 // OGCG-DAG: %struct.s1 = type { i32, i64 }
 
@@ -20,7 +20,7 @@ struct s2 {
     int c : 30;
 } Clip;
 
-// CIR-DAG: !rec_s2 = !cir.struct<"s2" {!s32i, !s8i, !s32i}>
+// CIR-DAG: !rec_s2 = !cir.struct<"s2" {data !s32i, data !s8i, data !s32i}>
 // LLVM-DAG: %struct.s2 = type { i32, i8, i32 }
 // OGCG-DAG: %struct.s2 = type { i32, i8, i32 }
 
@@ -30,7 +30,7 @@ struct s3 {
     int c : 14;
 } zero_bit;
 
-// CIR-DAG:  !rec_s3 = !cir.struct<"s3" {!s32i, !s32i}>
+// CIR-DAG:  !rec_s3 = !cir.struct<"s3" {data !s32i, data !s32i}>
 // LLVM-DAG: %struct.s3 = type { i32, i32 }
 // OGCG-DAG: %struct.s3 = type { i32, i32 }
 
@@ -45,7 +45,7 @@ struct Inner {
 
 #pragma pack (pop)
 
-// CIR-DAG: !rec_Inner = !cir.struct<"Inner" {!u32i, !u32i}>
+// CIR-DAG: !rec_Inner = !cir.struct<"Inner" {data !u32i, data !u32i}>
 // LLVM-DAG: %struct.Inner = type { i32, i32 }
 // OGCG-DAG: %struct.Inner = type { i32, i32 }
 
@@ -67,8 +67,8 @@ union HEADER {
 
 #pragma pack(pop)
 
-// CIR-DAG: !rec_A = !cir.struct<"A" {!s32i, !s32i, !s32i}>
-// CIR-DAG: !rec_HEADER = !cir.union<"HEADER" {!rec_A}>
+// CIR-DAG: !rec_A = !cir.struct<"A" {data !s32i, data !s32i, data !s32i}>
+// CIR-DAG: !rec_HEADER = !cir.union<"HEADER" {data !rec_A}>
 // LLVM-DAG: %struct.A = type { i32, i32, i32 }
 // LLVM-DAG: %union.HEADER = type { %struct.A }
 // OGCG-DAG: %struct.A = type { i32, i32, i32 }
diff --git a/clang/test/CIR/CodeGen/multi-vtable.cpp b/clang/test/CIR/CodeGen/multi-vtable.cpp
index b2afa831d7f66..fd51c6bf80807 100644
--- a/clang/test/CIR/CodeGen/multi-vtable.cpp
+++ b/clang/test/CIR/CodeGen/multi-vtable.cpp
@@ -30,12 +30,12 @@ void Mother::MotherKey() {}
 void Father::FatherKey() {}
 void Child::MotherKey() {}
 
-// CIR-DAG: [[MOTHER_VTABLE_TYPE:.*]] = !cir.struct<{!cir.array<!cir.ptr<!u8i> x 4>}>
-// CIR-DAG: [[FATHER_VTABLE_TYPE:.*]] = !cir.struct<{!cir.array<!cir.ptr<!u8i> x 3>}>
-// CIR-DAG: [[CHILD_VTABLE_TYPE:.*]] = !cir.struct<{!cir.array<!cir.ptr<!u8i> x 4>, !cir.array<!cir.ptr<!u8i> x 3>}>
-// CIR-DAG: !rec_Father = !cir.struct<class "Father" {!cir.vptr}
-// CIR-DAG: !rec_Mother = !cir.struct<class "Mother" {!cir.vptr}
-// CIR-DAG: !rec_Child = !cir.struct<class "Child" {!rec_Mother, !rec_Father}
+// CIR-DAG: [[MOTHER_VTABLE_TYPE:.*]] = !cir.struct<{data !cir.array<!cir.ptr<!u8i> x 4>}>
+// CIR-DAG: [[FATHER_VTABLE_TYPE:.*]] = !cir.struct<{data !cir.array<!cir.ptr<!u8i> x 3>}>
+// CIR-DAG: [[CHILD_VTABLE_TYPE:.*]] = !cir.struct<{data !cir.array<!cir.ptr<!u8i> x 4>, data !cir.array<!cir.ptr<!u8i> x 3>}>
+// CIR-DAG: !rec_Father = !cir.struct<class "Father" {data !cir.vptr}
+// CIR-DAG: !rec_Mother = !cir.struct<class "Mother" {data !cir.vptr}
+// CIR-DAG: !rec_Child = !cir.struct<class "Child" {data !rec_Mother, data !rec_Father}
 
 // Child vtable
 
diff --git a/clang/test/CIR/CodeGen/no-proto-fn-ptr-global-init.c b/clang/test/CIR/CodeGen/no-proto-fn-ptr-global-init.c
index 3a58e0f820355..7ac221bb806bd 100644
--- a/clang/test/CIR/CodeGen/no-proto-fn-ptr-global-init.c
+++ b/clang/test/CIR/CodeGen/no-proto-fn-ptr-global-init.c
@@ -9,10 +9,10 @@
 // The initializer must use the struct field's function-pointer type (not the
 // FuncOp's no-prototype type) so CIR stays valid after the replacement.
 
-// CHECK: !{{[^ ]+}} = !cir.struct<"S1" {!cir.ptr<!cir.func<()>>, !s32i, !s32i}>
-// CHECK: !{{[^ ]+}} = !cir.struct<"S2" {!cir.ptr<!cir.func<(...)>>, !s32i, !s32i}>
-// CHECK: !{{[^ ]+}} = !cir.struct<"S3" {!cir.ptr<!cir.func<(...)>>, !s32i, !s32i}>
-// CHECK: !{{[^ ]+}} = !cir.struct<"S4" {!cir.ptr<!cir.func<(...)>>, !s32i, !s32i}>
+// CHECK: !{{[^ ]+}} = !cir.struct<"S1" {data !cir.ptr<!cir.func<()>>, data !s32i, data !s32i}>
+// CHECK: !{{[^ ]+}} = !cir.struct<"S2" {data !cir.ptr<!cir.func<(...)>>, data !s32i, data !s32i}>
+// CHECK: !{{[^ ]+}} = !cir.struct<"S3" {data !cir.ptr<!cir.func<(...)>>, data !s32i, data !s32i}>
+// CHECK: !{{[^ ]+}} = !cir.struct<"S4" {data !cir.ptr<!cir.func<(...)>>, data !s32i, data !s32i}>
 
 struct S1 {
   void (*fn_ptr)(void);
diff --git a/clang/test/CIR/CodeGen/no-unique-address.cpp b/clang/test/CIR/CodeGen/no-unique-address.cpp
index 54d5d92040c15..a4e70c41bb28e 100644
--- a/clang/test/CIR/CodeGen/no-unique-address.cpp
+++ b/clang/test/CIR/CodeGen/no-unique-address.cpp
@@ -29,8 +29,8 @@ struct Outer {
 // [[no_unique_address]] field, allowing 'extra' to overlap with
 // 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.array<!u8i x 2>}>
+// CIR: !rec_Middle2Ebase = !cir.struct<"Middle.base" packed {data !rec_Base, data !s8i}>
+// CIR: !rec_Outer = !cir.struct<"Outer" padded {data !rec_Middle2Ebase, data !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>
@@ -184,17 +184,17 @@ struct OuterUnionPadAfterStorage {
 
 OuterUnionPadAfterStorage oupas;
 
-// CIR-NUA-DAG: !rec_FinalForNUA = !cir.struct<"FinalForNUA" {!s32i, !s8i}>
-// CIR-NUA-DAG: !rec_UnionForNUA = !cir.union<"UnionForNUA" {!s32i, !s64i}>
-// CIR-NUA-DAG: !rec_OuterFinal = !cir.struct<"OuterFinal" {!rec_FinalForNUA, !s8i}>
-// CIR-NUA-DAG: !rec_OuterUnion = !cir.struct<"OuterUnion" {!rec_UnionForNUA, !s32i}>
-// CIR-NUA-DAG: !rec_UnionWithPadding2Ebase = !cir.struct<"UnionWithPadding.base" {!u8i}>
-// CIR-NUA-DAG: !rec_OuterUnionPad = !cir.struct<"OuterUnionPad" {!rec_UnionWithPadding2Ebase, !cir.bool}>
-// CIR-NUA-DAG: !rec_FinalUnionWithPadding2Ebase = !cir.struct<"FinalUnionWithPadding.base" {!u8i}>
-// CIR-NUA-DAG: !rec_OuterFinalUnionPad = !cir.struct<"OuterFinalUnionPad" {!rec_FinalUnionWithPadding2Ebase, !cir.bool}>
-// CIR-NUA-DAG: !rec_TailBig = !cir.struct<"TailBig" packed padded {!s16i, !cir.array<!s8i x 5>, pad !u8i}>
-// CIR-NUA-DAG: !rec_UnionPadAfterStorage2Ebase = !cir.struct<"UnionPadAfterStorage.base" packed padded {!s32i, pad !cir.array<!u8i x 3>}>
-// CIR-NUA-DAG: !rec_OuterUnionPadAfterStorage = !cir.struct<"OuterUnionPadAfterStorage" {!rec_UnionPadAfterStorage2Ebase, !cir.bool}>
+// CIR-NUA-DAG: !rec_FinalForNUA = !cir.struct<"FinalForNUA" {data !s32i, data !s8i}>
+// CIR-NUA-DAG: !rec_UnionForNUA = !cir.union<"UnionForNUA" {data !s32i, data !s64i}>
+// CIR-NUA-DAG: !rec_OuterFinal = !cir.struct<"OuterFinal" {data !rec_FinalForNUA, data !s8i}>
+// CIR-NUA-DAG: !rec_OuterUnion = !cir.struct<"OuterUnion" {data !rec_UnionForNUA, data !s32i}>
+// CIR-NUA-DAG: !rec_UnionWithPadding2Ebase = !cir.struct<"UnionWithPadding.base" {data !u8i}>
+// CIR-NUA-DAG: !rec_OuterUnionPad = !cir.struct<"OuterUnionPad" {data !rec_UnionWithPadding2Ebase, data !cir.bool}>
+// CIR-NUA-DAG: !rec_FinalUnionWithPadding2Ebase = !cir.struct<"FinalUnionWithPadding.base" {data !u8i}>
+// CIR-NUA-DAG: !rec_OuterFinalUnionPad = !cir.struct<"OuterFinalUnionPad" {data !rec_FinalUnionWithPadding2Ebase, data !cir.bool}>
+// CIR-NUA-DAG: !rec_TailBig = !cir.struct<"TailBig" packed padded {data !s16i, data !cir.array<!s8i x 5>, pad !u8i}>
+// CIR-NUA-DAG: !rec_UnionPadAfterStorage2Ebase = !cir.struct<"UnionPadAfterStorage.base" packed padded {data !s32i, pad !cir.array<!u8i x 3>}>
+// CIR-NUA-DAG: !rec_OuterUnionPadAfterStorage = !cir.struct<"OuterUnionPadAfterStorage" {data !rec_UnionPadAfterStorage2Ebase, data !cir.bool}>
 // CIR-NUA-DAG: cir.global external @ou = #cir.zero : !rec_OuterUnion
 // CIR-NUA-DAG: cir.global external @of = #cir.zero : !rec_OuterFinal
 // CIR-NUA-DAG: cir.global external @oup = #cir.zero : !rec_OuterUnionPad
@@ -233,9 +233,9 @@ struct OuterAllEmpty {
 
 OuterAllEmpty oae;
 
-// CIR-NUA-DAG: !rec_OuterAllEmpty = !cir.struct<"OuterAllEmpty" {!cir.bool}>
+// CIR-NUA-DAG: !rec_OuterAllEmpty = !cir.struct<"OuterAllEmpty" {data !cir.bool}>
 // CIR-NUA-DAG: cir.global external @oae = #cir.zero : !rec_OuterAllEmpty
 
-// CIR-NUA-DAG: !rec_UnionZeroDataSize = !cir.union<"UnionZeroDataSize" {!rec_EmptyForNUA, !s32i}>
-// CIR-NUA-DAG: !rec_OuterZeroData = !cir.struct<"OuterZeroData" {!rec_UnionZeroDataSize, !cir.bool}>
+// CIR-NUA-DAG: !rec_UnionZeroDataSize = !cir.union<"UnionZeroDataSize" {data !rec_EmptyForNUA, data !s32i}>
+// CIR-NUA-DAG: !rec_OuterZeroData = !cir.struct<"OuterZeroData" {data !rec_UnionZeroDataSize, data !cir.bool}>
 // CIR-NUA-DAG: cir.global external @ozd = #cir.zero : !rec_OuterZeroData
diff --git a/clang/test/CIR/CodeGen/nonzeroinit-struct.cpp b/clang/test/CIR/CodeGen/nonzeroinit-struct.cpp
index 3540ad0d71d2f..e6166547232d1 100644
--- a/clang/test/CIR/CodeGen/nonzeroinit-struct.cpp
+++ b/clang/test/CIR/CodeGen/nonzeroinit-struct.cpp
@@ -21,13 +21,13 @@ struct Trivial {
     decltype(&Other::x) ptr;
 };
 
-// CIR-BEFORE-DAG: !rec_Other = !cir.struct<"Other" {!s32i}>
-// CIR-BEFORE-DAG: !rec_Trivial = !cir.struct<"Trivial" {!s32i, !cir.double, !cir.data_member<!s32i in !rec_Other>}>
-// CIR-BEFORE-DAG: !rec_WithMemPtr = !cir.struct<"WithMemPtr" {!s32i, !cir.double, !cir.method<!cir.func<(!cir.ptr<!rec_Other>, !s32i, !cir.float)> in !rec_Other>}>
-// CIR-AFTER-DAG: !rec_Other = !cir.struct<"Other" {!s32i}>
-// CIR-AFTER-DAG: !rec_Trivial = !cir.struct<"Trivial" {!s32i, !cir.double, !s64i}>
-// CIR-AFTER-DAG: !rec_anon_struct = !cir.struct<{!s64i, !s64i}>
-// CIR-AFTER-DAG: !rec_WithMemPtr = !cir.struct<"WithMemPtr" {!s32i, !cir.double, !rec_anon_struct}>
+// CIR-BEFORE-DAG: !rec_Other = !cir.struct<"Other" {data !s32i}>
+// CIR-BEFORE-DAG: !rec_Trivial = !cir.struct<"Trivial" {data !s32i, data !cir.double, data !cir.data_member<!s32i in !rec_Other>}>
+// CIR-BEFORE-DAG: !rec_WithMemPtr = !cir.struct<"WithMemPtr" {data !s32i, data !cir.double, data !cir.method<!cir.func<(!cir.ptr<!rec_Other>, !s32i, !cir.float)> in !rec_Other>}>
+// CIR-AFTER-DAG: !rec_Other = !cir.struct<"Other" {data !s32i}>
+// CIR-AFTER-DAG: !rec_Trivial = !cir.struct<"Trivial" {data !s32i, data !cir.double, data !s64i}>
+// CIR-AFTER-DAG: !rec_anon_struct = !cir.struct<{data !s64i, data !s64i}>
+// CIR-AFTER-DAG: !rec_WithMemPtr = !cir.struct<"WithMemPtr" {data !s32i, data !cir.double, data !rec_anon_struct}>
 
 // LLVM-DAG: %struct.WithMemPtr = type { i32, double, { i64, i64 } }
 // LLVM-DAG: %struct.Trivial = type { i32, double, i64 }
diff --git a/clang/test/CIR/CodeGen/paren-list-agg-init.cpp b/clang/test/CIR/CodeGen/paren-list-agg-init.cpp
index 3184919b04768..b9b877f258665 100644
--- a/clang/test/CIR/CodeGen/paren-list-agg-init.cpp
+++ b/clang/test/CIR/CodeGen/paren-list-agg-init.cpp
@@ -19,7 +19,7 @@ template <typename T>
 concept SameAsChar = (bool)IsInt<T>();
 
 // LLVM-DAG: [[STRUCT_A:%.*]] = type { i8, double }
-// CIR-DAG: ![[STRUCT_A:.*]] = !cir.struct<"A" {!s8i, !cir.double}>
+// CIR-DAG: ![[STRUCT_A:.*]] = !cir.struct<"A" {data !s8i, data !cir.double}>
 struct A {
   char i;
   double j;
@@ -29,20 +29,20 @@ struct A {
 };
 
 // LLVM-DAG: [[STRUCT_B:%.*]] = type { [[STRUCT_A]], i32 }
-// CIR-DAG: ![[STRUCT_B:.*]] = !cir.struct<"B" {![[STRUCT_A]], !s32i}>
+// CIR-DAG: ![[STRUCT_B:.*]] = !cir.struct<"B" {data ![[STRUCT_A]], data !s32i}>
 struct B {
   A a;
   int 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 padded {data ![[STRUCT_B]], data ![[STRUCT_A]], data !s32i, pad !cir.array<!u8i x 4>}>
 struct C : public B, public A {
   int c;
 };
 
 // LLVM-DAG: [[STRUCT_D:%.*]] = type { [[STRUCT_A]], [[STRUCT_A]], i8, [[STRUCT_A]] }
-// CIR-DAG: ![[STRUCT_D:.*]] = !cir.struct<"D" {![[STRUCT_A]], ![[STRUCT_A]], !u8i, ![[STRUCT_A]]}>
+// CIR-DAG: ![[STRUCT_D:.*]] = !cir.struct<"D" {data ![[STRUCT_A]], data ![[STRUCT_A]], data !u8i, data ![[STRUCT_A]]}>
 struct D {
   A a;
   A b = A{2, 2.0};
@@ -51,7 +51,7 @@ struct D {
 };
 
 // LLVM-DAG: [[STRUCT_E:%.*]] = type { i32, ptr }
-// CIR-DAG: ![[STRUCT_E:.*]] = !cir.struct<"E" {!s32i, !cir.ptr<!s8i>}>
+// CIR-DAG: ![[STRUCT_E:.*]] = !cir.struct<"E" {data !s32i, data !cir.ptr<!s8i>}>
 struct E {
   int a;
   const char* fn = __builtin_FUNCTION();
@@ -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 padded {data !s32i, data !rec_F, pad !cir.array<!u8i x 3>}>
 struct G {
   int a;
   F f;
@@ -75,7 +75,7 @@ struct G {
 
 // LLVM-DAG: [[UNION_U:%.*]] = type { [[STRUCT_A]] }
 // LLVM-DAG: [[STR:@.*]] = private {{.*}}constant [6 x i8] {{.*}}foo18{{.*}}, align 1
-// CIR-DAG: ![[UNION_U:.*]] = !cir.union<"U" {!u8i, ![[STRUCT_A]], !s8i}>
+// CIR-DAG: ![[UNION_U:.*]] = !cir.union<"U" {data !u8i, data ![[STRUCT_A]], data !s8i}>
 union U {
   unsigned : 1;
   A a;
@@ -94,14 +94,14 @@ namespace gh61145 {
 
   // LLVMCIR-DAG: [[STRUCT_S1:%.*]] = type { %"struct.gh61145::Vec" }
   // OGCG-DAG:    [[STRUCT_S1:%.*]] = type { i8 }
-  // CIR-DAG: ![[STRUCT_S1:.*]] = !cir.struct<"gh61145::S1" {!rec_gh611453A3AVec}>
+  // CIR-DAG: ![[STRUCT_S1:.*]] = !cir.struct<"gh61145::S1" {data !rec_gh611453A3AVec}>
   struct S1 {
     Vec v;
   };
 
   // LLVMCIR-DAG: [[STRUCT_S2:%.*]] = type { %"struct.gh61145::Vec", i8 }
   // OGCG-DAG:    [[STRUCT_S2:%.*]] = type { i8, i8 }
-  // CIR-DAG: ![[STRUCT_S2:.*]] = !cir.struct<"gh61145::S2" {!rec_gh611453A3AVec, !s8i}>
+  // CIR-DAG: ![[STRUCT_S2:.*]] = !cir.struct<"gh61145::S2" {data !rec_gh611453A3AVec, data !s8i}>
   struct S2 {
     Vec v;
     char c;
@@ -110,7 +110,7 @@ namespace gh61145 {
 
 namespace gh62266 {
   // LLVM-DAG: [[STRUCT_H:%.*H.*]] = type { i32, i32 }
-  // CIR-DAG: ![[STRUCT_H:.*]] = !cir.struct<"gh62266::H<2>" {!s32i, !s32i}>
+  // CIR-DAG: ![[STRUCT_H:.*]] = !cir.struct<"gh62266::H<2>" {data !s32i, data !s32i}>
   template <int J>
   struct H {
     int i;
@@ -120,7 +120,7 @@ namespace gh62266 {
 
 namespace gh61567 {
   // LLVM-DAG: [[STRUCT_I:%.*I.*]] = type { i32, ptr }
-  // CIR-DAG: ![[STRUCT_I:.*]] = !cir.struct<"gh61567::I" {!s32i, !cir.ptr<!s32i>}>
+  // CIR-DAG: ![[STRUCT_I:.*]] = !cir.struct<"gh61567::I" {data !s32i, data !cir.ptr<!s32i>}>
   struct I {
     int a;
     int&& r = 2;
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 7abc62e3c0b5f..ee093a57c1f79 100644
--- a/clang/test/CIR/CodeGen/pointer-to-empty-data-member.cpp
+++ b/clang/test/CIR/CodeGen/pointer-to-empty-data-member.cpp
@@ -14,7 +14,7 @@ struct HasEmpty {
   int size;
   Empty s;
 };
-// CIR-DAG: !rec_HasEmpty = !cir.struct<"HasEmpty" {!s32i, !rec_Empty}>
+// CIR-DAG: !rec_HasEmpty = !cir.struct<"HasEmpty" {data !s32i, data !rec_Empty}>
 // LLVMCIR-DAG: %struct.HasEmpty = type { i32, %struct.Empty }
 // OGCG-DAG:    %struct.HasEmpty = type { i32, [4 x i8] }
 
@@ -27,7 +27,7 @@ struct HasEmpty2 {
   Empty s;
   int size;
 };
-// CIR-DAG: !rec_HasEmpty2 = !cir.struct<"HasEmpty2" {!rec_Empty, !s32i}>
+// CIR-DAG: !rec_HasEmpty2 = !cir.struct<"HasEmpty2" {data !rec_Empty, data !s32i}>
 // LLVMCIR-DAG: %struct.HasEmpty2 = type { %struct.Empty, i32 }
 // OGCG-DAG:    %struct.HasEmpty2 = type { [4 x i8], i32 }
 
@@ -41,10 +41,10 @@ struct EmptyBase{};
 // CIR-BEFORE-DAG: !rec_EmptyBase = !cir.struct<"EmptyBase" padded {pad !u8i}>
 
 struct Base { int i; };
-// CIR-DAG: !rec_Base = !cir.struct<"Base" {!s32i}>
+// CIR-DAG: !rec_Base = !cir.struct<"Base" {data !s32i}>
 // LLVMCIR-DAG: %struct.Base = type { i32 }
 struct D : EmptyBase, Base {};
-// CIR-DAG: !rec_D = !cir.struct<"D" {!rec_Base}>
+// CIR-DAG: !rec_D = !cir.struct<"D" {data !rec_Base}>
 // LLVMCIR-DAG: %struct.D = type { %struct.Base }
 int D::* d_i = &D::i;
 // CIR-BEFORE-DAG: cir.global external @d_i = #cir.data_member<[0, 0]> : !cir.data_member<!s32i in !rec_D>
@@ -52,13 +52,13 @@ int D::* d_i = &D::i;
 // LLVM-DAG: @d_i = global i64 0
 
 struct EmptyBase2 { Empty s; };
-// CIR-DAG: !rec_EmptyBase2 = !cir.struct<"EmptyBase2" {!rec_Empty}>
+// CIR-DAG: !rec_EmptyBase2 = !cir.struct<"EmptyBase2" {data !rec_Empty}>
 // LLVMCIR-DAG: %struct.EmptyBase2 = type { %struct.Empty }
 struct Base2 { int i; };
-// CIR-DAG: !rec_Base2 = !cir.struct<"Base2" {!s32i}>
+// CIR-DAG: !rec_Base2 = !cir.struct<"Base2" {data !s32i}>
 // LLVMCIR-DAG: %struct.Base2 = type { i32 }
 struct D2 : EmptyBase2, Base2 {};
-// CIR-DAG: !rec_D2 = !cir.struct<"D2" {!rec_EmptyBase2, !rec_Base2}>
+// CIR-DAG: !rec_D2 = !cir.struct<"D2" {data !rec_EmptyBase2, data !rec_Base2}>
 // LLVMCIR-DAG: %struct.D2 = type { %struct.EmptyBase2, %struct.Base2 }
 Empty D2::* d2_s = &D2::s;
 // CIR-BEFORE-DAG: cir.global external @d2_s = #cir.data_member<[0, 0]> : !cir.data_member<!rec_Empty in !rec_D2>
@@ -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" padded {data !s32i, pad !cir.array<!u8i x 4>}>
 // LLVM-DAG: %struct.hasNUA = type { i32, [4 x i8] }
 
 const hasNUA nua = {{},{},{},{},{},{}, 1};
@@ -143,7 +143,7 @@ union U1 {
   EmptyBase eb;
 };
 // Rewrite of the data_member makes this unreferenced 'after'.
-// CIR-BEFORE-DAG: !rec_U1 = !cir.union<"U1" {!rec_EmptyBase}>
+// CIR-BEFORE-DAG: !rec_U1 = !cir.union<"U1" {data !rec_EmptyBase}>
 
 EmptyBase U1::* u1eb = &U1::eb;
 // CIR-BEFORE-DAG: cir.global external @u1eb = #cir.data_member<[0]> : !cir.data_member<!rec_EmptyBase in !rec_U1>
@@ -155,7 +155,7 @@ union U2 {
   EmptyBase eb;
 };
 // Rewrite of the data_member makes this unreferenced 'after'.
-// CIR-BEFORE-DAG: !rec_U2 = !cir.union<"U2" {!s32i, !rec_EmptyBase}>
+// CIR-BEFORE-DAG: !rec_U2 = !cir.union<"U2" {data !s32i, data !rec_EmptyBase}>
 
 int U2::* u2i = &U2::i;
 // CIR-BEFORE-DAG: cir.global external @u2i = #cir.data_member<[0]> : !cir.data_member<!s32i in !rec_U2>
@@ -171,7 +171,7 @@ union U3 {
   int i;
 };
 // Rewrite of the data_member makes this unreferenced 'after'.
-// CIR-BEFORE-DAG: !rec_U3 = !cir.union<"U3" {!rec_EmptyBase, !s32i}>
+// CIR-BEFORE-DAG: !rec_U3 = !cir.union<"U3" {data !rec_EmptyBase, data !s32i}>
 
 int U3::* u3i = &U3::i;
 // CIR-BEFORE-DAG: cir.global external @u3i = #cir.data_member<[1]> : !cir.data_member<!s32i in !rec_U3>
@@ -187,7 +187,7 @@ union U4 {
   EmptyBase eb2;
   int i;
 };
-// CIR-BEFORE-DAG: !rec_U4 = !cir.union<"U4" {!rec_EmptyBase, !rec_EmptyBase, !s32i}>
+// CIR-BEFORE-DAG: !rec_U4 = !cir.union<"U4" {data !rec_EmptyBase, data !rec_EmptyBase, data !s32i}>
 
 int U4::* u4i = &U4::i;
 // CIR-BEFORE-DAG: cir.global external @u4i = #cir.data_member<[2]> : !cir.data_member<!s32i in !rec_U4>
@@ -207,7 +207,7 @@ union U5 {
   EmptyBase eb;
   EmptyBase eb2;
 };
-// CIR-BEFORE-DAG: !rec_U5 = !cir.union<"U5" {!s32i, !rec_EmptyBase, !rec_EmptyBase}>
+// CIR-BEFORE-DAG: !rec_U5 = !cir.union<"U5" {data !s32i, data !rec_EmptyBase, data !rec_EmptyBase}>
 
 int U5::* u5i = &U5::i;
 // CIR-BEFORE-DAG: cir.global external @u5i = #cir.data_member<[0]> : !cir.data_member<!s32i in !rec_U5>
@@ -229,7 +229,7 @@ union U6 {
   EmptyBase eb2;
   int i;
 };
-// CIR-BEFORE-DAG: !rec_U6 = !cir.union<"U6" {!rec_EmptyBase, !rec_EmptyBase, !s32i}>
+// CIR-BEFORE-DAG: !rec_U6 = !cir.union<"U6" {data !rec_EmptyBase, data !rec_EmptyBase, data !s32i}>
 int U6::* u6i = &U6::i;
 // CIR-BEFORE-DAG: cir.global external @u6i = #cir.data_member<[2]> : !cir.data_member<!s32i in !rec_U6>
 // CIR-AFTER-DAG: cir.global external @u6i = #cir.int<0> : !s64i
@@ -251,7 +251,7 @@ union U7 {
   [[no_unique_address]]
   EmptyBase eb2;
 };
-// CIR-BEFORE-DAG: !rec_U7 = !cir.union<"U7" {!s32i, !rec_EmptyBase, !rec_EmptyBase}>
+// CIR-BEFORE-DAG: !rec_U7 = !cir.union<"U7" {data !s32i, data !rec_EmptyBase, data !rec_EmptyBase}>
 int U7::* u7i = &U7::i;
 // CIR-BEFORE-DAG: cir.global external @u7i = #cir.data_member<[0]> : !cir.data_member<!s32i in !rec_U7>
 // CIR-AFTER-DAG: cir.global external @u7i = #cir.int<0> : !s64i
diff --git a/clang/test/CIR/CodeGen/pointer-width-32bit.cpp b/clang/test/CIR/CodeGen/pointer-width-32bit.cpp
index 0e2a7a1bc1390..f811fdc5361fb 100644
--- a/clang/test/CIR/CodeGen/pointer-width-32bit.cpp
+++ b/clang/test/CIR/CodeGen/pointer-width-32bit.cpp
@@ -35,11 +35,11 @@ M m;
 
 // Each 4-byte pointer is followed by 'x' at offset 4 with no padding; records
 // are 4-byte aligned.
-// CIR-DAG: !rec_S = !cir.struct<"S" {!cir.ptr<!s32i>, !s32i}>
-// CIR-DAG: !rec_A = !cir.struct<class "A" {!cir.vptr, !s32i}>
+// CIR-DAG: !rec_S = !cir.struct<"S" {data !cir.ptr<!s32i>, data !s32i}>
+// CIR-DAG: !rec_A = !cir.struct<class "A" {data !cir.vptr, data !s32i}>
 // -emit-cir prints after cir-cxxabi-lowering, so M's member pointer is
 // already a 32-bit integer here.
-// CIR-DAG: !rec_M = !cir.struct<"M" {!s32i, !s32i}>
+// CIR-DAG: !rec_M = !cir.struct<"M" {data !s32i, data !s32i}>
 // CIR-DAG: !cir.ptr<!cir.void> = #cir.ptr_spec<size = 32, abi = 32, preferred = 32, index = 32>
 // CIR: cir.global external @s = #cir.zero : !rec_S {alignment = 4 : i64}
 // CIR: cir.global external @m = #cir.const_record<{#cir.int<-1> : !s32i, #cir.int<0> : !s32i}> : !rec_M {alignment = 4 : i64}
diff --git a/clang/test/CIR/CodeGen/record-type-metadata.cpp b/clang/test/CIR/CodeGen/record-type-metadata.cpp
index 5cf7909f860e2..e50f29023a224 100644
--- a/clang/test/CIR/CodeGen/record-type-metadata.cpp
+++ b/clang/test/CIR/CodeGen/record-type-metadata.cpp
@@ -17,10 +17,10 @@ void takesAligned(Aligned a) {}
 void takesNTD(NonTrivialDtor n) {}
 
 // Record types should NOT contain ABI metadata keywords.
-// CIR-DAG: !rec_Trivial = !cir.struct<"Trivial" {!s32i, !s32i}>
+// CIR-DAG: !rec_Trivial = !cir.struct<"Trivial" {data !s32i, data !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_NonTrivialDtor = !cir.struct<class "NonTrivialDtor" {!s32i}>
+// CIR-DAG: !rec_Aligned = !cir.struct<"Aligned" padded {data !s32i, data !s32i, pad !cir.array<!u8i x 8>}>
+// CIR-DAG: !rec_NonTrivialDtor = !cir.struct<class "NonTrivialDtor" {data !s32i}>
 
 // ABI metadata lives in module-level cir.record_layouts attribute.
 // CIR-DAG: Trivial = #cir.record_layout<arg_passing_kind = can_pass_in_regs, has_trivial_dtor = true, record_align = 4>
diff --git a/clang/test/CIR/CodeGen/record-with-padded-union.cpp b/clang/test/CIR/CodeGen/record-with-padded-union.cpp
index 13b241755f1bd..6c0eba263fe53 100644
--- a/clang/test/CIR/CodeGen/record-with-padded-union.cpp
+++ b/clang/test/CIR/CodeGen/record-with-padded-union.cpp
@@ -15,8 +15,8 @@ struct SSO {
 };
 
 // Inner union's tail padding must not bleed into the outer record.
-// CIR: !rec_anon{{.*}} = !cir.union<"anon{{.*}}" {!cir.array<!s8i x 16>, !u64i}, padding = {!cir.array<!u8i x 8>}>
-// CIR: !rec_SSO = !cir.struct<"SSO" {!cir.ptr<!s8i>, !u64i, !rec_anon{{.*}}}>
+// CIR: !rec_anon{{.*}} = !cir.union<"anon{{.*}}" {data !cir.array<!s8i x 16>, data !u64i}, padding = {!cir.array<!u8i x 8>}>
+// CIR: !rec_SSO = !cir.struct<"SSO" {data !cir.ptr<!s8i>, data !u64i, data !rec_anon{{.*}}}>
 
 // LLVM: %struct.SSO = type { ptr, i64, %union.anon{{.*}} }
 // LLVM: %union.anon{{.*}} = type { i64, [8 x i8] }
diff --git a/clang/test/CIR/CodeGen/record-zero-init-padding.c b/clang/test/CIR/CodeGen/record-zero-init-padding.c
index 9bb4df0d99e3b..8137f00194271 100644
--- a/clang/test/CIR/CodeGen/record-zero-init-padding.c
+++ b/clang/test/CIR/CodeGen/record-zero-init-padding.c
@@ -35,10 +35,10 @@ void test_zero_init_padding(void) {
 }
 
 // Type definitions for anonymous structs with padding
-// CIR-DAG: !rec_bitfield_with_padding = !cir.struct<"bitfield_with_padding" {!u8i, !s32i}>
-// CIR-DAG: !rec_multiple_padding = !cir.struct<"multiple_padding" {!s8i, !s16i, !s64i}>
-// CIR-DAG: !rec_padding_after_field = !cir.struct<"padding_after_field" {!s8i, !s32i}>
-// CIR-DAG: !rec_tail_padding = !cir.struct<"tail_padding" {!s32i, !s8i}>
+// CIR-DAG: !rec_bitfield_with_padding = !cir.struct<"bitfield_with_padding" {data !u8i, data !s32i}>
+// CIR-DAG: !rec_multiple_padding = !cir.struct<"multiple_padding" {data !s8i, data !s16i, data !s64i}>
+// CIR-DAG: !rec_padding_after_field = !cir.struct<"padding_after_field" {data !s8i, data !s32i}>
+// CIR-DAG: !rec_tail_padding = !cir.struct<"tail_padding" {data !s32i, data !s8i}>
 
 // paf: char + 3 bytes padding + int
 // CIR-DAG: cir.global "private" constant internal dso_local @test_zero_init_padding.paf = #cir.const_record<{#cir.int<1> : !s8i, #cir.int<42> : !s32i}> : !rec_padding_after_field
diff --git a/clang/test/CIR/CodeGen/struct.c b/clang/test/CIR/CodeGen/struct.c
index 7d5ea46ea8bf8..1e215f5ef813b 100644
--- a/clang/test/CIR/CodeGen/struct.c
+++ b/clang/test/CIR/CodeGen/struct.c
@@ -8,18 +8,18 @@
 // For LLVM IR checks, the structs are defined before the variables, so these
 // checks are at the top.
 // CIR-DAG: !rec_IncompleteS = !cir.struct<"IncompleteS" incomplete>
-// CIR-DAG: !rec_CompleteS = !cir.struct<"CompleteS" {!s32i, !s8i}>
-// 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_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>}>
-// CIR-DAG: !rec_CycleEnd = !cir.struct<"CycleEnd" {!cir.ptr<!cir.struct<"CycleStart" {!cir.ptr<!cir.struct<"CycleMiddle" {!cir.ptr<!cir.struct<"CycleEnd">>}>>}>>}>
-// CIR-DAG: !rec_CycleMiddle = !cir.struct<"CycleMiddle" {!cir.ptr<!rec_CycleEnd>}>
-// CIR-DAG: !rec_CycleStart = !cir.struct<"CycleStart" {!cir.ptr<!rec_CycleMiddle>}>
-// CIR-DAG: !rec_IncompleteArray = !cir.struct<"IncompleteArray" {!cir.array<!s32i x 0>}>
+// CIR-DAG: !rec_CompleteS = !cir.struct<"CompleteS" {data !s32i, data !s8i}>
+// CIR-DAG: !rec_OuterS = !cir.struct<"OuterS" {data !rec_InnerS, data !s32i}>  
+// CIR-DAG: !rec_InnerS = !cir.struct<"InnerS" {data !s32i, data !s8i}>
+// CIR-DAG: !rec_PackedS = !cir.struct<"PackedS" packed {data !s32i, data !s8i}>
+// CIR-DAG: !rec_PackedAndPaddedS = !cir.struct<"PackedAndPaddedS" packed padded {data !s32i, data !s8i, pad !u8i}>
+// CIR-DAG: !rec_NodeS = !cir.struct<"NodeS" {data !cir.ptr<!cir.struct<"NodeS">>}>
+// CIR-DAG: !rec_RightS = !cir.struct<"RightS" {data !cir.ptr<!cir.struct<"LeftS" {data !cir.ptr<!cir.struct<"RightS">>}>>}>
+// CIR-DAG: !rec_LeftS = !cir.struct<"LeftS" {data !cir.ptr<!rec_RightS>}>
+// CIR-DAG: !rec_CycleEnd = !cir.struct<"CycleEnd" {data !cir.ptr<!cir.struct<"CycleStart" {data !cir.ptr<!cir.struct<"CycleMiddle" {data !cir.ptr<!cir.struct<"CycleEnd">>}>>}>>}>
+// CIR-DAG: !rec_CycleMiddle = !cir.struct<"CycleMiddle" {data !cir.ptr<!rec_CycleEnd>}>
+// CIR-DAG: !rec_CycleStart = !cir.struct<"CycleStart" {data !cir.ptr<!rec_CycleMiddle>}>
+// CIR-DAG: !rec_IncompleteArray = !cir.struct<"IncompleteArray" {data !cir.array<!s32i x 0>}>
 // LLVM-DAG: %struct.CompleteS = type { i32, i8 }
 // LLVM-DAG: %struct.OuterS = type { %struct.InnerS, i32 }
 // LLVM-DAG: %struct.InnerS = type { i32, i8 }
diff --git a/clang/test/CIR/CodeGen/three-way-cmp.cpp b/clang/test/CIR/CodeGen/three-way-cmp.cpp
index feb1da8578792..9345af86ba354 100644
--- a/clang/test/CIR/CodeGen/three-way-cmp.cpp
+++ b/clang/test/CIR/CodeGen/three-way-cmp.cpp
@@ -14,8 +14,8 @@
 
 // BEFORE: #cmpinfo_partial_ltn1eq0gt1unn127 = #cir.cmp3way_info<partial, lt = -1, eq = 0, gt = 1, unordered = -127>
 // BEFORE: #cmpinfo_strong_ltn1eq0gt1 = #cir.cmp3way_info<strong, lt = -1, eq = 0, gt = 1>
-// BEFORE: !rec_std3A3A__13A3Apartial_ordering = !cir.struct<class "std::__1::partial_ordering" {!s8i}>
-// BEFORE: !rec_std3A3A__13A3Astrong_ordering = !cir.struct<class "std::__1::strong_ordering" {!s8i}>
+// BEFORE: !rec_std3A3A__13A3Apartial_ordering = !cir.struct<class "std::__1::partial_ordering" {data !s8i}>
+// BEFORE: !rec_std3A3A__13A3Astrong_ordering = !cir.struct<class "std::__1::strong_ordering" {data !s8i}>
 
 auto three_way_strong(int x, int y) {
   return x <=> y;
diff --git a/clang/test/CIR/CodeGen/union.c b/clang/test/CIR/CodeGen/union.c
index 28bd9081ca17a..85a34eaa0a938 100644
--- a/clang/test/CIR/CodeGen/union.c
+++ b/clang/test/CIR/CodeGen/union.c
@@ -10,7 +10,7 @@ union U1 {
   char c;
 };
 
-// CIR:  !rec_U1 = !cir.union<"U1" {!s32i, !s8i}>
+// CIR:  !rec_U1 = !cir.union<"U1" {data !s32i, data !s8i}>
 // LLVM: %union.U1 = type { i32 }
 // OGCG: %union.U1 = type { i32 }
 
@@ -22,7 +22,7 @@ union U2 {
   double d;
 };
 
-// CIR:  !rec_U2 = !cir.union<"U2" {!s8i, !s16i, !s32i, !cir.float, !cir.double}>
+// CIR:  !rec_U2 = !cir.union<"U2" {data !s8i, data !s16i, data !s32i, data !cir.float, data !cir.double}>
 // LLVM: %union.U2 = type { double }
 // OGCG: %union.U2 = type { double }
 
@@ -31,7 +31,7 @@ union U3 {
   int i;
 } __attribute__((packed));
 
-// CIR:  !rec_U3 = !cir.union<"U3" packed {!cir.array<!s8i x 5>, !s32i}, padding = {!u8i}>
+// CIR:  !rec_U3 = !cir.union<"U3" packed {data !cir.array<!s8i x 5>, data !s32i}, padding = {!u8i}>
 // LLVM: %union.U3 = type <{ i32, i8 }>
 // OGCG: %union.U3 = type <{ i32, i8 }>
 
@@ -40,7 +40,7 @@ union U4 {
   int i;
 };
 
-// CIR:  !rec_U4 = !cir.union<"U4" {!cir.array<!s8i x 5>, !s32i}, padding = {!cir.array<!u8i x 4>}>
+// CIR:  !rec_U4 = !cir.union<"U4" {data !cir.array<!s8i x 5>, data !s32i}, padding = {!cir.array<!u8i x 4>}>
 // LLVM: %union.U4 = type { i32, [4 x i8] }
 // OGCG: %union.U4 = type { i32, [4 x i8] }
 
diff --git a/clang/test/CIR/CodeGen/union.cpp b/clang/test/CIR/CodeGen/union.cpp
index dc9b121eeb745..59ad22ca6e6a9 100644
--- a/clang/test/CIR/CodeGen/union.cpp
+++ b/clang/test/CIR/CodeGen/union.cpp
@@ -13,7 +13,7 @@ union U {
   float f;
   double d;
 };
-// CIR: !rec_U = !cir.union<"U" {!cir.bool, !s16i, !s32i, !cir.float, !cir.double}>
+// CIR: !rec_U = !cir.union<"U" {data !cir.bool, data !s16i, data !s32i, data !cir.float, data !cir.double}>
 // LLVM: %union.U = type { double }
 // OGCG: %union.U = type { double }
 
diff --git a/clang/test/CIR/CodeGen/unions-with-zero-init.cpp b/clang/test/CIR/CodeGen/unions-with-zero-init.cpp
index 595734d2c98c5..992925ef85c2c 100644
--- a/clang/test/CIR/CodeGen/unions-with-zero-init.cpp
+++ b/clang/test/CIR/CodeGen/unions-with-zero-init.cpp
@@ -7,34 +7,34 @@
 // RUN: FileCheck --check-prefix=LLVM,OGCG --input-file=%t.ll %s
 
 // 'S' doesn't end up in the 'after' IR because it loses its uses.
-// CIR-BEFORE-DAG: !rec_S = !cir.struct<"S" {!s32i}>
+// CIR-BEFORE-DAG: !rec_S = !cir.struct<"S" {data !s32i}>
 
-// CIR-BEFORE-DAG: !rec_inner_aggregate = !cir.union<"inner_aggregate" {!cir.data_member<!s32i in !rec_S>, !s32i}>
-// CIR-AFTER-DAG:  !rec_inner_aggregate = !cir.union<"inner_aggregate" {!s64i, !s32i}>
+// CIR-BEFORE-DAG: !rec_inner_aggregate = !cir.union<"inner_aggregate" {data !cir.data_member<!s32i in !rec_S>, data !s32i}>
+// CIR-AFTER-DAG:  !rec_inner_aggregate = !cir.union<"inner_aggregate" {data !s64i, data !s32i}>
 // LLVM-DAG: %union.inner_aggregate = type { i64 }
 
-// CIR-BEFORE-DAG: !rec_inner_aggregate2 = !cir.union<"inner_aggregate2" {!s32i, !cir.data_member<!s32i in !rec_S>}>
-// CIR-AFTER-DAG:  !rec_inner_aggregate2 = !cir.union<"inner_aggregate2" {!s32i, !s64i}>
+// CIR-BEFORE-DAG: !rec_inner_aggregate2 = !cir.union<"inner_aggregate2" {data !s32i, data !cir.data_member<!s32i in !rec_S>}>
+// CIR-AFTER-DAG:  !rec_inner_aggregate2 = !cir.union<"inner_aggregate2" {data !s32i, data !s64i}>
 // In LLVM, inner_aggregate2 was lowered to a literal, so the type went away.
 
-// CIR-BEFORE-DAG: !rec_outer_aggregate = !cir.union<"outer_aggregate" {!cir.data_member<!s32i in !rec_S>, !s32i}>
-// CIR-AFTER-DAG:  !rec_outer_aggregate = !cir.union<"outer_aggregate" {!s64i, !s32i}>
+// CIR-BEFORE-DAG: !rec_outer_aggregate = !cir.union<"outer_aggregate" {data !cir.data_member<!s32i in !rec_S>, data !s32i}>
+// CIR-AFTER-DAG:  !rec_outer_aggregate = !cir.union<"outer_aggregate" {data !s64i, data !s32i}>
 // LLVM-DAG: %union.outer_aggregate = type { i64 }
 
-// CIR-BEFORE-DAG: !rec_outer_aggregate2 = !cir.union<"outer_aggregate2" {!s32i, !cir.data_member<!s32i in !rec_S>}>
-// CIR-AFTER-DAG:  !rec_outer_aggregate2 = !cir.union<"outer_aggregate2" {!s32i, !s64i}>
+// CIR-BEFORE-DAG: !rec_outer_aggregate2 = !cir.union<"outer_aggregate2" {data !s32i, data !cir.data_member<!s32i in !rec_S>}>
+// CIR-AFTER-DAG:  !rec_outer_aggregate2 = !cir.union<"outer_aggregate2" {data !s32i, data !s64i}>
 // LLVM-DAG: %union.inner_aggregate2 = type { i64 }
 
-// CIR-BEFORE-DAG: !rec_outer_aggregate3 = !cir.union<"outer_aggregate3" {!cir.data_member<!s32i in !rec_S>, !s32i}>
-// CIR-AFTER-DAG:  !rec_outer_aggregate3 = !cir.union<"outer_aggregate3" {!s64i, !s32i}>
+// CIR-BEFORE-DAG: !rec_outer_aggregate3 = !cir.union<"outer_aggregate3" {data !cir.data_member<!s32i in !rec_S>, data !s32i}>
+// CIR-AFTER-DAG:  !rec_outer_aggregate3 = !cir.union<"outer_aggregate3" {data !s64i, data !s32i}>
 // LLVM-DAG: %union.outer_aggregate3 = type { i64 }
 
-// CIR-BEFORE-DAG: !rec_HasPtrToMember = !cir.struct<"HasPtrToMember" {!cir.data_member<!s32i in !rec_S>}>
-// CIR-AFTER-DAG: !rec_HasPtrToMember = !cir.struct<"HasPtrToMember" {!s64i}>
+// CIR-BEFORE-DAG: !rec_HasPtrToMember = !cir.struct<"HasPtrToMember" {data !cir.data_member<!s32i in !rec_S>}>
+// CIR-AFTER-DAG: !rec_HasPtrToMember = !cir.struct<"HasPtrToMember" {data !s64i}>
 // LLVM-DAG: %struct.HasPtrToMember = type { i64 }
 
-// CIR-BEFORE-DAG: !rec_U = !cir.union<"U" {!s32i, !rec_HasPtrToMember}>
-// CIR-AFTER-DAG: !rec_U = !cir.union<"U" {!s32i, !rec_HasPtrToMember}>
+// CIR-BEFORE-DAG: !rec_U = !cir.union<"U" {data !s32i, data !rec_HasPtrToMember}>
+// CIR-AFTER-DAG: !rec_U = !cir.union<"U" {data !s32i, data !rec_HasPtrToMember}>
 // LLVM-DAG: %union.U = type { %struct.HasPtrToMember }
 
 // This gets promoted to a constant, so it is up here.
diff --git a/clang/test/CIR/CodeGen/var_arg.c b/clang/test/CIR/CodeGen/var_arg.c
index f58ce05c945e1..bc744fef4bb51 100644
--- a/clang/test/CIR/CodeGen/var_arg.c
+++ b/clang/test/CIR/CodeGen/var_arg.c
@@ -10,7 +10,7 @@
 // pipeline and OG CodeGen, so they only check IR that is common to both
 // pipelines.
 
-// CIR: !rec___va_list_tag = !cir.struct<"__va_list_tag" {!u32i, !u32i, !cir.ptr<!void>, !cir.ptr<!void>}
+// CIR: !rec___va_list_tag = !cir.struct<"__va_list_tag" {data !u32i, data !u32i, data !cir.ptr<!void>, data !cir.ptr<!void>}
 // LLVM: %struct.__va_list_tag = type { i32, i32, ptr, ptr }
 
 int varargs(int count, ...) {
diff --git a/clang/test/CIR/CodeGen/variable-template-specialization.cpp b/clang/test/CIR/CodeGen/variable-template-specialization.cpp
index 4840a5364c7bf..958b614a1337e 100644
--- a/clang/test/CIR/CodeGen/variable-template-specialization.cpp
+++ b/clang/test/CIR/CodeGen/variable-template-specialization.cpp
@@ -16,7 +16,7 @@ template<> int var_template<0>;
 template<> int var_template<1> = 1;
 template<> some_struct var_template<2>;
 
-// CIR: !rec_some_struct = !cir.struct<"some_struct" {!s32i}>
+// CIR: !rec_some_struct = !cir.struct<"some_struct" {data !s32i}>
 // CIR: cir.global external @_Z12var_templateILi0EE = #cir.int<0> : !s32i
 // CIR: cir.global external @_Z12var_templateILi1EE = #cir.int<1> : !s32i
 // CIR: cir.global external @_Z12var_templateILi2EE = #cir.zero : !rec_some_struct
diff --git a/clang/test/CIR/CodeGen/vbase.cpp b/clang/test/CIR/CodeGen/vbase.cpp
index 26dffcac6fa0a..e1b8d04f5247b 100644
--- a/clang/test/CIR/CodeGen/vbase.cpp
+++ b/clang/test/CIR/CodeGen/vbase.cpp
@@ -25,9 +25,9 @@ void g() {
   df.f();
 }
 
-// CIR: !rec_Base = !cir.struct<class "Base" {!cir.vptr}>
-// CIR: !rec_Derived = !cir.struct<class "Derived" {!rec_Base}>
-// CIR: !rec_DerivedFinal = !cir.struct<class "DerivedFinal" {!rec_Base}>
+// CIR: !rec_Base = !cir.struct<class "Base" {data !cir.vptr}>
+// CIR: !rec_Derived = !cir.struct<class "Derived" {data !rec_Base}>
+// CIR: !rec_DerivedFinal = !cir.struct<class "DerivedFinal" {data !rec_Base}>
 
 // LLVM: %class.Derived = type { %class.Base }
 // LLVM: %class.Base = type { ptr }
diff --git a/clang/test/CIR/CodeGen/virtual-function-calls.cpp b/clang/test/CIR/CodeGen/virtual-function-calls.cpp
index 896bc271b5fe3..b286cd0520417 100644
--- a/clang/test/CIR/CodeGen/virtual-function-calls.cpp
+++ b/clang/test/CIR/CodeGen/virtual-function-calls.cpp
@@ -13,8 +13,8 @@ struct A {
 // This should initialize the vtable pointer.
 A::A() {}
 
-// CIR: !rec_A = !cir.struct<"A" {!cir.vptr}>
-// CIR: !rec_anon_struct = !cir.struct<{!cir.array<!cir.ptr<!u8i> x 3>}>
+// CIR: !rec_A = !cir.struct<"A" {data !cir.vptr}>
+// CIR: !rec_anon_struct = !cir.struct<{data !cir.array<!cir.ptr<!u8i> x 3>}>
 
 // CIR: cir.global "private" external @_ZTV1A : !rec_anon_struct
 
diff --git a/clang/test/CIR/CodeGen/vtable-emission.cpp b/clang/test/CIR/CodeGen/vtable-emission.cpp
index b83a17b96b2b3..d1522db762ba0 100644
--- a/clang/test/CIR/CodeGen/vtable-emission.cpp
+++ b/clang/test/CIR/CodeGen/vtable-emission.cpp
@@ -15,7 +15,7 @@ struct S {
 
 void S::key() {}
 
-// CHECK-DAG: !rec_anon_struct = !cir.struct<{!cir.array<!cir.ptr<!u8i> x 4>}>
+// CHECK-DAG: !rec_anon_struct = !cir.struct<{data !cir.array<!cir.ptr<!u8i> x 4>}>
 
 // The definition of the key function should result in the vtable being emitted.
 // CHECK:      cir.global "private" external @_ZTV1S = #cir.vtable<{
diff --git a/clang/test/CIR/CodeGen/vtable-nyi-nonconvertible-functype.cpp b/clang/test/CIR/CodeGen/vtable-nyi-nonconvertible-functype.cpp
index 08fdbe3964dd4..2675044179cb2 100644
--- a/clang/test/CIR/CodeGen/vtable-nyi-nonconvertible-functype.cpp
+++ b/clang/test/CIR/CodeGen/vtable-nyi-nonconvertible-functype.cpp
@@ -7,8 +7,8 @@
 
 // CIR-DAG: !rec_Param2 = !cir.struct<"Param2" incomplete>
 // CIR-DAG: !rec_Ret1 = !cir.struct<"Ret1" incomplete>
-// CIR-DAG: !rec_S1 = !cir.struct<"S1" {!cir.vptr}>
-// CIR-DAG: !rec_S2 = !cir.struct<"S2" {!cir.vptr}>
+// CIR-DAG: !rec_S1 = !cir.struct<"S1" {data !cir.vptr}>
+// CIR-DAG: !rec_S2 = !cir.struct<"S2" {data !cir.vptr}>
 // LLVMCIR-DAG: %struct.Ret1 = type {}
 // LLVMCIR-DAG: %struct.Param2 = type {}
 
diff --git a/clang/test/CIR/CodeGen/vtt.cpp b/clang/test/CIR/CodeGen/vtt.cpp
index 59b674747990b..90146b5662df9 100644
--- a/clang/test/CIR/CodeGen/vtt.cpp
+++ b/clang/test/CIR/CodeGen/vtt.cpp
@@ -46,17 +46,17 @@ void f(D *d) {}
 // Trigger vtable and VTT emission for D.
 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_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}>
-
-// CIR-RTTI:   ![[REC_TYPE_INFO_VTABLE:.*]]= !cir.struct<{!cir.ptr<!u8i>, !cir.ptr<!u8i>, !u32i, !u32i, !cir.ptr<!u8i>, !s64i, !cir.ptr<!u8i>, !s64i}>
-// CIR-COMMON: ![[REC_D_VTABLE:.*]] = !cir.struct<{!cir.array<!cir.ptr<!u8i> x 5>, !cir.array<!cir.ptr<!u8i> x 4>, !cir.array<!cir.ptr<!u8i> x 4>}>
-// CIR-COMMON: ![[REC_B_OR_C_IN_D_VTABLE:.*]]= !cir.struct<{!cir.array<!cir.ptr<!u8i> x 4>, !cir.array<!cir.ptr<!u8i> x 4>}>
+// CIR-COMMON: !rec_A2Ebase = !cir.struct<"A.base" packed {data !cir.vptr, data !s32i}>
+// CIR-COMMON: !rec_B2Ebase = !cir.struct<"B.base" packed {data !cir.vptr, data !s32i}>
+// CIR-COMMON: !rec_C2Ebase = !cir.struct<"C.base" {data !cir.vptr, data !s64i}>
+// CIR-COMMON: !rec_A = !cir.struct<class "A" packed padded {data !cir.vptr, data !s32i, pad !cir.array<!u8i x 4>}>
+// CIR-COMMON: !rec_B = !cir.struct<class "B" packed padded {data !cir.vptr, data !s32i, pad !cir.array<!u8i x 4>, data !rec_A2Ebase, pad !cir.array<!u8i x 4>}>
+// CIR-COMMON: !rec_C = !cir.struct<class "C" {data !cir.vptr, data !s64i, data !rec_A2Ebase}>
+// CIR-COMMON: !rec_D = !cir.struct<class "D" {data !rec_B2Ebase, data !rec_C2Ebase, data !s64i, data !rec_A2Ebase}>
+
+// CIR-RTTI:   ![[REC_TYPE_INFO_VTABLE:.*]]= !cir.struct<{data !cir.ptr<!u8i>, data !cir.ptr<!u8i>, data !u32i, data !u32i, data !cir.ptr<!u8i>, data !s64i, data !cir.ptr<!u8i>, data !s64i}>
+// CIR-COMMON: ![[REC_D_VTABLE:.*]] = !cir.struct<{data !cir.array<!cir.ptr<!u8i> x 5>, data !cir.array<!cir.ptr<!u8i> x 4>, data !cir.array<!cir.ptr<!u8i> x 4>}>
+// CIR-COMMON: ![[REC_B_OR_C_IN_D_VTABLE:.*]]= !cir.struct<{data !cir.array<!cir.ptr<!u8i> x 4>, data !cir.array<!cir.ptr<!u8i> x 4>}>
 
 // Vtable for D
 
diff --git a/clang/test/CIR/CodeGenBuiltins/X86/avx512vlvp2intersect-builtins.c b/clang/test/CIR/CodeGenBuiltins/X86/avx512vlvp2intersect-builtins.c
index c624099d2d8b8..81d880f775ec2 100644
--- a/clang/test/CIR/CodeGenBuiltins/X86/avx512vlvp2intersect-builtins.c
+++ b/clang/test/CIR/CodeGenBuiltins/X86/avx512vlvp2intersect-builtins.c
@@ -13,9 +13,9 @@
 
 #include <immintrin.h>
 
-// CIR: !rec_anon_struct = !cir.struct<{!cir.vector<8 x !cir.bool>, !cir.vector<8 x !cir.bool>}>
-// CIR: !rec_anon_struct1 = !cir.struct<{!cir.vector<4 x !cir.bool>, !cir.vector<4 x !cir.bool>}>
-// CIR: !rec_anon_struct2 = !cir.struct<{!cir.vector<2 x !cir.bool>, !cir.vector<2 x !cir.bool>}>
+// CIR: !rec_anon_struct = !cir.struct<{data !cir.vector<8 x !cir.bool>, data !cir.vector<8 x !cir.bool>}>
+// CIR: !rec_anon_struct1 = !cir.struct<{data !cir.vector<4 x !cir.bool>, data !cir.vector<4 x !cir.bool>}>
+// CIR: !rec_anon_struct2 = !cir.struct<{data !cir.vector<2 x !cir.bool>, data !cir.vector<2 x !cir.bool>}>
 void test_mm256_2intersect_epi32(__m256i a, __m256i b, __mmask8 *m0, __mmask8 *m1) {
   // CIR-LABEL: mm256_2intersect_epi32
   // CIR: %[[RES:.*]] = cir.call_llvm_intrinsic "x86.avx512.vp2intersect.d.256" %{{.*}}, %{{.*}} : (!cir.vector<8 x !s32i>, !cir.vector<8 x !s32i>) -> !rec_anon_struct
diff --git a/clang/test/CIR/CodeGenBuiltins/X86/avx512vp2intersect-builtins.c b/clang/test/CIR/CodeGenBuiltins/X86/avx512vp2intersect-builtins.c
index 28ade5f481253..444f0857c6e17 100644
--- a/clang/test/CIR/CodeGenBuiltins/X86/avx512vp2intersect-builtins.c
+++ b/clang/test/CIR/CodeGenBuiltins/X86/avx512vp2intersect-builtins.c
@@ -14,8 +14,8 @@
 #include <immintrin.h>
 
 
-// CIR: !rec_anon_struct = !cir.struct<{!cir.vector<16 x !cir.bool>, !cir.vector<16 x !cir.bool>}>
-// CIR: !rec_anon_struct1 = !cir.struct<{!cir.vector<8 x !cir.bool>, !cir.vector<8 x !cir.bool>}>
+// CIR: !rec_anon_struct = !cir.struct<{data !cir.vector<16 x !cir.bool>, data !cir.vector<16 x !cir.bool>}>
+// CIR: !rec_anon_struct1 = !cir.struct<{data !cir.vector<8 x !cir.bool>, data !cir.vector<8 x !cir.bool>}>
 void test_mm512_2intersect_epi32(__m512i a, __m512i b, __mmask16 *m0, __mmask16 *m1) {
   // CIR-LABEL: mm512_2intersect_epi32
   // CIR: %[[RES:.*]] = cir.call_llvm_intrinsic "x86.avx512.vp2intersect.d.512" %{{.*}}, %{{.*}} : (!cir.vector<16 x !s32i>, !cir.vector<16 x !s32i>) -> !rec_anon_struct
diff --git a/clang/test/CIR/CodeGenBuiltins/X86/keylocker.c b/clang/test/CIR/CodeGenBuiltins/X86/keylocker.c
index c925810e651f1..33be2651c0298 100644
--- a/clang/test/CIR/CodeGenBuiltins/X86/keylocker.c
+++ b/clang/test/CIR/CodeGenBuiltins/X86/keylocker.c
@@ -16,10 +16,10 @@
 
 #include <x86intrin.h>
 
-// CIR: !rec_anon_struct = !cir.struct<{!u8i, !cir.vector<2 x !s64i>}>
-// CIR: !rec_anon_struct1 = !cir.struct<{!u8i, !cir.vector<2 x !s64i>, !cir.vector<2 x !s64i>, !cir.vector<2 x !s64i>, !cir.vector<2 x !s64i>, !cir.vector<2 x !s64i>, !cir.vector<2 x !s64i>, !cir.vector<2 x !s64i>, !cir.vector<2 x !s64i>}>
-// CIR: !rec_anon_struct2 = !cir.struct<{!u32i, !cir.vector<2 x !u64i>, !cir.vector<2 x !u64i>, !cir.vector<2 x !u64i>, !cir.vector<2 x !u64i>, !cir.vector<2 x !u64i>, !cir.vector<2 x !u64i>}>
-// CIR: !rec_anon_struct3 = !cir.struct<{!u32i, !cir.vector<2 x !u64i>, !cir.vector<2 x !u64i>, !cir.vector<2 x !u64i>, !cir.vector<2 x !u64i>, !cir.vector<2 x !u64i>, !cir.vector<2 x !u64i>, !cir.vector<2 x !u64i>}>
+// CIR: !rec_anon_struct = !cir.struct<{data !u8i, data !cir.vector<2 x !s64i>}>
+// CIR: !rec_anon_struct1 = !cir.struct<{data !u8i, data !cir.vector<2 x !s64i>, data !cir.vector<2 x !s64i>, data !cir.vector<2 x !s64i>, data !cir.vector<2 x !s64i>, data !cir.vector<2 x !s64i>, data !cir.vector<2 x !s64i>, data !cir.vector<2 x !s64i>, data !cir.vector<2 x !s64i>}>
+// CIR: !rec_anon_struct2 = !cir.struct<{data !u32i, data !cir.vector<2 x !u64i>, data !cir.vector<2 x !u64i>, data !cir.vector<2 x !u64i>, data !cir.vector<2 x !u64i>, data !cir.vector<2 x !u64i>, data !cir.vector<2 x !u64i>}>
+// CIR: !rec_anon_struct3 = !cir.struct<{data !u32i, data !cir.vector<2 x !u64i>, data !cir.vector<2 x !u64i>, data !cir.vector<2 x !u64i>, data !cir.vector<2 x !u64i>, data !cir.vector<2 x !u64i>, data !cir.vector<2 x !u64i>, data !cir.vector<2 x !u64i>}>
 
 unsigned char test_mm_aesenc256kl_u8(__m128i *odata, __m128i idata, const void *h) {
   // CIR-LABEL: _mm_aesenc256kl_u8
diff --git a/clang/test/CIR/CodeGenCXX/zero_init_bases.cpp b/clang/test/CIR/CodeGenCXX/zero_init_bases.cpp
index 82bed271c22ab..98078212c53e3 100644
--- a/clang/test/CIR/CodeGenCXX/zero_init_bases.cpp
+++ b/clang/test/CIR/CodeGenCXX/zero_init_bases.cpp
@@ -22,10 +22,10 @@ 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_Base2 = !cir.struct<"Base2" {data !cir.float, data !cir.float, data !cir.float}>
+// CIR: !rec_Base1 = !cir.struct<"Base1" {data !s32i, data !s32i, data !s32i}>
+// CIR: !rec_Inherits = !cir.struct<"Inherits" {data !rec_Base1, data !rec_Base2, data !s32i, data !s32i, data !s32i}>
+// CIR: !rec_VirtualInherits = !cir.struct<"VirtualInherits" packed padded {data !cir.vptr, data !s32i, data !s32i, data !s32i, data !rec_Base1, data !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/IR/array-ctor.cir b/clang/test/CIR/IR/array-ctor.cir
index d93111c12cb26..7e122adb3dda1 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" padded {data !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..f7f835881ec38 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" padded {data !u8i}>
 
 module {
   cir.func private @_ZN1SD1Ev(!cir.ptr<!rec_S>)
diff --git a/clang/test/CIR/IR/bitfield_info.cir b/clang/test/CIR/IR/bitfield_info.cir
index cc993cd499ff9..43c2ad6e75491 100644
--- a/clang/test/CIR/IR/bitfield_info.cir
+++ b/clang/test/CIR/IR/bitfield_info.cir
@@ -4,7 +4,7 @@
 !u32i = !cir.int<u, 32>
 
 
-!rec_S = !cir.struct<"S" {!u32i}>
+!rec_S = !cir.struct<"S" {data !u32i}>
 #bfi_c = #cir.bitfield_info<name = "c", storage_type = !u32i, size = 17, offset = 15, is_signed = true>
 
 // CHECK: #bfi_c = #cir.bitfield_info<name = "c", storage_type = !u32i, size = 17, offset = 15, is_signed = true>
diff --git a/clang/test/CIR/IR/const-record-flexible-array.cir b/clang/test/CIR/IR/const-record-flexible-array.cir
index 4e44e66ea4074..81373114edc48 100644
--- a/clang/test/CIR/IR/const-record-flexible-array.cir
+++ b/clang/test/CIR/IR/const-record-flexible-array.cir
@@ -2,7 +2,7 @@
 
 !s8i = !cir.int<s, 8>
 !s32i = !cir.int<s, 32>
-!rec_S = !cir.struct<"S" {!s32i, !cir.array<!s8i x 0>}>
+!rec_S = !cir.struct<"S" {data !s32i, data !cir.array<!s8i x 0>}>
 
 // A flexible array member is declared as a zero-length array, but its
 // initializer may be a larger array of the *same element type*. The verifier
diff --git a/clang/test/CIR/IR/const-record-union.cir b/clang/test/CIR/IR/const-record-union.cir
index 0d491f59c0356..7c8c9ecaacd58 100644
--- a/clang/test/CIR/IR/const-record-union.cir
+++ b/clang/test/CIR/IR/const-record-union.cir
@@ -2,7 +2,7 @@
 
 !s32i = !cir.int<s, 32>
 !u8i = !cir.int<u, 8>
-!rec_U = !cir.union<"U" {!s32i, !u8i}, padding = {!cir.array<!u8i x 3>}>
+!rec_U = !cir.union<"U" {data !s32i, data !u8i}, padding = {!cir.array<!u8i x 3>}>
 
 // A union ConstRecordAttr has exactly one element, whose type must be one of
 // the union's member types (the active member). The active member may be any
diff --git a/clang/test/CIR/IR/construct-catch-param.cir b/clang/test/CIR/IR/construct-catch-param.cir
index 11c8af5e1b1c1..7a77b81308126 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" padded {data !u8i}>
 
 module {
 
diff --git a/clang/test/CIR/IR/copy.cir b/clang/test/CIR/IR/copy.cir
index 1366ca92af8b8..cc60f931c227b 100644
--- a/clang/test/CIR/IR/copy.cir
+++ b/clang/test/CIR/IR/copy.cir
@@ -1,7 +1,7 @@
 // RUN: cir-opt %s --verify-roundtrip | FileCheck %s
 
 !s32i = !cir.int<s, 32>
-!rec = !cir.struct<"S" {!s32i, !cir.int<s, 8>}>
+!rec = !cir.struct<"S" {data !s32i, data !cir.int<s, 8>}>
 module {
   cir.func @shouldParseCopyOp(%arg0 : !cir.ptr<!s32i>, %arg1 : !cir.ptr<!s32i>) {
     cir.copy %arg0 to %arg1 : !cir.ptr<!s32i>
diff --git a/clang/test/CIR/IR/dynamic-cast.cir b/clang/test/CIR/IR/dynamic-cast.cir
index 8d96857cd6e05..6930db82ca0da 100644
--- a/clang/test/CIR/IR/dynamic-cast.cir
+++ b/clang/test/CIR/IR/dynamic-cast.cir
@@ -4,8 +4,8 @@
 !u8i = !cir.int<u, 8>
 !void = !cir.void
 
-!rec_Base = !cir.struct<"Base" {!cir.vptr}>
-!rec_Derived = !cir.struct<"Derived" {!rec_Base}>
+!rec_Base = !cir.struct<"Base" {data !cir.vptr}>
+!rec_Derived = !cir.struct<"Derived" {data !rec_Base}>
 
 #dyn_cast_info__ZTI4Base__ZTI7Derived = #cir.dyn_cast_info<src_rtti = #cir.global_view<@_ZTI4Base> : !cir.ptr<!u8i>, dest_rtti = #cir.global_view<@_ZTI7Derived> : !cir.ptr<!u8i>, runtime_func = @__dynamic_cast, bad_cast_func = @__cxa_bad_cast, offset_hint = #cir.int<0> : !s64i>
 
diff --git a/clang/test/CIR/IR/func-attrs.cir b/clang/test/CIR/IR/func-attrs.cir
index 351e731231751..a96d88bf53cee 100644
--- a/clang/test/CIR/IR/func-attrs.cir
+++ b/clang/test/CIR/IR/func-attrs.cir
@@ -5,8 +5,8 @@
 !s8i = !cir.int<s, 8>
 !u8i = !cir.int<u, 8>
 !void = !cir.void
-!rec_Struct = !cir.struct<"Struct" padded {!u8i}>
-!rec_anon_struct = !cir.struct<{!s64i, !s64i}>
+!rec_Struct = !cir.struct<"Struct" padded {data !u8i}>
+!rec_anon_struct = !cir.struct<{data !s64i, data !s64i}>
 
 cir.func no_inline dso_local @Func1(%arg0: !s32i {llvm.noundef}, %arg1: !cir.float {llvm.noundef}) -> (!cir.float {llvm.noundef}) {
 // CHECK: 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/func.cir b/clang/test/CIR/IR/func.cir
index 4c6bd33b386ab..ac74fb8a09355 100644
--- a/clang/test/CIR/IR/func.cir
+++ b/clang/test/CIR/IR/func.cir
@@ -164,7 +164,7 @@ cir.func @global_dtor_with_priority() global_dtor(201) {
 
 }
 
-!rec_Foo = !cir.struct<"Foo" {!s32i}>
+!rec_Foo = !cir.struct<"Foo" {data !s32i}>
 
 cir.func @Foo_default() func_info<#cir.cxx_ctor<!rec_Foo, default>> {
   cir.return
diff --git a/clang/test/CIR/IR/global-init.cir b/clang/test/CIR/IR/global-init.cir
index e1311ed026768..a393005bcadb0 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" padded {data !u8i}>
+!rec_NeedsDtor = !cir.struct<"NeedsDtor" padded {data !u8i}>
+!rec_NeedsCtorDtor = !cir.struct<"NeedsCtorDtor" padded {data !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..5527670af2ab2 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" padded {data !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" padded {data !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" padded {data !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" padded {data !u8i}>
+!rec_T = !cir.struct<"T" padded {data !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" padded {data !u8i}>
+!rec_T = !cir.struct<"T" padded {data !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" padded {data !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" padded {data !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" padded {data !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" padded {data !u8i}>
+!rec_T = !cir.struct<"T" padded {data !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" padded {data !u8i}>
+!rec_T = !cir.struct<"T" padded {data !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" padded {data !u8i}>
+!rec_T = !cir.struct<"T" padded {data !u8i}>
 
 module {
   cir.func private @_ZN1SD1Ev(!cir.ptr<!rec_S>)
diff --git a/clang/test/CIR/IR/invalid-cast.cir b/clang/test/CIR/IR/invalid-cast.cir
index 4ba8025106a39..9d7f4a6442837 100644
--- a/clang/test/CIR/IR/invalid-cast.cir
+++ b/clang/test/CIR/IR/invalid-cast.cir
@@ -3,7 +3,7 @@
 // -----
 
 !s32i = !cir.int<s, 32>
-!rec_Foo = !cir.struct<"Foo" {!s32i}>
+!rec_Foo = !cir.struct<"Foo" {data !s32i}>
 
 module {
   cir.func no_inline dso_local @_Z7to_boolM3Fooi(%arg0: !cir.data_member<!s32i in !rec_Foo>) -> !s32i {
@@ -16,7 +16,7 @@ module {
 // -----
 
 !s32i = !cir.int<s, 32>
-!rec_Foo = !cir.struct<"Foo" {!s32i}>
+!rec_Foo = !cir.struct<"Foo" {data !s32i}>
 
 module {
   cir.func no_inline dso_local @_Z7to_boolM3Fooi(%arg0: !s32i) -> !cir.bool {
diff --git a/clang/test/CIR/IR/invalid-const-record.cir b/clang/test/CIR/IR/invalid-const-record.cir
index 3c2fe607872e2..5192f1794e194 100644
--- a/clang/test/CIR/IR/invalid-const-record.cir
+++ b/clang/test/CIR/IR/invalid-const-record.cir
@@ -1,7 +1,7 @@
 // RUN: cir-opt %s -verify-diagnostics -split-input-file
 
 !s32i = !cir.int<s, 32>
-!rec_anon_struct = !cir.struct< packed {!s32i, !s32i, !cir.array<!s32i x 8>}>
+!rec_anon_struct = !cir.struct< packed {data !s32i, data !s32i, data !cir.array<!s32i x 8>}>
 
 // expected-error @below {{expected !cir.struct or !cir.union type}}
 cir.global external @e = #cir.const_record<{#cir.int<1> : !s32i, #cir.int<2> : !s32i, #cir.zero : !cir.array<!s32i x 8>}> : !cir.ptr<!rec_anon_struct>
@@ -9,7 +9,7 @@ cir.global external @e = #cir.const_record<{#cir.int<1> : !s32i, #cir.int<2> : !
 // -----
 
 !s32i = !cir.int<s, 32>
-!rec_anon_struct = !cir.struct< packed {!s32i, !s32i, !cir.array<!s32i x 8>}>
+!rec_anon_struct = !cir.struct< packed {data !s32i, data !s32i, data !cir.array<!s32i x 8>}>
 
 // expected-error @below {{number of elements must match}}
 cir.global external @e = #cir.const_record<{#cir.int<1> : !s32i, #cir.zero : !cir.array<!s32i x 8>}> : !rec_anon_struct
@@ -17,7 +17,7 @@ cir.global external @e = #cir.const_record<{#cir.int<1> : !s32i, #cir.zero : !ci
 // -----
 
 !s32i = !cir.int<s, 32>
-!rec_anon_struct = !cir.struct< packed {!s32i, !s32i, !cir.array<!s32i x 8>}>
+!rec_anon_struct = !cir.struct< packed {data !s32i, data !s32i, data !cir.array<!s32i x 8>}>
 
 // expected-error @below {{element at index 1 has type '!cir.float' but the expected type for this element is '!cir.int<s, 32>'}}
 cir.global external @e = #cir.const_record<{#cir.int<1> : !s32i, #cir.fp<2.000000e+00> : !cir.float, #cir.zero : !cir.array<!s32i x 8>}> : !rec_anon_struct
@@ -26,7 +26,7 @@ cir.global external @e = #cir.const_record<{#cir.int<1> : !s32i, #cir.fp<2.00000
 
 !s32i = !cir.int<s, 32>
 !u8i = !cir.int<u, 8>
-!rec_U = !cir.union<"U" {!s32i, !u8i}, padding = {!cir.array<!u8i x 3>}>
+!rec_U = !cir.union<"U" {data !s32i, data !u8i}, padding = {!cir.array<!u8i x 3>}>
 
 // A union constant must have exactly one element (the active member).
 // expected-error @below {{union constant must have exactly one element, got 2}}
@@ -36,7 +36,7 @@ cir.global external @u = #cir.const_record<{#cir.int<5> : !s32i, #cir.int<7> : !
 
 !s32i = !cir.int<s, 32>
 !u8i = !cir.int<u, 8>
-!rec_U = !cir.union<"U" {!s32i, !u8i}, padding = {!cir.array<!u8i x 3>}>
+!rec_U = !cir.union<"U" {data !s32i, data !u8i}, padding = {!cir.array<!u8i x 3>}>
 
 // expected-error @below {{union constant must have exactly one element, got 0}}
 cir.global external @u = #cir.const_record<{}> : !rec_U
@@ -45,7 +45,7 @@ cir.global external @u = #cir.const_record<{}> : !rec_U
 
 !s32i = !cir.int<s, 32>
 !u8i = !cir.int<u, 8>
-!rec_U = !cir.union<"U" {!s32i, !u8i}, padding = {!cir.array<!u8i x 3>}>
+!rec_U = !cir.union<"U" {data !s32i, data !u8i}, padding = {!cir.array<!u8i x 3>}>
 
 // The single element's type must be one of the union's member types.
 // expected-error @below {{union element type '!cir.float' is not a member of}}
@@ -55,7 +55,7 @@ cir.global external @u = #cir.const_record<{#cir.fp<2.000000e+00> : !cir.float}>
 
 !s8i = !cir.int<s, 8>
 !s32i = !cir.int<s, 32>
-!rec_S = !cir.struct<"S" {!s32i, !cir.array<!s8i x 0>}>
+!rec_S = !cir.struct<"S" {data !s32i, data !cir.array<!s8i x 0>}>
 
 // The flexible array member (trailing zero-length array) must be initialized
 // with an array; a non-array initializer is rejected.
@@ -66,7 +66,7 @@ cir.global external @fam_non_array = #cir.const_record<{#cir.int<3> : !s32i, #ci
 
 !s8i = !cir.int<s, 8>
 !s32i = !cir.int<s, 32>
-!rec_S = !cir.struct<"S" {!s32i, !cir.array<!s8i x 0>}>
+!rec_S = !cir.struct<"S" {data !s32i, data !cir.array<!s8i x 0>}>
 
 // The flexible array member initializer must have the same element type as the
 // declared zero-length array.
@@ -77,7 +77,7 @@ cir.global external @fam_wrong_elt = #cir.const_record<{#cir.int<3> : !s32i, #ci
 
 !s8i = !cir.int<s, 8>
 !s32i = !cir.int<s, 32>
-!rec_F = !cir.struct<"F" {!s32i, !cir.array<!s8i x 2>}>
+!rec_F = !cir.struct<"F" {data !s32i, data !cir.array<!s8i x 2>}>
 
 // The oversized-array relaxation only applies when the declared trailing array
 // is zero-length; a non-zero-length array still requires an exact type match.
diff --git a/clang/test/CIR/IR/invalid-construct-catch-param.cir b/clang/test/CIR/IR/invalid-construct-catch-param.cir
index 7a750c222df1c..375801a131a10 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" padded {data !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" padded {data !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" padded {data !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" padded {data !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" padded {data !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" padded {data !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" padded {data !u8i}>
 
 module {
 
diff --git a/clang/test/CIR/IR/invalid-data-member.cir b/clang/test/CIR/IR/invalid-data-member.cir
index d072d164d17b1..ecb50e9899256 100644
--- a/clang/test/CIR/IR/invalid-data-member.cir
+++ b/clang/test/CIR/IR/invalid-data-member.cir
@@ -4,7 +4,7 @@
 
 !u16i = !cir.int<u, 16>
 !u32i = !cir.int<u, 32>
-!struct1 = !cir.struct<"Struct1" {!u16i, !u32i}>
+!struct1 = !cir.struct<"Struct1" {data !u16i, data !u32i}>
 
 // expected-error at +1 {{member type of a #cir.data_member attribute must match the attribute type}}
 #invalid_member_ty = #cir.data_member<[0]> : !cir.data_member<!u32i in !struct1>
@@ -13,7 +13,7 @@
 
 !u16i = !cir.int<u, 16>
 !u32i = !cir.int<u, 32>
-!struct1 = !cir.struct<"Struct1" {!u16i, !u32i}>
+!struct1 = !cir.struct<"Struct1" {data !u16i, data !u32i}>
 
 // expected-error at +1 {{#cir.data_member path index 2 at step 0 is out of range}}
 #invalid_member_ty = #cir.data_member<[2]> : !cir.data_member<!u32i in !struct1>
@@ -22,7 +22,7 @@
 
 !u16i = !cir.int<u, 16>
 !u32i = !cir.int<u, 32>
-!struct1 = !cir.struct<"Struct1" {!u16i, !u32i}>
+!struct1 = !cir.struct<"Struct1" {data !u16i, data !u32i}>
 
 // expected-error at +1 {{#cir.data_member path must not be empty}}
 #empty_path = #cir.data_member<[]> : !cir.data_member<!u32i in !struct1>
@@ -31,8 +31,8 @@
 
 !u16i = !cir.int<u, 16>
 !u32i = !cir.int<u, 32>
-!struct1 = !cir.struct<"Struct1" {!u16i, !u32i}>
-!struct2 = !cir.struct<"Struct2" {!u16i, !u32i}>
+!struct1 = !cir.struct<"Struct1" {data !u16i, data !u32i}>
+!struct2 = !cir.struct<"Struct2" {data !u16i, data !u32i}>
 
 module {
   cir.func @invalid_base_type(%arg0 : !cir.data_member<!u32i in !struct1>) {
@@ -47,7 +47,7 @@ module {
 
 !u16i = !cir.int<u, 16>
 !u32i = !cir.int<u, 32>
-!struct1 = !cir.struct<"Struct1" {!u16i, !u32i}>
+!struct1 = !cir.struct<"Struct1" {data !u16i, data !u32i}>
 
 module {
   cir.func @invalid_base_type(%arg0 : !cir.data_member<!u32i in !struct1>) {
diff --git a/clang/test/CIR/IR/invalid-delete-array.cir b/clang/test/CIR/IR/invalid-delete-array.cir
index 2263c95de6e03..eaffbdfb2923a 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" padded {data !u8i}>
 
 module {
   cir.func private @_ZdaPvm(!cir.ptr<!void>)
diff --git a/clang/test/CIR/IR/invalid-dyn-cast.cir b/clang/test/CIR/IR/invalid-dyn-cast.cir
index 1d79b59ea093a..c5c7df784d4fc 100644
--- a/clang/test/CIR/IR/invalid-dyn-cast.cir
+++ b/clang/test/CIR/IR/invalid-dyn-cast.cir
@@ -6,8 +6,8 @@
 !u8i = !cir.int<u, 8>
 !void = !cir.void
 
-!Base = !cir.struct<"Base" {!cir.ptr<!cir.ptr<!cir.func<() -> !cir.int<u, 32>>>>}>
-!Derived = !cir.struct<"Derived" {!cir.struct<"Base" {!cir.ptr<!cir.ptr<!cir.func<() -> !cir.int<u, 32>>>>}>}>
+!Base = !cir.struct<"Base" {data !cir.ptr<!cir.ptr<!cir.func<() -> !cir.int<u, 32>>>>}>
+!Derived = !cir.struct<"Derived" {data !cir.struct<"Base" {data !cir.ptr<!cir.ptr<!cir.func<() -> !cir.int<u, 32>>>>}>}>
 
 module {
   cir.global "private" constant external @_ZTI4Base : !cir.ptr<!u32i>
@@ -28,8 +28,8 @@ module {
 !u8i = !cir.int<u, 8>
 !void = !cir.void
 
-!Base = !cir.struct<"Base" {!cir.ptr<!cir.ptr<!cir.func<() -> !cir.int<u, 32>>>>}>
-!Derived = !cir.struct<"Derived" {!cir.struct<"Base" {!cir.ptr<!cir.ptr<!cir.func<() -> !cir.int<u, 32>>>>}>}>
+!Base = !cir.struct<"Base" {data !cir.ptr<!cir.ptr<!cir.func<() -> !cir.int<u, 32>>>>}>
+!Derived = !cir.struct<"Derived" {data !cir.struct<"Base" {data !cir.ptr<!cir.ptr<!cir.func<() -> !cir.int<u, 32>>>>}>}>
 
 module {
   cir.global "private" constant external @_ZTI4Base : !cir.ptr<!u8i>
diff --git a/clang/test/CIR/IR/invalid-loop-cleanup.cir b/clang/test/CIR/IR/invalid-loop-cleanup.cir
index f48ac44cd1541..02519dfdaf33e 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" padded {data !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" padded {data !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" padded {data !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" padded {data !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/invalid-record-member-kinds.cir b/clang/test/CIR/IR/invalid-record-member-kinds.cir
index 2d9f85d4cec1f..9524b6ea93d70 100644
--- a/clang/test/CIR/IR/invalid-record-member-kinds.cir
+++ b/clang/test/CIR/IR/invalid-record-member-kinds.cir
@@ -1,7 +1,16 @@
 // RUN: cir-opt %s -verify-diagnostics -split-input-file
 
+// Every member is spelled with a mark, so a bare type has nothing to parse.
 !u8i = !cir.int<u, 8>
-// expected-error @below {{expected non-function type}}
+// expected-error @below {{expected valid keyword}}
+!rec_S = !cir.struct<"S" {!u8i}>
+
+module {}
+
+// -----
+
+!u8i = !cir.int<u, 8>
+// expected-error @below {{expected a record member kind}}
 !rec_S = !cir.struct<"S" {bogus !u8i}>
 
 module {}
@@ -20,7 +29,7 @@ module {}
 !u8i = !cir.int<u, 8>
 !s32i = !cir.int<s, 32>
 // expected-error @below {{a union's tail padding takes no kind mark}}
-!rec_U = !cir.union<"U" {!s32i}, padding = {pad !cir.array<!u8i x 4>}>
+!rec_U = !cir.union<"U" {data !s32i}, padding = {pad !cir.array<!u8i x 4>}>
 
 module {}
 
@@ -29,7 +38,7 @@ module {}
 !u8i = !cir.int<u, 8>
 !s32i = !cir.int<s, 32>
 // expected-error @below {{a union's tail padding takes no kind mark}}
-!rec_U = !cir.union<"U" {!s32i}, padding = {data !cir.array<!u8i x 4>}>
+!rec_U = !cir.union<"U" {data !s32i}, padding = {data !cir.array<!u8i x 4>}>
 
 module {}
 
@@ -38,7 +47,7 @@ module {}
 !u8i = !cir.int<u, 8>
 !s32i = !cir.int<s, 32>
 // expected-error @below {{a union's tail padding takes no kind mark}}
-!rec_U = !cir.union<"U" {!s32i}, padding = {empty !cir.array<!u8i x 4>}>
+!rec_U = !cir.union<"U" {data !s32i}, padding = {empty !cir.array<!u8i x 4>}>
 
 module {}
 
@@ -47,6 +56,6 @@ module {}
 !u8i = !cir.int<u, 8>
 !s32i = !cir.int<s, 32>
 // expected-error @below {{a union member cannot be marked pad}}
-!rec_U = !cir.union<"U" {!s32i, pad !u8i}>
+!rec_U = !cir.union<"U" {data !s32i, pad !u8i}>
 
 module {}
diff --git a/clang/test/CIR/IR/invalid-struct.cir b/clang/test/CIR/IR/invalid-struct.cir
index 3547c518f511b..baf8a6a9394c8 100644
--- a/clang/test/CIR/IR/invalid-struct.cir
+++ b/clang/test/CIR/IR/invalid-struct.cir
@@ -2,7 +2,7 @@
 
 !u8i = !cir.int<u, 8>
 !u32i = !cir.int<u, 32>
-!rec_S = !cir.struct<"S" {!u8i, !u32i}>
+!rec_S = !cir.struct<"S" {data !u8i, data !u32i}>
 
 module  {
   cir.func @struct_extract_member_invalid_element_type() {
@@ -17,7 +17,7 @@ module  {
 
 !u8i = !cir.int<u, 8>
 !u32i = !cir.int<u, 32>
-!rec_S = !cir.struct<"S" {!u8i, !u32i}>
+!rec_S = !cir.struct<"S" {data !u8i, data !u32i}>
 
 module  {
   cir.func @struct_extract_member_invalid_index_value() {
@@ -31,7 +31,7 @@ module  {
 // -----
 
 !u32i = !cir.int<u, 32>
-!rec_U = !cir.union<"U" {!u32i}>
+!rec_U = !cir.union<"U" {data !u32i}>
 
 module  {
   cir.func @extract_member_invalid_for_union() {
@@ -46,7 +46,7 @@ module  {
 
 !u8i = !cir.int<u, 8>
 !u32i = !cir.int<u, 32>
-!rec_S = !cir.struct<"S" {!u8i, !u32i}>
+!rec_S = !cir.struct<"S" {data !u8i, data !u32i}>
 
 module  {
   cir.func @struct_insert_member_invalid_element_type() {
@@ -62,7 +62,7 @@ module  {
 
 !u8i = !cir.int<u, 8>
 !u32i = !cir.int<u, 32>
-!rec_S = !cir.struct<"S" {!u8i, !u32i}>
+!rec_S = !cir.struct<"S" {data !u8i, data !u32i}>
 
 module  {
   cir.func @struct_extract_member_invalid_index_value() {
@@ -77,7 +77,7 @@ module  {
 // -----
 
 !u32i = !cir.int<u, 32>
-!rec_U = !cir.union<"U" {!u32i}>
+!rec_U = !cir.union<"U" {data !u32i}>
 
 module  {
   cir.func @extract_member_invalid_for_union() {
diff --git a/clang/test/CIR/IR/invalid-type-info.cir b/clang/test/CIR/IR/invalid-type-info.cir
index 93d1691b606f1..a3b130a72e0af 100644
--- a/clang/test/CIR/IR/invalid-type-info.cir
+++ b/clang/test/CIR/IR/invalid-type-info.cir
@@ -2,7 +2,7 @@
 
 !u8i = !cir.int<u, 8>
 
-!rec_anon_struct = !cir.struct<{!cir.ptr<!u8i>, !cir.ptr<!u8i>, !cir.ptr<!u8i>}>
+!rec_anon_struct = !cir.struct<{data !cir.ptr<!u8i>, data !cir.ptr<!u8i>, data !cir.ptr<!u8i>}>
 
 // expected-error @below {{expected !cir.struct or !cir.union type}}
 cir.global constant external @type_info = #cir.typeinfo<{#cir.global_view<@_ZTVN10__cxxabiv120__si_class_type_infoE, [2 : i32]> : !cir.ptr<!u8i>, #cir.global_view<@_ZTS1B> : !cir.ptr<!u8i>, #cir.global_view<@_ZTI1A> : !cir.ptr<!u8i>}> : !u8i
@@ -11,7 +11,7 @@ cir.global constant external @type_info = #cir.typeinfo<{#cir.global_view<@_ZTVN
 
 !u8i = !cir.int<u, 8>
 
-!rec_anon_struct = !cir.struct<{!u8i, !u8i, !u8i}>
+!rec_anon_struct = !cir.struct<{data !u8i, data !u8i, data !u8i}>
 
 // expected-error @below {{integer or global view array attribute}}
 cir.global constant external @type_info = #cir.typeinfo<{ #cir.undef : !u8i, #cir.int<1> : !u8i, #cir.int<1> : !u8i}> : !rec_anon_struct
diff --git a/clang/test/CIR/IR/invalid-vtable.cir b/clang/test/CIR/IR/invalid-vtable.cir
index 522a438fc634f..f16f26127ce19 100644
--- a/clang/test/CIR/IR/invalid-vtable.cir
+++ b/clang/test/CIR/IR/invalid-vtable.cir
@@ -20,9 +20,9 @@ cir.func @reference_non_vtable() {
 
 // -----
 
-!rec_S = !cir.struct<"S" {!cir.vptr}>
+!rec_S = !cir.struct<"S" {data !cir.vptr}>
 !u8i = !cir.int<u, 8>
-!rec_anon_struct = !cir.struct<{!cir.array<!cir.ptr<!u8i> x 4>}>
+!rec_anon_struct = !cir.struct<{data !cir.array<!cir.ptr<!u8i> x 4>}>
 module {
   // expected-error @below {{expected !cir.struct or !cir.union type result}}
   cir.global external @_ZTV1S = #cir.vtable<{#cir.const_array<[#cir.ptr<null> : !cir.ptr<!u8i>, #cir.ptr<null> : !cir.ptr<!u8i>, #cir.global_view<@_ZN1S3keyEv> : !cir.ptr<!u8i>, #cir.global_view<@_ZN1S6nonKeyEv> : !cir.ptr<!u8i>]> : !cir.array<!cir.ptr<!u8i> x 4>}> : !cir.ptr<!rec_anon_struct>
@@ -32,7 +32,7 @@ module {
 
 // -----
 
-!rec_S = !cir.struct<"S" {!cir.vptr}>
+!rec_S = !cir.struct<"S" {data !cir.vptr}>
 !u8i = !cir.int<u, 8>
 !rec_anon_struct = !cir.struct<{}>
 module {
@@ -44,9 +44,9 @@ module {
 
 // -----
 
-!rec_S = !cir.struct<"S" {!cir.vptr}>
+!rec_S = !cir.struct<"S" {data !cir.vptr}>
 !u8i = !cir.int<u, 8>
-!rec_anon_struct = !cir.struct<{!cir.ptr<!u8i>}>
+!rec_anon_struct = !cir.struct<{data !cir.ptr<!u8i>}>
 module {
   // expected-error @below {{expected constant array subtype}}
   cir.global external @_ZTV1S = #cir.vtable<{#cir.ptr<null> : !cir.ptr<!u8i>}> : !rec_anon_struct {alignment = 8 : i64}
@@ -56,9 +56,9 @@ module {
 
 // -----
 
-!rec_S = !cir.struct<"S" {!cir.vptr}>
+!rec_S = !cir.struct<"S" {data !cir.vptr}>
 !u64i = !cir.int<u, 64>
-!rec_anon_struct = !cir.struct<{!cir.array<!u64i x 4>}>
+!rec_anon_struct = !cir.struct<{data !cir.array<!u64i x 4>}>
 module {
   // expected-error @below {{expected GlobalViewAttr or ConstPtrAttr}}
   cir.global external @_ZTV1S = #cir.vtable<{#cir.const_array<[#cir.int<1> : !u64i, #cir.int<1> : !u64i, #cir.int<3> : !u64i, #cir.int<4> : !u64i]> : !cir.array<!u64i x 4>}> : !rec_anon_struct {alignment = 8 : i64}
@@ -68,11 +68,11 @@ module {
 
 // -----
 
-!rec_Q = !cir.struct<"Q" {!cir.vptr}>
-!rec_S = !cir.struct<"S" {!cir.vptr}>
-!rec_S2 = !cir.struct<"S2" {!rec_Q, !rec_S}>
+!rec_Q = !cir.struct<"Q" {data !cir.vptr}>
+!rec_S = !cir.struct<"S" {data !cir.vptr}>
+!rec_S2 = !cir.struct<"S2" {data !rec_Q, data !rec_S}>
 !u8i = !cir.int<u, 8>
-!rec_anon_struct = !cir.struct<{!cir.array<!cir.ptr<!u8i> x 4>, !cir.ptr<!u8i>}>
+!rec_anon_struct = !cir.struct<{data !cir.array<!cir.ptr<!u8i> x 4>, data !cir.ptr<!u8i>}>
 module {
   // expected-error @below {{expected constant array subtype}}
   cir.global external @_ZTV2S2 = #cir.vtable<{#cir.const_array<[#cir.ptr<null> : !cir.ptr<!u8i>, #cir.ptr<null> : !cir.ptr<!u8i>, #cir.global_view<@_ZN1S3keyEv> : !cir.ptr<!u8i>, #cir.global_view<@_ZN1S6nonKeyEv> : !cir.ptr<!u8i>]> : !cir.array<!cir.ptr<!u8i> x 4>, #cir.ptr<null> : !cir.ptr<!u8i>}> : !rec_anon_struct {alignment = 8 : i64}
@@ -97,7 +97,7 @@ cir.func @reference_unknown_vtt() {
 !u8i = !cir.int<u, 8>
 !u32i = !cir.int<u, 32>
 !void = !cir.void
-!rec_anon_struct = !cir.struct<{!cir.array<!cir.ptr<!u8i> x 4>}>
+!rec_anon_struct = !cir.struct<{data !cir.array<!cir.ptr<!u8i> x 4>}>
 cir.global external @_ZTV1S = #cir.vtable<{#cir.const_array<[#cir.ptr<null> : !cir.ptr<!u8i>, #cir.ptr<null> : !cir.ptr<!u8i>, #cir.global_view<@_ZN1S3keyEv> : !cir.ptr<!u8i>, #cir.global_view<@_ZN1S6nonKeyEv> : !cir.ptr<!u8i>]> : !cir.array<!cir.ptr<!u8i> x 4>}> : !rec_anon_struct {alignment = 8 : i64}
 cir.func @reference_non_vtt() {
   // expected-error @below {{'cir.vtt.address_point' op Expected constant array in initializer for global VTT '_ZTV1S'}}
@@ -110,8 +110,8 @@ cir.func @reference_non_vtt() {
 !u8i = !cir.int<u, 8>
 !u32i = !cir.int<u, 32>
 !void = !cir.void
-!rec_anon_struct = !cir.struct<{!cir.array<!cir.ptr<!u8i> x 4>}>
-!rec_C = !cir.struct<class "C" {!cir.vptr}>
+!rec_anon_struct = !cir.struct<{data !cir.array<!cir.ptr<!u8i> x 4>}>
+!rec_C = !cir.struct<class "C" {data !cir.vptr}>
 cir.global linkonce_odr @_ZTT1C = #cir.const_array<[#cir.global_view<@_ZTV1C, [0 : i32, 3 : i32]> : !cir.ptr<!u8i>, #cir.global_view<@_ZTC1C0_1B, [0 : i32, 3 : i32]> : !cir.ptr<!u8i>]> : !cir.array<!cir.ptr<!u8i> x 2> {alignment = 8 : i64}
 cir.func @reference_name_and_value(%arg0: !cir.ptr<!rec_C>, %arg1: !cir.ptr<!cir.ptr<!void>>) {
   // expected-error @below {{'cir.vtt.address_point' op should use either a symbol or value, but not both}}
@@ -124,8 +124,8 @@ cir.func @reference_name_and_value(%arg0: !cir.ptr<!rec_C>, %arg1: !cir.ptr<!cir
 !u8i = !cir.int<u, 8>
 !u32i = !cir.int<u, 32>
 !void = !cir.void
-!rec_anon_struct = !cir.struct<{!cir.array<!cir.ptr<!u8i> x 4>}>
-!rec_C = !cir.struct<class "C" {!cir.vptr}>
+!rec_anon_struct = !cir.struct<{data !cir.array<!cir.ptr<!u8i> x 4>}>
+!rec_C = !cir.struct<class "C" {data !cir.vptr}>
 cir.global linkonce_odr @_ZTT1C = #cir.const_array<[#cir.global_view<@_ZTV1C, [0 : i32, 3 : i32]> : !cir.ptr<!u8i>, #cir.global_view<@_ZTC1C0_1B, [0 : i32, 3 : i32]> : !cir.ptr<!u8i>]> : !cir.array<!cir.ptr<!u8i> x 2> {alignment = 8 : i64}
 cir.func @bad_return_type_for_vtt_addrpoint() {
   // expected-error @below {{result type must be '!cir.ptr<!cir.ptr<!cir.void>>', but provided result type is '!cir.ptr<!cir.int<u, 8>>'}}
diff --git a/clang/test/CIR/IR/loop-cleanup.cir b/clang/test/CIR/IR/loop-cleanup.cir
index ee995f8ca3262..666709399d100 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" padded {data !u8i}>
 
 module {
   cir.func private @ctor(!cir.ptr<!rec_S>)
diff --git a/clang/test/CIR/IR/method-attr.cir b/clang/test/CIR/IR/method-attr.cir
index 732152d6c5713..af81813f12ece 100644
--- a/clang/test/CIR/IR/method-attr.cir
+++ b/clang/test/CIR/IR/method-attr.cir
@@ -2,7 +2,7 @@
 
 !void = !cir.void
 
-!rec_Foo = !cir.struct<"Foo" {!cir.vptr}>
+!rec_Foo = !cir.struct<"Foo" {data !cir.vptr}>
 !s32i = !cir.int<s, 32>
 module {
   cir.func private @_ZN3Foo2m1Ei(!s32i)
diff --git a/clang/test/CIR/IR/struct.cir b/clang/test/CIR/IR/struct.cir
index 3048aaaac59ef..30e278efcec8c 100644
--- a/clang/test/CIR/IR/struct.cir
+++ b/clang/test/CIR/IR/struct.cir
@@ -13,84 +13,80 @@
 // CHECK-DAG: !rec_S = !cir.struct<"S" incomplete>
 // CHECK-DAG: !rec_U = !cir.union<"U" incomplete>
 
-!rec_anon_struct = !cir.struct< packed {!s32i, !s32i, !cir.array<!s32i x 8>}>
-!rec_anon_struct1 = !cir.struct<{!cir.array<!cir.ptr<!u8i> x 5>}>
-!rec_anon_struct2 = !cir.struct<{!cir.ptr<!u8i>, !cir.ptr<!u8i>, !cir.ptr<!u8i>}>
-!rec_S1 = !cir.struct<"S1" {!s32i, !s32i}>
-!rec_Sc = !cir.struct<"Sc" {!u8i, !u16i, !u32i}>
-
-// CHECK-DAG: ![[ARR_TY:rec_anon_struct[0-9]*]] = !cir.struct<packed {!s32i, !s32i, !cir.array<!s32i x 8>}>
-// CHECK-DAG: !cir.struct<{!cir.array<!cir.ptr<!u8i> x 5>}>
-// CHECK-DAG: !cir.struct<{!cir.ptr<!u8i>, !cir.ptr<!u8i>, !cir.ptr<!u8i>}>
-// CHECK-DAG: !rec_S1 = !cir.struct<"S1" {!s32i, !s32i}>
-// CHECK-DAG: !rec_Sc = !cir.struct<"Sc" {!u8i, !u16i, !u32i}>
+!rec_anon_struct = !cir.struct< packed {data !s32i, data !s32i, data !cir.array<!s32i x 8>}>
+!rec_anon_struct1 = !cir.struct<{data !cir.array<!cir.ptr<!u8i> x 5>}>
+!rec_anon_struct2 = !cir.struct<{data !cir.ptr<!u8i>, data !cir.ptr<!u8i>, data !cir.ptr<!u8i>}>
+!rec_S1 = !cir.struct<"S1" {data !s32i, data !s32i}>
+!rec_Sc = !cir.struct<"Sc" {data !u8i, data !u16i, data !u32i}>
+
+// CHECK-DAG: ![[ARR_TY:rec_anon_struct[0-9]*]] = !cir.struct<packed {data !s32i, data !s32i, data !cir.array<!s32i x 8>}>
+// CHECK-DAG: !cir.struct<{data !cir.array<!cir.ptr<!u8i> x 5>}>
+// CHECK-DAG: !cir.struct<{data !cir.ptr<!u8i>, data !cir.ptr<!u8i>, data !cir.ptr<!u8i>}>
+// CHECK-DAG: !rec_S1 = !cir.struct<"S1" {data !s32i, data !s32i}>
+// CHECK-DAG: !rec_Sc = !cir.struct<"Sc" {data !u8i, data !u16i, data !u32i}>
 
 // Packed and padded structs
-!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_P1 = !cir.struct<"P1" packed {data !s32i, data !s32i}>
+!rec_P2 = !cir.struct<"P2" padded {data !u8i, data !u16i, data !u32i}>
+!rec_P3 = !cir.struct<"P3" packed padded {data !u8i, data !u16i, data !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_P6 = !cir.struct<"P6" {data !u32i, empty !cir.array<!u8i x 3>, pad !u8i}>
+!rec_P7 = !cir.struct<"P7" packed padded {data !u8i, pad !u8i}>
 
-// 'data' parses and prints without a mark.
-!rec_P8 = !cir.struct<"P8" {data !u8i, pad !u8i}>
-
-// CHECK-DAG: !rec_P1 = !cir.struct<"P1" packed {!s32i, !s32i}>
-// CHECK-DAG: !rec_P2 = !cir.struct<"P2" padded {!u8i, !u16i, !u32i}>
-// CHECK-DAG: !rec_P3 = !cir.struct<"P3" packed padded {!u8i, !u16i, !u32i}>
+// CHECK-DAG: !rec_P1 = !cir.struct<"P1" packed {data !s32i, data !s32i}>
+// CHECK-DAG: !rec_P2 = !cir.struct<"P2" padded {data !u8i, data !u16i, data !u32i}>
+// CHECK-DAG: !rec_P3 = !cir.struct<"P3" packed padded {data !u8i, data !u16i, data !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_P8 = !cir.struct<"P8" {!u8i, pad !u8i}>
+// CHECK-DAG: !rec_P6 = !cir.struct<"P6" {data !u32i, empty !cir.array<!u8i x 3>, pad !u8i}>
+// CHECK-DAG: !rec_P7 = !cir.struct<"P7" packed padded {data !u8i, pad !u8i}>
 
 // Records with identical member types, spelled apart by their kinds.
-!rec_M1 = !cir.struct<"M1" {!u8i, pad !u8i}>
-!rec_M2 = !cir.struct<"M2" {!u8i, empty !u8i}>
-!rec_anon_pad = !cir.struct<{!u8i, pad !u8i}>
-!rec_anon_empty = !cir.struct<{!u8i, empty !u8i}>
-
-// CHECK-DAG: !rec_M1 = !cir.struct<"M1" {!u8i, pad !u8i}>
-// CHECK-DAG: !rec_M2 = !cir.struct<"M2" {!u8i, empty !u8i}>
-// CHECK-DAG: !cir.struct<{!u8i, pad !u8i}>
-// CHECK-DAG: !cir.struct<{!u8i, empty !u8i}>
-
-!rec_U1 = !cir.union<"U1" {!s32i, !u8i}, padding = {!u8i}>
-!rec_U2 = !cir.union<"U2" packed {!s32i}, padding = {!cir.array<!u8i x 4>}>
-!rec_anon_u_empty = !cir.union<{!s32i, empty !u8i}>
-!rec_anon_u_plain = !cir.union<{!s32i, !u8i}>
+!rec_M1 = !cir.struct<"M1" {data !u8i, pad !u8i}>
+!rec_M2 = !cir.struct<"M2" {data !u8i, empty !u8i}>
+!rec_anon_pad = !cir.struct<{data !u8i, pad !u8i}>
+!rec_anon_empty = !cir.struct<{data !u8i, empty !u8i}>
+
+// CHECK-DAG: !rec_M1 = !cir.struct<"M1" {data !u8i, pad !u8i}>
+// CHECK-DAG: !rec_M2 = !cir.struct<"M2" {data !u8i, empty !u8i}>
+// CHECK-DAG: !cir.struct<{data !u8i, pad !u8i}>
+// CHECK-DAG: !cir.struct<{data !u8i, empty !u8i}>
+
+!rec_U1 = !cir.union<"U1" {data !s32i, data !u8i}, padding = {!u8i}>
+!rec_U2 = !cir.union<"U2" packed {data !s32i}, padding = {!cir.array<!u8i x 4>}>
+!rec_anon_u_empty = !cir.union<{data !s32i, empty !u8i}>
+!rec_anon_u_plain = !cir.union<{data !s32i, data !u8i}>
 !rec_U3 = !cir.union<"U3" {empty !u8i}>
-!rec_U4 = !cir.union<"U4" {!s32i, empty !u8i}, padding = {!cir.array<!u8i x 4>}>
+!rec_U4 = !cir.union<"U4" {data !s32i, empty !u8i}, padding = {!cir.array<!u8i x 4>}>
 
-// CHECK-DAG: !rec_U1 = !cir.union<"U1" {!s32i, !u8i}, padding = {!u8i}>
-// CHECK-DAG: !rec_U2 = !cir.union<"U2" packed {!s32i}, padding = {!cir.array<!u8i x 4>}>
-// CHECK-DAG: !cir.union<{!s32i, empty !u8i}>
-// CHECK-DAG: !cir.union<{!s32i, !u8i}>
+// CHECK-DAG: !rec_U1 = !cir.union<"U1" {data !s32i, data !u8i}, padding = {!u8i}>
+// CHECK-DAG: !rec_U2 = !cir.union<"U2" packed {data !s32i}, padding = {!cir.array<!u8i x 4>}>
+// CHECK-DAG: !cir.union<{data !s32i, empty !u8i}>
+// CHECK-DAG: !cir.union<{data !s32i, data !u8i}>
 // CHECK-DAG: !rec_U3 = !cir.union<"U3" {empty !u8i}>
-// CHECK-DAG: !rec_U4 = !cir.union<"U4" {!s32i, empty !u8i}, padding = {!cir.array<!u8i x 4>}>
+// CHECK-DAG: !rec_U4 = !cir.union<"U4" {data !s32i, empty !u8i}, padding = {!cir.array<!u8i x 4>}>
 
 
 // Complete a previously incomplete record
 !rec_A = !cir.struct<class "A" incomplete>
-!rec_Ac = !cir.struct<class "A" {!u8i, !s32i}>
-// CHECK-DAG: !rec_A = !cir.struct<class "A" {!u8i, !s32i}>
+!rec_Ac = !cir.struct<class "A" {data !u8i, data !s32i}>
+// CHECK-DAG: !rec_A = !cir.struct<class "A" {data !u8i, data !s32i}>
 
 // Complete a previously incomplete record whose members carry marks.
 !rec_B = !cir.struct<class "B" incomplete>
-!rec_Bc = !cir.struct<class "B" {!u8i, pad !cir.array<!u8i x 3>, !s32i}>
-// CHECK-DAG: !rec_B = !cir.struct<class "B" {!u8i, pad !cir.array<!u8i x 3>, !s32i}>
+!rec_Bc = !cir.struct<class "B" {data !u8i, pad !cir.array<!u8i x 3>, data !s32i}>
+// CHECK-DAG: !rec_B = !cir.struct<class "B" {data !u8i, pad !cir.array<!u8i x 3>, data !s32i}>
 
 // Test recursive struct parsing/printing.
-!rec_Node = !cir.struct<"Node" {!cir.ptr<!cir.struct<"Node">>}>
-// CHECK-DAG: !cir.struct<"Node" {!cir.ptr<!cir.struct<"Node">>}>
+!rec_Node = !cir.struct<"Node" {data !cir.ptr<!cir.struct<"Node">>}>
+// CHECK-DAG: !cir.struct<"Node" {data !cir.ptr<!cir.struct<"Node">>}>
 
 // A mark survives the cyclic-print guard on a self-referential record.
-!rec_PadNode = !cir.struct<"PadNode" {!cir.ptr<!cir.struct<"PadNode">>, pad !u8i}>
-// CHECK-DAG: !cir.struct<"PadNode" {!cir.ptr<!cir.struct<"PadNode">>, pad !u8i}>
+!rec_PadNode = !cir.struct<"PadNode" {data !cir.ptr<!cir.struct<"PadNode">>, pad !u8i}>
+// CHECK-DAG: !cir.struct<"PadNode" {data !cir.ptr<!cir.struct<"PadNode">>, pad !u8i}>
 
 
 
@@ -128,21 +124,12 @@ module  {
                      %arg20: !rec_PadNode,
                      %arg21: !rec_P7,
                      %arg22: !rec_anon_u_empty,
-                     %arg23: !rec_anon_u_plain,
-                     %arg24: !rec_P8) {
-    cir.return
-  }
-
-  // The 'data' spelling must reach the same type as the unmarked one, which an
-  // anonymous record shows because it keys on its whole body.
-  cir.func @anonDataUnifies(%arg0: !cir.struct<{!u8i, pad !u8i}>,
-                            %arg1: !cir.struct<{data !u8i, pad !u8i}>) {
+                     %arg23: !rec_anon_u_plain) {
     cir.return
   }
-// CHECK: cir.func @anonDataUnifies(%arg0: ![[ANON:rec_anon_struct[0-9]*]], %arg1: ![[ANON]])
 
   cir.func @structs() {
-    %0 = cir.alloca "sc" align(8) init : !cir.ptr<!cir.ptr<!cir.struct<"Sc" {!u8i, !u16i, !u32i}>>>
+    %0 = cir.alloca "sc" align(8) init : !cir.ptr<!cir.ptr<!cir.struct<"Sc" {data !u8i, data !u16i, data !u32i}>>>
     %1 = cir.alloca "u" align(8) init : !cir.ptr<!cir.ptr<!cir.union<"U" incomplete>>>
     cir.return
   }
diff --git a/clang/test/CIR/IR/vtable-addrpt.cir b/clang/test/CIR/IR/vtable-addrpt.cir
index d34b95dd030aa..068fbcd0f4017 100644
--- a/clang/test/CIR/IR/vtable-addrpt.cir
+++ b/clang/test/CIR/IR/vtable-addrpt.cir
@@ -4,8 +4,8 @@
 
 !u32i = !cir.int<u, 32>
 !u8i = !cir.int<u, 8>
-!rec_anon_struct = !cir.struct<{!cir.array<!cir.ptr<!u8i> x 4>}>
-!rec_S = !cir.struct<"S" {!cir.vptr}>
+!rec_anon_struct = !cir.struct<{data !cir.array<!cir.ptr<!u8i> x 4>}>
+!rec_S = !cir.struct<"S" {data !cir.vptr}>
 
 module {
   cir.global "private" external @_ZTV1S : !rec_anon_struct {alignment = 8 : i64}
diff --git a/clang/test/CIR/IR/vtable-attr.cir b/clang/test/CIR/IR/vtable-attr.cir
index c46bafcec879c..47d01aece8b3d 100644
--- a/clang/test/CIR/IR/vtable-attr.cir
+++ b/clang/test/CIR/IR/vtable-attr.cir
@@ -1,11 +1,11 @@
 // RUN: cir-opt %s --verify-roundtrip | FileCheck %s
 
-!rec_Q = !cir.struct<"Q" {!cir.vptr}>
-!rec_S = !cir.struct<"S" {!cir.vptr}>
-!rec_S2 = !cir.struct<"S2" {!rec_Q, !rec_S}>
+!rec_Q = !cir.struct<"Q" {data !cir.vptr}>
+!rec_S = !cir.struct<"S" {data !cir.vptr}>
+!rec_S2 = !cir.struct<"S2" {data !rec_Q, data !rec_S}>
 !u8i = !cir.int<u, 8>
-!rec_anon_struct = !cir.struct<{!cir.array<!cir.ptr<!u8i> x 4>}>
-!rec_anon_struct1 = !cir.struct<{!cir.array<!cir.ptr<!u8i> x 4>, !cir.array<!cir.ptr<!u8i> x 3>}>
+!rec_anon_struct = !cir.struct<{data !cir.array<!cir.ptr<!u8i> x 4>}>
+!rec_anon_struct1 = !cir.struct<{data !cir.array<!cir.ptr<!u8i> x 4>, data !cir.array<!cir.ptr<!u8i> x 3>}>
 module {
   cir.global external @_ZTV1S = #cir.vtable<{#cir.const_array<[#cir.ptr<null> : !cir.ptr<!u8i>, #cir.ptr<null> : !cir.ptr<!u8i>, #cir.global_view<@_ZN1S3keyEv> : !cir.ptr<!u8i>, #cir.global_view<@_ZN1S6nonKeyEv> : !cir.ptr<!u8i>]> : !cir.array<!cir.ptr<!u8i> x 4>}> : !rec_anon_struct {alignment = 8 : i64}
   // CHECK: cir.global external @_ZTV1S = #cir.vtable<{#cir.const_array<[#cir.ptr<null> : !cir.ptr<!u8i>, #cir.ptr<null> : !cir.ptr<!u8i>, #cir.global_view<@_ZN1S3keyEv> : !cir.ptr<!u8i>, #cir.global_view<@_ZN1S6nonKeyEv> : !cir.ptr<!u8i>]> : !cir.array<!cir.ptr<!u8i> x 4>}> : !rec_anon_struct {alignment = 8 : i64}
diff --git a/clang/test/CIR/IR/vtt-addrpoint.cir b/clang/test/CIR/IR/vtt-addrpoint.cir
index 5081d3a0d72f9..b3da6dcdd561a 100644
--- a/clang/test/CIR/IR/vtt-addrpoint.cir
+++ b/clang/test/CIR/IR/vtt-addrpoint.cir
@@ -5,10 +5,10 @@
 
 !u8i = !cir.int<u, 8>
 !void = !cir.void
-!rec_A = !cir.struct<"A" {!u8i}>
-!rec_B = !cir.struct<"B" {!cir.vptr}>
-!rec_C = !cir.struct<"C" {!rec_B}>
-!rec_anon_struct = !cir.struct<{!cir.array<!cir.ptr<!u8i> x 3>}>
+!rec_A = !cir.struct<"A" {data !u8i}>
+!rec_B = !cir.struct<"B" {data !cir.vptr}>
+!rec_C = !cir.struct<"C" {data !rec_B}>
+!rec_anon_struct = !cir.struct<{data !cir.array<!cir.ptr<!u8i> x 3>}>
 module {
   cir.func private @_ZN1AC2Ev(!cir.ptr<!rec_A>)
   cir.func private @_ZN1BC2Ev(!cir.ptr<!rec_B>, !cir.ptr<!cir.ptr<!void>>)
diff --git a/clang/test/CIR/Lowering/const-array-bulk-lowering-fallbacks.cir b/clang/test/CIR/Lowering/const-array-bulk-lowering-fallbacks.cir
index f5ad2903055d8..0b4a09d179d44 100644
--- a/clang/test/CIR/Lowering/const-array-bulk-lowering-fallbacks.cir
+++ b/clang/test/CIR/Lowering/const-array-bulk-lowering-fallbacks.cir
@@ -4,12 +4,12 @@
 !s8i = !cir.int<s, 8>
 !u16i = !cir.int<u, 16>
 !bi33 = !cir.int<s, 33, bitint>
-!rec_data = !cir.struct<{!cir.array<!s8i x 4>, !cir.array<!s8i x 2>}>
-!rec_mixed = !cir.struct<{!s32i, !cir.ptr<!s32i>}>
-!rec_entry = !cir.struct<{!u16i, !s32i}>
-!rec_iptr = !cir.struct<{!cir.ptr<!s8i>}>
-!rec_bitint = !cir.struct<{!bi33, !s32i}>
-!rec_poison = !cir.struct<{!s32i}>
+!rec_data = !cir.struct<{data !cir.array<!s8i x 4>, data !cir.array<!s8i x 2>}>
+!rec_mixed = !cir.struct<{data !s32i, data !cir.ptr<!s32i>}>
+!rec_entry = !cir.struct<{data !u16i, data !s32i}>
+!rec_iptr = !cir.struct<{data !cir.ptr<!s8i>}>
+!rec_bitint = !cir.struct<{data !bi33, data !s32i}>
+!rec_poison = !cir.struct<{data !s32i}>
 
 module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
   cir.global external @tz_arr =
diff --git a/clang/test/CIR/Lowering/const-array-of-pointers.cir b/clang/test/CIR/Lowering/const-array-of-pointers.cir
index 5d590e086fe8b..d1dcc1fa3b209 100644
--- a/clang/test/CIR/Lowering/const-array-of-pointers.cir
+++ b/clang/test/CIR/Lowering/const-array-of-pointers.cir
@@ -1,7 +1,7 @@
 // RUN: cir-opt %s --cir-to-llvm -o - | FileCheck %s
 
 !s32i = !cir.int<s, 32>
-!rec_mixed = !cir.struct<{!s32i, !cir.ptr<!s32i>}>
+!rec_mixed = !cir.struct<{data !s32i, data !cir.ptr<!s32i>}>
 
 module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
   cir.global "private" constant cir_private dso_local @g0 = #cir.int<0> : !s32i
diff --git a/clang/test/CIR/Lowering/struct.cir b/clang/test/CIR/Lowering/struct.cir
index 6fddb58ced7dd..b056c4383efe6 100644
--- a/clang/test/CIR/Lowering/struct.cir
+++ b/clang/test/CIR/Lowering/struct.cir
@@ -3,7 +3,7 @@
 
 !s32i = !cir.int<s, 32>
 !u8i = !cir.int<u, 8>
-!ty_S = !cir.struct<"S" {!u8i, !s32i}>
+!ty_S = !cir.struct<"S" {data !u8i, data !s32i}>
 
 module {
   // CHECK-LABEL: @test_value
diff --git a/clang/test/CIR/Lowering/vtt-addrpoint.cir b/clang/test/CIR/Lowering/vtt-addrpoint.cir
index 2a9a79df7881b..6fa8015cc3c22 100644
--- a/clang/test/CIR/Lowering/vtt-addrpoint.cir
+++ b/clang/test/CIR/Lowering/vtt-addrpoint.cir
@@ -6,10 +6,10 @@
 
 !u8i = !cir.int<u, 8>
 !void = !cir.void
-!rec_A = !cir.struct<"A" {!u8i}>
-!rec_B = !cir.struct<"B" {!cir.vptr}>
-!rec_C = !cir.struct<"C" {!rec_B}>
-!rec_anon_struct = !cir.struct<{!cir.array<!cir.ptr<!u8i> x 3>}>
+!rec_A = !cir.struct<"A" {data !u8i}>
+!rec_B = !cir.struct<"B" {data !cir.vptr}>
+!rec_C = !cir.struct<"C" {data !rec_B}>
+!rec_anon_struct = !cir.struct<{data !cir.array<!cir.ptr<!u8i> x 3>}>
 module {
   cir.func private @_ZN1AC2Ev(!cir.ptr<!rec_A>)
   cir.func private @_ZN1BC2Ev(!cir.ptr<!rec_B>, !cir.ptr<!cir.ptr<!void>>)
diff --git a/clang/test/CIR/Transforms/abi-lowering/byval-sret-arg-attr-lowering.cir b/clang/test/CIR/Transforms/abi-lowering/byval-sret-arg-attr-lowering.cir
index 8f121e5ed78dd..97d579a19095b 100644
--- a/clang/test/CIR/Transforms/abi-lowering/byval-sret-arg-attr-lowering.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/byval-sret-arg-attr-lowering.cir
@@ -6,7 +6,7 @@
 // RUN:   | FileCheck %s --check-prefix=LLVM
 
 !s64i = !cir.int<s, 64>
-!rec_Big = !cir.struct<"Big" {!s64i, !s64i, !s64i}>
+!rec_Big = !cir.struct<"Big" {data !s64i, data !s64i, data !s64i}>
 
 #byval_arg = {
   return = { kind = "direct" },
diff --git a/clang/test/CIR/Transforms/abi-lowering/coerce-int-to-record.cir b/clang/test/CIR/Transforms/abi-lowering/coerce-int-to-record.cir
index 2f9f05386d921..fb837b0654f5c 100644
--- a/clang/test/CIR/Transforms/abi-lowering/coerce-int-to-record.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/coerce-int-to-record.cir
@@ -3,7 +3,7 @@
 
 !s32i = !cir.int<s, 32>
 !s64i = !cir.int<s, 64>
-!rec_Pair = !cir.struct<"Pair" {!s32i, !s32i}>
+!rec_Pair = !cir.struct<"Pair" {data !s32i, data !s32i}>
 
 #coerce_pair_return_to_i64 = {
   return = { kind = "direct", coerced_type = !s64i },
diff --git a/clang/test/CIR/Transforms/abi-lowering/coerce-record-return-larger.cir b/clang/test/CIR/Transforms/abi-lowering/coerce-record-return-larger.cir
index c13c52182bfff..8b2798bf979c3 100644
--- a/clang/test/CIR/Transforms/abi-lowering/coerce-record-return-larger.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/coerce-record-return-larger.cir
@@ -3,8 +3,8 @@
 
 !s32i = !cir.int<s, 32>
 !s64i = !cir.int<s, 64>
-!rec_Vec3   = !cir.struct<"Vec3"   {!s32i, !s32i, !s32i}>
-!rec_TwoI64 = !cir.struct<"TwoI64" {!s64i, !s64i}>
+!rec_Vec3   = !cir.struct<"Vec3"   {data !s32i, data !s32i, data !s32i}>
+!rec_TwoI64 = !cir.struct<"TwoI64" {data !s64i, data !s64i}>
 
 #coerce_vec3_return_to_twoi64 = {
   return = { kind = "direct", coerced_type = !rec_TwoI64 },
diff --git a/clang/test/CIR/Transforms/abi-lowering/coerce-record-to-int.cir b/clang/test/CIR/Transforms/abi-lowering/coerce-record-to-int.cir
index 0597d5ce5dd3a..3cca31ac3a4af 100644
--- a/clang/test/CIR/Transforms/abi-lowering/coerce-record-to-int.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/coerce-record-to-int.cir
@@ -3,7 +3,7 @@
 
 !s32i = !cir.int<s, 32>
 !s64i = !cir.int<s, 64>
-!rec_Pair = !cir.struct<"Pair" {!s32i, !s32i}>
+!rec_Pair = !cir.struct<"Pair" {data !s32i, data !s32i}>
 
 #coerce_pair_to_i64 = {
   return = { kind = "direct" },
diff --git a/clang/test/CIR/Transforms/abi-lowering/coerce-record-to-record-via-memory.cir b/clang/test/CIR/Transforms/abi-lowering/coerce-record-to-record-via-memory.cir
index 1517a5db948dd..98ad8dca446ee 100644
--- a/clang/test/CIR/Transforms/abi-lowering/coerce-record-to-record-via-memory.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/coerce-record-to-record-via-memory.cir
@@ -3,8 +3,8 @@
 
 !s32i = !cir.int<s, 32>
 !s64i = !cir.int<s, 64>
-!rec_Vec4   = !cir.struct<"Vec4"   {!s32i, !s32i, !s32i, !s32i}>
-!rec_TwoI64 = !cir.struct<"TwoI64" {!s64i, !s64i}>
+!rec_Vec4   = !cir.struct<"Vec4"   {data !s32i, data !s32i, data !s32i, data !s32i}>
+!rec_TwoI64 = !cir.struct<"TwoI64" {data !s64i, data !s64i}>
 
 #coerce_vec4_to_twoi64 = {
   return = { kind = "direct" },
diff --git a/clang/test/CIR/Transforms/abi-lowering/direct-flatten.cir b/clang/test/CIR/Transforms/abi-lowering/direct-flatten.cir
index 0914cbbd479a2..d9d1442cf9fdd 100644
--- a/clang/test/CIR/Transforms/abi-lowering/direct-flatten.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/direct-flatten.cir
@@ -5,18 +5,18 @@
 !s64i = !cir.int<s, 64>
 
 // Named source structs.
-!rec_TwoLong  = !cir.struct<"TwoLong"  {!s64i, !s64i}>
-!rec_ThreeInt = !cir.struct<"ThreeInt" {!s32i, !s32i, !s32i}>
+!rec_TwoLong  = !cir.struct<"TwoLong"  {data !s64i, data !s64i}>
+!rec_ThreeInt = !cir.struct<"ThreeInt" {data !s32i, data !s32i, data !s32i}>
 // dim3-equivalent: three i32 fields totalling 12 bytes.
-!rec_Dim3     = !cir.struct<"Dim3"     {!s32i, !s32i, !s32i}>
+!rec_Dim3     = !cir.struct<"Dim3"     {data !s32i, data !s32i, data !s32i}>
 
 // ABI-coerced anonymous structs.  MLIR auto-generates their aliases
 // (!rec_anon_struct, !rec_anon_struct1, etc.) in encounter order.
-!rec_coerced_two   = !cir.struct<{!s64i, !s64i}>
-!rec_coerced_three = !cir.struct<{!s32i, !s32i, !s32i}>
+!rec_coerced_two   = !cir.struct<{data !s64i, data !s64i}>
+!rec_coerced_three = !cir.struct<{data !s32i, data !s32i, data !s32i}>
 // x86-64 SysV packs dim3's first two i32s into an i64; the third i32
 // is a separate half of the second eightbyte.
-!rec_coerced_dim3  = !cir.struct<{!s64i, !s32i}>
+!rec_coerced_dim3  = !cir.struct<{data !s64i, data !s32i}>
 
 #flatten_two = {
   return = { kind = "direct" },
diff --git a/clang/test/CIR/Transforms/abi-lowering/expand-struct-arg.cir b/clang/test/CIR/Transforms/abi-lowering/expand-struct-arg.cir
index 5ffbc4aed6331..544c025fb8265 100644
--- a/clang/test/CIR/Transforms/abi-lowering/expand-struct-arg.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/expand-struct-arg.cir
@@ -3,9 +3,9 @@
 
 !s64i = !cir.int<s, 64>
 !s32i = !cir.int<s, 32>
-!rec_TwoLong = !cir.struct<"TwoLong" {!s64i, !s64i}>
-!rec_ThreeInt = !cir.struct<"ThreeInt" {!s32i, !s32i, !s32i}>
-!rec_OneInt = !cir.struct<"OneInt" {!s32i}>
+!rec_TwoLong = !cir.struct<"TwoLong" {data !s64i, data !s64i}>
+!rec_ThreeInt = !cir.struct<"ThreeInt" {data !s32i, data !s32i, data !s32i}>
+!rec_OneInt = !cir.struct<"OneInt" {data !s32i}>
 
 #expand_two = {
   return = { kind = "direct" },
diff --git a/clang/test/CIR/Transforms/abi-lowering/indirect-byval.cir b/clang/test/CIR/Transforms/abi-lowering/indirect-byval.cir
index 887d8f5b0d7cb..07a951e8054f7 100644
--- a/clang/test/CIR/Transforms/abi-lowering/indirect-byval.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/indirect-byval.cir
@@ -3,7 +3,7 @@
 
 !s32i = !cir.int<s, 32>
 !s64i = !cir.int<s, 64>
-!rec_Big = !cir.struct<"Big" {!s64i, !s64i, !s64i, !s64i}>
+!rec_Big = !cir.struct<"Big" {data !s64i, data !s64i, data !s64i, data !s64i}>
 
 #byval_arg = {
   return = { kind = "direct" },
diff --git a/clang/test/CIR/Transforms/abi-lowering/indirect-call-test-target.cir b/clang/test/CIR/Transforms/abi-lowering/indirect-call-test-target.cir
index 794487e756ffe..943ed8b3bb261 100644
--- a/clang/test/CIR/Transforms/abi-lowering/indirect-call-test-target.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/indirect-call-test-target.cir
@@ -2,7 +2,7 @@
 
 !s32i = !cir.int<s, 32>
 !s64i = !cir.int<s, 64>
-!rec_Big = !cir.struct<"Big" {!s64i, !s64i, !s64i, !s64i}>
+!rec_Big = !cir.struct<"Big" {data !s64i, data !s64i, data !s64i, data !s64i}>
 
 module attributes {
   dlti.dl_spec = #dlti.dl_spec<
diff --git a/clang/test/CIR/Transforms/abi-lowering/indirect-call.cir b/clang/test/CIR/Transforms/abi-lowering/indirect-call.cir
index 6a6f7fe7a5a0b..c6b16630ad5a6 100644
--- a/clang/test/CIR/Transforms/abi-lowering/indirect-call.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/indirect-call.cir
@@ -3,8 +3,8 @@
 !s16i = !cir.int<s, 16>
 !s32i = !cir.int<s, 32>
 !s64i = !cir.int<s, 64>
-!rec_Big = !cir.struct<"Big" {!s64i, !s64i, !s64i}>
-!rec_Pair = !cir.struct<"Pair" {!s32i, !s32i}>
+!rec_Big = !cir.struct<"Big" {data !s64i, data !s64i, data !s64i}>
+!rec_Pair = !cir.struct<"Pair" {data !s32i, data !s32i}>
 !rec_Empty = !cir.struct<"Empty" {}>
 
 module attributes {
diff --git a/clang/test/CIR/Transforms/abi-lowering/indirect-return-sret.cir b/clang/test/CIR/Transforms/abi-lowering/indirect-return-sret.cir
index 98b77e51ba3da..e3a569f21c336 100644
--- a/clang/test/CIR/Transforms/abi-lowering/indirect-return-sret.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/indirect-return-sret.cir
@@ -4,7 +4,7 @@
 !s8i = !cir.int<s, 8>
 !s32i = !cir.int<s, 32>
 !s64i = !cir.int<s, 64>
-!rec_Big = !cir.struct<"Big" {!s64i, !s64i, !s64i, !s64i}>
+!rec_Big = !cir.struct<"Big" {data !s64i, data !s64i, data !s64i, data !s64i}>
 
 #indirect_return = {
   return = { kind = "indirect", indirect_align = 8 },
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..849458e7ab8c0 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
@@ -4,19 +4,19 @@
 !s16i = !cir.int<s, 16>
 !s32i = !cir.int<s, 32>
 !u8i = !cir.int<u, 8>
-!rec_UPacked = !cir.union<"UPacked" packed {!s32i, !cir.array<!s8i x 5>}, padding = {!u8i}>
-!rec_ULongDouble = !cir.union<"ULongDouble" {!cir.long_double<!cir.f80>, !s32i}>
-!rec_UFloats = !cir.union<"UFloats" {!cir.array<!cir.float x 2>, !cir.array<!cir.float x 2>}>
-!rec_UOverAligned = !cir.union<"UOverAligned" {!s32i}, padding = {!cir.array<!u8i x 12>}>
-!rec_UShortStorage = !cir.union<"UShortStorage" {!s16i, !cir.array<!s8i x 3>}, padding = {!cir.array<!u8i x 2>}>
-!rec_UByteBlobs = !cir.union<"UByteBlobs" {!u8i, !u8i}, padding = {!cir.array<!u8i x 3>}>
-!rec_SWrapsOverAligned = !cir.struct<"SWrapsOverAligned" {!cir.double, !rec_UOverAligned}>
+!rec_UPacked = !cir.union<"UPacked" packed {data !s32i, data !cir.array<!s8i x 5>}, padding = {!u8i}>
+!rec_ULongDouble = !cir.union<"ULongDouble" {data !cir.long_double<!cir.f80>, data !s32i}>
+!rec_UFloats = !cir.union<"UFloats" {data !cir.array<!cir.float x 2>, data !cir.array<!cir.float x 2>}>
+!rec_UOverAligned = !cir.union<"UOverAligned" {data !s32i}, padding = {!cir.array<!u8i x 12>}>
+!rec_UShortStorage = !cir.union<"UShortStorage" {data !s16i, data !cir.array<!s8i x 3>}, padding = {!cir.array<!u8i x 2>}>
+!rec_UByteBlobs = !cir.union<"UByteBlobs" {data !u8i, data !u8i}, padding = {!cir.array<!u8i x 3>}>
+!rec_SWrapsOverAligned = !cir.struct<"SWrapsOverAligned" {data !cir.double, data !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_FF = !cir.struct<"FF" {!cir.float, !cir.float}>
-!rec_RetFF = !cir.struct<"RetFF" {!cir.float, !cir.float}>
+!rec_P = !cir.struct<"P" packed {data !s8i, data !s32i}>
+!rec_Ov = !cir.struct<"Ov" padded {data !s32i, data !cir.array<!u8i x 12>}>
+!rec_E = !cir.struct<"E" padded {data !u8i}>
+!rec_FF = !cir.struct<"FF" {data !cir.float, data !cir.float}>
+!rec_RetFF = !cir.struct<"RetFF" {data !cir.float, data !cir.float}>
 
 module attributes {
   dlti.dl_spec = #dlti.dl_spec<
@@ -138,7 +138,7 @@ module attributes {
 
   // A three-float struct coerces to a record with a vector field; the
   // unmappable field propagates out as NYI too.
-  cir.func @take_f3(%arg0: !cir.struct<"F3" {!cir.float, !cir.float, !cir.float}>) {
+  cir.func @take_f3(%arg0: !cir.struct<"F3" {data !cir.float, data !cir.float, data !cir.float}>) {
     cir.return
   }
 
diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-bitint.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-bitint.cir
index 13c81882e9d0b..0d020aabeb7ec 100644
--- a/clang/test/CIR/Transforms/abi-lowering/x86_64-bitint.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-bitint.cir
@@ -15,11 +15,11 @@
 !b127i = !cir.int<s, 127, bitint>
 !b128i = !cir.int<s, 128, bitint>
 !s8i = !cir.int<s, 8>
-!rec_S = !cir.struct<"S" {!b17i}>
-!rec_P = !cir.struct<"P" {!b65i}>
-!rec_W = !cir.struct<"W" {!b128i}>
-!rec_O = !cir.struct<"O" {!b128i, !s8i}>
-!rec_Arr = !cir.struct<"Arr" {!cir.array<!b65i x 2>}>
+!rec_S = !cir.struct<"S" {data !b17i}>
+!rec_P = !cir.struct<"P" {data !b65i}>
+!rec_W = !cir.struct<"W" {data !b128i}>
+!rec_O = !cir.struct<"O" {data !b128i, data !s8i}>
+!rec_Arr = !cir.struct<"Arr" {data !cir.array<!b65i x 2>}>
 
 module attributes {
   dlti.dl_spec = #dlti.dl_spec<
diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-get-global.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-get-global.cir
index a9b257fccaf7f..3234bf7f9ac7b 100644
--- a/clang/test/CIR/Transforms/abi-lowering/x86_64-get-global.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-get-global.cir
@@ -3,8 +3,8 @@
 !s8i = !cir.int<s, 8>
 !s32i = !cir.int<s, 32>
 !s64i = !cir.int<s, 64>
-!rec_Big = !cir.struct<"Big" {!s64i, !s64i, !s64i, !s64i}>
-!rec_Pair2 = !cir.struct<"Pair2" {!s32i, !s32i}>
+!rec_Big = !cir.struct<"Big" {data !s64i, data !s64i, data !s64i, data !s64i}>
+!rec_Pair2 = !cir.struct<"Pair2" {data !s32i, data !s32i}>
 !rec_E0 = !cir.struct<"E0" {}>
 
 module attributes {
diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-int-nyi.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-int-nyi.cir
index ebda399c0d081..683551cafcb36 100644
--- a/clang/test/CIR/Transforms/abi-lowering/x86_64-int-nyi.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-int-nyi.cir
@@ -3,7 +3,7 @@
 !b129i = !cir.int<s, 129, bitint>
 !b200i = !cir.int<s, 200, bitint>
 !i96 = !cir.int<s, 96>
-!rec_D = !cir.struct<"D" {!b200i}>
+!rec_D = !cir.struct<"D" {data !b200i}>
 
 module attributes {
   dlti.dl_spec = #dlti.dl_spec<
diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-struct-direct.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-struct-direct.cir
index 22cd3c68f2149..f857529ee5d07 100644
--- a/clang/test/CIR/Transforms/abi-lowering/x86_64-struct-direct.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-struct-direct.cir
@@ -7,12 +7,12 @@
 !s32i = !cir.int<s, 32>
 !s64i = !cir.int<s, 64>
 !s128i = !cir.int<s, 128>
-!rec_Pair = !cir.struct<"Pair" {!s32i, !s32i}>
-!rec_Two = !cir.struct<"Two" {!s64i, !s64i}>
-!rec_Mixed = !cir.struct<"Mixed" {!s8i, !s32i}>
-!rec_CharBuf = !cir.struct<"CharBuf" {!cir.array<!s8i x 4>}>
-!rec_Char1 = !cir.struct<"Char1" {!cir.array<!s8i x 1>}>
-!rec_S1 = !cir.struct<"S1" {!s128i}>
+!rec_Pair = !cir.struct<"Pair" {data !s32i, data !s32i}>
+!rec_Two = !cir.struct<"Two" {data !s64i, data !s64i}>
+!rec_Mixed = !cir.struct<"Mixed" {data !s8i, data !s32i}>
+!rec_CharBuf = !cir.struct<"CharBuf" {data !cir.array<!s8i x 4>}>
+!rec_Char1 = !cir.struct<"Char1" {data !cir.array<!s8i x 1>}>
+!rec_S1 = !cir.struct<"S1" {data !s128i}>
 
 module attributes {
   cir.triple = "x86_64-unknown-linux-gnu",
@@ -120,7 +120,7 @@ module attributes {
 
   // An anonymous struct has no record-layout entry, so it defaults to
   // can-pass-in-registers and coerces like its named counterpart.
-  cir.func @take_anon(%arg0: !cir.struct<{!s32i, !s32i}>) {
+  cir.func @take_anon(%arg0: !cir.struct<{data !s32i, data !s32i}>) {
     cir.return
   }
 
diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-struct-indirect.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-struct-indirect.cir
index ddcd0690e94bb..95609d59b4f35 100644
--- a/clang/test/CIR/Transforms/abi-lowering/x86_64-struct-indirect.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-struct-indirect.cir
@@ -4,9 +4,9 @@
 !s32i = !cir.int<s, 32>
 !s64i = !cir.int<s, 64>
 !s128i = !cir.int<s, 128>
-!rec_Big = !cir.struct<"Big" {!s64i, !s64i, !s64i}>
-!rec_NoRegs = !cir.struct<"NoRegs" {!s32i, !s32i}>
-!rec_Big128 = !cir.struct<"Big128" {!s128i, !s8i}>
+!rec_Big = !cir.struct<"Big" {data !s64i, data !s64i, data !s64i}>
+!rec_NoRegs = !cir.struct<"NoRegs" {data !s32i, data !s32i}>
+!rec_Big128 = !cir.struct<"Big128" {data !s128i, data !s8i}>
 
 module attributes {
   cir.record_layouts = {NoRegs = #cir.record_layout<
diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-union-coerce-shapes.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-union-coerce-shapes.cir
index e7cc09d739de1..32ff2eec40dc5 100644
--- a/clang/test/CIR/Transforms/abi-lowering/x86_64-union-coerce-shapes.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-union-coerce-shapes.cir
@@ -8,10 +8,10 @@
 !s32i = !cir.int<s, 32>
 !s64i = !cir.int<s, 64>
 !s128i = !cir.int<s, 128>
-!rec_U128 = !cir.union<"U128" {!s128i, !cir.array<!s8i x 16>}>
-!rec_UMixed = !cir.union<"UMixed" {!cir.array<!cir.double x 2>, !s64i}>
-!rec_UD2 = !cir.union<"UD2" {!cir.array<!cir.double x 2>}>
-!rec_U12 = !cir.union<"U12" {!cir.array<!s8i x 12>, !s32i}, padding = {!cir.array<!u8i x 8>}>
+!rec_U128 = !cir.union<"U128" {data !s128i, data !cir.array<!s8i x 16>}>
+!rec_UMixed = !cir.union<"UMixed" {data !cir.array<!cir.double x 2>, data !s64i}>
+!rec_UD2 = !cir.union<"UD2" {data !cir.array<!cir.double x 2>}>
+!rec_U12 = !cir.union<"U12" {data !cir.array<!s8i x 12>, data !s32i}, padding = {!cir.array<!u8i x 8>}>
 !rec_UEmpty16 = !cir.union<"UEmpty16" {}, padding = {!cir.array<!u8i x 16>}>
 
 module attributes {
diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-union.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-union.cir
index cad6cfd37d7a4..da0d9e20529a9 100644
--- a/clang/test/CIR/Transforms/abi-lowering/x86_64-union.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-union.cir
@@ -7,20 +7,20 @@
 !s32i = !cir.int<s, 32>
 !s64i = !cir.int<s, 64>
 !u8i = !cir.int<u, 8>
-!rec_UIntFloat = !cir.union<"UIntFloat" {!s32i, !cir.float}>
-!rec_UFloatInt = !cir.union<"UFloatInt" {!cir.float, !s32i}>
-!rec_ULongDouble = !cir.union<"ULongDouble" {!s64i, !cir.double}>
-!rec_UDoubleLong = !cir.union<"UDoubleLong" {!cir.double, !s64i}>
-!rec_UFloats = !cir.union<"UFloats" {!cir.float, !cir.float}>
-!rec_UThree = !cir.union<"UThree" {!cir.array<!s8i x 3>}>
-!rec_UNarrowStorage = !cir.union<"UNarrowStorage" {!s32i, !cir.array<!s8i x 8>}, padding = {!cir.array<!u8i x 4>}>
-!rec_UTwoEightbytes = !cir.union<"UTwoEightbytes" {!s64i, !cir.array<!s8i x 16>}, padding = {!cir.array<!u8i x 8>}>
-!rec_UBig = !cir.union<"UBig" {!cir.array<!s8i x 32>}>
-!rec_UBigOverAligned = !cir.union<"UBigOverAligned" {!cir.array<!s8i x 32>}>
-!rec_SOverAligned = !cir.struct<"SOverAligned" {!cir.array<!s8i x 32>}>
+!rec_UIntFloat = !cir.union<"UIntFloat" {data !s32i, data !cir.float}>
+!rec_UFloatInt = !cir.union<"UFloatInt" {data !cir.float, data !s32i}>
+!rec_ULongDouble = !cir.union<"ULongDouble" {data !s64i, data !cir.double}>
+!rec_UDoubleLong = !cir.union<"UDoubleLong" {data !cir.double, data !s64i}>
+!rec_UFloats = !cir.union<"UFloats" {data !cir.float, data !cir.float}>
+!rec_UThree = !cir.union<"UThree" {data !cir.array<!s8i x 3>}>
+!rec_UNarrowStorage = !cir.union<"UNarrowStorage" {data !s32i, data !cir.array<!s8i x 8>}, padding = {!cir.array<!u8i x 4>}>
+!rec_UTwoEightbytes = !cir.union<"UTwoEightbytes" {data !s64i, data !cir.array<!s8i x 16>}, padding = {!cir.array<!u8i x 8>}>
+!rec_UBig = !cir.union<"UBig" {data !cir.array<!s8i x 32>}>
+!rec_UBigOverAligned = !cir.union<"UBigOverAligned" {data !cir.array<!s8i x 32>}>
+!rec_SOverAligned = !cir.struct<"SOverAligned" {data !cir.array<!s8i x 32>}>
 !rec_UEmpty = !cir.union<"UEmpty" {}, padding = {!u8i}>
-!rec_UNoRegs = !cir.union<"UNoRegs" {!s32i, !cir.float}>
-!rec_SWithUnion = !cir.struct<"SWithUnion" {!rec_UIntFloat, !s32i}>
+!rec_UNoRegs = !cir.union<"UNoRegs" {data !s32i, data !cir.float}>
+!rec_SWithUnion = !cir.struct<"SWithUnion" {data !rec_UIntFloat, data !s32i}>
 
 module attributes {
   cir.triple = "x86_64-unknown-linux-gnu",
diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-variadic-call.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-variadic-call.cir
index 7a531604073fb..d40370b11642a 100644
--- a/clang/test/CIR/Transforms/abi-lowering/x86_64-variadic-call.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-variadic-call.cir
@@ -6,9 +6,9 @@
 !s8i = !cir.int<s, 8>
 !s32i = !cir.int<s, 32>
 !s64i = !cir.int<s, 64>
-!rec_Pair = !cir.struct<"Pair" {!s32i, !s32i}>
-!rec_Two = !cir.struct<"Two" {!s64i, !s64i}>
-!rec_Big = !cir.struct<"Big" {!s64i, !s64i, !s64i}>
+!rec_Pair = !cir.struct<"Pair" {data !s32i, data !s32i}>
+!rec_Two = !cir.struct<"Two" {data !s64i, data !s64i}>
+!rec_Big = !cir.struct<"Big" {data !s64i, data !s64i, data !s64i}>
 !rec_E0 = !cir.struct<"E0" {}>
 
 module attributes {
diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-variadic-nyi.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-variadic-nyi.cir
index aae2cf94c8125..5a94ec0b20a41 100644
--- a/clang/test/CIR/Transforms/abi-lowering/x86_64-variadic-nyi.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-variadic-nyi.cir
@@ -3,7 +3,7 @@
 
 !s8i = !cir.int<s, 8>
 !s32i = !cir.int<s, 32>
-!rec_Pair = !cir.struct<"Pair" {!s32i, !s32i}>
+!rec_Pair = !cir.struct<"Pair" {data !s32i, data !s32i}>
 
 module attributes {
   cir.triple = "x86_64-unknown-linux-gnu",
@@ -30,7 +30,7 @@ module attributes {
 !s8i = !cir.int<s, 8>
 !s32i = !cir.int<s, 32>
 !s64i = !cir.int<s, 64>
-!rec_Big = !cir.struct<"Big" {!s64i, !s64i, !s64i}>
+!rec_Big = !cir.struct<"Big" {data !s64i, data !s64i, data !s64i}>
 
 module attributes {
   cir.triple = "x86_64-unknown-linux-gnu",
@@ -56,7 +56,7 @@ module attributes {
 
 !s8i = !cir.int<s, 8>
 !s64i = !cir.int<s, 64>
-!rec_Big = !cir.struct<"Big" {!s64i, !s64i, !s64i}>
+!rec_Big = !cir.struct<"Big" {data !s64i, data !s64i, data !s64i}>
 
 module attributes {
   cir.triple = "x86_64-unknown-linux-gnu",
@@ -82,7 +82,7 @@ module attributes {
 
 !s8i = !cir.int<s, 8>
 !s32i = !cir.int<s, 32>
-!rec_FF = !cir.struct<"FF" {!cir.float, !cir.float}>
+!rec_FF = !cir.struct<"FF" {data !cir.float, data !cir.float}>
 
 module attributes {
   cir.triple = "x86_64-unknown-linux-gnu",
@@ -104,7 +104,7 @@ module attributes {
     cir.return
   }
 
-  // CHECK: error: 'cir.call' op x86_64 calling-convention lowering not yet implemented for the ABI coercion of type '!cir.struct<"FF" {!cir.float, !cir.float}>'
+  // CHECK: error: 'cir.call' op x86_64 calling-convention lowering not yet implemented for the ABI coercion of type '!cir.struct<"FF" {data !cir.float, data !cir.float}>'
 }
 
 // -----
@@ -139,7 +139,7 @@ module attributes {
 !s32i = !cir.int<s, 32>
 !s64i = !cir.int<s, 64>
 !void = !cir.void
-!rec_Big = !cir.struct<"Big" {!s64i, !s64i, !s64i}>
+!rec_Big = !cir.struct<"Big" {data !s64i, data !s64i, data !s64i}>
 
 module attributes {
   cir.triple = "x86_64-unknown-linux-gnu",
@@ -177,7 +177,7 @@ module attributes {
 
 !s32i = !cir.int<s, 32>
 !void = !cir.void
-!rec_Pair = !cir.struct<"Pair" {!s32i, !s32i}>
+!rec_Pair = !cir.struct<"Pair" {data !s32i, data !s32i}>
 
 module attributes {
   cir.triple = "x86_64-unknown-linux-gnu",
@@ -215,7 +215,7 @@ module attributes {
 // -----
 
 !s32i = !cir.int<s, 32>
-!rec_Pair = !cir.struct<"Pair" {!s32i, !s32i}>
+!rec_Pair = !cir.struct<"Pair" {data !s32i, data !s32i}>
 
 module attributes {
   cir.triple = "x86_64-unknown-linux-gnu",
diff --git a/clang/test/CIR/Transforms/canonicalize-cleanup-scope.cir b/clang/test/CIR/Transforms/canonicalize-cleanup-scope.cir
index e32cd49c5ead7..67be879d73b9d 100644
--- a/clang/test/CIR/Transforms/canonicalize-cleanup-scope.cir
+++ b/clang/test/CIR/Transforms/canonicalize-cleanup-scope.cir
@@ -1,7 +1,7 @@
 // RUN: cir-opt %s -cir-canonicalize -o - | FileCheck %s
 
 !s32i = !cir.int<s, 32>
-!rec_S = !cir.struct<"S" {!s32i}>
+!rec_S = !cir.struct<"S" {data !s32i}>
 
 module {
 
diff --git a/clang/test/CIR/Transforms/cxx-abi-lowering-attrs.cir b/clang/test/CIR/Transforms/cxx-abi-lowering-attrs.cir
index 2faf2acf421d4..ebc54e9875dea 100644
--- a/clang/test/CIR/Transforms/cxx-abi-lowering-attrs.cir
+++ b/clang/test/CIR/Transforms/cxx-abi-lowering-attrs.cir
@@ -3,9 +3,9 @@
 
 !u8i = !cir.int<u, 8>
 !s32i = !cir.int<s, 32>
-!rec_Other = !cir.struct<"Other" padded {!u8i}>
-!rec_Struct = !cir.struct<"Struct" {!cir.data_member<!s32i in !rec_Other>}>
-// CHECK: !rec_Struct = !cir.struct<"Struct" {!s64i}>
+!rec_Other = !cir.struct<"Other" padded {data !u8i}>
+!rec_Struct = !cir.struct<"Struct" {data !cir.data_member<!s32i in !rec_Other>}>
+// CHECK: !rec_Struct = !cir.struct<"Struct" {data !s64i}>
 
 !dmp = !cir.data_member<!s32i in !rec_Other>
 !dmp_ptr = !cir.ptr<!cir.data_member<!s32i in !rec_Other>>
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..25e1365b59da1 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" padded {data !u8i}>
 
 module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
 
@@ -17,7 +17,7 @@ cir.global constant linkonce_odr @_ZTI1E
     = #cir.typeinfo<{
         #cir.global_view<@_ZTVN10__cxxabiv117__class_type_infoE, [2 : i32]>
             : !cir.ptr<!u8i>}>
-    : !cir.struct<{!cir.ptr<!u8i>}>
+    : !cir.struct<{data !cir.ptr<!u8i>}>
 
 cir.func private @_Z8mayThrowv()
 cir.func private @_ZN1EC1ERKS_(!cir.ptr<!rec_E>, !cir.ptr<!rec_E>)
@@ -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" padded {data !u8i}>
 
 module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
 
@@ -75,7 +75,7 @@ cir.global constant linkonce_odr @_ZTI1E
     = #cir.typeinfo<{
         #cir.global_view<@_ZTVN10__cxxabiv117__class_type_infoE, [2 : i32]>
             : !cir.ptr<!u8i>}>
-    : !cir.struct<{!cir.ptr<!u8i>}>
+    : !cir.struct<{data !cir.ptr<!u8i>}>
 
 cir.func private @_Z8mayThrowv()
 
@@ -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" padded {data !u8i}>
 
 module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
 
@@ -128,7 +128,7 @@ cir.global constant linkonce_odr @_ZTI1E
     = #cir.typeinfo<{
         #cir.global_view<@_ZTVN10__cxxabiv117__class_type_infoE, [2 : i32]>
             : !cir.ptr<!u8i>}>
-    : !cir.struct<{!cir.ptr<!u8i>}>
+    : !cir.struct<{data !cir.ptr<!u8i>}>
 
 cir.func private @_Z8mayThrowv()
 cir.func private @_ZN1EC1ERKS_(!cir.ptr<!rec_E>, !cir.ptr<!rec_E>)
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..81c17ae8f64c3 100644
--- a/clang/test/CIR/Transforms/eh-abi-lowering-construct-catch.cir
+++ b/clang/test/CIR/Transforms/eh-abi-lowering-construct-catch.cir
@@ -22,8 +22,8 @@
 !u32i = !cir.int<u, 32>
 !u8i = !cir.int<u, 8>
 !void = !cir.void
-!rec_E = !cir.struct<"E" padded {!u8i}>
-!rec_anon_struct = !cir.struct<{!cir.ptr<!u8i>, !cir.ptr<!u8i>}>
+!rec_E = !cir.struct<"E" padded {data !u8i}>
+!rec_anon_struct = !cir.struct<{data !cir.ptr<!u8i>, data !cir.ptr<!u8i>}>
 
 module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
 
diff --git a/clang/test/CIR/Transforms/eh-abi-lowering-itanium.cir b/clang/test/CIR/Transforms/eh-abi-lowering-itanium.cir
index 6e557227c1de7..06bae1fbdc5bc 100644
--- a/clang/test/CIR/Transforms/eh-abi-lowering-itanium.cir
+++ b/clang/test/CIR/Transforms/eh-abi-lowering-itanium.cir
@@ -9,8 +9,8 @@
 !u32i = !cir.int<u, 32>
 !u8i = !cir.int<u, 8>
 !void = !cir.void
-!rec_SomeClass = !cir.struct<"SomeClass" {!s32i}>
-!rec_exception = !cir.struct<"std::exception" {!s32i}>
+!rec_SomeClass = !cir.struct<"SomeClass" {data !s32i}>
+!rec_exception = !cir.struct<"std::exception" {data !s32i}>
 
 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..5fc9f4fdbe42f 100644
--- a/clang/test/CIR/Transforms/flatten-cleanup-scope-eh.cir
+++ b/clang/test/CIR/Transforms/flatten-cleanup-scope-eh.cir
@@ -3,8 +3,8 @@
 
 !s32i = !cir.int<s, 32>
 !u8i = !cir.int<u, 8>
-!rec_SomeClass = !cir.struct<"SomeClass" {!s32i}>
-!rec_NonTrivial = !cir.struct<"NonTrivial" padded {!u8i}>
+!rec_SomeClass = !cir.struct<"SomeClass" {data !s32i}>
+!rec_NonTrivial = !cir.struct<"NonTrivial" padded {data !u8i}>
 #false = #cir.bool<false> : !cir.bool
 #true = #cir.bool<true> : !cir.bool
 
diff --git a/clang/test/CIR/Transforms/flatten-cleanup-scope-multi-exit.cir b/clang/test/CIR/Transforms/flatten-cleanup-scope-multi-exit.cir
index c5bfed3e058aa..62413b2c9c1ce 100644
--- a/clang/test/CIR/Transforms/flatten-cleanup-scope-multi-exit.cir
+++ b/clang/test/CIR/Transforms/flatten-cleanup-scope-multi-exit.cir
@@ -2,7 +2,7 @@
 // RUN: FileCheck --input-file=%t.cir %s
 
 !s32i = !cir.int<s, 32>
-!rec_SomeClass = !cir.struct<"SomeClass" {!s32i}>
+!rec_SomeClass = !cir.struct<"SomeClass" {data !s32i}>
 
 // Test that a cleanup scope with break that branches through cleanup is
 // properly flattened with a destination slot and switch dispatch.
diff --git a/clang/test/CIR/Transforms/flatten-cleanup-scope-simple.cir b/clang/test/CIR/Transforms/flatten-cleanup-scope-simple.cir
index 7209c04c44578..9ffe3018bd7f8 100644
--- a/clang/test/CIR/Transforms/flatten-cleanup-scope-simple.cir
+++ b/clang/test/CIR/Transforms/flatten-cleanup-scope-simple.cir
@@ -2,7 +2,7 @@
 // RUN: FileCheck --input-file=%t.cir %s
 
 !s32i = !cir.int<s, 32>
-!rec_SomeClass = !cir.struct<"SomeClass" {!s32i}>
+!rec_SomeClass = !cir.struct<"SomeClass" {data !s32i}>
 
 // Test basic cleanup scope flattening with normal cleanup.
 // The body yields to cleanup, cleanup yields to continue.
diff --git a/clang/test/CIR/Transforms/flatten-loop-cleanup.cir b/clang/test/CIR/Transforms/flatten-loop-cleanup.cir
index 40adb54633520..b0dded0f931a2 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" padded {data !u8i}>
 
 module {
   cir.func private @ctor(!cir.ptr<!rec_S>)
diff --git a/clang/test/CIR/Transforms/flatten-preserve-attrs.cir b/clang/test/CIR/Transforms/flatten-preserve-attrs.cir
index f91c3be99583f..c3333386ce15c 100644
--- a/clang/test/CIR/Transforms/flatten-preserve-attrs.cir
+++ b/clang/test/CIR/Transforms/flatten-preserve-attrs.cir
@@ -2,7 +2,7 @@
 // RUN: FileCheck --input-file=%t.cir %s
 
 !s32i = !cir.int<s, 32>
-!rec_SomeClass = !cir.struct<"SomeClass" {!s32i}>
+!rec_SomeClass = !cir.struct<"SomeClass" {data !s32i}>
 
 // Test that argument attributes on a cir.call are preserved on the resulting
 // cir.try_call after flattening.
diff --git a/clang/test/CIR/Transforms/flatten-throwing-in-cleanup.cir b/clang/test/CIR/Transforms/flatten-throwing-in-cleanup.cir
index a563300f8c768..e2031b4db3692 100644
--- a/clang/test/CIR/Transforms/flatten-throwing-in-cleanup.cir
+++ b/clang/test/CIR/Transforms/flatten-throwing-in-cleanup.cir
@@ -3,7 +3,7 @@
 
 !s32i = !cir.int<s, 32>
 !u8i = !cir.int<u, 8>
-!rec_SomeClass = !cir.struct<"SomeClass" {!s32i}>
+!rec_SomeClass = !cir.struct<"SomeClass" {data !s32i}>
 
 // Test that a throwing call in an EH cleanup region is replaced with
 // cir.try_call that unwinds to a terminate block.
diff --git a/clang/test/CIR/Transforms/flatten-try-op.cir b/clang/test/CIR/Transforms/flatten-try-op.cir
index 8c1127c35390b..e39fdfb1acdf9 100644
--- a/clang/test/CIR/Transforms/flatten-try-op.cir
+++ b/clang/test/CIR/Transforms/flatten-try-op.cir
@@ -4,8 +4,8 @@
 !s32i = !cir.int<s, 32>
 !u8i = !cir.int<u, 8>
 !void = !cir.void
-!rec_SomeClass = !cir.struct<"SomeClass" {!s32i}>
-!rec_exception = !cir.struct<"std::exception" {!s32i}>
+!rec_SomeClass = !cir.struct<"SomeClass" {data !s32i}>
+!rec_exception = !cir.struct<"std::exception" {data !s32i}>
 
 // Test a simple try with no handlers and no throwing calls.
 cir.func @test_try_no_handlers() {
diff --git a/clang/test/CIR/Transforms/pure-ptr-arithmetic.cir b/clang/test/CIR/Transforms/pure-ptr-arithmetic.cir
index 57ff10a0f304f..c2fd5e4587f6f 100644
--- a/clang/test/CIR/Transforms/pure-ptr-arithmetic.cir
+++ b/clang/test/CIR/Transforms/pure-ptr-arithmetic.cir
@@ -7,9 +7,9 @@
 !u8i = !cir.int<u, 8>
 !void = !cir.void
 
-!rec_S = !cir.struct<"S" {!s32i, !s32i}>
-!rec_Base = !cir.struct<"Base" {!u8i}>
-!rec_Derived = !cir.struct<"Derived" {!rec_Base, !s32i}>
+!rec_S = !cir.struct<"S" {data !s32i, data !s32i}>
+!rec_Base = !cir.struct<"Base" {data !u8i}>
+!rec_Derived = !cir.struct<"Derived" {data !rec_Base, data !s32i}>
 
 module {
 
diff --git a/clang/unittests/CIR/CallOpTest.cpp b/clang/unittests/CIR/CallOpTest.cpp
index 5b2e4780b1496..96e2e000f09ff 100644
--- a/clang/unittests/CIR/CallOpTest.cpp
+++ b/clang/unittests/CIR/CallOpTest.cpp
@@ -36,7 +36,7 @@ class CIRCallOpTest : public ::testing::Test {
 // call with no callee symbol at all, and a cir.try_call to the marked callee.
 constexpr const char *moduleText = R"CIR(
 !s32i = !cir.int<s, 32>
-!rec_S = !cir.struct<"S" {!s32i}>
+!rec_S = !cir.struct<"S" {data !s32i}>
 
 module {
   cir.func @marked() func_info<#cir.cxx_ctor<!rec_S, default>> {

>From 2d129d1ac0037dfdf9383d8a30419a72adfa5278 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Wed, 12 Aug 2026 14:43:18 -0700
Subject: [PATCH 6/6] [CIR] Test the atomic wrapper's rejection at the pass
 level

CIRGen wraps an atomic narrower than its lock-free size in an anonymous padded
record whose trailing member is pad.  The x86_64 classifier rejects padded
records.  That rejection is pass behavior, so it belongs with the other cases
in x86_64-aggregate-nyi.cir rather than in a driver test of its own.

It also covers the anonymous record path, which every case in that file had
missed.

Assisted-by: Cursor / claude-opus-5
---
 .../CodeGen/call-conv-lowering-x86_64-atomic-nyi.c    | 10 ----------
 .../Transforms/abi-lowering/x86_64-aggregate-nyi.cir  | 11 +++++++++++
 2 files changed, 11 insertions(+), 10 deletions(-)
 delete mode 100644 clang/test/CIR/CodeGen/call-conv-lowering-x86_64-atomic-nyi.c

diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-atomic-nyi.c b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-atomic-nyi.c
deleted file mode 100644
index c6b81c945be2e..0000000000000
--- a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-atomic-nyi.c
+++ /dev/null
@@ -1,10 +0,0 @@
-// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir \
-// RUN:     -clangir-enable-call-conv-lowering -emit-cir %s -o %t.cir 2>&1 \
-// RUN:   | FileCheck %s
-
-struct S1 { short x, y, z; };
-
-// The wrapper that inflates the value to the atomic size holds a pad member,
-// and the classifier does not tell padding from data yet.
-// CHECK: error: 'cir.func' op x86_64 calling-convention lowering not yet implemented for type '!cir.struct<padded {{.*}}pad !cir.array<!cir.int<s, 8> x 2>}>'
-void take_atomic(_Atomic struct S1 s) {}
diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir
index 849458e7ab8c0..7daf7e49cf78a 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
@@ -17,6 +17,8 @@
 !rec_E = !cir.struct<"E" padded {data !u8i}>
 !rec_FF = !cir.struct<"FF" {data !cir.float, data !cir.float}>
 !rec_RetFF = !cir.struct<"RetFF" {data !cir.float, data !cir.float}>
+!rec_S1 = !cir.struct<"S1" {data !s16i, data !s16i, data !s16i}>
+!rec_AtomicWrapper = !cir.struct<padded {data !rec_S1, pad !cir.array<!s8i x 2>}>
 
 module attributes {
   dlti.dl_spec = #dlti.dl_spec<
@@ -121,6 +123,15 @@ module attributes {
 
   // CHECK: not yet implemented for type '!cir.struct<"E" padded
 
+  // CIRGen wraps an atomic whose value is narrower than its lock-free size in
+  // an anonymous record with a trailing pad member, which the padded check
+  // rejects for the same reason.
+  cir.func @take_atomic_wrapper(%arg0: !rec_AtomicWrapper) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for type '!cir.struct<padded {{.*}}pad !cir.array<!cir.int<s, 8> x 2>}>'
+
   // An all-float struct classifies to an SSE vector coerce this bridge does
   // not represent, so it is reported NYI rather than passed unchanged.
   cir.func @take_ff(%arg0: !rec_FF) {



More information about the cfe-commits mailing list