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

Reply via email to