[clang] [CIR] Mark bit-field access units as their own member kinds (PR #216864)

Adam Smith via cfe-commits cfe-commits at lists.llvm.org
Wed Aug 19 09:59:26 PDT 2026


================
@@ -624,29 +624,42 @@ def CIR_VoidType : CIR_Type<"Void", "void"> {
 //===----------------------------------------------------------------------===//
 // RecordMemberKind
 //
-// What a record member holds, for members that do not hold source data.
+// What a record member holds, and whether its extent is a declared one.
 //===----------------------------------------------------------------------===//
 
 def CIR_RecordMemberKind : CIR_I32EnumAttr<
     "RecordMemberKind", "what a record member holds", [
   I32EnumAttrCase<"Data", 0, "data">,
   I32EnumAttrCase<"Pad", 1, "pad">,
-  I32EnumAttrCase<"Empty", 2, "empty">
+  I32EnumAttrCase<"Empty", 2, "empty">,
----------------
adams381 wrote:

I went looking for the example you requested, and tried everything I would think of: plain fields, arrays of empty records, empty base subobjects, `[[no_unique_address]]`, over-aligned empty structs, zero-length arrays.  Every `empty` member CIRGen emits is either zero-sized or gets its bytes from unnamed bit-fields.  So the pass can work it out without the `empty_bitfield` mark: an `empty` member that occupies bytes has to stay in the field list, one that occupies nothing can be dropped.  That rule gives the right answer on every shape I tried, so `empty_bitfield` is not carrying its weight and I will drop it.

However, `bitfield` is a different story.  Consider this pair:

```c
struct A { long long x : 32; long long y; };  // classic: (i64, i64)
struct B { unsigned  x;      long long y; };  // classic: (i32, i64)
```

```mlir
!cir.struct<"A" {bitfield !u32i, data !s64i}>
!cir.struct<"B" {data     !u32i, data !s64i}>
```

Same size, same member types, same offsets, no padding members in either.  The mark is the only difference in the whole type, and classic passes the first register differently.

There is one more wrinkle.  My next planned PR is for zero-width bit-fields and is a sibling of this PR.  I can't find a way to work around needing a mark in this example:

```c
struct ZA { int x; int a[0];     } __attribute__((aligned(16)));  // classic: i32
struct ZB { int x; unsigned : 0; } __attribute__((aligned(16)));  // classic: i64
```

`ZB`'s whole module is byte-identical to `struct { int x; } __attribute__((aligned(16)))`, because CIRGen emits nothing at all for the zero-width bit-field.  `ZA` is `{data !s32i, empty !cir.array<!s32i x 0>, pad ...}`.  So the obvious spelling for `ZB`, an `empty` member at zero size, is already taken by `ZA`, and the two have to come out opposite ways.

My plan there is a zero-size member carrying the declared type, `!cir.array<T x 0>`, under a mark of its own.  It cannot be `empty`, for the reason above.  It cannot be `bitfield` either, because by definition `holdsDataForABI` is true for that and `struct { int : 0; }` would stop being empty for the ABI and take a register where classic passes nothing.

I'm convinced now that `empty_bitfield` can go, but I think we still need five marks:

1) data
2) pad
3) empty
4) bitfield
5) zero_width_bitfield

That would allow the pair above to be emitted as

```mlir
// already emitted today
!cir.struct<"ZA" {data !s32i, empty !cir.array<!s32i x 0>, pad !cir.array<!u8i x 12>}>
// proposed zero_width_bitfield
!cir.struct<"ZB" {data !s32i, zero_width_bitfield !cir.array<!u32i x 0>, pad !cir.array<!u8i x 12>}>
```

https://github.com/llvm/llvm-project/pull/216864


More information about the cfe-commits mailing list