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

isSupportedType rejected any union containing an ABI-empty member outright.  
Dropping the reject alone is not enough.  A union mixing a data-free member 
that spans the record with a bit-field access unit can still mis-lower.  The 
new accept rule requires that a data-supplying member span the record whenever 
a bit-field access unit is present.

mapCIRType's union loop now only maps members that hold data for the ABI, so an 
unnamed bit-field's storage is not mapped as a field either.

Assisted-by: Cursor / claude-opus-5


>From 2da5cc7367af883ea1952d8f0067db45e7b321e2 Mon Sep 17 00:00:00 2001
From: Adam Smith <[email protected]>
Date: Tue, 25 Aug 2026 08:57:34 -0700
Subject: [PATCH] [CIR] Accept a union with an ABI-empty member

isSupportedType rejected any union containing an ABI-empty member outright.
Dropping the reject alone is not enough.  A union mixing a data-free member
that spans the record with a bit-field access unit can still mis-lower.  The
new accept rule requires that a data-supplying member span the record
whenever a bit-field access unit is present.

mapCIRType's union loop now only maps members that hold data for the ABI,
so an unnamed bit-field's storage is not mapped as a field either.

Assisted-by: Cursor / claude-opus-5
---
 .../Transforms/CallConvLoweringPass.cpp       |  29 ++-
 .../call-conv-lowering-x86_64-empty.cpp       |  92 +++++++
 .../abi-lowering/x86_64-aggregate-nyi.cir     |  32 ++-
 .../Transforms/abi-lowering/x86_64-union.cir  | 226 ++++++++++++++++++
 4 files changed, 351 insertions(+), 28 deletions(-)

diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp 
b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
index c9bf699ad5561..f96c87f44ec94 100644
--- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
@@ -79,7 +79,8 @@ namespace {
 // vectors, a padded record reached through a named bit-field access unit, a
 // record holding an empty-for-ABI member that occupies bytes or a zero-sized
 // one off its own alignment, a union no member of which spans its declared
-// size, and a union with an empty-record member are reported NYI by
+// size, and a union whose only spanning member is a bit-field access unit
+// are reported NYI by
 // classifyX86_64Function so an unsupported signature fails the pass instead of
 // being misclassified.
 
//===----------------------------------------------------------------------===//
@@ -227,12 +228,15 @@ static bool isSupportedType(mlir::Type ty, const 
DataLayout &dl) {
         };
         if (!llvm::any_of(members, spansRecord))
           return false;
-        // Classic sizes a union's coercion from the bytes that hold data, so 
an
-        // empty member contributes none.  The library instead reduces the 
union
-        // to one member, picked by alignment and then by size, and coerces 
from
-        // that member: an empty one can win either comparison and widen the
-        // coercion past what classic emits.
-        if (llvm::any_of(members, memberIsEmptyRecord))
+        // A bit-field access unit's width can understate the bit-fields it
+        // holds.  When the union has such a unit, a spanning member is not
+        // enough on its own: it must also supply data, either the unit
+        // itself or another member.
+        llvm::ArrayRef<cir::RecordMemberKind> kinds = recTy.getMemberKinds();
+        if (llvm::any_of(kinds, cir::isBitFieldAccessUnit) &&
+            !llvm::any_of(members, [&](mlir::Type m) {
+              return spansRecord(m) && !memberIsEmptyRecord(m);
+            }))
           return false;
       }
     } else if (recTy.getPadded() && reachesNamedBitFieldUnit(recTy)) {
@@ -384,9 +388,14 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type,
         // 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)));
+          // Only data members are classified.  An unnamed bit-field's storage
+          // is a member here but contributes no class in classic CodeGen, so
+          // mapping it would pass an argument classic drops.
+          for (auto [fieldTy, kind] :
+               llvm::zip_equal(recTy.getMembers(), recTy.getMemberKinds()))
+            if (cir::holdsDataForABI(fieldTy, kind))
+              fields.push_back(llvm::abi::FieldInfo(
+                  mapCIRType(fieldTy, typeMapper, dl, modOp)));
           return tb.getUnionType(fields, sizeBits, align,
                                  llvm::abi::StructPacking::Default, flags);
         }
diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-empty.cpp 
b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-empty.cpp
index 126470331ce01..30999935ef9d0 100644
--- a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-empty.cpp
+++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-empty.cpp
@@ -27,6 +27,16 @@ struct FloatEmptyFirst { Empty e; float a; };
 struct alignas(32) Big32 {};
 union UBits { unsigned : 3; };
 union UNone {};
+union UEmptyInt { Empty e; int i; };
+union UEmptyAligned { Aligned e; int i; };
+union UArrEmpty { Empty a[2]; char c; };
+union UEmptyOnly { Empty e; };
+union UEmptyDouble { Empty e; double d; };
+union UEmptyBytes { Empty e; char c[8]; };
+union UBigEmpty { Big32 e; int i; };
+union UEmptyBaseMem { HasEmptyBase e; int i; };
+union UValue { Empty mono; int i; long long ll; double d; const char *s; };
+struct ArgStore { UValue value; unsigned char type; };
 
 // An empty class is passed in no register at all.
 int takeEmpty(Empty v, int k) { return k; }
@@ -161,6 +171,88 @@ int takeUNone(UNone v, int k) { return k; }
 // CIR: cir.func {{.*}}@_Z9takeUNone5UNonei(%arg0: !s32i {{.*}}) -> (!s32i
 // LLVM: define dso_local noundef i32 @_Z9takeUNone5UNonei(i32 noundef 
%{{[^,]+}})
 
+// A union with an empty member coerces from the member that supplies bytes,
+// not from the union's size.
+int takeUEmptyInt(UEmptyInt v) { return v.i; }
+
+// CIR: cir.func {{.*}}@_Z13takeUEmptyInt9UEmptyInt(%arg0: !s32i {{.*}}) -> 
(!s32i
+// LLVM: define dso_local noundef i32 @_Z13takeUEmptyInt9UEmptyInt(i32 
%{{[^,]+}})
+
+// Here the empty member's alignment (16) outranks the int's (4), so the same
+// rule matters more: a member supplying no bytes still cannot decide the
+// storage type, and the 16-byte union coerces to the int's eightbyte rather
+// than widening to i64.
+int takeUEmptyAligned(UEmptyAligned v) { return v.i; }
+
+// CIR: cir.func {{.*}}@_Z17takeUEmptyAligned13UEmptyAligned(%arg0: !s32i 
{{.*}}) -> (!s32i
+// LLVM: define dso_local noundef i32 
@_Z17takeUEmptyAligned13UEmptyAligned(i32 %{{[^,]+}})
+
+// An alignment tie is broken by size, so an array of empty records outranks
+// the byte of data while holding none itself.
+int takeUArrEmpty(UArrEmpty v) { return v.c; }
+
+// CIR: cir.func {{.*}}@_Z13takeUArrEmpty9UArrEmpty(%arg0: !s8i {{.*}}) -> 
(!s32i
+// LLVM: define dso_local noundef i32 @_Z13takeUArrEmpty9UArrEmpty(i8 
%{{[^,]+}})
+
+// A union of nothing but an empty member is dropped, like an empty class.
+int takeUEmptyOnly(UEmptyOnly v, int k) { return k; }
+
+// CIR: cir.func {{.*}}@_Z14takeUEmptyOnly10UEmptyOnlyi(%arg0: !s32i {{.*}}) 
-> (!s32i
+// LLVM: define dso_local noundef i32 @_Z14takeUEmptyOnly10UEmptyOnlyi(i32 
noundef %{{[^,]+}})
+
+// Skipping the empty member leaves the class to the member that remains, so
+// this passes in an SSE register rather than an integer one.
+double takeUEmptyDouble(UEmptyDouble v) { return v.d; }
+
+// CIR: cir.func {{.*}}@_Z16takeUEmptyDouble12UEmptyDouble(%arg0: !cir.double 
{{.*}}) -> (!cir.double
+// LLVM: define dso_local noundef double 
@_Z16takeUEmptyDouble12UEmptyDouble(double %{{[^,]+}})
+
+// Where the data member fills the eightbyte there is nothing to narrow.
+int takeUEmptyBytes(UEmptyBytes v) { return v.c[0]; }
+
+// CIR: cir.func {{.*}}@_Z15takeUEmptyBytes11UEmptyBytes(%arg0: !u64i {{.*}}) 
-> (!s32i
+// LLVM: define dso_local noundef i32 @_Z15takeUEmptyBytes11UEmptyBytes(i64 
%{{[^,]+}})
+
+// Past two eightbytes SysV says memory whatever the content, so the empty
+// member changes nothing here.
+int takeUBigEmpty(UBigEmpty v, int k) { return k; }
+
+// CIR: cir.func {{.*}}@_Z13takeUBigEmpty9UBigEmptyi(%arg0: 
!cir.ptr<!rec_UBigEmpty> {llvm.align = 32 : i64, llvm.byval = !rec_UBigEmpty, 
llvm.noalias, llvm.noundef}{{.*}}, %arg1: !s32i {{.*}}) -> (!s32i
+// LLVM-CIR: define dso_local noundef i32 @_Z13takeUBigEmpty9UBigEmptyi(ptr 
noalias noundef byval(%union.UBigEmpty) align 32 %{{[^,]+}}, i32 noundef 
%{{[^,]+}})
+// LLVM-OGCG: define dso_local noundef i32 @_Z13takeUBigEmpty9UBigEmptyi(ptr 
noundef byval(%union.UBigEmpty) align 32 %{{[^,]+}}, i32 noundef %{{[^,]+}})
+
+// The same union returned uses sret at that alignment.
+UBigEmpty retUBigEmpty() { return UBigEmpty{}; }
+
+// CIR: cir.func {{.*}}@_Z12retUBigEmptyv(%arg0: !cir.ptr<!rec_UBigEmpty> 
{llvm.align = 32 : i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = 
!rec_UBigEmpty, llvm.writable}
+// LLVM: define dso_local void @_Z12retUBigEmptyv(ptr dead_on_unwind noalias 
writable sret(%union.UBigEmpty) align 32 %{{[^,]+}})
+
+// Emptiness reaches the union member through a base class as well.
+int takeUEmptyBaseMem(UEmptyBaseMem v) { return v.i; }
+
+// CIR: cir.func {{.*}}@_Z17takeUEmptyBaseMem13UEmptyBaseMem(%arg0: !s32i 
{{.*}}) -> (!s32i
+// LLVM: define dso_local noundef i32 
@_Z17takeUEmptyBaseMem13UEmptyBaseMem(i32 %{{[^,]+}})
+
+// Several scalars alongside one empty member: the widest of the scalars 
decides
+// the coercion.
+long long takeUValue(UValue v) { return v.ll; }
+
+// CIR: cir.func {{.*}}@_Z10takeUValue6UValue(%arg0: !s64i {{.*}}) -> (!s64i
+// LLVM: define dso_local noundef i64 @_Z10takeUValue6UValue(i64 %{{[^,]+}})
+
+// The same union as a struct member, where the struct's eightbytes are what
+// gets classified.
+long long takeArgStore(ArgStore a) { return a.value.ll; }
+
+// CIR: cir.func {{.*}}@_Z12takeArgStore8ArgStore(%arg0: !s64i {{.*}}, %arg1: 
!u8i {{.*}}) -> (!s64i
+// LLVM: define dso_local noundef i64 @_Z12takeArgStore8ArgStore(i64 
%{{[^,]+}}, i8 %{{[^,]+}})
+
+// The union returned by value round-trips through its coercion.
+UValue retUValue() { return UValue{}; }
+
+// CIR: cir.func {{.*}}@_Z9retUValuev() -> !s64i
+// LLVM: define dso_local i64 @_Z9retUValuev()
+
 // An empty return is dropped to void.
 Empty retEmpty() { return Empty{}; }
 
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 74abbdb0de626..f2a159c560385 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
@@ -17,12 +17,10 @@
     !cir.struct<"NamedPlusZeroWidth" {bitfield !u8i, pad !cir.array<!u8i x 3>,
                                       bitfield !cir.array<!s32i x 0>,
                                       data !s32i, pad !cir.array<!u8i x 8>}>
-!rec_E = !cir.struct<"E" {pad !u8i}>
-!rec_EOver = !cir.struct<"EOver" {pad !cir.array<!u8i x 16>}>
 !rec_UPadByte = !cir.union<"UPadByte" {data !u8i}, padding = {!cir.array<!u8i 
x 3>}>
-!rec_UEmptyOnly = !cir.union<"UEmptyOnly" {data !rec_E}>
-!rec_UEmptyOver = !cir.union<"UEmptyOver" {data !rec_EOver, data !s32i}, 
padding = {!cir.array<!u8i x 12>}>
-!rec_UArrEmpty = !cir.union<"UArrEmpty" {data !cir.array<!rec_E x 2>, data 
!s8i}>
+!rec_E = !cir.struct<"E" {pad !u8i}>
+!rec_UBitEmpty = !cir.union<"UBitEmpty" {data !cir.array<!rec_E x 4>, bitfield 
!u8i}>
+!rec_UEmptyNarrow = !cir.union<"UEmptyNarrow" {data !rec_E, data !s16i}, 
padding = {!cir.array<!u8i x 2>}>
 !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_BitPad = !cir.struct<"BitPad" {bitfield !u8i, pad !cir.array<!u8i x 15>}>
@@ -123,27 +121,25 @@ module attributes {
 
   // CHECK: not yet implemented for type '!cir.union<"UPadByte"
 
-  // A union with an empty member coerces wider than classic, so it is left NYI
-  // rather than accepted along with the empty class itself.
-  cir.func @take_union_empty_over(%arg0: !rec_UEmptyOver) {
+  // A bit-field access unit's width can understate the bit-fields it holds.
+  // Here the only member that spans the union (the empty-record array)
+  // supplies no bytes, so the unit alone would coerce to i8 where classic
+  // gives i32.
+  cir.func @take_bitfield_empty_span(%arg0: !rec_UBitEmpty) {
     cir.return
   }
 
-  // CHECK: not yet implemented for type '!cir.union<"UEmptyOver"
+  // CHECK: not yet implemented for type '!cir.union<"UBitEmpty"
 
-  // Deferred with it, though this one holds no data at all.
-  cir.func @take_union_empty_only(%arg0: !rec_UEmptyOnly) {
+  // 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<"UEmptyOnly"
-
-  // An array of empty records supplies no bytes the union coercion can read.
-  cir.func @take_union_arr_empty(%arg0: !rec_UArrEmpty) {
-    cir.return
-  }
+  // CHECK: not yet implemented for type '!cir.union<"UEmptyNarrow"
 
-  // CHECK: not yet implemented for type '!cir.union<"UArrEmpty"
   // A bit-field access unit is narrower than the declaration it holds, so
   // classifying around this record's padding would coerce to i8 where classic
   // CodeGen widens to i64.
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 da0d9e20529a9..8f457bc95f0f9 100644
--- a/clang/test/CIR/Transforms/abi-lowering/x86_64-union.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-union.cir
@@ -21,6 +21,28 @@
 !rec_UEmpty = !cir.union<"UEmpty" {}, padding = {!u8i}>
 !rec_UNoRegs = !cir.union<"UNoRegs" {data !s32i, data !cir.float}>
 !rec_SWithUnion = !cir.struct<"SWithUnion" {data !rec_UIntFloat, data !s32i}>
+!rec_E = !cir.struct<"E" {pad !u8i}>
+!rec_E2 = !cir.struct<"E2" {pad !u8i}>
+!rec_EOver = !cir.struct<"EOver" {pad !cir.array<!u8i x 16>}>
+!rec_EBig = !cir.struct<"EBig" {pad !cir.array<!u8i x 32>}>
+!rec_UEmptyInt = !cir.union<"UEmptyInt" {data !rec_E, data !s32i}>
+!rec_UTwoEmpty = !cir.union<"UTwoEmpty" {data !rec_E, data !rec_E2, data 
!s32i}>
+!rec_UEmptyTwoEightbytes = !cir.union<"UEmptyTwoEightbytes" {data !rec_E, data 
!cir.array<!s8i x 16>}>
+!rec_SWithEmptyUnion = !cir.struct<"SWithEmptyUnion" {data !rec_UEmptyInt, 
data !s32i}>
+!rec_UEmptyOver = !cir.union<"UEmptyOver" {data !rec_EOver, data !s32i}, 
padding = {!cir.array<!u8i x 12>}>
+!rec_UEmptyOnly = !cir.union<"UEmptyOnly" {data !rec_E}>
+!rec_UEmptyOverOnly = !cir.union<"UEmptyOverOnly" {data !rec_EOver}>
+!rec_UEmptyDouble = !cir.union<"UEmptyDouble" {data !rec_E, data !cir.double}>
+!rec_UEmptyBytes = !cir.union<"UEmptyBytes" {data !rec_E, data !cir.array<!s8i 
x 8>}>
+!rec_UArrEmpty = !cir.union<"UArrEmpty" {data !cir.array<!rec_E x 2>, data 
!s8i}>
+!rec_UArr8Empty = !cir.union<"UArr8Empty" {data !cir.array<!rec_E x 8>, data 
!s8i}>
+!rec_UBigEmptyInt = !cir.union<"UBigEmptyInt" {data !rec_EBig, data !s32i}, 
padding = {!cir.array<!u8i x 28>}>
+!rec_UEmptyUnnamedBits = !cir.union<"UEmptyUnnamedBits" {data 
!cir.array<!rec_E x 4>, empty !cir.array<!u8i x 3>}>
+!rec_SFloats = !cir.struct<"SFloats" {data !cir.float, data !cir.float}>
+!rec_UEmptyFloats = !cir.union<"UEmptyFloats" {data !rec_E, data !rec_SFloats}>
+!u32i = !cir.int<u, 32>
+!rec_UBitSpans = !cir.union<"UBitSpans" {bitfield !u32i}>
+!rec_UBitPlusLong = !cir.union<"UBitPlusLong" {bitfield !u32i, data !s64i}>
 
 module attributes {
   cir.triple = "x86_64-unknown-linux-gnu",
@@ -32,6 +54,27 @@ module attributes {
       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>,
+    E = #cir.record_layout<
+      arg_passing_kind = can_pass_in_regs, has_trivial_dtor = true,
+      record_align = 1>,
+    E2 = #cir.record_layout<
+      arg_passing_kind = can_pass_in_regs, has_trivial_dtor = true,
+      record_align = 1>,
+    EOver = #cir.record_layout<
+      arg_passing_kind = can_pass_in_regs, has_trivial_dtor = true,
+      record_align = 16>,
+    EBig = #cir.record_layout<
+      arg_passing_kind = can_pass_in_regs, has_trivial_dtor = true,
+      record_align = 32>,
+    UEmptyOver = #cir.record_layout<
+      arg_passing_kind = can_pass_in_regs, has_trivial_dtor = true,
+      record_align = 16>,
+    UEmptyOverOnly = #cir.record_layout<
+      arg_passing_kind = can_pass_in_regs, has_trivial_dtor = true,
+      record_align = 16>,
+    UBigEmptyInt = #cir.record_layout<
       arg_passing_kind = can_pass_in_regs, has_trivial_dtor = true,
       record_align = 32>},
   dlti.dl_spec = #dlti.dl_spec<
@@ -180,6 +223,169 @@ module attributes {
   // CHECK:   cir.alloca "u" align(1) : !cir.ptr<!rec_UEmpty>
   // CHECK:   cir.return{{$}}
 
+  // An empty member does not reject a union: the int is the only member
+  // supplying bytes, so it is what the coercion is built from.
+  cir.func @take_empty_int(%arg0: !rec_UEmptyInt) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_empty_int(%arg0: !s32i)
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %{{.*}} : !cir.ptr<!s32i> -> 
!cir.ptr<!rec_UEmptyInt>
+
+  // The empty member's declared alignment (16) outranks the int's (4).  A
+  // member supplying no bytes is skipped when the union's storage type is
+  // chosen, so this 16-byte union still coerces to the int's eightbyte
+  // instead of widening to i64.
+  cir.func @take_empty_over(%arg0: !rec_UEmptyOver) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_empty_over(%arg0: !s32i)
+  // CHECK:   %[[SLOT:.*]] = cir.alloca "coerce" align(4) : 
!cir.ptr<!rec_UEmptyOver>
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %[[SLOT]] : 
!cir.ptr<!rec_UEmptyOver> -> !cir.ptr<!s32i>
+
+  // A union of nothing but an empty member classifies Ignore, the same as the
+  // member-less shape above.
+  cir.func @take_empty_only(%arg0: !rec_UEmptyOnly) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_empty_only()
+
+  // Still Ignore at two eightbytes, where the empty member is over-aligned.
+  cir.func @take_empty_over_only(%arg0: !rec_UEmptyOverOnly) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_empty_over_only()
+
+  // Skipping the empty member does not force an integer coercion.  The
+  // member that remains decides the eightbyte's class.
+  cir.func @take_empty_double(%arg0: !rec_UEmptyDouble) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_empty_double(%arg0: !cir.double)
+
+  // Where the other member does fill the eightbyte there is nothing to narrow,
+  // so this stays i64.
+  cir.func @take_empty_bytes(%arg0: !rec_UEmptyBytes) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_empty_bytes(%arg0: !u64i)
+
+  // The reduction breaks an alignment tie by size, so an array of empty
+  // records beats the one byte of data without holding any itself.
+  cir.func @take_arr_empty(%arg0: !rec_UArrEmpty) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_arr_empty(%arg0: !s8i)
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %{{.*}} : 
!cir.ptr<!rec_UArrEmpty> -> !cir.ptr<!s8i>
+
+  // The same array spanning a whole eightbyte, where reading the union's size
+  // instead of its data would coerce to i64.
+  cir.func @take_arr8_empty(%arg0: !rec_UArr8Empty) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_arr8_empty(%arg0: !s8i)
+
+  // Past two eightbytes SysV says memory whatever the content, so the empty
+  // member changes nothing and the union is passed byval.
+  cir.func @take_big_empty_int(%arg0: !rec_UBigEmptyInt) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_big_empty_int(%arg0: 
!cir.ptr<!rec_UBigEmptyInt> {llvm.align = 32 : i64, llvm.byval = 
!rec_UBigEmptyInt, llvm.noalias, llvm.noundef})
+
+  // More than one empty member is skipped the same way.
+  cir.func @take_two_empty(%arg0: !rec_UTwoEmpty) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_two_empty(%arg0: !s32i)
+
+  // Only data members are classified, so the unnamed bit-field's storage is
+  // not mapped and the union is left holding nothing that supplies bytes.
+  cir.func @take_empty_unnamed_bits(%arg0: !rec_UEmptyUnnamedBits) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_empty_unnamed_bits()
+
+  // The remaining member decides the class, so a pair of floats in one
+  // eightbyte still coerces to a vector rather than an integer.
+  cir.func @take_empty_floats(%arg0: !rec_UEmptyFloats) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_empty_floats(%arg0: !cir.vector<2 x 
!cir.float>)
+
+  // A named bit-field access unit that spans the union is itself the data
+  // the coercion reads from, so it is accepted on its own.
+  cir.func @take_bit_spans(%arg0: !rec_UBitSpans) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_bit_spans(%arg0: !u32i)
+
+  // Here the bit-field access unit does not span, but the long does, so the
+  // union is accepted from the long's eightbyte rather than the unit's.
+  cir.func @take_bit_plus_long(%arg0: !rec_UBitPlusLong) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_bit_plus_long(%arg0: !s64i)
+
+  // Both eightbytes classify INTEGER and are flattened into one argument each,
+  // so the empty member does not disturb a multi-eightbyte coercion.
+  cir.func @take_empty_two_eightbytes(%arg0: !rec_UEmptyTwoEightbytes) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_empty_two_eightbytes(%arg0: !u64i, %arg1: 
!u64i)
+
+  // SWithEmptyUnion embeds UEmptyInt as a member, so the struct's own
+  // eightbyte classification has to look through the union: this pins that
+  // the enclosing struct still coerces correctly, not just the bare union.
+  cir.func @take_struct_with_empty_union(%arg0: !rec_SWithEmptyUnion) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_struct_with_empty_union(%arg0: !u64i)
+
+  // A coerced union return round-trips through the coercion type.
+  cir.func @ret_empty_int(%arg0: !rec_UEmptyInt) -> !rec_UEmptyInt {
+    %0 = cir.alloca "u" align(4) : !cir.ptr<!rec_UEmptyInt>
+    cir.store %arg0, %0 : !rec_UEmptyInt, !cir.ptr<!rec_UEmptyInt>
+    %1 = cir.load %0 : !cir.ptr<!rec_UEmptyInt>, !rec_UEmptyInt
+    cir.return %1 : !rec_UEmptyInt
+  }
+
+  // CHECK: cir.func{{.*}} @ret_empty_int(%arg0: !s32i) -> !s32i
+  // CHECK:   cir.return %{{.*}} : !s32i
+
+  // A union of only empty members is dropped from the return as well.
+  cir.func @ret_empty_only() -> !rec_UEmptyOnly {
+    %0 = cir.alloca "u" align(1) : !cir.ptr<!rec_UEmptyOnly>
+    %1 = cir.load %0 : !cir.ptr<!rec_UEmptyOnly>, !rec_UEmptyOnly
+    cir.return %1 : !rec_UEmptyOnly
+  }
+
+  // CHECK: cir.func{{.*}} @ret_empty_only()
+  // CHECK:   cir.return{{$}}
+
+  // The call site coerces the same way the callee expects.
+  cir.func @call_empty_int(%arg0: !rec_UEmptyInt) {
+    cir.call @take_empty_int(%arg0) : (!rec_UEmptyInt) -> ()
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @call_empty_int(%arg0: !s32i)
+  // CHECK:   cir.call @take_empty_int(%{{.*}}) : (!s32i) -> ()
+
   // 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) {
@@ -270,6 +476,26 @@ module attributes {
 // 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_empty_int(i32 %{{.+}})
+// LLVM: define void @take_empty_over(i32 %{{.+}})
+// LLVM: define void @take_empty_only()
+// LLVM: define void @take_empty_over_only()
+// LLVM: define void @take_empty_double(double %{{.+}})
+// LLVM: define void @take_empty_bytes(i64 %{{.+}})
+// LLVM: define void @take_arr_empty(i8 %{{.+}})
+// LLVM: define void @take_arr8_empty(i8 %{{.+}})
+// LLVM: define void @take_big_empty_int(ptr noalias noundef 
byval(%union.UBigEmptyInt) align 32 %{{.+}})
+// LLVM: define void @take_two_empty(i32 %{{.+}})
+// LLVM: define void @take_empty_unnamed_bits()
+// LLVM: define void @take_empty_floats(<2 x float> %{{.+}})
+// LLVM: define void @take_bit_spans(i32 %{{.+}})
+// LLVM: define void @take_bit_plus_long(i64 %{{.+}})
+// LLVM: define void @take_empty_two_eightbytes(i64 %{{.+}}, i64 %{{.+}})
+// LLVM: define void @take_struct_with_empty_union(i64 %{{.+}})
+// LLVM: define i32 @ret_empty_int(i32 %{{.+}})
+// LLVM: define void @ret_empty_only()
+// LLVM: define void @call_empty_int(i32 %{{.+}})
+// LLVM:   call void @take_empty_int(i32 %{{.+}})
 // 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 %{{.+}})

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

Reply via email to