https://github.com/adams381 updated https://github.com/llvm/llvm-project/pull/223594
>From b355ea4e66a6a304c9bf028aada340f1751aeb4b Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Mon, 14 Sep 2026 20:38:03 -0700 Subject: [PATCH 1/2] [CIR] Accept a union whose members do not cover its declared size Past two eightbytes the x86_64 classifier never reads a union's members to pick a coerce type. It either classifies the record as memory, which needs no coerce type at all, or it classifies SSE followed by SSEUP, and then the coerce type is a vector as wide as the whole record. Assisted-by: Cursor / claude-opus-5 --- .../Transforms/CallConvLoweringPass.cpp | 56 ++++-- .../call-conv-lowering-x86_64-non-byval.cpp | 24 +++ ...-conv-lowering-x86_64-union-tail-padding.c | 162 ++++++++++++++++++ .../abi-lowering/x86_64-aggregate-nyi.cir | 64 +++++++ .../Transforms/abi-lowering/x86_64-union.cir | 41 +++++ 5 files changed, 335 insertions(+), 12 deletions(-) create mode 100644 clang/test/CIR/CodeGen/call-conv-lowering-x86_64-union-tail-padding.c diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp index 3d878fd7866ef..d25f3d0657d33 100644 --- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp +++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp @@ -74,17 +74,8 @@ namespace { // Maps CIR types to llvm::abi::Type, runs the LLVM ABI Lowering Library's SysV // x86_64 classifier, and converts the result back into the dialect-agnostic // mlir::abi::FunctionClassification that CIRABIRewriteContext consumes. -// Integer (including `_BitInt` of any width and `__int128`) / 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 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. +// isSupportedType says which CIR types the bridge handles, and a signature +// naming any other fails the pass instead of being misclassified. //===----------------------------------------------------------------------===// /// Whether a struct's declared argument-passing kind (from the module's @@ -98,6 +89,39 @@ static bool recordCanPassInRegs(ModuleOp modOp, cir::RecordType recTy) { return layout.getArgPassingKind() == cir::ArgPassingKind::CanPassInRegs; } +/// Whether the classifier could put this type, or one an array or record +/// holds, in the SSEUP class. Only a vector of 128 bits or wider and an +/// IEEE-quad float reach it. A complex quad does not, since the classifier +/// gives it memory. +static bool mayReachSseUp(mlir::Type ty, const DataLayout &dl) { + if (isa<cir::VectorType>(ty)) + return dl.getTypeSizeInBits(ty).getFixedValue() >= 128; + if (auto fpTy = dyn_cast<cir::FPTypeInterface>(ty)) + return &fpTy.getFloatSemantics() == &llvm::APFloat::IEEEquad(); + if (auto arrTy = dyn_cast<cir::ArrayType>(ty)) + return mayReachSseUp(arrTy.getElementType(), dl); + auto recTy = dyn_cast<cir::RecordType>(ty); + if (!recTy || !recTy.isComplete()) + return false; + return llvm::any_of(recTy.getMembers(), + [&](mlir::Type m) { return mayReachSseUp(m, dl); }); +} + +/// Whether no SSEUP coerce could be named from the record's size. That coerce +/// is a vector as wide as the record, and only 128, 256 and 512 bits have one, +/// a size past 512 classifying memory before a coerce is asked for. A true +/// answer is conservative, since reaching SSEUP also takes a target whose +/// vectors are that wide. +static bool sseUpCoerceSizeUnsupported(uint64_t recordBits, + llvm::ArrayRef<mlir::Type> members, + const DataLayout &dl) { + if (recordBits <= 128 || recordBits > 512 || recordBits == 256 || + recordBits == 512) + return false; + return llvm::any_of(members, + [&](mlir::Type m) { return mayReachSseUp(m, dl); }); +} + /// Whether a member is an empty record, looking through arrays, since an array /// of empty records supplies no bytes either. static bool memberIsEmptyRecord(mlir::Type ty) { @@ -226,13 +250,21 @@ static bool isSupportedType(mlir::Type ty, const DataLayout &dl) { // classic CodeGen coerces them to i32 and i8 respectively. llvm::ArrayRef<mlir::Type> members = recTy.getMembers(); uint64_t recordBits = dl.getTypeSizeInBits(recTy).getFixedValue(); + // A size no coerce can be named from is refused outright, since a + // spanning member would otherwise carry it past the checks below. + if (sseUpCoerceSizeUnsupported(recordBits, members, dl)) + return false; if (members.empty()) { // A member-less union is all padding, which classifies Ignore up to two // eightbytes. Past that SysV says MEMORY regardless of content, and // there is no member here to build the Indirect coercion from. if (recordBits > 128) return false; - } else { + } else if (recordBits <= 128) { + // Within two eightbytes the members have to account for the union's + // bytes. Past them it classifies memory, or SSE then SSEUP with the + // coerce named from its size, so they do not. + // 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 diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-non-byval.cpp b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-non-byval.cpp index a01414569e6fa..02323a8b96843 100644 --- a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-non-byval.cpp +++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-non-byval.cpp @@ -115,6 +115,30 @@ void callByval() { // An inherited constructor forwards its by-value parameter with no temporary // of its own, so the base constructor operates on the object the caller // destroys. +struct NonTrivialPad { + char pad[17]; + NonTrivialPad(); + NonTrivialPad(const NonTrivialPad &); + ~NonTrivialPad(); +}; + +union TailPadNoRegs { + NonTrivialPad n; + long l; + TailPadNoRegs(); + TailPadNoRegs(const TailPadNoRegs &); + ~TailPadNoRegs(); +}; + +// Nothing spans this union's 24 declared bytes, and its non-trivial member +// keeps it out of registers, so it is indirect with no byval. +void takeTailPadNoRegs(TailPadNoRegs u) {} + +// CIR-LABEL: cir.func {{.*}}@_Z17takeTailPadNoRegs13TailPadNoRegs +// CIR-SAME: %{{[^:]*}}: !cir.ptr<!rec_TailPadNoRegs> {llvm.align = 8 : i64, llvm.dereferenceable = 24 : i64, llvm.nofreeobj, llvm.noundef} + +// LLVM: define dso_local void @_Z17takeTailPadNoRegs13TailPadNoRegs(ptr nofreeobj noundef align 8 dereferenceable(24) %{{.+}}) + struct Base { Base(WithDtor t); }; struct Derived : Base { using Base::Base; }; void callInheritedCtor(WithDtor t) { Derived d(t); } diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-union-tail-padding.c b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-union-tail-padding.c new file mode 100644 index 0000000000000..0ff5ae496718d --- /dev/null +++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-union-tail-padding.c @@ -0,0 +1,162 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --check-prefixes=CIR,CIR-SSE --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-prefixes=LLVM,LLVM-SSE --input-file=%t-cir.ll %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll +// RUN: FileCheck --check-prefixes=LLVM,LLVM-SSE --input-file=%t.ll %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -target-feature +avx512f -fclangir -emit-cir %s -o %t-avx.cir +// RUN: FileCheck --check-prefixes=CIR,CIR-AVX --input-file=%t-avx.cir %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -target-feature +avx512f -fclangir -emit-llvm %s -o %t-avx-cir.ll +// RUN: FileCheck --check-prefixes=LLVM,LLVM-AVX --input-file=%t-avx-cir.ll %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -target-feature +avx512f -emit-llvm %s -o %t-avx.ll +// RUN: FileCheck --check-prefixes=LLVM,LLVM-AVX --input-file=%t-avx.ll %s + +typedef float v4f __attribute__((vector_size(16))); +typedef float v2f __attribute__((vector_size(8))); + +typedef struct { unsigned short fam; char path[108]; } SockAddr; +typedef union { SockAddr s; void *err; } Expected; + +typedef struct { void *a; void *b; unsigned c; } Large; +typedef union { char inlineRep[36]; Large large; } DenseMap; + +typedef union { char c[17]; long l; } Odd24; +typedef struct { Odd24 u; _Bool checked : 1; } WrapsOdd24; +typedef struct { Odd24 a[2]; } ArrOfUnion; +typedef union { Odd24 inner; char pad[33]; } NestUnion; + +typedef union { int x : 20; char buf[17]; } BitFieldBig; +typedef union { int i; } __attribute__((aligned(32))) OverAligned32; + +typedef union { __float128 q; char c[17]; } Quad32; +typedef union { __float128 q; char c[49]; } Quad64; +typedef union { __float128 q; char c[65]; } Quad80; +typedef union { _Complex __float128 cq; char c[33]; } CplxQuad48; +typedef union { v4f v; char c[17]; } VecTailPad; +typedef union { v2f v; char c[17]; } NarrowVec; + +// CIR-DAG: !rec_SockAddr = !cir.struct<"SockAddr" {data !u16i, data !cir.array<!s8i x 108>}> +// CIR-DAG: !rec_Large = !cir.struct<"Large" {data !cir.ptr<!void>, data !cir.ptr<!void>, data !u32i}> +// CIR-DAG: !rec_Expected = !cir.union<"Expected" {data !rec_SockAddr, data !cir.ptr<!void>}, padding = {!cir.array<!u8i x 104>}> +// CIR-DAG: !rec_DenseMap = !cir.union<"DenseMap" {data !cir.array<!s8i x 36>, data !rec_Large}, padding = {!cir.array<!u8i x 16>}> +// CIR-DAG: !rec_Odd24 = !cir.union<"Odd24" {data !cir.array<!s8i x 17>, data !s64i}, padding = {!cir.array<!u8i x 16>}> +// CIR-DAG: !rec_ArrOfUnion = !cir.struct<"ArrOfUnion" {data !cir.array<!rec_Odd24 x 2>}> +// CIR-DAG: !rec_NestUnion = !cir.union<"NestUnion" {data !rec_Odd24, data !cir.array<!s8i x 33>}, padding = {!cir.array<!u8i x 16>}> +// CIR-DAG: !rec_BitFieldBig = !cir.union<"BitFieldBig" {bitfield !cir.bitfield<!cir.array<!u8i x 3>, [#cir.bitfield_decl<!s32i, 20>]>, data !cir.array<!s8i x 17>}, padding = {!cir.array<!u8i x 3>}> +// CIR-DAG: !rec_OverAligned32 = !cir.union<"OverAligned32" {data !s32i}, padding = {!cir.array<!u8i x 28>}> +// CIR-DAG: !rec_Quad32 = !cir.union<"Quad32" {data !cir.f128, data !cir.array<!s8i x 17>}, padding = {!cir.array<!u8i x 16>}> +// CIR-DAG: !rec_Quad64 = !cir.union<"Quad64" {data !cir.f128, data !cir.array<!s8i x 49>}, padding = {!cir.array<!u8i x 48>}> +// CIR-DAG: !rec_Quad80 = !cir.union<"Quad80" {data !cir.f128, data !cir.array<!s8i x 65>}, padding = {!cir.array<!u8i x 64>}> +// CIR-DAG: !rec_CplxQuad48 = !cir.union<"CplxQuad48" {data !cir.complex<!cir.f128>, data !cir.array<!s8i x 33>}, padding = {!cir.array<!u8i x 16>}> +// CIR-DAG: !rec_VecTailPad = !cir.union<"VecTailPad" {data !cir.vector<4 x !cir.float>, data !cir.array<!s8i x 17>}, padding = {!cir.array<!u8i x 16>}> +// CIR-DAG: !rec_NarrowVec = !cir.union<"NarrowVec" {data !cir.vector<2 x !cir.float>, data !cir.array<!s8i x 17>}, padding = {!cir.array<!u8i x 16>}> + +// LLVM-DAG: %struct.Large = type { ptr, ptr, i32 } +// LLVM-DAG: %union.Expected = type { ptr, [104 x i8] } +// LLVM-DAG: %union.DenseMap = type { %struct.Large, [16 x i8] } +// LLVM-DAG: %union.Odd24 = type { i64, [16 x i8] } +// LLVM-DAG: %struct.WrapsOdd24 = type { %union.Odd24, i8 } +// LLVM-DAG: %struct.ArrOfUnion = type { [2 x %union.Odd24] } +// LLVM-DAG: %union.NestUnion = type { %union.Odd24, [16 x i8] } +// LLVM-DAG: %union.OverAligned32 = type { i32, [28 x i8] } +// LLVM-DAG: %union.Quad32 = type { fp128, [16 x i8] } +// LLVM-DAG: %union.Quad64 = type { fp128, [48 x i8] } +// LLVM-DAG: %union.Quad80 = type { fp128, [64 x i8] } +// LLVM-DAG: %union.CplxQuad48 = type { { fp128, fp128 }, [16 x i8] } +// LLVM-DAG: %union.VecTailPad = type { <4 x float>, [16 x i8] } +// LLVM-DAG: %union.NarrowVec = type { <2 x float>, [16 x i8] } + +// 112 bytes against members of 110 and 8, so nothing spans the union. +void take_expected(Expected u) {} +// CIR: cir.func{{.*}} @take_expected(%arg0: !cir.ptr<!rec_Expected> {llvm.align = 8 : i64, llvm.byval = !rec_Expected, llvm.noundef} loc{{.*}}) +// LLVM: define{{.*}} void @take_expected(ptr noundef byval(%union.Expected) align 8 %{{.+}}) + +// 40 bytes against members of 36 and 24. +void take_densemap(DenseMap u) {} +// CIR: cir.func{{.*}} @take_densemap(%arg0: !cir.ptr<!rec_DenseMap> {llvm.align = 8 : i64, llvm.byval = !rec_DenseMap, llvm.noundef} loc{{.*}}) +// LLVM: define{{.*}} void @take_densemap(ptr noundef byval(%union.DenseMap) align 8 %{{.+}}) + +// 24 bytes against members of 17 and 8. +void take_odd24(Odd24 u) {} +// CIR: cir.func{{.*}} @take_odd24(%arg0: !cir.ptr<!rec_Odd24> {llvm.align = 8 : i64, llvm.byval = !rec_Odd24, llvm.noundef} loc{{.*}}) +// LLVM: define{{.*}} void @take_odd24(ptr noundef byval(%union.Odd24) align 8 %{{.+}}) + +// The union reaches the classifier as a member of an enclosing struct. +void take_wraps_odd24(WrapsOdd24 u) {} +// CIR: cir.func{{.*}} @take_wraps_odd24(%arg0: !cir.ptr<!rec_WrapsOdd24> {llvm.align = 8 : i64, llvm.byval = !rec_WrapsOdd24, llvm.noundef} loc{{.*}}) +// LLVM: define{{.*}} void @take_wraps_odd24(ptr noundef byval(%struct.WrapsOdd24) align 8 %{{.+}}) + +// Reached through an array member. +void take_arr_of_union(ArrOfUnion s) {} +// CIR: cir.func{{.*}} @take_arr_of_union(%arg0: !cir.ptr<!rec_ArrOfUnion> {llvm.align = 8 : i64, llvm.byval = !rec_ArrOfUnion, llvm.noundef} loc{{.*}}) +// LLVM: define{{.*}} void @take_arr_of_union(ptr noundef byval(%struct.ArrOfUnion) align 8 %{{.+}}) + +// Reached through another union of the same kind. +void take_nest_union(NestUnion u) {} +// CIR: cir.func{{.*}} @take_nest_union(%arg0: !cir.ptr<!rec_NestUnion> {llvm.align = 8 : i64, llvm.byval = !rec_NestUnion, llvm.noundef} loc{{.*}}) +// LLVM: define{{.*}} void @take_nest_union(ptr noundef byval(%union.NestUnion) align 8 %{{.+}}) + +// 20 bytes, with a named bit-field access unit no spanning member supplies +// data for. Past two eightbytes the size settles that too. +void take_bitfield_big(BitFieldBig u) {} +// CIR: cir.func{{.*}} @take_bitfield_big(%arg0: !cir.ptr<!rec_BitFieldBig> {llvm.align = 8 : i64, llvm.byval = !rec_BitFieldBig, llvm.noundef} loc{{.*}}) +// LLVM: define{{.*}} void @take_bitfield_big(ptr noundef byval(%union.BitFieldBig) align 8 %{{.+}}) + +// The declared alignment, not any member, is what put this past two +// eightbytes. +void take_over_aligned32(OverAligned32 u) {} +// CIR: cir.func{{.*}} @take_over_aligned32(%arg0: !cir.ptr<!rec_OverAligned32> {llvm.align = 32 : i64, llvm.byval = !rec_OverAligned32, llvm.noundef} loc{{.*}}) +// LLVM: define{{.*}} void @take_over_aligned32(ptr noundef byval(%union.OverAligned32) align 32 %{{.+}}) + +// 32 bytes against members of 16 and 17. The quad reaches SSEUP, so with AVX +// the coerce is named from the union's size, and without it memory. +void take_quad32(Quad32 u) {} +// CIR-SSE: cir.func{{.*}} @take_quad32(%arg0: !cir.ptr<!rec_Quad32> {llvm.align = 16 : i64, llvm.byval = !rec_Quad32, llvm.noundef} loc{{.*}}) +// CIR-AVX: cir.func{{.*}} @take_quad32(%arg0: !cir.vector<4 x !cir.double> loc{{.*}}) +// LLVM-SSE: define{{.*}} void @take_quad32(ptr noundef byval(%union.Quad32) align 16 %{{.+}}) +// LLVM-AVX: define{{.*}} void @take_quad32(<4 x double> %{{.+}}) + +// 64 bytes, the widest size an SSEUP coerce can be named from. +void take_quad64(Quad64 u) {} +// CIR-SSE: cir.func{{.*}} @take_quad64(%arg0: !cir.ptr<!rec_Quad64> {llvm.align = 16 : i64, llvm.byval = !rec_Quad64, llvm.noundef} loc{{.*}}) +// CIR-AVX: cir.func{{.*}} @take_quad64(%arg0: !cir.vector<8 x !cir.double> loc{{.*}}) +// LLVM-SSE: define{{.*}} void @take_quad64(ptr noundef byval(%union.Quad64) align 16 %{{.+}}) +// LLVM-AVX: define{{.*}} void @take_quad64(<8 x double> %{{.+}}) + +// 80 bytes, past 512, so the quad member cannot put it in registers. +void take_quad80(Quad80 u) {} +// CIR: cir.func{{.*}} @take_quad80(%arg0: !cir.ptr<!rec_Quad80> {llvm.align = 16 : i64, llvm.byval = !rec_Quad80, llvm.noundef} loc{{.*}}) +// LLVM: define{{.*}} void @take_quad80(ptr noundef byval(%union.Quad80) align 16 %{{.+}}) + +// A complex quad reaches SSEUP nowhere, so its 48 bytes are not refused. +void take_cplx_quad48(CplxQuad48 u) {} +// CIR: cir.func{{.*}} @take_cplx_quad48(%arg0: !cir.ptr<!rec_CplxQuad48> {llvm.align = 16 : i64, llvm.byval = !rec_CplxQuad48, llvm.noundef} loc{{.*}}) +// LLVM: define{{.*}} void @take_cplx_quad48(ptr noundef byval(%union.CplxQuad48) align 16 %{{.+}}) + +// A vector reaches SSEUP too, here with nothing spanning the union. +void take_vec_tail_pad(VecTailPad u) {} +// CIR-SSE: cir.func{{.*}} @take_vec_tail_pad(%arg0: !cir.ptr<!rec_VecTailPad> {llvm.align = 16 : i64, llvm.byval = !rec_VecTailPad, llvm.noundef} loc{{.*}}) +// CIR-AVX: cir.func{{.*}} @take_vec_tail_pad(%arg0: !cir.vector<4 x !cir.double> loc{{.*}}) +// LLVM-SSE: define{{.*}} void @take_vec_tail_pad(ptr noundef byval(%union.VecTailPad) align 16 %{{.+}}) +// LLVM-AVX: define{{.*}} void @take_vec_tail_pad(<4 x double> %{{.+}}) + +// A vector narrower than 128 bits never reaches SSEUP, so this union is +// classified from its size at every target. +void take_narrow_vec(NarrowVec u) {} +// CIR: cir.func{{.*}} @take_narrow_vec(%arg0: !cir.ptr<!rec_NarrowVec> {llvm.align = 8 : i64, llvm.byval = !rec_NarrowVec, llvm.noundef} loc{{.*}}) +// LLVM: define{{.*}} void @take_narrow_vec(ptr noundef byval(%union.NarrowVec) align 8 %{{.+}}) + +Expected ret_expected(Expected u) { return u; } +// CIR: cir.func{{.*}} @ret_expected(%arg0: !cir.ptr<!rec_Expected> {llvm.align = 8 : i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = !rec_Expected, llvm.writable} loc{{.*}}, %arg1: !cir.ptr<!rec_Expected> {llvm.align = 8 : i64, llvm.byval = !rec_Expected, llvm.noundef} loc{{.*}}) +// LLVM: define{{.*}} void @ret_expected(ptr dead_on_unwind noalias writable sret(%union.Expected) align 8 %{{[^,]+}}, ptr noundef byval(%union.Expected) align 8 %{{.+}}) + +Odd24 ret_odd24(Odd24 u) { return u; } +// CIR: cir.func{{.*}} @ret_odd24(%arg0: !cir.ptr<!rec_Odd24> {llvm.align = 8 : i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = !rec_Odd24, llvm.writable} loc{{.*}}, %arg1: !cir.ptr<!rec_Odd24> {llvm.align = 8 : i64, llvm.byval = !rec_Odd24, llvm.noundef} loc{{.*}}) +// LLVM: define{{.*}} void @ret_odd24(ptr dead_on_unwind noalias writable sret(%union.Odd24) align 8 %{{[^,]+}}, ptr noundef byval(%union.Odd24) align 8 %{{.+}}) + +void call_odd24(Odd24 u) { take_odd24(u); } +// CIR: cir.func{{.*}} @call_odd24(%arg0: !cir.ptr<!rec_Odd24> {llvm.align = 8 : i64, llvm.byval = !rec_Odd24, llvm.noundef} loc{{.*}}) +// CIR: %[[SLOT:.*]] = cir.alloca "byval" align(8) : !cir.ptr<!rec_Odd24> +// CIR: cir.call @take_odd24(%[[SLOT]]) +// LLVM: define{{.*}} void @call_odd24(ptr noundef byval(%union.Odd24) align 8 %{{.+}}) +// LLVM: call void @take_odd24(ptr noundef byval(%union.Odd24) align 8 %{{.+}}) 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 ebf11d1fa4865..e467c154cc031 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 @@ -43,6 +43,27 @@ !cir.union<"UWideBitUnnamed" {bitfield !cir.bitfield<!cir.array<!u8i x 3>, [#cir.bitfield_decl<!s32i, 24>]>, empty !cir.bitfield<!u64i, [#cir.bitfield_decl<!s64i, 64, unnamed>]>}> !rec_UEmptyNarrow = !cir.union<"UEmptyNarrow" {data !rec_E, data !s16i}, padding = {!cir.array<!u8i x 2>}> +!rec_SVec = !cir.struct<"SVec" {data !cir.vector<4 x !cir.float>}> +!rec_UVec384 = + !cir.union<"UVec384" {data !cir.vector<4 x !cir.float>, data !cir.array<!s8i x 33>}, + padding = {!cir.array<!u8i x 32>}> +!rec_UVecInArr = + !cir.union<"UVecInArr" {data !cir.array<!cir.vector<4 x !cir.float> x 2>, + data !cir.array<!s8i x 33>}, + padding = {!cir.array<!u8i x 16>}> +!rec_UVecInRec = + !cir.union<"UVecInRec" {data !rec_SVec, data !cir.array<!s8i x 33>}, + padding = {!cir.array<!u8i x 32>}> +!rec_UQuad384 = + !cir.union<"UQuad384" {data !cir.f128, data !cir.array<!s8i x 33>}, + padding = {!cir.array<!u8i x 32>}> +!rec_ULongDouble384 = + !cir.union<"ULongDouble384" {data !cir.long_double<!cir.f128>, + data !cir.array<!s8i x 33>}, + padding = {!cir.array<!u8i x 32>}> +!rec_UQuadPacked = + !cir.union<"UQuadPacked" packed {data !cir.f128, data !cir.array<!s8i x 17>}, + padding = {!u8i}> !rec_S1 = !cir.struct<"S1" {data !s16i, data !s16i, data !s16i}> !rec_AtomicWrapper = !cir.struct<{data !rec_S1, pad !cir.array<!s8i x 2>}> !rec_HoldsAllPad = !cir.struct<"HoldsAllPad" {data !s32i, empty !rec_E}> @@ -192,6 +213,49 @@ module attributes { // CHECK: not yet implemented for type '!cir.union<"UEmptyNarrow" + // 48 bytes with a vector member, a size no SSEUP coerce can be named from. + cir.func @take_vec384_union(%arg0: !rec_UVec384) { + cir.return + } + + // CHECK: not yet implemented for type '!cir.union<"UVec384" + + // The same vector, an array away. + cir.func @take_vec_in_arr_union(%arg0: !rec_UVecInArr) { + cir.return + } + + // CHECK: not yet implemented for type '!cir.union<"UVecInArr" + + // The same vector, a record away. + cir.func @take_vec_in_rec_union(%arg0: !rec_UVecInRec) { + cir.return + } + + // CHECK: not yet implemented for type '!cir.union<"UVecInRec" + + // An IEEE-quad float reaches SSEUP as well. + cir.func @take_quad384_union(%arg0: !rec_UQuad384) { + cir.return + } + + // CHECK: not yet implemented for type '!cir.union<"UQuad384" + + // A long double carrying quad semantics is the same float. + cir.func @take_long_double384_union(%arg0: !rec_ULongDouble384) { + cir.return + } + + // CHECK: not yet implemented for type '!cir.union<"ULongDouble384" + + // Packed, so 17 bytes, another size no coerce can be named from. Its + // 17-byte member spans the union, so only refusing the size catches it. + cir.func @take_quad_packed_union(%arg0: !rec_UQuadPacked) { + cir.return + } + + // CHECK: not yet implemented for type '!cir.union<"UQuadPacked" + // A bit-field carries the type its declaration named, so a record whose // padding a coercion would have to read through is classified rather than // refused; see x86_64-bitfield.cir. 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 d3d3ca779afb3..3229f2c079cf8 100644 --- a/clang/test/CIR/Transforms/abi-lowering/x86_64-union.cir +++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-union.cir @@ -6,6 +6,7 @@ !s8i = !cir.int<s, 8> !s32i = !cir.int<s, 32> !s64i = !cir.int<s, 64> +!s128i = !cir.int<s, 128> !u8i = !cir.int<u, 8> !rec_UIntFloat = !cir.union<"UIntFloat" {data !s32i, data !cir.float}> !rec_UFloatInt = !cir.union<"UFloatInt" {data !cir.float, data !s32i}> @@ -15,6 +16,9 @@ !rec_UThree = !cir.union<"UThree" {data !cir.array<!s8i x 3>}> !rec_UNarrowStorage = !cir.union<"UNarrowStorage" {data !s32i, data !cir.array<!s8i x 8>}, padding = {!cir.array<!u8i x 4>}> !rec_UTwoEightbytes = !cir.union<"UTwoEightbytes" {data !s64i, data !cir.array<!s8i x 16>}, padding = {!cir.array<!u8i x 8>}> +!rec_UTailPad = !cir.union<"UTailPad" {data !cir.array<!s8i x 17>, data !s64i}, padding = {!cir.array<!u8i x 16>}> +!rec_UTailPadNoRegs = !cir.union<"UTailPadNoRegs" {data !cir.array<!s8i x 17>, data !s64i}, padding = {!cir.array<!u8i x 16>}> +!rec_UInt384 = !cir.union<"UInt384" {data !s128i, data !cir.array<!s8i x 33>}, padding = {!cir.array<!u8i x 32>}> !rec_UBig = !cir.union<"UBig" {data !cir.array<!s8i x 32>}> !rec_UBigOverAligned = !cir.union<"UBigOverAligned" {data !cir.array<!s8i x 32>}> !rec_SOverAligned = !cir.struct<"SOverAligned" {data !cir.array<!s8i x 32>}> @@ -63,6 +67,12 @@ module attributes { UBitNoRegs = #cir.record_layout< arg_passing_kind = cannot_pass_in_regs, has_trivial_dtor = false, record_align = 4>, + UTailPadNoRegs = #cir.record_layout< + arg_passing_kind = cannot_pass_in_regs, has_trivial_dtor = false, + record_align = 8>, + UInt384 = #cir.record_layout< + arg_passing_kind = can_pass_in_regs, has_trivial_dtor = true, + record_align = 16>, UBigOverAligned = #cir.record_layout< arg_passing_kind = can_pass_in_regs, has_trivial_dtor = true, record_align = 32>, @@ -198,6 +208,33 @@ module attributes { // CHECK: cir.func{{.*}} @take_big(%arg0: !cir.ptr<!rec_UBig> {llvm.align = 8 : i64, llvm.byval = !rec_UBig, llvm.noundef}) // CHECK: %{{.*}} = cir.load %arg0 : !cir.ptr<!rec_UBig>, !rec_UBig + // Nothing spans this union's 24 declared bytes, the widest covering 17. No + // member can reach SSEUP, so its size alone sends it to memory. + cir.func @take_tail_pad(%arg0: !rec_UTailPad) { + cir.return + } + + // CHECK: cir.func{{.*}} @take_tail_pad(%arg0: !cir.ptr<!rec_UTailPad> {llvm.align = 8 : i64, llvm.byval = !rec_UTailPad, llvm.noundef}) + + cir.func private @return_tail_pad() -> !rec_UTailPad + + // CHECK: cir.func private @return_tail_pad(!cir.ptr<!rec_UTailPad> {llvm.align = 8 : i64, llvm.dead_on_unwind, llvm.sret = !rec_UTailPad, llvm.writable}) + + // Same shape, with a layout that forbids registers. + cir.func @take_tail_pad_no_regs(%arg0: !rec_UTailPadNoRegs) { + cir.return + } + + // CHECK: cir.func{{.*}} @take_tail_pad_no_regs(%arg0: !cir.ptr<!rec_UTailPadNoRegs> {llvm.align = 8 : i64, llvm.dereferenceable = 24 : i64, llvm.nofreeobj, llvm.noundef}) + + // 48 bytes with nothing spanning, accepted because an `__int128` member + // cannot reach SSEUP. + cir.func @take_int384(%arg0: !rec_UInt384) { + cir.return + } + + // CHECK: cir.func{{.*}} @take_int384(%arg0: !cir.ptr<!rec_UInt384> {llvm.align = 16 : i64, llvm.byval = !rec_UInt384, llvm.noundef}) + // The byval alignment comes from the record's declared alignment, which the // layout metadata carries because the members alone cannot express an // alignment attribute. Same members as take_big, alignment 32 rather than 8. @@ -584,6 +621,10 @@ module attributes { // LLVM: define void @take_narrow_storage(i64 %{{.+}}) // LLVM: define void @take_two_eightbytes(i64 %{{.+}}, i64 %{{.+}}) // LLVM: define void @take_big(ptr noundef byval(%union.UBig) align 8 %{{.+}}) +// LLVM: define void @take_tail_pad(ptr noundef byval(%union.UTailPad) align 8 %{{.+}}) +// LLVM: declare void @return_tail_pad(ptr dead_on_unwind writable sret(%union.UTailPad) align 8) +// LLVM: define void @take_tail_pad_no_regs(ptr nofreeobj noundef align 8 dereferenceable(24) %{{.+}}) +// LLVM: define void @take_int384(ptr noundef byval(%union.UInt384) align 16 %{{.+}}) // LLVM: define void @take_big_over_aligned(ptr noundef byval(%union.UBigOverAligned) align 32 %{{.+}}) // LLVM: define void @take_struct_over_aligned(ptr noundef byval(%struct.SOverAligned) align 32 %{{.+}}) // LLVM: define void @take_empty() >From 25e79c320b887cb5bb09fda178203df29010a8ec Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Tue, 15 Sep 2026 15:27:31 -0700 Subject: [PATCH 2/2] [CIR] Remove the union spanning precondition Bounding the rule at two eightbytes wasn't enough: a 16-byte union with a 12-byte member still got refused, which is the Expected<T> shape blocking the self-build. The rule was covering for a classifier bug, so it's gone, along with the query it needed. takeTailByteOrPtr needs #223859 and fails until that merges in. Assisted-by: Cursor / claude-opus-5 --- .../include/clang/CIR/Dialect/IR/CIRTypes.td | 5 - clang/lib/CIR/Dialect/IR/CIRTypes.cpp | 8 - .../Transforms/CallConvLoweringPass.cpp | 112 +++++-------- .../call-conv-lowering-x86_64-non-byval.cpp | 2 +- ...all-conv-lowering-x86_64-union-no-span.cpp | 152 ++++++++++++++++++ .../abi-lowering/x86_64-aggregate-nyi.cir | 96 ----------- 6 files changed, 197 insertions(+), 178 deletions(-) create mode 100644 clang/test/CIR/CodeGen/call-conv-lowering-x86_64-union-no-span.cpp diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td index 9d55ed059f049..4d09822460188 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td @@ -735,11 +735,6 @@ def CIR_BitFieldType : CIR_Type<"BitField", "bitfield", [ 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 5d678d5aee5bc..e4093000e8507 100644 --- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp @@ -1487,14 +1487,6 @@ 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 d25f3d0657d33..ce4acfaa275da 100644 --- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp +++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp @@ -89,10 +89,8 @@ static bool recordCanPassInRegs(ModuleOp modOp, cir::RecordType recTy) { return layout.getArgPassingKind() == cir::ArgPassingKind::CanPassInRegs; } -/// Whether the classifier could put this type, or one an array or record -/// holds, in the SSEUP class. Only a vector of 128 bits or wider and an -/// IEEE-quad float reach it. A complex quad does not, since the classifier -/// gives it memory. +/// Whether the classifier could give this type the SSEUP class, looking +/// through arrays and records at the types they hold. static bool mayReachSseUp(mlir::Type ty, const DataLayout &dl) { if (isa<cir::VectorType>(ty)) return dl.getTypeSizeInBits(ty).getFixedValue() >= 128; @@ -100,23 +98,27 @@ static bool mayReachSseUp(mlir::Type ty, const DataLayout &dl) { return &fpTy.getFloatSemantics() == &llvm::APFloat::IEEEquad(); if (auto arrTy = dyn_cast<cir::ArrayType>(ty)) return mayReachSseUp(arrTy.getElementType(), dl); - auto recTy = dyn_cast<cir::RecordType>(ty); - if (!recTy || !recTy.isComplete()) - return false; - return llvm::any_of(recTy.getMembers(), - [&](mlir::Type m) { return mayReachSseUp(m, dl); }); + if (auto recTy = dyn_cast<cir::RecordType>(ty)) + return recTy.isComplete() && + llvm::any_of(recTy.getMembers(), + [&](mlir::Type m) { return mayReachSseUp(m, dl); }); + // The rest classify integer, SSE or memory. A complex is SSE at float and + // double width and memory above that, so it needs no walk of its own. + assert((isa<cir::IntType, cir::BoolType, cir::PointerType, cir::VPtrType, + cir::VoidType, cir::ComplexType, cir::BitFieldType>(ty)) && + "unhandled type in the SSEUP walk"); + return false; } -/// Whether no SSEUP coerce could be named from the record's size. That coerce -/// is a vector as wide as the record, and only 128, 256 and 512 bits have one, -/// a size past 512 classifying memory before a coerce is asked for. A true -/// answer is conservative, since reaching SSEUP also takes a target whose -/// vectors are that wide. +/// Whether the record's size has no SSEUP coerce. That coerce is a vector as +/// wide as the record, so only 128, 256 and 512 bits have one. A record past +/// 512 bits classifies memory before any coerce is asked for. static bool sseUpCoerceSizeUnsupported(uint64_t recordBits, llvm::ArrayRef<mlir::Type> members, const DataLayout &dl) { - if (recordBits <= 128 || recordBits > 512 || recordBits == 256 || - recordBits == 512) + if (recordBits == 128 || recordBits == 256 || recordBits == 512) + return false; + if (recordBits < 128 || recordBits > 512) return false; return llvm::any_of(members, [&](mlir::Type m) { return mayReachSseUp(m, dl); }); @@ -242,61 +244,36 @@ static bool isSupportedType(mlir::Type ty, const DataLayout &dl) { // An incomplete record has no layout to classify. if (!recTy.isComplete()) return false; + // The members are checked first so that everything below reasons only + // about types the bridge handles. + if (!llvm::all_of(recTy.getMembers(), + [&](mlir::Type m) { return isSupportedType(m, dl); })) + return false; if (recTy.isUnion()) { - // The classifier sizes a union's eightbytes from the union itself, which - // is only sound when some member spans that size. Short of that, the - // remaining bytes are either tail padding or the rest of a bitfield - // storage unit, and the CIR type cannot tell those apart even though - // classic CodeGen coerces them to i32 and i8 respectively. llvm::ArrayRef<mlir::Type> members = recTy.getMembers(); uint64_t recordBits = dl.getTypeSizeInBits(recTy).getFixedValue(); - // A size no coerce can be named from is refused outright, since a - // spanning member would otherwise carry it past the checks below. + // Refuse a union that could reach SSEUP at a size no coerce exists for. + // The classifier asserts on those rather than returning a coerce. if (sseUpCoerceSizeUnsupported(recordBits, members, dl)) return false; - if (members.empty()) { - // A member-less union is all padding, which classifies Ignore up to two - // eightbytes. Past that SysV says MEMORY regardless of content, and - // there is no member here to build the Indirect coercion from. - if (recordBits > 128) - return false; - } else if (recordBits <= 128) { - // Within two eightbytes the members have to account for the union's - // bytes. Past them it classifies memory, or SSE then SSEUP with the - // coerce named from its size, so they do not. - - // 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) { - 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; - // A bit-field's access unit may be wider than the bits the field - // holds, so some member (that bit-field or another one) must both - // match the union's size and hold data. - llvm::ArrayRef<cir::RecordMemberKind> kinds = recTy.getMemberKinds(); - if (llvm::any_of(kinds, cir::isNamedBitField) && - !llvm::any_of( - llvm::zip_equal(members, kinds), [&](const auto &pair) { - auto [memberTy, kind] = pair; - return spansRecord(memberTy) && cir::holdsDataForABI(kind) && - !memberIsEmptyRecord(memberTy); - })) - return false; - } + // A member-less union is all padding, which classifies Ignore up to two + // eightbytes. Past that SysV says MEMORY regardless of content, and + // there is no member here to build the Indirect coercion from. + if (members.empty() && recordBits > 128) + return false; + // A `_BitInt` access unit past an eightbyte has byte-array storage in + // CIR and integer storage in classic CodeGen, so the narrowing walk + // finds an i8 in the second eightbyte where classic keeps an i64. + if (llvm::any_of(members, [&](mlir::Type m) { + auto bfTy = dyn_cast<cir::BitFieldType>(m); + if (!bfTy || dl.getTypeSizeInBits(bfTy).getFixedValue() <= 64) + return false; + return llvm::any_of(bfTy.getFields(), [](cir::BitFieldDeclAttr d) { + auto intTy = dyn_cast<cir::IntType>(d.getDeclaredType()); + return intTy && intTy.getIsBitInt(); + }); + })) + return false; } // An `empty` member that occupies bytes is later read as an unnamed // bit-field. One that is itself an empty-for-ABI record can occupy bytes @@ -316,8 +293,7 @@ static bool isSupportedType(mlir::Type ty, const DataLayout &dl) { return false; } } - return llvm::all_of(recTy.getMembers(), - [&](mlir::Type m) { return isSupportedType(m, dl); }); + return true; } return false; } diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-non-byval.cpp b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-non-byval.cpp index 02323a8b96843..374ad6683d66d 100644 --- a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-non-byval.cpp +++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-non-byval.cpp @@ -137,7 +137,7 @@ void takeTailPadNoRegs(TailPadNoRegs u) {} // CIR-LABEL: cir.func {{.*}}@_Z17takeTailPadNoRegs13TailPadNoRegs // CIR-SAME: %{{[^:]*}}: !cir.ptr<!rec_TailPadNoRegs> {llvm.align = 8 : i64, llvm.dereferenceable = 24 : i64, llvm.nofreeobj, llvm.noundef} -// LLVM: define dso_local void @_Z17takeTailPadNoRegs13TailPadNoRegs(ptr nofreeobj noundef align 8 dereferenceable(24) %{{.+}}) +// LLVM: define dso_local void @_Z17takeTailPadNoRegs13TailPadNoRegs(ptr nofreeobj noundef align 8 dereferenceable(24) %{{[^,]+}}) struct Base { Base(WithDtor t); }; struct Derived : Base { using Base::Base; }; diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-union-no-span.cpp b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-union-no-span.cpp new file mode 100644 index 0000000000000..680abe653ab27 --- /dev/null +++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-union-no-span.cpp @@ -0,0 +1,152 @@ +// 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 Empty {}; + +// The declared alignment stretches a 4-byte member over 16 bytes, and the +// eightbyte narrows to the member because the rest holds nothing. +union OverAligned { int i; } __attribute__((aligned(16))); +void takeOverAligned(OverAligned u) {} +// CIR: cir.func{{.*}} @_Z15takeOverAligned11OverAligned(%arg0: !s32i loc +// LLVM: define{{.*}} void @_Z15takeOverAligned11OverAligned(i32 %{{.+}}) + +// Three of the four declared bytes hold data, which rounds up to i32. +union ShortStorage { short s; char c[3]; }; +void takeShortStorage(ShortStorage u) {} +// CIR: cir.func{{.*}} @_Z16takeShortStorage12ShortStorage(%arg0: !u32i loc +// LLVM: define{{.*}} void @_Z16takeShortStorage12ShortStorage(i32 %{{.+}}) + +// One byte of data in four declared bytes. A union of narrow bit-fields has +// the same size and coerces to i32 instead, which take_bit_extent below pins. +union ByteBlobs { unsigned char c, d; } __attribute__((aligned(4))); +void takeByteBlobs(ByteBlobs u) {} +// CIR: cir.func{{.*}} @_Z13takeByteBlobs9ByteBlobs(%arg0: !u8i loc +// LLVM: define{{.*}} void @_Z13takeByteBlobs9ByteBlobs(i8 %{{.+}}) + +union PadByte { unsigned char c; } __attribute__((aligned(4))); +void takePadByte(PadByte u) {} +// CIR: cir.func{{.*}} @_Z11takePadByte7PadByte(%arg0: !u8i loc +// LLVM: define{{.*}} void @_Z11takePadByte7PadByte(i8 %{{.+}}) + +// The same union reached as a member, where the enclosing struct is too large +// for registers. +struct WrapsOverAligned { double d; OverAligned u; }; +void takeWrapsOverAligned(WrapsOverAligned s) {} +// CIR: cir.func{{.*}} @_Z20takeWrapsOverAligned16WrapsOverAligned(%arg0: !cir.ptr<!rec_WrapsOverAligned> {llvm.align = 16 : i64, llvm.byval = !rec_WrapsOverAligned, llvm.noundef} loc +// LLVM: define{{.*}} void @_Z20takeWrapsOverAligned16WrapsOverAligned(ptr noundef byval(%struct.WrapsOverAligned) align 16 %{{.+}}) + +// A bit-field access unit of one byte, in a union the alignment stretches to +// eight. +union BitOverAligned { int x : 3; } __attribute__((aligned(8))); +void takeBitOverAligned(BitOverAligned u) {} +// CIR: cir.func{{.*}} @_Z18takeBitOverAligned14BitOverAligned(%arg0: !u64i loc +// LLVM: define{{.*}} void @_Z18takeBitOverAligned14BitOverAligned(i64 %{{.+}}) + +// A declared type wider than the union's first eightbyte. +union WideDecl { __int128 x : 100; }; +void takeWideDecl(WideDecl u) {} +// CIR: cir.func{{.*}} @_Z12takeWideDecl8WideDecl(%arg0: !u64i loc{{.*}}, %arg1: !u64i loc +// LLVM: define{{.*}} void @_Z12takeWideDecl8WideDecl(i64 %{{[^,]+}}, i64 %{{.+}}) + +// One access unit holding two declarations. +union MultiDecl { char a : 4; int b : 4; }; +void takeMultiDecl(MultiDecl u) {} +// CIR: cir.func{{.*}} @_Z13takeMultiDecl9MultiDecl(%arg0: !u32i loc +// LLVM: define{{.*}} void @_Z13takeMultiDecl9MultiDecl(i32 %{{.+}}) + +// A named unit beside an unnamed one that reaches further. +union NamedPlusUnnamed { int x : 3; long long : 40; }; +void takeNamedPlusUnnamed(NamedPlusUnnamed u) {} +// CIR: cir.func{{.*}} @_Z20takeNamedPlusUnnamed16NamedPlusUnnamed(%arg0: !u64i loc +// LLVM: define{{.*}} void @_Z20takeNamedPlusUnnamed16NamedPlusUnnamed(i64 %{{.+}}) + +union BitUnnamed { int x : 8; long long : 64; }; +void takeBitUnnamed(BitUnnamed u) {} +// CIR: cir.func{{.*}} @_Z14takeBitUnnamed10BitUnnamed(%arg0: !u64i loc +// LLVM: define{{.*}} void @_Z14takeBitUnnamed10BitUnnamed(i64 %{{.+}}) + +union WideBitUnnamed { int x : 24; long long : 64; }; +void takeWideBitUnnamed(WideBitUnnamed u) {} +// CIR: cir.func{{.*}} @_Z18takeWideBitUnnamed14WideBitUnnamed(%arg0: !u64i loc +// LLVM: define{{.*}} void @_Z18takeWideBitUnnamed14WideBitUnnamed(i64 %{{.+}}) + +// An empty member supplies no bytes, so the short is what the eightbyte is +// sized from. +union EmptyNarrow { Empty e; short s; }; +void takeEmptyNarrow(EmptyNarrow u) {} +// CIR: cir.func{{.*}} @_Z15takeEmptyNarrow11EmptyNarrow(%arg0: !s16i loc +// LLVM: define{{.*}} void @_Z15takeEmptyNarrow11EmptyNarrow(i16 %{{.+}}) + +// A union of narrow bit-fields, for contrast with takeByteBlobs above. +union BitExtent { unsigned a : 1; unsigned b : 1; }; +void takeBitExtent(BitExtent u) {} +// CIR: cir.func{{.*}} @_Z13takeBitExtent9BitExtent(%arg0: !u32i loc +// LLVM: define{{.*}} void @_Z13takeBitExtent9BitExtent(i32 %{{.+}}) + +// The array member covers 12 of the union's 16 declared bytes, and the pointer +// member sets the alignment that rounds it up. +union PayloadOrPtr { unsigned Words[3]; void *Ptr; }; +PayloadOrPtr byValue(PayloadOrPtr x) { return x; } +// CIR: cir.func{{.*}} @_Z7byValue12PayloadOrPtr(%arg0: !cir.ptr<!void> loc{{.*}}, %arg1: !u64i loc{{.*}}) -> !rec_anon_struct +// LLVM: define{{.*}} { ptr, i64 } @_Z7byValue12PayloadOrPtr(ptr %{{[^,]+}}, i64 %{{.+}}) + +// The same shape with a member that does cover all 16 bytes, which coerces +// identically. +union PayloadOrPtr16 { unsigned Words[4]; void *Ptr; }; +PayloadOrPtr16 byValue16(PayloadOrPtr16 x) { return x; } +// CIR: cir.func{{.*}} @_Z9byValue1614PayloadOrPtr16(%arg0: !cir.ptr<!void> loc{{.*}}, %arg1: !u64i loc{{.*}}) -> !rec_anon_struct +// LLVM: define{{.*}} { ptr, i64 } @_Z9byValue1614PayloadOrPtr16(ptr %{{[^,]+}}, i64 %{{.+}}) + +// Nine of the union's 16 bytes hold data, so the second eightbyte narrows to +// the single byte there rather than spanning the tail. +union TailByteOrPtr { char Bytes[9]; void *Ptr; }; +void takeTailByteOrPtr(TailByteOrPtr x) {} +// CIR: cir.func{{.*}} @_Z17takeTailByteOrPtr13TailByteOrPtr(%arg0: !cir.ptr<!void> loc{{.*}}, %arg1: !u8i loc +// LLVM: define{{.*}} void @_Z17takeTailByteOrPtr13TailByteOrPtr(ptr %{{[^,]+}}, i8 %{{.+}}) + +struct ErrorInfoBase; +struct UniquePtrLike { ErrorInfoBase *Ptr; }; +struct Payload { unsigned A, B, C; }; + +// A 16-byte union no member covers, under a bit-field unit that pushes the +// record out of registers. +struct ExpectedLike { + union { Payload TStorage; UniquePtrLike ErrorStorage; }; + bool HasError : 1; + bool Unchecked : 1; +}; +void takeExpectedLike(ExpectedLike); +ExpectedLike returnExpectedLike(unsigned V) { + ExpectedLike R{}; + R.TStorage.A = V; + return R; +} +// CIR: cir.func{{.*}} @_Z18returnExpectedLikej(%arg0: !cir.ptr<!rec_ExpectedLike> {llvm.align = 8 : i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = !rec_ExpectedLike, llvm.writable} +// LLVM: define{{.*}} void @_Z18returnExpectedLikej(ptr dead_on_unwind noalias writable sret(%struct.ExpectedLike) align 8 %{{[^,]+}}, i32 noundef %{{.+}}) + +// A 16-byte union with no bit-fields, inside a larger record. +struct Rec; +struct ResOperand { + enum { RenderAsmOperand, TiedOperand } Kind; + struct TiedOperandsTuple { unsigned ResOpnd, SrcOpnd1Idx, SrcOpnd2Idx; }; + union { + unsigned AsmOperandNum; + TiedOperandsTuple TiedOperands; + long long ImmVal; + const Rec *Register; + }; + unsigned MINumOperands; +}; +ResOperand getTiedOp(unsigned Tied) { + ResOperand X{}; + X.Kind = ResOperand::TiedOperand; + X.AsmOperandNum = Tied; + X.MINumOperands = 1; + return X; +} +// CIR: cir.func{{.*}} @_Z9getTiedOpj(%arg0: !cir.ptr<!rec_ResOperand> {llvm.align = 8 : i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = !rec_ResOperand, llvm.writable} +// LLVM: define{{.*}} void @_Z9getTiedOpj(ptr dead_on_unwind noalias writable sret(%struct.ResOperand) align 8 %{{[^,]+}}, i32 noundef %{{.+}}) 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 e467c154cc031..8a35bde4f3d7e 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 @@ -77,39 +77,9 @@ module attributes { #dlti.dl_entry<f64, dense<64>: vector<2xi64>>> } { - // No member of this union spans its 16-byte declared size, so the bytes past - // the int cannot be told apart from the rest of a wider storage unit, and the - // eightbyte the classifier would build from the union's size is a guess. - cir.func @take_over_aligned_union(%arg0: !rec_UOverAligned) { - cir.return - } - - // CHECK: not yet implemented for type '!cir.union<"UOverAligned" - - // Same rule one eightbyte down: the widest member covers 3 of the union's 4 - // declared bytes. - cir.func @take_short_storage_union(%arg0: !rec_UShortStorage) { - cir.return - } - - // CHECK: not yet implemented for type '!cir.union<"UShortStorage" - - // A union of narrow bit-fields and a union of two aligned `unsigned char` - // members produce this same shape, and classic CodeGen coerces them - // differently, so neither can be accepted. - cir.func @take_byte_blob_union(%arg0: !rec_UByteBlobs) { - cir.return - } - // CHECK: not yet implemented for type '!cir.union<"UByteBlobs" - // The reject propagates out of an enclosing struct rather than being silently - // dropped at the member level. - cir.func @take_struct_wrapping_over_aligned(%arg0: !rec_SWrapsOverAligned) { - cir.return - } - // CHECK: not yet implemented for type '!cir.struct<"SWrapsOverAligned" // A member-less union past two eightbytes still classifies Indirect in // classic CodeGen, but this bridge has no member to build an Indirect @@ -130,22 +100,7 @@ module attributes { // CHECK: not yet implemented for type '!cir.struct<"ZeroLenArr" packed - // A union holds padding in a slot of its own, so its lone byte member is - // data. Rejected by the union rule (no member spans the declared size), not - // the empty-class one. - cir.func @take_padded_byte_union(%arg0: !rec_UPadByte) { - cir.return - } - - // CHECK: not yet implemented for type '!cir.union<"UPadByte" - - // 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<"UBitOverAligned" // Past one eightbyte the declared extent settles nothing: the coercion // follows this 9-byte unit and would give i8 for the second eightbyte, @@ -156,62 +111,11 @@ module attributes { // 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 - // coerce to i8 where classic gives i64. - cir.func @take_bitfield_unnamed_span(%arg0: !rec_UBitUnnamed) { - cir.return - } - // CHECK: not yet implemented for type '!cir.union<"UBitUnnamed" - // The same shape with a wider named unit, whose coercion happens to reach - // classic's i64 anyway once the union sizes its eightbyte. The rule cannot - // tell that apart from the case above, where the narrower unit coerces to - // i8, so this one is NYI as well. - cir.func @take_wide_bitfield_unnamed_span(%arg0: !rec_UWideBitUnnamed) { - cir.return - } - - // CHECK: not yet implemented for type '!cir.union<"UWideBitUnnamed" - - // An empty member does not exempt a union from the spanning rule above: - // as with take_short_storage_union, neither member reaches the union's - // 4 declared bytes. - cir.func @take_empty_narrow_union(%arg0: !rec_UEmptyNarrow) { - cir.return - } - // CHECK: not yet implemented for type '!cir.union<"UEmptyNarrow" // 48 bytes with a vector member, a size no SSEUP coerce can be named from. cir.func @take_vec384_union(%arg0: !rec_UVec384) { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
