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

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


>From 963e51ba1b0e371ceef43111d1388b3bf02b5812 Mon Sep 17 00:00:00 2001
From: Adam Smith <[email protected]>
Date: Sun, 9 Aug 2026 09:35:09 -0700
Subject: [PATCH] [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());
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to