[clang] [CIR] Let a record type mark what each member holds (PR #215174)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Aug 9 20:59:27 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clangir
Author: Adam Smith (adams381)
<details>
<summary>Changes</summary>
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
---
Patch is 49.93 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/215174.diff
8 Files Affected:
- (modified) clang/include/clang/CIR/Dialect/IR/CIRTypes.h (+16-1)
- (modified) clang/include/clang/CIR/Dialect/IR/CIRTypes.td (+72-15)
- (modified) clang/include/clang/CIR/Dialect/IR/CIRTypesDetails.h (+66-31)
- (modified) clang/lib/CIR/Dialect/IR/CIRTypes.cpp (+148-30)
- (added) clang/test/CIR/IR/invalid-record-member-kinds.cir (+41)
- (modified) clang/test/CIR/IR/struct.cir (+57-2)
- (modified) clang/unittests/CIR/CMakeLists.txt (+1)
- (added) clang/unittests/CIR/RecordMemberKindTest.cpp (+169)
``````````diff
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/cla...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/215174
More information about the cfe-commits
mailing list