Author: Adam Smith
Date: 2026-09-14T11:05:01-05:00
New Revision: e4315a87a4128cc36470b2cb5c6009672df5f447

URL: 
https://github.com/llvm/llvm-project/commit/e4315a87a4128cc36470b2cb5c6009672df5f447
DIFF: 
https://github.com/llvm/llvm-project/commit/e4315a87a4128cc36470b2cb5c6009672df5f447.diff

LOG: [CIR] Accept a union covered only by a bit-field's declared type (#222108)

The size of a union is calculated by its largest member. When that
member is a bit-field, the unit storing it can be narrower than the type
it was declared with, and it is the declared type that accounts for the
union's bytes. BitFieldType gains a query for that declared type, and
the x86_64 union rule reads it rather than the stored size, so `union {
int x : 3; }` and `union { int x : 3; char c; }` now both pass as i32.

A union larger than one eightbyte still needs a member covering it
outright, since the coerce basis skips the entry carrying the
declaration.

Assisted-by: Cursor / claude-opus-5

Added: 
    clang/test/CIR/CodeGen/call-conv-lowering-x86_64-union-bitfield.c
    clang/test/CIR/CodeGen/call-conv-lowering-x86_64-union-bitfield.cpp

Modified: 
    clang/include/clang/CIR/Dialect/IR/CIRTypes.td
    clang/lib/CIR/Dialect/IR/CIRTypes.cpp
    clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
    clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir
    clang/test/CIR/Transforms/abi-lowering/x86_64-union.cir

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td 
b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
index f3f8159799cdf..9d55ed059f049 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
@@ -734,6 +734,12 @@ def CIR_BitFieldType : CIR_Type<"BitField", "bitfield", [
         offset += decl.getWidth();
       return offset;
     }
+
+    /// The size of the type this unit's bit-field was declared with, which
+    /// can exceed the storage type's size.  Returns nullopt when the unit
+    /// holds more than one bit-field.
+    std::optional<uint64_t>
+    getSoleDeclaredExtentInBits(const mlir::DataLayout &dataLayout) const;
   }];
 
   let genVerifyDecl = 1;

diff  --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp 
b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
index e4093000e8507..5d678d5aee5bc 100644
--- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
@@ -1487,6 +1487,14 @@ BitFieldType::getABIAlignment(const mlir::DataLayout 
&dataLayout,
   return 1;
 }
 
+std::optional<uint64_t> BitFieldType::getSoleDeclaredExtentInBits(
+    const mlir::DataLayout &dataLayout) const {
+  if (getFields().size() != 1)
+    return std::nullopt;
+  return dataLayout.getTypeSizeInBits(getFields().front().getDeclaredType())
+      .getFixedValue();
+}
+
 
//===----------------------------------------------------------------------===//
 // VectorType Definitions
 
//===----------------------------------------------------------------------===//

diff  --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp 
b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
index 601dc54f754a6..b925e8115cca4 100644
--- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
@@ -77,12 +77,14 @@ namespace {
 // Integer (including `_BitInt` up to 128 bits) / pointer / vtable pointer /
 // bool / floating-point scalars are handled, as are struct / union / array
 // aggregates, `_Complex`, and a fixed-width vector whose width is a power
-// of two.  Other vectors, a padded record reached through a named bit-field
-// access unit, a record holding an empty-for-ABI member that occupies bytes
-// or a zero-sized one off its own alignment, a union no member of which spans
-// its declared size, and a union with a bit-field access unit no spanning
-// member of which supplies data are reported NYI by classifyX86_64Function
-// so an unsupported signature fails the pass instead of being misclassified.
+// of two.  Other vectors, a record holding an empty-for-ABI member that
+// occupies bytes or a zero-sized one off its own alignment, a union no member
+// of which spans its declared size (a single-declaration bit-field member
+// counting as far as its declared type extends, and only for a union of one
+// eightbyte or less), and a union with a named bit-field access unit no
+// spanning member of which supplies data are reported NYI by
+// classifyX86_64Function so an unsupported signature fails the pass instead of
+// being misclassified.
 
//===----------------------------------------------------------------------===//
 
 /// Whether a struct's declared argument-passing kind (from the module's
@@ -234,8 +236,22 @@ static bool isSupportedType(mlir::Type ty, const 
DataLayout &dl) {
         if (recordBits > 128)
           return false;
       } else {
+        // A declared type may reach past its unit and overshoot the union,
+        // which stored bytes never do, hence the inequality.  It counts only
+        // within the first eightbyte: past that reduceUnionForX8664 picks the
+        // coerce basis from the fields the union stores.
+        const bool declaredExtentCounts = recordBits <= 64;
         auto spansRecord = [&](mlir::Type m) {
-          return dl.getTypeSizeInBits(m).getFixedValue() == recordBits;
+          if (dl.getTypeSizeInBits(m).getFixedValue() == recordBits)
+            return true;
+          if (!declaredExtentCounts)
+            return false;
+          auto bfTy = dyn_cast<cir::BitFieldType>(m);
+          if (!bfTy)
+            return false;
+          std::optional<uint64_t> extentBits =
+              bfTy.getSoleDeclaredExtentInBits(dl);
+          return extentBits && *extentBits >= recordBits;
         };
         if (!llvm::any_of(members, spansRecord))
           return false;

diff  --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-union-bitfield.c 
b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-union-bitfield.c
new file mode 100644
index 0000000000000..33fb1c470c4af
--- /dev/null
+++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-union-bitfield.c
@@ -0,0 +1,172 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o 
%t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o 
%t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
+
+typedef union { int x : 3; } BitExtent;
+typedef union { int x : 3; char c; } BitExtentPlusChar;
+typedef union { long long x : 32; int y; } BitExtentWide;
+typedef union { long long x : 32; float f; } BitFloatSibling;
+typedef union { int x : 20; } BitArrayUnit;
+typedef union { int x : 3; long long y : 40; } TwoBitUnits;
+typedef union { int : 20; short s; } UnnamedBitExtent;
+typedef union { int x : 3; int : 0; } ZeroWidthTail;
+typedef struct { union { int x : 20; } u; int k; } WrapsUnion;
+typedef union { long long x : 3; } __attribute__((packed)) BitExtentPacked;
+typedef union { long long x : 3; } __attribute__((packed, aligned(2))) 
Overshoot;
+typedef union { _BitInt(72) x : 40; } WideBitIntDecl;
+typedef union { __int128 x : 40; } __attribute__((packed, aligned(8))) 
WideInt128Decl;
+typedef union { int x : 20; } __attribute__((packed)) ArrayUnitPacked;
+typedef union { int : 20; } UnnamedOnly;
+typedef struct { BitExtent u; long long k; } Wrap16;
+typedef struct { BitExtent u; char big[32]; } Wrap40;
+typedef struct { BitExtent a[2]; } WrapArr;
+typedef union { BitExtent inner; int y; } NestUnion;
+
+// CIR-DAG: !rec_ZeroWidthTail = !cir.union<"ZeroWidthTail" {bitfield 
!cir.bitfield<!u8i, [#cir.bitfield_decl<!s32i, 3>]>}, padding = 
{!cir.array<!u8i x 3>}>
+// CIR-DAG: ![[PAIR_RET:rec_anon_struct[0-9]*]] = !cir.struct<{data !u64i, 
data !s64i}>
+
+// The access unit is one byte where the union is four, and the `int` the
+// bit-field was declared with is what accounts for the rest.
+void take_bit_extent(BitExtent u) {}
+// CIR: cir.func{{.*}} @take_bit_extent(%arg0: !u32i loc
+// LLVM: define{{.*}} void @take_bit_extent(i32 %{{[^,)]+}})
+
+void take_bit_extent_plus_char(BitExtentPlusChar u) {}
+// CIR: cir.func{{.*}} @take_bit_extent_plus_char(%arg0: !u32i loc
+// LLVM: define{{.*}} void @take_bit_extent_plus_char(i32 %{{[^,)]+}})
+
+// The declared `long long` reaches all eight bytes where the sibling `int` and
+// the four-byte unit both stop at four.
+void take_bit_extent_wide(BitExtentWide u) {}
+// CIR: cir.func{{.*}} @take_bit_extent_wide(%arg0: !u64i loc
+// LLVM: define{{.*}} void @take_bit_extent_wide(i64 %{{[^,)]+}})
+
+// The sibling classifies SSE and the unit INTEGER, and the merge takes
+// INTEGER, so the declaration decides the size and not the class.
+void take_bit_float_sibling(BitFloatSibling u) {}
+// CIR: cir.func{{.*}} @take_bit_float_sibling(%arg0: !u64i loc
+// LLVM: define{{.*}} void @take_bit_float_sibling(i64 %{{[^,)]+}})
+
+// A 20-bit field takes a three-byte unit, so the unit is an array rather than
+// an integer.
+void take_bit_array_unit(BitArrayUnit u) {}
+// CIR: cir.func{{.*}} @take_bit_array_unit(%arg0: !u32i loc
+// LLVM: define{{.*}} void @take_bit_array_unit(i32 %{{[^,)]+}})
+
+// Each variant of a union is its own access unit, so these two bit-fields do
+// not share one and the widest declaration among them is what counts.
+void take_two_bit_units(TwoBitUnits u) {}
+// CIR: cir.func{{.*}} @take_two_bit_units(%arg0: !u64i loc
+// LLVM: define{{.*}} void @take_two_bit_units(i64 %{{[^,)]+}})
+
+// An access unit of nothing but unnamed bit-fields still carries a declared
+// type, and the union has no other member that reaches four bytes.
+void take_unnamed_bit_extent(UnnamedBitExtent u) {}
+// CIR: cir.func{{.*}} @take_unnamed_bit_extent(%arg0: !u32i loc
+// LLVM: define{{.*}} void @take_unnamed_bit_extent(i32 %{{[^,)]+}})
+
+// A zero-width bit-field is no variant of the union, which is left with the
+// named unit and its declaration.
+void take_zero_width_tail(ZeroWidthTail u) {}
+// CIR: cir.func{{.*}} @take_zero_width_tail(%arg0: !u32i loc
+// LLVM: define{{.*}} void @take_zero_width_tail(i32 %{{[^,)]+}})
+
+// The union is reached as a struct member, so the struct's own eightbyte is
+// what gets classified.
+void take_wraps_union(WrapsUnion s) {}
+// CIR: cir.func{{.*}} @take_wraps_union(%arg0: !u64i loc
+// LLVM: define{{.*}} void @take_wraps_union(i64 %{{[^,)]+}})
+
+void take_wrap16(Wrap16 s) {}
+// CIR: cir.func{{.*}} @take_wrap16(%arg0: !u64i loc{{.*}}, %arg1: !s64i 
loc{{.*}}) attributes
+// LLVM: define{{.*}} void @take_wrap16(i64 %{{[^,)]+}}, i64 %{{[^,)]+}})
+
+Wrap16 ret_wrap16(void) {
+  Wrap16 s;
+  s.u.x = 1;
+  s.k = 2;
+  return s;
+}
+// CIR: cir.func{{.*}} @ret_wrap16() -> ![[PAIR_RET]] attributes
+// LLVM: define{{.*}} { i64, i64 } @ret_wrap16()
+
+void take_wrap40(Wrap40 s) {}
+// CIR: cir.func{{.*}} @take_wrap40(%arg0: !cir.ptr<!rec_Wrap40> {llvm.align = 
8 : i64, llvm.byval = !rec_Wrap40, llvm.noundef} loc
+// LLVM: define{{.*}} void @take_wrap40(ptr noundef byval(%struct.Wrap40) 
align 8 %{{[^,)]+}})
+
+Wrap40 ret_wrap40(void) {
+  Wrap40 s;
+  s.u.x = 1;
+  return s;
+}
+// CIR: cir.func{{.*}} @ret_wrap40(%arg0: !cir.ptr<!rec_Wrap40> {llvm.align = 
4 : i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = !rec_Wrap40, 
llvm.writable} loc
+// LLVM: define{{.*}} void @ret_wrap40(ptr dead_on_unwind noalias writable 
sret(%struct.Wrap40) align 4 %{{[^,)]+}})
+
+// The declared extent has to be found through the array.
+void take_wraparr(WrapArr s) {}
+// CIR: cir.func{{.*}} @take_wraparr(%arg0: !u64i loc
+// LLVM: define{{.*}} void @take_wraparr(i64 %{{[^,)]+}})
+
+// And here through the outer union.
+void take_nest_union(NestUnion u) {}
+// CIR: cir.func{{.*}} @take_nest_union(%arg0: !u32i loc
+// LLVM: define{{.*}} void @take_nest_union(i32 %{{[^,)]+}})
+
+// Packed, so the one-byte unit covers this union without help from the
+// declared `long long`.
+void take_bit_extent_packed(BitExtentPacked u) {}
+// CIR: cir.func{{.*}} @take_bit_extent_packed(%arg0: !u8i loc
+// LLVM: define{{.*}} void @take_bit_extent_packed(i8 %{{[^,)]+}})
+
+// Here neither the one-byte unit nor a sibling covers the two-byte union, so
+// the declared `long long` is what covers it, overshooting by six bytes.
+void take_overshoot(Overshoot u) {}
+// CIR: cir.func{{.*}} @take_overshoot(%arg0: !u16i loc
+// LLVM: define{{.*}} void @take_overshoot(i16 %{{[^,)]+}})
+
+// A `_BitInt` declaration reaches as far as the width its alignment rounds it
+// up to, which is eight bytes here rather than the seventy-two bits declared.
+void take_wide_bitint_decl(WideBitIntDecl u) {}
+// CIR: cir.func{{.*}} @take_wide_bitint_decl(%arg0: !u64i loc
+// LLVM: define{{.*}} void @take_wide_bitint_decl(i64 %{{[^,)]+}})
+
+// The same 128-bit declaration the UBitWideDecl reject row carries, on a union
+// small enough for the declared extent to be read at all.
+void take_wide_int128_decl(WideInt128Decl u) {}
+// CIR: cir.func{{.*}} @take_wide_int128_decl(%arg0: !u64i loc
+// LLVM: define{{.*}} void @take_wide_int128_decl(i64 %{{[^,)]+}})
+
+// A named unit covering its union on its own, so the second gate is satisfied
+// without the declaration.
+void take_array_unit_packed(ArrayUnitPacked u) {}
+// CIR: cir.func{{.*}} @take_array_unit_packed(%arg0: !cir.int<u, 24> loc
+// LLVM: define{{.*}} void @take_array_unit_packed(i24 %{{[^,)]+}})
+
+// Here the unit covers the union on its own, without the declaration.
+void take_unnamed_only(UnnamedOnly u) {}
+// CIR: cir.func{{.*}} @take_unnamed_only(%arg0: !cir.int<u, 24> loc
+// LLVM: define{{.*}} void @take_unnamed_only(i24 %{{[^,)]+}})
+
+BitArrayUnit ret_bit_array_unit(void) {
+  BitArrayUnit u;
+  u.x = 1;
+  return u;
+}
+// CIR: cir.func{{.*}} @ret_bit_array_unit() -> !u32i
+// LLVM: define{{.*}} i32 @ret_bit_array_unit()
+
+void call_bit_array_unit(void) { take_bit_array_unit(ret_bit_array_unit()); }
+// CIR: cir.func{{.*}} @call_bit_array_unit()
+// CIR:   cir.call @take_bit_array_unit(%{{.+}}) : (!u32i) -> ()
+// LLVM: define{{.*}} void @call_bit_array_unit()
+// LLVM:   call void @take_bit_array_unit(i32 %{{[^,)]+}})
+
+void vsink(int n, ...);
+void call_variadic(BitExtent u) { vsink(1, u); }
+// CIR: cir.func{{.*}} @call_variadic(%arg0: !u32i loc
+// CIR:   cir.call @vsink(%{{.+}}, %{{.+}}) : (!s32i {llvm.noundef}, !u32i) -> 
()
+// LLVM: define{{.*}} void @call_variadic(i32 %{{[^,)]+}})
+// LLVM:   call void (i32, ...) @vsink(i32 noundef 1, i32 %{{[^,)]+}})

diff  --git 
a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-union-bitfield.cpp 
b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-union-bitfield.cpp
new file mode 100644
index 0000000000000..1014801df5dd6
--- /dev/null
+++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-union-bitfield.cpp
@@ -0,0 +1,34 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o 
%t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o 
%t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
+
+struct E {};
+
+union BitEmptySpan {
+  E e[4];
+  int x : 8;
+};
+
+union NoRegs {
+  int x : 3;
+  NoRegs() {}
+  ~NoRegs() {}
+};
+
+// CIR-DAG: !rec_BitEmptySpan = !cir.union<"BitEmptySpan" {data 
!cir.array<!rec_E x 4>, bitfield !cir.bitfield<!u8i, [#cir.bitfield_decl<!s32i, 
8>]>}>
+// CIR-DAG: !rec_NoRegs = !cir.union<"NoRegs" {bitfield !cir.bitfield<!u8i, 
[#cir.bitfield_decl<!s32i, 3>]>}, padding = {!cir.array<!u8i x 3>}>
+
+// The empty-record array covers the union but supplies no data, so the
+// bit-field has to cover it too, which it does through its declared type.
+void take_bit_empty_span(BitEmptySpan u) {}
+// CIR: cir.func{{.*}} @_Z19take_bit_empty_span12BitEmptySpan(%arg0: !u32i loc
+// LLVM: define{{.*}} void @_Z19take_bit_empty_span12BitEmptySpan(i32 
%{{[^,)]+}})
+
+// A union that cannot pass in registers is returned through an sret slot, so
+// the declared extent decides only whether it can be classified at all.
+NoRegs ret_no_regs() { return NoRegs(); }
+// CIR: cir.func{{.*}} @_Z11ret_no_regsv(%arg0: !cir.ptr<!rec_NoRegs> 
{llvm.align = 4 : i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = 
!rec_NoRegs, llvm.writable} loc
+// LLVM: define{{.*}} void @_Z11ret_no_regsv(ptr dead_on_unwind noalias 
writable sret(%union.NoRegs) align 4 %{{[^,)]+}})

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 3d32555e57dc4..ebf11d1fa4865 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,6 +4,7 @@
 !s16i = !cir.int<s, 16>
 !s32i = !cir.int<s, 32>
 !s64i = !cir.int<s, 64>
+!s128i = !cir.int<s, 128>
 !u8i = !cir.int<u, 8>
 !u32i = !cir.int<u, 32>
 !u64i = !cir.int<u, 64>
@@ -15,9 +16,26 @@
 !rec_ZeroLenArr = !cir.struct<"ZeroLenArr" packed {data !s8i, empty 
!cir.array<!s32i x 0>}>
 !rec_UPadByte = !cir.union<"UPadByte" {data !u8i}, padding = {!cir.array<!u8i 
x 3>}>
 !rec_E = !cir.struct<"E" {pad !u8i}>
-!rec_UBitEmpty =
-    !cir.union<"UBitEmpty" {data !cir.array<!rec_E x 4>,
-                            bitfield !cir.bitfield<!u8i, 
[#cir.bitfield_decl<!s32i, 8>]>}>
+!rec_UBitOverAligned =
+    !cir.union<"UBitOverAligned" {bitfield !cir.bitfield<!u8i, 
[#cir.bitfield_decl<!s32i, 3>]>},
+               padding = {!cir.array<!u8i x 7>}>
+!rec_UBitIntUnit =
+    !cir.union<"UBitIntUnit" {bitfield !cir.bitfield<!cir.array<!u8i x 9>,
+                                                     
[#cir.bitfield_decl<!cir.int<s, 72, bitint>, 70>]>},
+               padding = {!cir.array<!u8i x 7>}>
+!rec_UBitWideDecl =
+    !cir.union<"UBitWideDecl" {bitfield !cir.bitfield<!cir.array<!u8i x 13>,
+                                                      
[#cir.bitfield_decl<!s128i, 100>]>},
+               padding = {!cir.array<!u8i x 3>}>
+!rec_UMultiDecl =
+    !cir.union<"UMultiDecl" {bitfield !cir.bitfield<!u8i, 
[#cir.bitfield_decl<!s8i, 4>,
+                                                           
#cir.bitfield_decl<!s32i, 4>]>},
+               padding = {!cir.array<!u8i x 3>}>
+!rec_UNamedPlusUnnamedSpan =
+    !cir.union<"UNamedPlusUnnamedSpan" {bitfield !cir.bitfield<!u8i, 
[#cir.bitfield_decl<!s32i, 3>]>,
+                                        empty !cir.bitfield<!cir.array<!u8i x 
5>,
+                                                            
[#cir.bitfield_decl<!s64i, 40, unnamed>]>},
+               padding = {!cir.array<!u8i x 3>}>
 !rec_UBitUnnamed =
     !cir.union<"UBitUnnamed" {bitfield !cir.bitfield<!u8i, 
[#cir.bitfield_decl<!s32i, 8>]>,
                               empty !cir.bitfield<!u64i, 
[#cir.bitfield_decl<!s64i, 64, unnamed>]>}>
@@ -100,15 +118,51 @@ module attributes {
 
   // CHECK: not yet implemented for type '!cir.union<"UPadByte"
 
-  // A bit-field access unit's width can understate the bit-fields it holds.
-  // Here the only member that spans the union (the empty-record array)
-  // supplies no bytes, so the unit alone would coerce to i8 where classic
-  // gives i32.
-  cir.func @take_bitfield_empty_span(%arg0: !rec_UBitEmpty) {
+  // A bit-field's declared type reaches past its access unit, but `int` still
+  // leaves 4 of this union's 8 bytes to nothing at all.
+  cir.func @take_bitfield_over_aligned_union(%arg0: !rec_UBitOverAligned) {
     cir.return
   }
 
-  // CHECK: not yet implemented for type '!cir.union<"UBitEmpty"
+  // CHECK: not yet implemented for type '!cir.union<"UBitOverAligned"
+
+  // Past one eightbyte the declared extent settles nothing: the coercion
+  // follows this 9-byte unit and would give i8 for the second eightbyte,
+  // where classic sizes it from the record and gives i64.
+  cir.func @take_bitint_unit_union(%arg0: !rec_UBitIntUnit) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for type '!cir.union<"UBitIntUnit"
+
+  // A 13-byte unit does land on classic's i64 here, but by covering 5 bytes of
+  // the second eightbyte itself rather than through the declaration the basis
+  // skipped, so this is refused alongside the case above.
+  cir.func @take_wide_decl_union(%arg0: !rec_UBitWideDecl) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for type '!cir.union<"UBitWideDecl"
+
+  // A unit's second declaration is a field of its own at a nonzero offset, so
+  // covering the union from there would put that field past the union's bytes.
+  // A unit holding more than one declaration is refused outright rather than
+  // credited for the `int` here, which starts a byte in.
+  cir.func @take_multi_decl_union(%arg0: !rec_UMultiDecl) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for type '!cir.union<"UMultiDecl"
+
+  // Unlike take_bitfield_unnamed_span below, the unnamed-only unit here covers
+  // the union only through its declared `long long`, its storage being 5 of
+  // the 8 bytes.  It still supplies no data, and the named unit that does
+  // stops at 4.
+  cir.func @take_named_plus_unnamed_span(%arg0: !rec_UNamedPlusUnnamedSpan) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for type '!cir.union<"UNamedPlusUnnamedSpan"
 
   // The spanning member here is an access unit holding only unnamed
   // bit-fields, which supplies no data either, so the named unit alone would

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 e41f9b451f379..d3d3ca779afb3 100644
--- a/clang/test/CIR/Transforms/abi-lowering/x86_64-union.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-union.cir
@@ -43,6 +43,16 @@
 !u32i = !cir.int<u, 32>
 !rec_UBitSpans = !cir.union<"UBitSpans" {bitfield !cir.bitfield<!u32i, 
[#cir.bitfield_decl<!s32i, 20>]>}>
 !rec_UBitPlusLong = !cir.union<"UBitPlusLong" {bitfield !cir.bitfield<!u32i, 
[#cir.bitfield_decl<!s32i, 20>]>, data !s64i}>
+!s16i = !cir.int<s, 16>
+!rec_UBitExtent = !cir.union<"UBitExtent" {bitfield !cir.bitfield<!u8i, 
[#cir.bitfield_decl<!s32i, 3>]>}, padding = {!cir.array<!u8i x 3>}>
+!rec_UBitExtentWide = !cir.union<"UBitExtentWide" {bitfield 
!cir.bitfield<!u32i, [#cir.bitfield_decl<!s64i, 32>]>, data !s32i}, padding = 
{!cir.array<!u8i x 4>}>
+!rec_UBitArrayUnit = !cir.union<"UBitArrayUnit" {bitfield 
!cir.bitfield<!cir.array<!u8i x 3>, [#cir.bitfield_decl<!s32i, 20>]>}, padding 
= {!u8i}>
+!rec_UTwoBitUnits = !cir.union<"UTwoBitUnits" {bitfield !cir.bitfield<!u8i, 
[#cir.bitfield_decl<!s32i, 3>]>, bitfield !cir.bitfield<!cir.array<!u8i x 5>, 
[#cir.bitfield_decl<!s64i, 40>]>}, padding = {!cir.array<!u8i x 3>}>
+!rec_UUnnamedBitExtent = !cir.union<"UUnnamedBitExtent" {empty 
!cir.bitfield<!cir.array<!u8i x 3>, [#cir.bitfield_decl<!s32i, 20, unnamed>]>, 
data !s16i}, padding = {!cir.array<!u8i x 2>}>
+!rec_UBitEmptySpan = !cir.union<"UBitEmptySpan" {data !cir.array<!rec_E x 4>, 
bitfield !cir.bitfield<!u8i, [#cir.bitfield_decl<!s32i, 8>]>}>
+!rec_UBitExtentPacked = !cir.union<"UBitExtentPacked" {bitfield 
!cir.bitfield<!u8i, [#cir.bitfield_decl<!s64i, 3>]>}>
+!rec_UOvershoot = !cir.union<"UOvershoot" {bitfield !cir.bitfield<!u8i, 
[#cir.bitfield_decl<!s64i, 3>]>}, padding = {!u8i}>
+!rec_UBitNoRegs = !cir.union<"UBitNoRegs" {bitfield !cir.bitfield<!u8i, 
[#cir.bitfield_decl<!s32i, 3>]>}, padding = {!cir.array<!u8i x 3>}>
 
 module attributes {
   cir.triple = "x86_64-unknown-linux-gnu",
@@ -50,6 +60,9 @@ module attributes {
     UNoRegs = #cir.record_layout<
       arg_passing_kind = cannot_pass_in_regs, has_trivial_dtor = false,
       record_align = 4>,
+    UBitNoRegs = #cir.record_layout<
+      arg_passing_kind = cannot_pass_in_regs, has_trivial_dtor = false,
+      record_align = 4>,
     UBigOverAligned = #cir.record_layout<
       arg_passing_kind = can_pass_in_regs, has_trivial_dtor = true,
       record_align = 32>,
@@ -339,6 +352,96 @@ module attributes {
 
   // CHECK: cir.func{{.*}} @take_bit_plus_long(%arg0: !s64i)
 
+  // The access unit is one byte where the union is four, and the `int` the
+  // bit-field was declared with is what accounts for the rest.
+  cir.func @take_bit_extent(%arg0: !rec_UBitExtent) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_bit_extent(%arg0: !u32i)
+
+  // The declared `long long` reaches all 8 bytes where the sibling `int` and
+  // the 4-byte unit both stop at 4.
+  cir.func @take_bit_extent_wide(%arg0: !rec_UBitExtentWide) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_bit_extent_wide(%arg0: !u64i)
+
+  // A 20-bit field takes a 3-byte unit, so the unit is an array rather than an
+  // integer.
+  cir.func @take_bit_array_unit(%arg0: !rec_UBitArrayUnit) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_bit_array_unit(%arg0: !u32i)
+  // CHECK:   %[[SLOT:.*]] = cir.alloca "coerce" align(4) : !cir.ptr<!u32i>
+  // CHECK:   cir.store %arg0, %[[SLOT]] : !u32i, !cir.ptr<!u32i>
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %[[SLOT]] : !cir.ptr<!u32i> -> 
!cir.ptr<!rec_UBitArrayUnit>
+  // CHECK:   %{{.*}} = cir.load %[[CAST]] : !cir.ptr<!rec_UBitArrayUnit>, 
!rec_UBitArrayUnit
+
+  // Each variant of a union is its own access unit, so the two bit-fields here
+  // do not share one.  The widest declaration among them is what counts.
+  cir.func @take_two_bit_units(%arg0: !rec_UTwoBitUnits) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_two_bit_units(%arg0: !u64i)
+
+  // An access unit of nothing but unnamed bit-fields is marked empty rather
+  // than bitfield.
+  cir.func @take_unnamed_bit_extent(%arg0: !rec_UUnnamedBitExtent) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_unnamed_bit_extent(%arg0: !u32i)
+
+  // The empty-record array covers the union but supplies no data, so the
+  // bit-field has to cover it too, which it does through its declared type.
+  cir.func @take_bit_empty_span(%arg0: !rec_UBitEmptySpan) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_bit_empty_span(%arg0: !u32i)
+
+  // Packed, so the one-byte unit covers this union without help from the
+  // declared `long long`.
+  cir.func @take_bit_extent_packed(%arg0: !rec_UBitExtentPacked) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_bit_extent_packed(%arg0: !u8i)
+
+  // Here neither the one-byte unit nor a sibling covers the two-byte union, so
+  // the declared `long long` is what covers it, overshooting by 6 bytes.
+  cir.func @take_overshoot(%arg0: !rec_UOvershoot) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_overshoot(%arg0: !u16i)
+
+  cir.func @ret_bit_array_unit(%arg0: !rec_UBitArrayUnit) -> 
!rec_UBitArrayUnit {
+    %0 = cir.alloca "u" align(4) : !cir.ptr<!rec_UBitArrayUnit>
+    cir.store %arg0, %0 : !rec_UBitArrayUnit, !cir.ptr<!rec_UBitArrayUnit>
+    %1 = cir.load %0 : !cir.ptr<!rec_UBitArrayUnit>, !rec_UBitArrayUnit
+    cir.return %1 : !rec_UBitArrayUnit
+  }
+
+  // CHECK: cir.func{{.*}} @ret_bit_array_unit(%arg0: !u32i) -> !u32i
+  // CHECK:   %[[RETSLOT:.*]] = cir.alloca "coerce" align(4) : 
!cir.ptr<!rec_UBitArrayUnit>
+  // CHECK:   %[[RETCAST:.*]] = cir.cast bitcast %[[RETSLOT]] : 
!cir.ptr<!rec_UBitArrayUnit> -> !cir.ptr<!u32i>
+  // CHECK:   %[[RETVAL:.*]] = cir.load %[[RETCAST]] : !cir.ptr<!u32i>, !u32i
+  // CHECK:   cir.return %[[RETVAL]] : !u32i
+
+  cir.func @call_bit_array_unit(%arg0: !rec_UBitArrayUnit) {
+    cir.call @take_bit_array_unit(%arg0) : (!rec_UBitArrayUnit) -> ()
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @call_bit_array_unit(%arg0: !u32i)
+  // CHECK:   %[[ARGVAL:.*]] = cir.load %{{.*}} : !cir.ptr<!u32i>, !u32i
+  // CHECK:   cir.call @take_bit_array_unit(%[[ARGVAL]]) : (!u32i) -> ()
+
   // Both eightbytes classify INTEGER and are flattened into one argument each,
   // so the empty member does not disturb a multi-eightbyte coercion.
   cir.func @take_empty_two_eightbytes(%arg0: !rec_UEmptyTwoEightbytes) {
@@ -394,6 +497,15 @@ module attributes {
 
   // CHECK: cir.func{{.*}} @take_no_regs(%arg0: !cir.ptr<!rec_UNoRegs> 
{llvm.align = 4 : i64, llvm.dereferenceable = 4 : i64, llvm.nofreeobj, 
llvm.noundef})
 
+  // A union that covers itself only through a bit-field's declared type is
+  // classified like take_no_regs above, and its argument-passing kind still
+  // sends it to memory.
+  cir.func @take_bit_no_regs(%arg0: !rec_UBitNoRegs) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_bit_no_regs(%arg0: !cir.ptr<!rec_UBitNoRegs> 
{llvm.align = 4 : i64, llvm.dereferenceable = 4 : i64, llvm.nofreeobj, 
llvm.noundef})
+
   // A struct member that is itself a union is mapped through the same union
   // handling, so the enclosing 8-byte struct coerces to one i64.
   cir.func @take_struct_with_union(%arg0: !rec_SWithUnion) {
@@ -490,6 +602,17 @@ module attributes {
 // LLVM: define void @take_empty_floats(<2 x float> %{{.+}})
 // LLVM: define void @take_bit_spans(i32 %{{.+}})
 // LLVM: define void @take_bit_plus_long(i64 %{{.+}})
+// LLVM: define void @take_bit_extent(i32 %{{[^,)]+}})
+// LLVM: define void @take_bit_extent_wide(i64 %{{[^,)]+}})
+// LLVM: define void @take_bit_array_unit(i32 %{{[^,)]+}})
+// LLVM: define void @take_two_bit_units(i64 %{{[^,)]+}})
+// LLVM: define void @take_unnamed_bit_extent(i32 %{{[^,)]+}})
+// LLVM: define void @take_bit_empty_span(i32 %{{[^,)]+}})
+// LLVM: define void @take_bit_extent_packed(i8 %{{[^,)]+}})
+// LLVM: define void @take_overshoot(i16 %{{[^,)]+}})
+// LLVM: define i32 @ret_bit_array_unit(i32 %{{[^,)]+}})
+// LLVM: define void @call_bit_array_unit(i32 %{{[^,)]+}})
+// LLVM:   call void @take_bit_array_unit(i32 %{{[^,)]+}})
 // LLVM: define void @take_empty_two_eightbytes(i64 %{{.+}}, i64 %{{.+}})
 // LLVM: define void @take_struct_with_empty_union(i64 %{{.+}})
 // LLVM: define i32 @ret_empty_int(i32 %{{.+}})
@@ -497,6 +620,7 @@ module attributes {
 // LLVM: define void @call_empty_int(i32 %{{.+}})
 // LLVM:   call void @take_empty_int(i32 %{{.+}})
 // LLVM: define void @take_no_regs(ptr nofreeobj noundef align 4 
dereferenceable(4) %{{.+}})
+// LLVM: define void @take_bit_no_regs(ptr nofreeobj noundef align 4 
dereferenceable(4) %{{[^,)]+}})
 // LLVM: define void @take_struct_with_union(i64 %{{.+}})
 // LLVM: define i32 @ret_int_float(i32 %{{.+}})
 // LLVM: define void @ret_big(ptr dead_on_unwind noalias writable 
sret(%union.UBig) align 1 %{{.+}}, ptr noundef byval(%union.UBig) align 8 
%{{.+}})


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

Reply via email to