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
