[clang] [CIR] Accept a union whose members do not cover its declared size (PR #223594)
Adam Smith via cfe-commits
cfe-commits at lists.llvm.org
Wed Sep 16 09:03:21 PDT 2026
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 <adams at nvidia.com>
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 <adams at nvidia.com>
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) {
More information about the cfe-commits
mailing list