https://github.com/adams381 created 
https://github.com/llvm/llvm-project/pull/214129

Passing a union to a function does not work. The x86_64 bridge rejects every 
one, so the pass fails on any signature naming a union. Additionally, indirect 
arguments have their byval and sret alignment wrong, because `mapCIRType` asks 
DataLayout for it, and DataLayout only sees a record's members, never 
`__attribute__((aligned(N)))`.

Unions now go through the ABI library's union type, which puts every member at 
offset zero and sizes each eightbyte from the union rather than from a single 
member. The alignment comes from the record-layout metadata the AST already 
fills in, which fixes over-aligned structs too, since they share that lookup.

Assisted-by: Cursor / claude-opus-5


>From b4754df8f2ea91af527a938100ca854e49581375 Mon Sep 17 00:00:00 2001
From: Adam Smith <[email protected]>
Date: Tue, 4 Aug 2026 20:50:26 -0700
Subject: [PATCH] [CIR] Accept unions in x86_64 calling-convention lowering

Passing a union to a function does not work.  The x86_64 bridge rejects every
one, so the pass fails on any signature naming a union.  Additionally, indirect
arguments have their byval and sret alignment wrong, because `mapCIRType` asks
DataLayout for it, and DataLayout only sees a record's members, never
`__attribute__((aligned(N)))`.

Unions now go through the ABI library's union type, which puts every member at
offset zero and sizes each eightbyte from the union rather than from a single
member.  The alignment comes from the record-layout metadata the AST already
fills in, which fixes over-aligned structs too, since they share that lookup.

Assisted-by: Cursor / claude-opus-5
---
 .../include/clang/CIR/Dialect/IR/CIRDialect.h |   5 +
 clang/lib/CIR/Dialect/IR/CIRAttrs.cpp         |  16 +-
 .../Transforms/CallConvLoweringPass.cpp       | 123 +++++---
 .../CIR/CodeGen/call-conv-lowering-x86_64.c   |  84 ++++++
 .../abi-lowering/x86_64-aggregate-nyi.cir     |  82 ++++-
 .../x86_64-union-coerce-shapes.cir            |  80 +++++
 .../Transforms/abi-lowering/x86_64-union.cir  | 280 ++++++++++++++++++
 7 files changed, 617 insertions(+), 53 deletions(-)
 create mode 100644 
clang/test/CIR/Transforms/abi-lowering/x86_64-union-coerce-shapes.cir
 create mode 100644 clang/test/CIR/Transforms/abi-lowering/x86_64-union.cir

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.h 
b/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
index c6f6c80206bfe..8cd70bf47ce5a 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
@@ -84,6 +84,11 @@ class FenvOpTrait : public 
mlir::OpTrait::TraitBase<ConcreteType, FenvOpTrait> {
 /// Look up the RecordLayoutAttr for a named record in the module's
 /// cir.record_layouts dictionary.  Asserts if the entry is missing.
 RecordLayoutAttr getRecordLayout(mlir::ModuleOp module, mlir::StringAttr name);
+
+/// Same lookup as getRecordLayout, but returns a null attribute instead of
+/// asserting when the record has no layout entry.
+RecordLayoutAttr tryGetRecordLayout(mlir::ModuleOp module,
+                                    mlir::StringAttr name);
 } // namespace cir
 
 // TableGen'erated files for MLIR dialects require that a macro be defined when
diff --git a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp 
b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
index 264e836718c81..74a3deb5b2508 100644
--- a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
@@ -920,12 +920,20 @@ LogicalResult DynamicCastInfoAttr::verify(
 // RecordLayout lookup
 
//===----------------------------------------------------------------------===//
 
-RecordLayoutAttr cir::getRecordLayout(mlir::ModuleOp module,
-                                      mlir::StringAttr name) {
+RecordLayoutAttr cir::tryGetRecordLayout(mlir::ModuleOp module,
+                                         mlir::StringAttr name) {
+  if (!name)
+    return {};
   auto dict = module->getAttrOfType<mlir::DictionaryAttr>(
       CIRDialect::getRecordLayoutsAttrName());
-  assert(dict && "module missing cir.record_layouts attribute");
-  auto attr = dict.getAs<RecordLayoutAttr>(name);
+  if (!dict)
+    return {};
+  return dict.getAs<RecordLayoutAttr>(name);
+}
+
+RecordLayoutAttr cir::getRecordLayout(mlir::ModuleOp module,
+                                      mlir::StringAttr name) {
+  RecordLayoutAttr attr = tryGetRecordLayout(module, name);
   assert(attr && "record layout entry missing for named record");
   return attr;
 }
diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp 
b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
index 54c4487db6195..1baa145251bc9 100644
--- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
@@ -68,10 +68,11 @@ namespace {
 // SysV x86_64 classifier, and converts the result back into the
 // dialect-agnostic mlir::abi::FunctionClassification that CIRABIRewriteContext
 // consumes.  Integer (including `_BitInt` up to 128 bits) / pointer / bool /
-// f32 / f64 scalars and struct / array aggregates are handled.  Unions,
-// `_Complex`, vectors, wider floats, and packed or padded records are reported
-// NYI by classifyX86_64Function so an unsupported signature fails the pass
-// instead of being misclassified.
+// f32 / f64 scalars and struct / union / array aggregates are handled.
+// `_Complex`, vectors, wider floats, packed or padded records, and a union no
+// member of which spans its declared size are reported NYI by
+// classifyX86_64Function so an unsupported signature fails the pass instead of
+// being misclassified.
 
//===----------------------------------------------------------------------===//
 
 /// Whether a struct's declared argument-passing kind (from the module's
@@ -79,27 +80,32 @@ namespace {
 /// no layout entry (e.g. an anonymous struct) has no C++ non-trivial reason to
 /// be forced to memory, so it defaults to can-pass-in-registers.
 static bool recordCanPassInRegs(ModuleOp modOp, cir::RecordType recTy) {
-  mlir::StringAttr name = recTy.getName();
-  if (!name)
-    return true;
-  auto dict = modOp->getAttrOfType<DictionaryAttr>(
-      cir::CIRDialect::getRecordLayoutsAttrName());
-  if (!dict)
-    return true;
-  auto layout = dict.getAs<cir::RecordLayoutAttr>(name);
+  auto layout = cir::tryGetRecordLayout(modOp, recTy.getName());
   if (!layout)
     return true;
   return layout.getArgPassingKind() == cir::ArgPassingKind::CanPassInRegs;
 }
 
+/// A record's declared alignment, which the ABI uses for the byval and sret
+/// alignment of an indirect argument.  DataLayout derives alignment from the
+/// members, so it cannot see `__attribute__((aligned(N)))`.  The declared 
value
+/// comes from the module's record-layout metadata instead.  CIRGen emits an
+/// entry for every record it names, so the computed fallback only serves
+/// hand-written CIR.
+static llvm::Align recordDeclaredAlign(ModuleOp modOp, cir::RecordType recTy,
+                                       const DataLayout &dl) {
+  auto layout = cir::tryGetRecordLayout(modOp, recTy.getName());
+  if (!layout)
+    return llvm::Align(dl.getTypeABIAlignment(recTy));
+  return llvm::Align(layout.getRecordAlign());
+}
+
 /// The CIR types the x86_64 bridge handles.  Scalars: an integer up to 128
 /// bits (including `_BitInt` and `__int128`), pointer, bool, void, f32, or 
f64.
-/// Aggregates: a complete struct whose fields are all themselves supported, or
-/// an array of a supported element type.  A `_BitInt` wider than 128 bits,
-/// unions, `_Complex`, vectors, wider floats, and packed or padded records are
-/// not handled and are reported NYI at the reject() choke point in
-/// classifyX86_64Function.
-static bool isSupportedType(mlir::Type ty) {
+/// Aggregates: a complete struct or union whose members are all themselves
+/// supported, or an array of a supported element type.  Everything else is
+/// reported NYI at the reject() choke point in classifyX86_64Function.
+static bool isSupportedType(mlir::Type ty, const DataLayout &dl) {
   // A pointer is only handled in the default address space (null) or an
   // already-lowered target address space.  A LangAddressSpaceAttr must be
   // lowered before this pass, so reject it rather than silently dropping it.
@@ -124,21 +130,40 @@ static bool isSupportedType(mlir::Type ty) {
     return intTy.getWidth() <= 64 || intTy.getWidth() == 128;
   }
   if (auto arrTy = dyn_cast<cir::ArrayType>(ty))
-    return isSupportedType(arrTy.getElementType());
+    return isSupportedType(arrTy.getElementType(), dl);
   if (auto recTy = dyn_cast<cir::RecordType>(ty)) {
-    // Unions and packed / padded records each need classification this bridge
-    // does not implement (a union widen fixup and pad-aware eightbyte
-    // classification), so reject them here and report NYI rather than
-    // misclassify.  A zero-field record (a C empty struct) classifies as
-    // Ignore and is dropped from the lowered signature.  CIRGen lays out an
-    // empty C++ class as a single padded byte, which the padded check rejects.
-    // A real one-byte struct such as `{char[1]}` has a field and is not
-    // padded, so it is classified normally.
-    if (recTy.isUnion() || !recTy.isComplete() || recTy.getPacked() ||
-        recTy.getPadded())
+    // An incomplete record has no layout to classify, and a packed one needs
+    // pad-aware eightbyte classification this bridge does not implement.
+    if (!recTy.isComplete() || recTy.getPacked())
       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();
+      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 {
+        auto spansRecord = [&](mlir::Type m) {
+          return dl.getTypeSizeInBits(m).getFixedValue() == recordBits;
+        };
+        if (!llvm::any_of(members, spansRecord))
+          return false;
+      }
+    } else if (recTy.getPadded()) {
+      // A struct's padding is a member the classifier would have to recognize
+      // as padding rather than data, which is not implemented.
+      return false;
+    }
     return llvm::all_of(recTy.getMembers(),
-                        [](mlir::Type m) { return isSupportedType(m); });
+                        [&](mlir::Type m) { return isSupportedType(m, dl); });
   }
   return false;
 }
@@ -220,11 +245,28 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type,
                                dl.getTypeSizeInBits(type).getFixedValue());
       })
       .Case([&](cir::RecordType recTy) -> const llvm::abi::Type * {
-        // isSupportedType rejects unions, packed / padded, and empty-for-ABI
-        // records, so this handles a plain struct: map each field at its
-        // naturally-aligned offset.
+        llvm::abi::RecordFlags flags = llvm::abi::RecordFlags::None;
+        if (recordCanPassInRegs(modOp, recTy))
+          flags = flags | llvm::abi::RecordFlags::CanPassInRegisters;
+        llvm::TypeSize sizeBits = llvm::TypeSize::getFixed(
+            dl.getTypeSizeInBits(type).getFixedValue());
+        llvm::Align align = recordDeclaredAlign(modOp, recTy, dl);
         SmallVector<llvm::abi::FieldInfo> fields;
         fields.reserve(recTy.getMembers().size());
+
+        // The size passed here spans the tail padding, so an eightbyte covers
+        // the whole union rather than just the member the classifier reduces
+        // it to.
+        if (recTy.isUnion()) {
+          for (mlir::Type fieldTy : recTy.getMembers())
+            fields.push_back(llvm::abi::FieldInfo(
+                mapCIRType(fieldTy, typeMapper, dl, modOp)));
+          return tb.getUnionType(fields, sizeBits, align,
+                                 llvm::abi::StructPacking::Default, flags);
+        }
+
+        // isSupportedType rejects packed and padded structs, so every field
+        // here sits at its naturally-aligned offset.
         uint64_t offsetBits = 0;
         for (mlir::Type fieldTy : recTy.getMembers()) {
           const llvm::abi::Type *mappedField =
@@ -234,16 +276,9 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type,
           fields.push_back(llvm::abi::FieldInfo(mappedField, offsetBits));
           offsetBits += dl.getTypeSizeInBits(fieldTy).getFixedValue();
         }
-        llvm::abi::RecordFlags flags = llvm::abi::RecordFlags::None;
-        if (recordCanPassInRegs(modOp, recTy))
-          flags = flags | llvm::abi::RecordFlags::CanPassInRegisters;
-        return tb.getRecordType(fields,
-                                llvm::TypeSize::getFixed(
-                                    
dl.getTypeSizeInBits(type).getFixedValue()),
-                                llvm::Align(dl.getTypeABIAlignment(type)),
-                                llvm::abi::StructPacking::Default,
-                                /*BaseClasses=*/{}, /*VirtualBaseClasses=*/{},
-                                flags);
+        return tb.getRecordType(
+            fields, sizeBits, align, llvm::abi::StructPacking::Default,
+            /*BaseClasses=*/{}, /*VirtualBaseClasses=*/{}, flags);
       })
       .Default([](mlir::Type) -> const llvm::abi::Type * {
         llvm_unreachable(
@@ -388,7 +423,7 @@ static std::optional<FunctionClassification> 
classifyX86_64Signature(
   bool voidRet = isa<cir::VoidType>(retCIR);
 
   auto reject = [&](mlir::Type t) -> bool {
-    if (isSupportedType(t))
+    if (isSupportedType(t, dl))
       return false;
     emitError()
         << "x86_64 calling-convention lowering not yet implemented for type "
diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64.c 
b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64.c
index 6931198a90c3d..8382b15bb5d9e 100644
--- a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64.c
+++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64.c
@@ -11,6 +11,11 @@ typedef struct { long a, b, c, d; } Big;
 typedef struct { long a; double b; } IntSSE;
 typedef struct { double a; double b; } SSE2;
 typedef struct { } Empty;
+typedef union { int i; float f; } UIntFloat;
+typedef union { float f; float g; } UFloats;
+typedef union { int i; char c[8]; } UNarrowStorage;
+typedef union { char c[32]; } UBig;
+typedef union { char c[32]; } __attribute__((aligned(32))) UBigOverAligned;
 
 // Narrow signed integer sign-extended in a register.
 signed char ext_schar(signed char c) { return c; }
@@ -79,3 +84,82 @@ void take_big(Big b) { (void)b; }
 // CIR: cir.func {{.*}}@take_big(%arg0: !cir.ptr<!rec_Big> {{.*}}llvm.byval = 
!rec_Big{{.*}})
 // LLVM-CIR: define dso_local void @take_big(ptr noalias noundef 
byval(%struct.Big) align 8 %{{.+}})
 // LLVM-OGCG: define dso_local void @take_big(ptr noundef byval(%struct.Big) 
align 8 %{{.+}})
+
+// Union members all start at offset zero, so a 4-byte union takes one INTEGER
+// eightbyte and coerces to i32.
+void take_union(UIntFloat u) { (void)u; }
+
+// CIR: cir.func {{.*}}@take_union(%arg0: !s32i{{.*}})
+// LLVM: define dso_local void @take_union(i32 %{{.+}})
+
+// A union of floats classifies SSE, so it coerces to a float register.
+void take_union_floats(UFloats u) { (void)u; }
+
+// CIR: cir.func {{.*}}@take_union_floats(%arg0: !cir.float{{.*}})
+// LLVM: define dso_local void @take_union_floats(float %{{.+}})
+
+// The union's highest-aligned member is the 4-byte int, but its size comes
+// from the 8-byte array, and the eightbyte is sized from the union.
+void take_union_narrow_storage(UNarrowStorage u) { (void)u; }
+
+// CIR: cir.func {{.*}}@take_union_narrow_storage(%arg0: !u64i{{.*}})
+// LLVM: define dso_local void @take_union_narrow_storage(i64 %{{.+}})
+
+// A coerced union return round-trips through the coercion type.
+UIntFloat ret_union(int a) { UIntFloat u; u.i = a; return u; }
+
+// CIR: cir.func {{.*}}@ret_union(%arg0: !s32i {{.*}}) -> !s32i
+// LLVM: define dso_local i32 @ret_union(i32 noundef %{{.+}})
+
+// A union too large for registers is passed byval, with the same noalias
+// divergence as a large struct.
+void take_union_big(UBig u) { (void)u; }
+
+// CIR: cir.func {{.*}}@take_union_big(%arg0: !cir.ptr<!rec_UBig> 
{{.*}}llvm.byval = !rec_UBig{{.*}})
+// LLVM-CIR: define dso_local void @take_union_big(ptr noalias noundef 
byval(%union.UBig) align 8 %{{.+}})
+// LLVM-OGCG: define dso_local void @take_union_big(ptr noundef 
byval(%union.UBig) align 8 %{{.+}})
+
+// The byval alignment follows the union's declared alignment, not the 
alignment
+// its members imply, which is 1 here.
+void take_union_big_over_aligned(UBigOverAligned u) { (void)u; }
+
+// CIR: cir.func {{.*}}@take_union_big_over_aligned(%arg0: 
!cir.ptr<!rec_UBigOverAligned> {{.*}}llvm.align = 32 : i64{{.*}}llvm.byval = 
!rec_UBigOverAligned{{.*}})
+// LLVM-CIR: define dso_local void @take_union_big_over_aligned(ptr noalias 
noundef byval(%union.UBigOverAligned) align 32 %{{.+}})
+// LLVM-OGCG: define dso_local void @take_union_big_over_aligned(ptr noundef 
byval(%union.UBigOverAligned) align 32 %{{.+}})
+
+void call_union(UIntFloat u) { take_union(u); }
+
+// CIR: cir.func {{.*}}@call_union(%arg0: !s32i
+// CIR:   cir.call @take_union(%{{.+}}) : (!s32i) -> ()
+// LLVM: define dso_local void @call_union(i32 %{{.+}})
+// LLVM:   call void @take_union(i32 %{{.+}})
+
+void call_union_big_over_aligned(UBigOverAligned u) {
+  take_union_big_over_aligned(u);
+}
+
+// CIR: cir.func {{.*}}@call_union_big_over_aligned(%arg0: 
!cir.ptr<!rec_UBigOverAligned> {{.*}}llvm.align = 32 : i64{{.*}})
+// CIR:   cir.call @take_union_big_over_aligned(%{{.+}}) : 
(!cir.ptr<!rec_UBigOverAligned> {{.*}}llvm.align = 32 : i64{{.*}}) -> ()
+// LLVM-CIR: define dso_local void @call_union_big_over_aligned(ptr noalias 
noundef byval(%union.UBigOverAligned) align 32 %{{.+}})
+// LLVM-CIR:   alloca %union.UBigOverAligned, i64 1, align 32
+// LLVM-CIR:   call void @take_union_big_over_aligned(ptr noalias noundef 
byval(%union.UBigOverAligned) align 32 %{{.+}})
+// LLVM-OGCG: define dso_local void @call_union_big_over_aligned(ptr noundef 
byval(%union.UBigOverAligned) align 32 %{{.+}})
+// LLVM-OGCG:   call void @take_union_big_over_aligned(ptr noundef 
byval(%union.UBigOverAligned) align 32 %{{.+}})
+
+// The declared alignment reaches the sret slot of an indirect return too, not
+// just a byval argument.
+UBigOverAligned ret_union_big_over_aligned(void);
+void call_ret_union_big_over_aligned(void) { 
(void)ret_union_big_over_aligned(); }
+
+// CIR: cir.func 
{{.*}}@ret_union_big_over_aligned(!cir.ptr<!rec_UBigOverAligned> 
{{.*}}llvm.align = 32 : i64{{.*}}llvm.sret = !rec_UBigOverAligned{{.*}})
+// LLVM: declare void @ret_union_big_over_aligned(ptr dead_on_unwind writable 
sret(%union.UBigOverAligned) align 32)
+
+// The same declared-alignment source feeds an over-aligned struct, since
+// mapCIRType's alignment lookup is on the shared record path, not a
+// union-specific one.
+typedef struct { char c[32]; } __attribute__((aligned(32))) SOverAligned;
+void take_struct_over_aligned(SOverAligned s) { (void)s; }
+
+// CIR: cir.func {{.*}}@take_struct_over_aligned(%arg0: 
!cir.ptr<!rec_SOverAligned> {{.*}}llvm.align = 32 : i64{{.*}}llvm.byval = 
!rec_SOverAligned{{.*}})
+// LLVM-CIR: define dso_local void @take_struct_over_aligned(ptr noalias 
noundef byval(%struct.SOverAligned) align 32 %{{.+}})
+// LLVM-OGCG: define dso_local void @take_struct_over_aligned(ptr noundef 
byval(%struct.SOverAligned) align 32 %{{.+}})
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 85f05fef9f96b..83d36ce22b970 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
@@ -1,10 +1,17 @@
 // RUN: not cir-opt %s -cir-call-conv-lowering=target=x86_64 2>&1 | FileCheck 
%s
 
 !s8i = !cir.int<s, 8>
+!s16i = !cir.int<s, 16>
 !s32i = !cir.int<s, 32>
 !u8i = !cir.int<u, 8>
-!u32i = !cir.int<u, 32>
-!rec_U = !cir.union<"U" {!s32i, !u32i}>
+!rec_UPacked = !cir.union<"UPacked" packed {!s32i, !cir.array<!s8i x 5>}, 
padding = {!u8i}>
+!rec_ULongDouble = !cir.union<"ULongDouble" {!cir.long_double<!cir.f80>, 
!s32i}>
+!rec_UFloats = !cir.union<"UFloats" {!cir.array<!cir.float x 2>, 
!cir.array<!cir.float x 2>}>
+!rec_UOverAligned = !cir.union<"UOverAligned" {!s32i}, padding = 
{!cir.array<!u8i x 12>}>
+!rec_UShortStorage = !cir.union<"UShortStorage" {!s16i, !cir.array<!s8i x 3>}, 
padding = {!cir.array<!u8i x 2>}>
+!rec_UByteBlobs = !cir.union<"UByteBlobs" {!u8i, !u8i}, padding = 
{!cir.array<!u8i x 3>}>
+!rec_SWrapsOverAligned = !cir.struct<"SWrapsOverAligned" {!cir.double, 
!rec_UOverAligned}>
+!rec_UEmptyLarge = !cir.union<"UEmptyLarge" {}, padding = {!cir.array<!u8i x 
32>}>
 !rec_P = !cir.struct<"P" packed {!s8i, !s32i}>
 !rec_Ov = !cir.struct<"Ov" padded {!s32i, !cir.array<!u8i x 12>}>
 !rec_E = !cir.struct<"E" padded {!u8i}>
@@ -19,12 +26,77 @@ module attributes {
     #dlti.dl_entry<f64, dense<64>: vector<2xi64>>>
 } {
 
-  // A union is rejected: its register coercion needs a widen fixup.
-  cir.func @take_union(%arg0: !rec_U) {
+  // A packed union is rejected for the same reason a packed struct is: its
+  // members no longer sit at their natural alignment.
+  cir.func @take_packed_union(%arg0: !rec_UPacked) {
     cir.return
   }
 
-  // CHECK: not yet implemented for type '!cir.union<"U"
+  // CHECK: not yet implemented for type '!cir.union<"UPacked" packed
+
+  // A union member the bridge does not map keeps the whole union unsupported.
+  cir.func @take_union_long_double(%arg0: !rec_ULongDouble) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for type '!cir.union<"ULongDouble"
+
+  // A union whose highest-aligned member is an all-float array classifies to
+  // an SSE vector coerce this bridge does not represent, so it is reported NYI
+  // rather than passed unchanged.
+  cir.func @take_union_float_arrays(%arg0: !rec_UFloats) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for the ABI coercion of type 
'!cir.union<"UFloats"
+
+  // 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"
+
+  // This is the shape CIRGen produces for a union of narrow bitfields, where
+  // the byte-sized members understate a wider storage unit that is all user
+  // data.  It is also the shape of a union of two `unsigned char` members
+  // carrying an alignment attribute, where the same bytes really are padding.
+  // The two coerce differently in classic CodeGen and are identical here, 
which
+  // is why neither is 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 (SysV's MEMORY rule applies unconditionally above that
+  // size, regardless of content), but this bridge has no member to build an
+  // Indirect coercion from.  Below the threshold the same shape is accepted
+  // and classifies Ignore, matching classic; see x86_64-union.cir take_empty.
+  cir.func @take_empty_large_union(%arg0: !rec_UEmptyLarge) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for type '!cir.union<"UEmptyLarge"
 
   // A packed struct is rejected: it needs pad-aware classification.
   cir.func @take_packed(%arg0: !rec_P) {
diff --git 
a/clang/test/CIR/Transforms/abi-lowering/x86_64-union-coerce-shapes.cir 
b/clang/test/CIR/Transforms/abi-lowering/x86_64-union-coerce-shapes.cir
new file mode 100644
index 0000000000000..e7cc09d739de1
--- /dev/null
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-union-coerce-shapes.cir
@@ -0,0 +1,80 @@
+// RUN: cir-opt %s -cir-call-conv-lowering=target=x86_64 | FileCheck %s
+// RUN: cir-opt %s -cir-call-conv-lowering=target=x86_64 -cir-to-llvm -o - 
2>/dev/null \
+// RUN:   | mlir-translate -mlir-to-llvmir --allow-unregistered-dialect \
+// RUN:   | FileCheck %s --check-prefix=LLVM
+
+!s8i = !cir.int<s, 8>
+!u8i = !cir.int<u, 8>
+!s32i = !cir.int<s, 32>
+!s64i = !cir.int<s, 64>
+!s128i = !cir.int<s, 128>
+!rec_U128 = !cir.union<"U128" {!s128i, !cir.array<!s8i x 16>}>
+!rec_UMixed = !cir.union<"UMixed" {!cir.array<!cir.double x 2>, !s64i}>
+!rec_UD2 = !cir.union<"UD2" {!cir.array<!cir.double x 2>}>
+!rec_U12 = !cir.union<"U12" {!cir.array<!s8i x 12>, !s32i}, padding = 
{!cir.array<!u8i x 8>}>
+!rec_UEmpty16 = !cir.union<"UEmpty16" {}, padding = {!cir.array<!u8i x 16>}>
+
+module attributes {
+  cir.triple = "x86_64-unknown-linux-gnu",
+  dlti.dl_spec = #dlti.dl_spec<
+    #dlti.dl_entry<i8, dense<8>: vector<2xi64>>,
+    #dlti.dl_entry<i64, dense<64>: vector<2xi64>>,
+    #dlti.dl_entry<i128, dense<128>: vector<2xi64>>,
+    #dlti.dl_entry<f64, dense<64>: vector<2xi64>>>
+} {
+
+  // A 16-byte union whose spanning member is one 128-bit integer coerces to 
that
+  // integer rather than to a pair of eightbytes.
+  cir.func @take_u128(%arg0: !rec_U128) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_u128(%arg0: !s128i)
+
+  // The two eightbytes need not share a class.  The first covers half of the
+  // double array and merges to INTEGER against the long, while the second is 
all
+  // double and stays SSE.
+  cir.func @take_umixed(%arg0: !rec_UMixed) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_umixed(%arg0: !u64i, %arg1: !cir.double)
+
+  // With no integer member to merge against, both eightbytes stay SSE.
+  cir.func @take_ud2(%arg0: !rec_UD2) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_ud2(%arg0: !cir.double, %arg1: !cir.double)
+
+  // The second eightbyte is partial.  The classifier reduces this union to its
+  // 4-byte integer, so an eightbyte sized from that member would cover only 
the
+  // first four bytes and drop the rest of the array.
+  cir.func @take_u12(%arg0: !rec_U12) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_u12(%arg0: !u64i, %arg1: !u32i)
+
+  // Two eightbytes in return position, flattened to an anonymous struct.
+  cir.func @ret_umixed(%arg0: !rec_UMixed) -> !rec_UMixed {
+    cir.return %arg0 : !rec_UMixed
+  }
+
+  // CHECK: cir.func{{.*}} @ret_umixed(%arg0: !u64i, %arg1: !cir.double) -> 
!rec_anon_struct1
+
+  // A member-less union at exactly two eightbytes still classifies Ignore.  
One
+  // byte more is MEMORY, which x86_64-aggregate-nyi.cir covers.
+  cir.func @take_empty16(%arg0: !rec_UEmpty16) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_empty16()
+}
+
+// LLVM: define void @take_u128(i128 %{{.+}})
+// LLVM: define void @take_umixed(i64 %{{.+}}, double %{{.+}})
+// LLVM: define void @take_ud2(double %{{.+}}, double %{{.+}})
+// LLVM: define void @take_u12(i64 %{{.+}}, i32 %{{.+}})
+// LLVM: define { i64, double } @ret_umixed(i64 %{{.+}}, double %{{.+}})
+// LLVM: define void @take_empty16()
diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-union.cir 
b/clang/test/CIR/Transforms/abi-lowering/x86_64-union.cir
new file mode 100644
index 0000000000000..cad6cfd37d7a4
--- /dev/null
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-union.cir
@@ -0,0 +1,280 @@
+// RUN: cir-opt %s -cir-call-conv-lowering=target=x86_64 | FileCheck %s
+// RUN: cir-opt %s -cir-call-conv-lowering=target=x86_64 -cir-to-llvm -o - 
2>/dev/null \
+// RUN:   | mlir-translate -mlir-to-llvmir --allow-unregistered-dialect \
+// RUN:   | FileCheck %s --check-prefix=LLVM
+
+!s8i = !cir.int<s, 8>
+!s32i = !cir.int<s, 32>
+!s64i = !cir.int<s, 64>
+!u8i = !cir.int<u, 8>
+!rec_UIntFloat = !cir.union<"UIntFloat" {!s32i, !cir.float}>
+!rec_UFloatInt = !cir.union<"UFloatInt" {!cir.float, !s32i}>
+!rec_ULongDouble = !cir.union<"ULongDouble" {!s64i, !cir.double}>
+!rec_UDoubleLong = !cir.union<"UDoubleLong" {!cir.double, !s64i}>
+!rec_UFloats = !cir.union<"UFloats" {!cir.float, !cir.float}>
+!rec_UThree = !cir.union<"UThree" {!cir.array<!s8i x 3>}>
+!rec_UNarrowStorage = !cir.union<"UNarrowStorage" {!s32i, !cir.array<!s8i x 
8>}, padding = {!cir.array<!u8i x 4>}>
+!rec_UTwoEightbytes = !cir.union<"UTwoEightbytes" {!s64i, !cir.array<!s8i x 
16>}, padding = {!cir.array<!u8i x 8>}>
+!rec_UBig = !cir.union<"UBig" {!cir.array<!s8i x 32>}>
+!rec_UBigOverAligned = !cir.union<"UBigOverAligned" {!cir.array<!s8i x 32>}>
+!rec_SOverAligned = !cir.struct<"SOverAligned" {!cir.array<!s8i x 32>}>
+!rec_UEmpty = !cir.union<"UEmpty" {}, padding = {!u8i}>
+!rec_UNoRegs = !cir.union<"UNoRegs" {!s32i, !cir.float}>
+!rec_SWithUnion = !cir.struct<"SWithUnion" {!rec_UIntFloat, !s32i}>
+
+module attributes {
+  cir.triple = "x86_64-unknown-linux-gnu",
+  cir.record_layouts = {
+    UNoRegs = #cir.record_layout<
+      arg_passing_kind = cannot_pass_in_regs, has_trivial_dtor = false,
+      record_align = 4>,
+    UBigOverAligned = #cir.record_layout<
+      arg_passing_kind = can_pass_in_regs, has_trivial_dtor = true,
+      record_align = 32>,
+    SOverAligned = #cir.record_layout<
+      arg_passing_kind = can_pass_in_regs, has_trivial_dtor = true,
+      record_align = 32>},
+  dlti.dl_spec = #dlti.dl_spec<
+    #dlti.dl_entry<i8, dense<8>: vector<2xi64>>,
+    #dlti.dl_entry<i16, dense<16>: vector<2xi64>>,
+    #dlti.dl_entry<i32, dense<32>: vector<2xi64>>,
+    #dlti.dl_entry<i64, dense<64>: vector<2xi64>>,
+    #dlti.dl_entry<f32, dense<32>: vector<2xi64>>,
+    #dlti.dl_entry<f64, dense<64>: vector<2xi64>>>
+} {
+
+  // Every union member sits at offset zero, so a 4-byte union of an int and a
+  // float classifies INTEGER on its single eightbyte and coerces to i32.
+  cir.func @take_int_float(%arg0: !rec_UIntFloat) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_int_float(%arg0: !s32i)
+  // CHECK:   %[[SLOT:.*]] = cir.alloca "coerce" align(4) : !cir.ptr<!s32i>
+  // CHECK:   cir.store %arg0, %[[SLOT]] : !s32i, !cir.ptr<!s32i>
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %[[SLOT]] : !cir.ptr<!s32i> -> 
!cir.ptr<!rec_UIntFloat>
+  // CHECK:   %{{.*}} = cir.load %[[CAST]] : !cir.ptr<!rec_UIntFloat>, 
!rec_UIntFloat
+
+  // Same members as take_int_float in the opposite declaration order.  The
+  // classifier merges eightbyte classes across every member (INTEGER beats
+  // SSE), so which member is listed first does not change the class: this
+  // still coerces to a 32-bit integer, not to the float that comes first.
+  // The coercion type is unsigned where take_int_float's is signed.  On a tie
+  // for widest member the classifier's reduction keeps the first field (float
+  // here), and resolving an integer coercion from a float storage type falls
+  // through to a byte-size fallback that is always unsigned.  That difference
+  // is confined to CIR: LLVM integers have no signedness, so both lower to
+  // i32, which the LLVM checks at the end of this file pin.
+  cir.func @take_float_int(%arg0: !rec_UFloatInt) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_float_int(%arg0: !u32i)
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %{{.*}} : !cir.ptr<!u32i> -> 
!cir.ptr<!rec_UFloatInt>
+
+  // An 8-byte union fills its eightbyte and coerces to i64.
+  cir.func @take_long_double(%arg0: !rec_ULongDouble) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_long_double(%arg0: !s64i)
+  // CHECK:   %[[SLOT:.*]] = cir.alloca "coerce" align(8) : !cir.ptr<!s64i>
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %[[SLOT]] : !cir.ptr<!s64i> -> 
!cir.ptr<!rec_ULongDouble>
+
+  // Same class-merge point at 8 bytes: double-first still coerces to a
+  // 64-bit integer, unsigned for the same reduction-tie reason as
+  // take_float_int above.
+  cir.func @take_double_long(%arg0: !rec_UDoubleLong) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_double_long(%arg0: !u64i)
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %{{.*}} : !cir.ptr<!u64i> -> 
!cir.ptr<!rec_UDoubleLong>
+
+  // A union of floats classifies SSE, so the coercion is a float register
+  // rather than an integer one.
+  cir.func @take_floats(%arg0: !rec_UFloats) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_floats(%arg0: !cir.float)
+  // CHECK:   %[[SLOT:.*]] = cir.alloca "coerce" align(4) : 
!cir.ptr<!cir.float>
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %[[SLOT]] : !cir.ptr<!cir.float> 
-> !cir.ptr<!rec_UFloats>
+
+  // A 3-byte union coerces to the i24 that spans it.
+  cir.func @take_three(%arg0: !rec_UThree) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_three(%arg0: !cir.int<u, 24>)
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %{{.*}} : !cir.ptr<!cir.int<u, 
24>> -> !cir.ptr<!rec_UThree>
+
+  // The highest-aligned member (the int) is narrower than the union, whose
+  // 8-byte size comes from the char array.  The eightbyte is sized from the
+  // union, not from that member, so this coerces to i64 rather than i32.
+  cir.func @take_narrow_storage(%arg0: !rec_UNarrowStorage) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_narrow_storage(%arg0: !u64i)
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %{{.*}} : !cir.ptr<!u64i> -> 
!cir.ptr<!rec_UNarrowStorage>
+
+  // A 16-byte union is two INTEGER eightbytes, flattened into one argument per
+  // eightbyte.
+  cir.func @take_two_eightbytes(%arg0: !rec_UTwoEightbytes) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_two_eightbytes(%arg0: !s64i, %arg1: !u64i)
+  // CHECK:   cir.alloca "coerce" align(8) : !cir.ptr<!rec_anon_struct>
+  // CHECK:   %[[FLAT:.*]] = cir.alloca "coerce" align(8) : 
!cir.ptr<!rec_anon_struct>
+  // CHECK:   %[[E0:.*]] = cir.get_member %[[FLAT]][0] {{.*}} : 
!cir.ptr<!rec_anon_struct> -> !cir.ptr<!s64i>
+  // CHECK:   cir.store %arg0, %[[E0]] : !s64i, !cir.ptr<!s64i>
+  // CHECK:   %[[E1:.*]] = cir.get_member %[[FLAT]][1] {{.*}} : 
!cir.ptr<!rec_anon_struct> -> !cir.ptr<!u64i>
+  // CHECK:   cir.store %arg1, %[[E1]] : !u64i, !cir.ptr<!u64i>
+  // CHECK:   %{{.*}} = cir.cast bitcast %{{.*}} : !cir.ptr<!rec_anon_struct> 
-> !cir.ptr<!rec_UTwoEightbytes>
+
+  // A union too large for registers is passed byval.
+  cir.func @take_big(%arg0: !rec_UBig) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_big(%arg0: !cir.ptr<!rec_UBig> {llvm.align = 
8 : i64, llvm.byval = !rec_UBig, llvm.noalias, llvm.noundef})
+  // CHECK:   %{{.*}} = cir.load %arg0 : !cir.ptr<!rec_UBig>, !rec_UBig
+
+  // 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.
+  cir.func @take_big_over_aligned(%arg0: !rec_UBigOverAligned) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_big_over_aligned(%arg0: 
!cir.ptr<!rec_UBigOverAligned> {llvm.align = 32 : i64, llvm.byval = 
!rec_UBigOverAligned, llvm.noalias, llvm.noundef})
+
+  // The same declared-alignment source feeds every accepted record, not just
+  // unions: an over-aligned STRUCT gets the same byval alignment fix, since
+  // mapCIRType's alignment lookup is on the shared record path.
+  cir.func @take_struct_over_aligned(%arg0: !rec_SOverAligned) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_struct_over_aligned(%arg0: 
!cir.ptr<!rec_SOverAligned> {llvm.align = 32 : i64, llvm.byval = 
!rec_SOverAligned, llvm.noalias, llvm.noundef})
+
+  // A union with no members classifies Ignore and is dropped from the
+  // signature.
+  cir.func @take_empty(%arg0: !rec_UEmpty) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_empty()
+
+  // An empty-union return is dropped too: the function returns void while its
+  // local storage slot survives.
+  cir.func @ret_empty() -> !rec_UEmpty {
+    %0 = cir.alloca "u" align(1) : !cir.ptr<!rec_UEmpty>
+    %1 = cir.load %0 : !cir.ptr<!rec_UEmpty>, !rec_UEmpty
+    cir.return %1 : !rec_UEmpty
+  }
+
+  // CHECK: cir.func{{.*}} @ret_empty()
+  // CHECK:   cir.alloca "u" align(1) : !cir.ptr<!rec_UEmpty>
+  // CHECK:   cir.return{{$}}
+
+  // A union the record layout marks as unable to pass in registers goes
+  // indirect without byval, however small it is.
+  cir.func @take_no_regs(%arg0: !rec_UNoRegs) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_no_regs(%arg0: !cir.ptr<!rec_UNoRegs> 
{llvm.align = 4 : i64, llvm.byref = !rec_UNoRegs})
+
+  // A struct member that is itself a union is mapped through the same union
+  // handling, so the enclosing 8-byte struct coerces to one i64.
+  cir.func @take_struct_with_union(%arg0: !rec_SWithUnion) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_struct_with_union(%arg0: !u64i)
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %{{.*}} : !cir.ptr<!u64i> -> 
!cir.ptr<!rec_SWithUnion>
+
+  // A coerced union return round-trips through the coercion type.
+  cir.func @ret_int_float(%arg0: !rec_UIntFloat) -> !rec_UIntFloat {
+    %0 = cir.alloca "u" align(4) : !cir.ptr<!rec_UIntFloat>
+    cir.store %arg0, %0 : !rec_UIntFloat, !cir.ptr<!rec_UIntFloat>
+    %1 = cir.load %0 : !cir.ptr<!rec_UIntFloat>, !rec_UIntFloat
+    cir.return %1 : !rec_UIntFloat
+  }
+
+  // CHECK: cir.func{{.*}} @ret_int_float(%arg0: !s32i) -> !s32i
+  // CHECK:   %[[RETSLOT:.*]] = cir.alloca "coerce" align(4) : 
!cir.ptr<!rec_UIntFloat>
+  // CHECK:   cir.store %{{.*}}, %[[RETSLOT]] : !rec_UIntFloat, 
!cir.ptr<!rec_UIntFloat>
+  // CHECK:   %[[RETCAST:.*]] = cir.cast bitcast %[[RETSLOT]] : 
!cir.ptr<!rec_UIntFloat> -> !cir.ptr<!s32i>
+  // CHECK:   %[[RET:.*]] = cir.load %[[RETCAST]] : !cir.ptr<!s32i>, !s32i
+  // CHECK:   cir.return %[[RET]] : !s32i
+
+  // A union return too large for registers uses the caller's sret slot.
+  cir.func @ret_big(%arg0: !rec_UBig) -> !rec_UBig {
+    %0 = cir.alloca "u" align(1) : !cir.ptr<!rec_UBig>
+    cir.store %arg0, %0 : !rec_UBig, !cir.ptr<!rec_UBig>
+    %1 = cir.load %0 : !cir.ptr<!rec_UBig>, !rec_UBig
+    cir.return %1 : !rec_UBig
+  }
+
+  // CHECK: cir.func{{.*}} @ret_big(%arg0: !cir.ptr<!rec_UBig> {llvm.align = 1 
: i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = !rec_UBig, 
llvm.writable}, %arg1: !cir.ptr<!rec_UBig> {llvm.align = 8 : i64, llvm.byval = 
!rec_UBig, llvm.noalias, llvm.noundef})
+  // CHECK:   %[[VAL:.*]] = cir.load %arg1 : !cir.ptr<!rec_UBig>, !rec_UBig
+  // CHECK:   cir.store %[[VAL]], %arg0 : !rec_UBig, !cir.ptr<!rec_UBig>
+
+  // The call site coerces the union argument the same way the callee expects
+  // it.
+  cir.func @call_int_float(%arg0: !rec_UIntFloat) {
+    cir.call @take_int_float(%arg0) : (!rec_UIntFloat) -> ()
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @call_int_float(%arg0: !s32i)
+  // CHECK:   %[[ARGSLOT:.*]] = cir.alloca "coerce" align(4) : 
!cir.ptr<!rec_UIntFloat>
+  // CHECK:   cir.store %{{.*}}, %[[ARGSLOT]] : !rec_UIntFloat, 
!cir.ptr<!rec_UIntFloat>
+  // CHECK:   %[[ARGCAST:.*]] = cir.cast bitcast %[[ARGSLOT]] : 
!cir.ptr<!rec_UIntFloat> -> !cir.ptr<!s32i>
+  // CHECK:   %[[ARG:.*]] = cir.load %[[ARGCAST]] : !cir.ptr<!s32i>, !s32i
+  // CHECK:   cir.call @take_int_float(%[[ARG]]) : (!s32i) -> ()
+
+  // Both eightbytes are decomposed at the call site.
+  cir.func @call_two_eightbytes(%arg0: !rec_UTwoEightbytes) {
+    cir.call @take_two_eightbytes(%arg0) : (!rec_UTwoEightbytes) -> ()
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @call_two_eightbytes(%arg0: !s64i, %arg1: !u64i)
+  // CHECK:   %[[CALLSLOT:.*]] = cir.alloca "coerce" align(8) : 
!cir.ptr<!rec_UTwoEightbytes>
+  // CHECK:   %[[CALLCAST:.*]] = cir.cast bitcast %[[CALLSLOT]] : 
!cir.ptr<!rec_UTwoEightbytes> -> !cir.ptr<!rec_anon_struct>
+  // CHECK:   %[[G0:.*]] = cir.get_member %[[CALLCAST]][0] {{.*}} : 
!cir.ptr<!rec_anon_struct> -> !cir.ptr<!s64i>
+  // CHECK:   %[[A0:.*]] = cir.load %[[G0]] : !cir.ptr<!s64i>, !s64i
+  // CHECK:   %[[G1:.*]] = cir.get_member %[[CALLCAST]][1] {{.*}} : 
!cir.ptr<!rec_anon_struct> -> !cir.ptr<!u64i>
+  // CHECK:   %[[A1:.*]] = cir.load %[[G1]] : !cir.ptr<!u64i>, !u64i
+  // CHECK:   cir.call @take_two_eightbytes(%[[A0]], %[[A1]]) : (!s64i, !u64i) 
-> ()
+}
+
+// LLVM: define void @take_int_float(i32 %{{.+}})
+// Declaration order does not reach the lowered signature: the CIR-level
+// signedness difference from the reduction tie disappears here, and both
+// orders land on the same integer register the class merge picked.
+// LLVM: define void @take_float_int(i32 %{{.+}})
+// LLVM: define void @take_long_double(i64 %{{.+}})
+// LLVM: define void @take_double_long(i64 %{{.+}})
+// LLVM: define void @take_floats(float %{{.+}})
+// LLVM: define void @take_three(i24 %{{.+}})
+// LLVM: define void @take_narrow_storage(i64 %{{.+}})
+// LLVM: define void @take_two_eightbytes(i64 %{{.+}}, i64 %{{.+}})
+// LLVM: define void @take_big(ptr noalias noundef byval(%union.UBig) align 8 
%{{.+}})
+// LLVM: define void @take_big_over_aligned(ptr noalias noundef 
byval(%union.UBigOverAligned) align 32 %{{.+}})
+// LLVM: define void @take_struct_over_aligned(ptr noalias noundef 
byval(%struct.SOverAligned) align 32 %{{.+}})
+// LLVM: define void @take_empty()
+// LLVM: define void @ret_empty()
+// LLVM: define void @take_no_regs(ptr byref(%union.UNoRegs) align 4 %{{.+}})
+// LLVM: define void @take_struct_with_union(i64 %{{.+}})
+// LLVM: define i32 @ret_int_float(i32 %{{.+}})
+// LLVM: define void @ret_big(ptr dead_on_unwind noalias writable 
sret(%union.UBig) align 1 %{{.+}}, ptr noalias noundef byval(%union.UBig) align 
8 %{{.+}})
+// LLVM: define void @call_int_float(i32 %{{.+}})
+// LLVM:   call void @take_int_float(i32 %{{.+}})
+// LLVM: define void @call_two_eightbytes(i64 %{{.+}}, i64 %{{.+}})
+// LLVM:   call void @take_two_eightbytes(i64 %{{.+}}, i64 %{{.+}})

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to