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

>From e192df0e5ebd24bed1a98fdca4a8ee9c10adc982 Mon Sep 17 00:00:00 2001
From: Adam Smith <[email protected]>
Date: Thu, 27 Aug 2026 14:30:15 -0700
Subject: [PATCH 1/2] [CIR] Classify a named bit-field access unit as a
 bit-field on x86_64

mapCIRType built the ABI FieldInfo for a bit-field access unit with IsBitField
set only for an unnamed one, so a named unit reached the classifier as an
ordinary field.  We now pass IsBitField with the unit's width to be able to
lower named bit-fields correctly.

Assisted-by: Cursor / claude-opus-5
---
 .../Transforms/CallConvLoweringPass.cpp       |  7 +++-
 .../call-conv-lowering-x86_64-packed.c        | 33 +++++++++++++++++++
 2 files changed, 39 insertions(+), 1 deletion(-)

diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp 
b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
index 11c2a66dba7b5..2141836967eb7 100644
--- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
@@ -429,10 +429,15 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type,
           assert((!isUnnamedUnit || !memberIsEmptyRecord(countedTy)) &&
                  "an empty-for-ABI member must not reach the classifier as an "
                  "unnamed bit-field");
+          // A named access unit is a bit-field to the classifier as well.  Its
+          // eightbyte classes come from the bits it spans, and the rule that
+          // sends a record with an unaligned field to memory does not apply to
+          // a bit-field, which may sit at any offset.
+          bool isAccessUnit = isUnnamedUnit || cir::isBitFieldAccessUnit(kind);
           fields.push_back(llvm::abi::FieldInfo(
               mapCIRType(countedTy, typeMapper, dl, modOp),
               recTy.getElementOffset(dl, idx) * 8,
-              /*IsBitField=*/isUnnamedUnit, isUnnamedUnit ? widthBits : 0,
+              /*IsBitField=*/isAccessUnit, isAccessUnit ? widthBits : 0,
               /*IsUnnamedBitField=*/isUnnamedUnit));
         }
 
diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-packed.c 
b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-packed.c
index 0c654754b135f..cc856cf91a8ef 100644
--- a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-packed.c
+++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-packed.c
@@ -6,6 +6,9 @@
 // RUN: FileCheck --check-prefixes=LLVM,LLVM-OGCG --input-file=%t.ll %s
 
 typedef struct __attribute__((packed)) { char c; int i; } CharInt;
+typedef struct __attribute__((packed)) { char c; int i : 32; } CharIntBF;
+typedef struct __attribute__((packed)) { char c; unsigned long long w : 64; } 
CharWideBF;
+typedef struct __attribute__((packed)) { char c; int i : 32; char pad[3]; 
double d; } BFDouble;
 typedef struct __attribute__((packed)) { int a; int b; char c; } Nine;
 typedef struct __attribute__((packed)) { short a; short b; char c; } FiveShort;
 typedef struct __attribute__((packed)) { double d; char c; } DoubleChar;
@@ -24,6 +27,9 @@ typedef struct { char c; int i; } PragmaPacked;
 #pragma pack()
 
 // CIR-DAG: !rec_CharInt = !cir.struct<"CharInt" packed {data !s8i, data 
!s32i}>
+// CIR-DAG: !rec_CharIntBF = !cir.struct<"CharIntBF" packed {data !s8i, 
bitfield !u32i}>
+// CIR-DAG: !rec_CharWideBF = !cir.struct<"CharWideBF" packed {data !s8i, 
bitfield !u64i}>
+// CIR-DAG: !rec_BFDouble = !cir.struct<"BFDouble" packed {data !s8i, bitfield 
!u32i, data !cir.array<!s8i x 3>, data !cir.double}>
 // CIR-DAG: !rec_Nine = !cir.struct<"Nine" packed {data !s32i, data !s32i, 
data !s8i}>
 // CIR-DAG: !rec_FiveShort = !cir.struct<"FiveShort" packed {data !s16i, data 
!s16i, data !s8i}>
 // CIR-DAG: !rec_DoubleChar = !cir.struct<"DoubleChar" packed {data 
!cir.double, data !s8i}>
@@ -40,6 +46,7 @@ typedef struct { char c; int i; } PragmaPacked;
 
 // Anonymous coercion records are numbered in print order, so capture them.
 // CIR-DAG: ![[I64I8:rec_anon_struct[0-9]*]] = !cir.struct<{data !u64i, data 
!s8i}>
+// CIR-DAG: ![[I64U8:rec_anon_struct[0-9]*]] = !cir.struct<{data !u64i, data 
!u8i}>
 // CIR-DAG: ![[F64I8:rec_anon_struct[0-9]*]] = !cir.struct<{data !cir.double, 
data !s8i}>
 // CIR-DAG: ![[V2F32I8:rec_anon_struct[0-9]*]] = !cir.struct<{data 
!cir.vector<2 x !cir.float>, data !s8i}>
 
@@ -56,6 +63,32 @@ CharInt ret_char_int(int x) { CharInt v = {0, x}; return v; }
 // LLVM-OGCG: define dso_local i32 @take_char_int(ptr noundef 
byval(%struct.CharInt) align 8 %{{.+}})
 // LLVM: define dso_local void @ret_char_int(ptr dead_on_unwind noalias 
writable sret(%struct.CharInt) align 1 %{{.+}}, i32 noundef %{{.+}})
 
+// The same record with the int declared as a bit-field.  A bit-field may sit
+// at any offset, so the rule above does not reach it and the five bytes stay
+// in a register.
+int take_char_int_bf(CharIntBF v) { return v.i; }
+CharIntBF ret_char_int_bf(int x) { CharIntBF v = {0, x}; return v; }
+
+// A unit that crosses the eightbyte boundary is classified on both sides of 
it.
+int take_char_wide_bf(CharWideBF v) { return (int)v.w; }
+CharWideBF ret_char_wide_bf(unsigned long long x) { CharWideBF v = {0, x}; 
return v; }
+
+// CIR: cir.func{{.*}} @take_char_int_bf(%arg0: !cir.int<u, 40>{{.*}}) -> !s32i
+// CIR: cir.func{{.*}} @ret_char_int_bf(%arg0: !s32i {llvm.noundef}{{.*}}) -> 
!cir.int<u, 40>
+// LLVM: define dso_local i32 @take_char_int_bf(i40 %{{.+}})
+// LLVM: define dso_local i40 @ret_char_int_bf(i32 noundef %{{.+}})
+
+// CIR: cir.func{{.*}} @take_char_wide_bf(%arg0: !u64i{{.*}}, %arg1: 
!u8i{{.*}}) -> !s32i
+// CIR: cir.func{{.*}} @ret_char_wide_bf(%arg0: !u64i {llvm.noundef}{{.*}}) -> 
![[I64U8]]
+// LLVM: define dso_local i32 @take_char_wide_bf(i64 %{{.+}}, i8 %{{.+}})
+// LLVM: define dso_local { i64, i8 } @ret_char_wide_bf(i64 noundef %{{.+}})
+
+// The unit decides the low eightbyte while the double decides the high one.
+double take_bf_double(BFDouble v) { return v.d; }
+
+// CIR: cir.func{{.*}} @take_bf_double(%arg0: !u64i{{.*}}, %arg1: 
!cir.double{{.*}}) -> !cir.double
+// LLVM: define dso_local double @take_bf_double(i64 %{{.+}}, double %{{.+}})
+
 // Every member is naturally aligned and only the nine-byte size earns the
 // packed mark, so this one is classified: an eightbyte of ints and a trailing
 // byte.

>From d1c3b1c09f05614ebda9e3d7f05c8d20274af618 Mon Sep 17 00:00:00 2001
From: Adam Smith <[email protected]>
Date: Fri, 28 Aug 2026 09:50:25 -0700
Subject: [PATCH 2/2] [CIR] Test bit-fields sharing a unit and a unit under its
 declared type

A named access unit holds every bit-field declared into it, and its storage
can be narrower than the type those were declared with.  Neither shape was
covered: one record packs three bit-fields into a single six-byte unit, and the
other declares an int bit-field whose unit occupies three bytes.

Assisted-by: Cursor / claude-opus-5
---
 .../call-conv-lowering-x86_64-packed.c        | 24 +++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-packed.c 
b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-packed.c
index cc856cf91a8ef..540e02d1db7f5 100644
--- a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-packed.c
+++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-packed.c
@@ -8,6 +8,8 @@
 typedef struct __attribute__((packed)) { char c; int i; } CharInt;
 typedef struct __attribute__((packed)) { char c; int i : 32; } CharIntBF;
 typedef struct __attribute__((packed)) { char c; unsigned long long w : 64; } 
CharWideBF;
+typedef struct __attribute__((packed)) { char c; int i : 25; int j : 17; int k 
: 4; } CharMultipleBFInt;
+typedef struct __attribute__((packed)) { char c; int i : 19; } 
CharUndersizedIntBF;
 typedef struct __attribute__((packed)) { char c; int i : 32; char pad[3]; 
double d; } BFDouble;
 typedef struct __attribute__((packed)) { int a; int b; char c; } Nine;
 typedef struct __attribute__((packed)) { short a; short b; char c; } FiveShort;
@@ -29,6 +31,8 @@ typedef struct { char c; int i; } PragmaPacked;
 // CIR-DAG: !rec_CharInt = !cir.struct<"CharInt" packed {data !s8i, data 
!s32i}>
 // CIR-DAG: !rec_CharIntBF = !cir.struct<"CharIntBF" packed {data !s8i, 
bitfield !u32i}>
 // CIR-DAG: !rec_CharWideBF = !cir.struct<"CharWideBF" packed {data !s8i, 
bitfield !u64i}>
+// CIR-DAG: !rec_CharMultipleBFInt = !cir.struct<"CharMultipleBFInt" {data 
!s8i, bitfield !cir.array<!u8i x 6>}>
+// CIR-DAG: !rec_CharUndersizedIntBF = !cir.struct<"CharUndersizedIntBF" {data 
!s8i, bitfield !cir.array<!u8i x 3>}>
 // CIR-DAG: !rec_BFDouble = !cir.struct<"BFDouble" packed {data !s8i, bitfield 
!u32i, data !cir.array<!s8i x 3>, data !cir.double}>
 // CIR-DAG: !rec_Nine = !cir.struct<"Nine" packed {data !s32i, data !s32i, 
data !s8i}>
 // CIR-DAG: !rec_FiveShort = !cir.struct<"FiveShort" packed {data !s16i, data 
!s16i, data !s8i}>
@@ -83,6 +87,26 @@ CharWideBF ret_char_wide_bf(unsigned long long x) { 
CharWideBF v = {0, x}; retur
 // LLVM: define dso_local i32 @take_char_wide_bf(i64 %{{.+}}, i8 %{{.+}})
 // LLVM: define dso_local { i64, i8 } @ret_char_wide_bf(i64 noundef %{{.+}})
 
+// Three bit-fields share one unit, so the unit spans the bits of all three
+// and the record coerces to the seven bytes it occupies.
+int take_multiple_bf(CharMultipleBFInt v) { return v.i; }
+CharMultipleBFInt ret_multiple_bf(int x) { CharMultipleBFInt v = {0, x, x, x}; 
return v; }
+
+// CIR: cir.func{{.*}} @take_multiple_bf(%arg0: !cir.int<u, 56>{{.*}}) -> !s32i
+// CIR: cir.func{{.*}} @ret_multiple_bf(%arg0: !s32i {llvm.noundef}{{.*}}) -> 
!cir.int<u, 56>
+// LLVM: define dso_local i32 @take_multiple_bf(i56 %{{.+}})
+// LLVM: define dso_local i56 @ret_multiple_bf(i32 noundef %{{.+}})
+
+// A unit narrower than the type its bit-field was declared with.  The three
+// bytes it occupies and the leading char round up to the same eightbyte.
+int take_undersized_bf(CharUndersizedIntBF v) { return v.i; }
+CharUndersizedIntBF ret_undersized_bf(int x) { CharUndersizedIntBF v = {0, x}; 
return v; }
+
+// CIR: cir.func{{.*}} @take_undersized_bf(%arg0: !u32i{{.*}}) -> !s32i
+// CIR: cir.func{{.*}} @ret_undersized_bf(%arg0: !s32i {llvm.noundef}{{.*}}) 
-> !u32i
+// LLVM: define dso_local i32 @take_undersized_bf(i32 %{{.+}})
+// LLVM: define dso_local i32 @ret_undersized_bf(i32 noundef %{{.+}})
+
 // The unit decides the low eightbyte while the double decides the high one.
 double take_bf_double(BFDouble v) { return v.d; }
 

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

Reply via email to