[clang] e4315a8 - [CIR] Accept a union covered only by a bit-field's declared type (#222108)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Sep 14 09:05:06 PDT 2026
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 %{{.+}})
More information about the cfe-commits
mailing list