[clang] ef23b68 - [CIR] Accept packed records in x86_64 callconv lowering (#218505)

via cfe-commits cfe-commits at lists.llvm.org
Tue Aug 25 06:15:02 PDT 2026


Author: Adam Smith
Date: 2026-08-25T08:14:57-05:00
New Revision: ef23b68265ac9fcca2cb10eb5a66ebbb91439049

URL: https://github.com/llvm/llvm-project/commit/ef23b68265ac9fcca2cb10eb5a66ebbb91439049
DIFF: https://github.com/llvm/llvm-project/commit/ef23b68265ac9fcca2cb10eb5a66ebbb91439049.diff

LOG: [CIR] Accept packed records in x86_64 callconv lowering (#218505)

isSupportedType turned down any record CIR marks packed, so a signature
carrying one failed the pass. The reject predates the bridge reading
field offsets. RecordType::getElementOffset and the record's size
already account for packing, so the reject has nothing left to guard.

Delete it, with one exception. mapCIRType drops a zero-sized member
before classification, so a trailing zero-length array in a packed
record passed in a register where classic uses byval. That gets a
narrower reject naming it.

A polymorphic class stays NYI for want of a vptr mapping, and a padded
record reached through a named bit-field access unit stays NYI on the
existing unit-width gap.

Assisted-by: Cursor / claude-opus-5

Added: 
    clang/test/CIR/CodeGen/call-conv-lowering-x86_64-packed.c
    clang/test/CIR/Transforms/abi-lowering/x86_64-record-packed.cir

Modified: 
    clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
    clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir

Removed: 
    


################################################################################
diff  --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
index ed706e7c13b10..c9bf699ad5561 100644
--- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
@@ -76,11 +76,12 @@ namespace {
 // consumes.  Integer (including `_BitInt` up to 128 bits) / 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, packed records, a padded record reached through a named bit-field
-// access unit, a record holding an empty-for-ABI member that occupies bytes, a
-// union no member of which spans its declared size, and a union with an
-// empty-record member are reported NYI by classifyX86_64Function so an
-// unsupported signature fails the pass instead of being misclassified.
+// 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
+// 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
@@ -203,9 +204,8 @@ static bool isSupportedType(mlir::Type ty, const DataLayout &dl) {
   if (auto arrTy = dyn_cast<cir::ArrayType>(ty))
     return isSupportedType(arrTy.getElementType(), dl);
   if (auto recTy = dyn_cast<cir::RecordType>(ty)) {
-    // 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())
+    // An incomplete record has no layout to classify.
+    if (!recTy.isComplete())
       return false;
     if (recTy.isUnion()) {
       // The classifier sizes a union's eightbytes from the union itself, which
@@ -245,13 +245,21 @@ static bool isSupportedType(mlir::Type ty, const DataLayout &dl) {
     // An `empty` member that occupies bytes is later read as an unnamed access
     // unit.  One that is itself an empty-for-ABI record can occupy bytes by
     // holding a unit of its own, which classic CodeGen reaches through the
-    // member's fields rather than as one unit.
-    for (auto [memberTy, kind] :
-         llvm::zip_equal(recTy.getMembers(), recTy.getMemberKinds()))
-      if (kind == cir::RecordMemberKind::Empty &&
-          dl.getTypeSizeInBits(memberTy).getFixedValue() &&
-          memberIsEmptyRecord(memberTy))
+    // member's fields rather than as one unit.  A zero-sized one is dropped
+    // before classification, so a misaligned one never reaches the rule that
+    // sends its record to memory.
+    for (auto [idx, memberTy, kind] :
+         llvm::enumerate(recTy.getMembers(), recTy.getMemberKinds())) {
+      if (kind != cir::RecordMemberKind::Empty)
+        continue;
+      if (dl.getTypeSizeInBits(memberTy).getFixedValue()) {
+        if (memberIsEmptyRecord(memberTy))
+          return false;
+      } else if (recTy.getElementOffset(dl, idx) %
+                 dl.getTypeABIAlignment(memberTy)) {
         return false;
+      }
+    }
     return llvm::all_of(recTy.getMembers(),
                         [&](mlir::Type m) { return isSupportedType(m, dl); });
   }

diff  --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-packed.c b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-packed.c
new file mode 100644
index 0000000000000..0c654754b135f
--- /dev/null
+++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-packed.c
@@ -0,0 +1,154 @@
+// 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-prefixes=LLVM,LLVM-CIR --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-OGCG --input-file=%t.ll %s
+
+typedef struct __attribute__((packed)) { char c; int i; } CharInt;
+typedef struct __attribute__((packed)) { int a; int b; char c; } Nine;
+typedef struct __attribute__((packed)) { short a; short b; char c; } FiveShort;
+typedef struct __attribute__((packed)) { double d; char c; } DoubleChar;
+typedef struct __attribute__((packed)) { float a; float b; char c; } TwoFloatChar;
+typedef struct __attribute__((packed)) { int a; int b; char c : 3; } NineBF;
+typedef struct __attribute__((packed, aligned(8))) { char c; int i; } PackedOv;
+typedef struct __attribute__((packed)) { int a[4]; char c; } Seventeen;
+typedef struct { CharInt ci; } NestPacked;
+typedef struct { CharInt a[2]; } ArrPacked;
+typedef struct { Nine n; } NestNine;
+typedef struct { Nine n[1]; } ArrNine;
+typedef union __attribute__((packed)) { int i; char c[5]; } UPacked;
+
+#pragma pack(1)
+typedef struct { char c; int i; } PragmaPacked;
+#pragma pack()
+
+// CIR-DAG: !rec_CharInt = !cir.struct<"CharInt" packed {data !s8i, data !s32i}>
+// CIR-DAG: !rec_Nine = !cir.struct<"Nine" packed {data !s32i, data !s32i, data !s8i}>
+// CIR-DAG: !rec_FiveShort = !cir.struct<"FiveShort" packed {data !s16i, data !s16i, data !s8i}>
+// CIR-DAG: !rec_DoubleChar = !cir.struct<"DoubleChar" packed {data !cir.double, data !s8i}>
+// CIR-DAG: !rec_TwoFloatChar = !cir.struct<"TwoFloatChar" packed {data !cir.float, data !cir.float, data !s8i}>
+// CIR-DAG: !rec_NineBF = !cir.struct<"NineBF" packed {data !s32i, data !s32i, bitfield !u8i}>
+// CIR-DAG: !rec_PackedOv = !cir.struct<"PackedOv" packed {data !s8i, data !s32i, pad !cir.array<!u8i x 3>}>
+// CIR-DAG: !rec_Seventeen = !cir.struct<"Seventeen" packed {data !cir.array<!s32i x 4>, data !s8i}>
+// CIR-DAG: !rec_NestPacked = !cir.struct<"NestPacked" {data !rec_CharInt}>
+// CIR-DAG: !rec_ArrPacked = !cir.struct<"ArrPacked" {data !cir.array<!rec_CharInt x 2>}>
+// CIR-DAG: !rec_NestNine = !cir.struct<"NestNine" {data !rec_Nine}>
+// CIR-DAG: !rec_ArrNine = !cir.struct<"ArrNine" {data !cir.array<!rec_Nine x 1>}>
+// CIR-DAG: !rec_UPacked = !cir.union<"UPacked" packed {data !s32i, data !cir.array<!s8i x 5>}, padding = {!u8i}>
+// CIR-DAG: !rec_PragmaPacked = !cir.struct<"PragmaPacked" packed {data !s8i, data !s32i}>
+
+// Anonymous coercion records are numbered in print order, so capture them.
+// CIR-DAG: ![[I64I8:rec_anon_struct[0-9]*]] = !cir.struct<{data !u64i, data !s8i}>
+// CIR-DAG: ![[F64I8:rec_anon_struct[0-9]*]] = !cir.struct<{data !cir.double, data !s8i}>
+// CIR-DAG: ![[V2F32I8:rec_anon_struct[0-9]*]] = !cir.struct<{data !cir.vector<2 x !cir.float>, data !s8i}>
+
+// The int sits at offset 1, and SysV sends a record with a member off its own
+// alignment to memory whatever its size.  The same rule takes the return.
+int take_char_int(CharInt v) { return v.i; }
+CharInt ret_char_int(int x) { CharInt v = {0, x}; return v; }
+
+// CIR: cir.func{{.*}} @take_char_int(%arg0: !cir.ptr<!rec_CharInt> {llvm.align = 8 : i64, llvm.byval = !rec_CharInt, llvm.noalias, llvm.noundef}{{.*}}) -> !s32i
+// CIR: cir.func{{.*}} @ret_char_int(%arg0: !cir.ptr<!rec_CharInt> {llvm.align = 1 : i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = !rec_CharInt, llvm.writable}{{.*}}, %arg1: !s32i {llvm.noundef}{{.*}})
+// CIR emits noalias on a byval argument where classic does not, here and
+// wherever else this file splits a byval line by backend.
+// LLVM-CIR: define dso_local i32 @take_char_int(ptr noalias noundef byval(%struct.CharInt) align 8 %{{.+}})
+// LLVM-OGCG: define dso_local i32 @take_char_int(ptr noundef byval(%struct.CharInt) align 8 %{{.+}})
+// LLVM: define dso_local void @ret_char_int(ptr dead_on_unwind noalias writable sret(%struct.CharInt) align 1 %{{.+}}, i32 noundef %{{.+}})
+
+// Every member is naturally aligned and only the nine-byte size earns the
+// packed mark, so this one is classified: an eightbyte of ints and a trailing
+// byte.
+int take_nine(Nine v) { return v.b; }
+Nine ret_nine(int x) { Nine v = {0, x, 0}; return v; }
+
+// CIR: cir.func{{.*}} @take_nine(%arg0: !u64i{{.*}}, %arg1: !s8i{{.*}}) -> !s32i
+// CIR: cir.func{{.*}} @ret_nine(%arg0: !s32i {llvm.noundef}{{.*}}) -> ![[I64I8]]
+// LLVM: define dso_local i32 @take_nine(i64 %{{.+}}, i8 %{{.+}})
+// LLVM: define dso_local { i64, i8 } @ret_nine(i32 noundef %{{.+}})
+
+// Same rule inside one eightbyte, so the five bytes coerce to an i40 rather
+// than being rounded up to an i64.
+int take_five_short(FiveShort v) { return v.b; }
+FiveShort ret_five_short(short x) { FiveShort v = {0, x, 0}; return v; }
+
+// CIR: cir.func{{.*}} @take_five_short(%arg0: !cir.int<u, 40>{{.*}}) -> !s32i
+// CIR: cir.func{{.*}} @ret_five_short(%arg0: !s16i {llvm.noundef, llvm.signext}{{.*}}) -> !cir.int<u, 40>
+// LLVM: define dso_local i32 @take_five_short(i40 %{{.+}})
+// LLVM: define dso_local i40 @ret_five_short(i16 noundef signext %{{.+}})
+
+// A named access unit is only ambiguous where padding lets classic read the
+// declared type through the gap, so a packed record whose every byte holds
+// data is classified rather than refused by the unit-width rule.
+int take_ninebf(NineBF v) { return v.b; }
+
+// CIR: cir.func{{.*}} @take_ninebf(%arg0: !u64i{{.*}}, %arg1: !u8i{{.*}}) -> !s32i
+// LLVM: define dso_local i32 @take_ninebf(i64 %{{.+}}, i8 %{{.+}})
+
+// The two eightbytes land in 
diff erent register classes.
+double take_double_char(DoubleChar v) { return v.d; }
+DoubleChar ret_double_char(double x) { DoubleChar v = {x, 0}; return v; }
+
+// CIR: cir.func{{.*}} @take_double_char(%arg0: !cir.double{{.*}}, %arg1: !s8i{{.*}}) -> !cir.double
+// CIR: cir.func{{.*}} @ret_double_char(%arg0: !cir.double {llvm.noundef}{{.*}}) -> ![[F64I8]]
+// LLVM: define dso_local double @take_double_char(double %{{.+}}, i8 %{{.+}})
+// LLVM: define dso_local { double, i8 } @ret_double_char(double noundef %{{.+}})
+
+// Two floats share the low eightbyte, so the SSE rules pack them into a vector
+// instead of widening to a double.
+float take_two_float_char(TwoFloatChar v) { return v.b; }
+TwoFloatChar ret_two_float_char(float x) { TwoFloatChar v = {x, x, 0}; return v; }
+
+// CIR: cir.func{{.*}} @take_two_float_char(%arg0: !cir.vector<2 x !cir.float>{{.*}}, %arg1: !s8i{{.*}}) -> !cir.float
+// CIR: cir.func{{.*}} @ret_two_float_char(%arg0: !cir.float {llvm.noundef}{{.*}}) -> ![[V2F32I8]]
+// LLVM: define dso_local float @take_two_float_char(<2 x float> %{{.+}}, i8 %{{.+}})
+// LLVM: define dso_local { <2 x float>, i8 } @ret_two_float_char(float noundef %{{.+}})
+
+// The other route to memory: every member is aligned, so it is the size past
+// two eightbytes that decides.
+int take_seventeen(Seventeen v) { return v.a[3]; }
+
+// CIR: cir.func{{.*}} @take_seventeen(%arg0: !cir.ptr<!rec_Seventeen> {llvm.align = 8 : i64, llvm.byval = !rec_Seventeen, llvm.noalias, llvm.noundef}{{.*}}) -> !s32i
+// LLVM-CIR: define dso_local i32 @take_seventeen(ptr noalias noundef byval(%struct.Seventeen) align 8 %{{.+}})
+// LLVM-OGCG: define dso_local i32 @take_seventeen(ptr noundef byval(%struct.Seventeen) align 8 %{{.+}})
+
+// Packed and over-aligned at once, so the record carries a pad member and the
+// packed mark together.  The misaligned int still decides it.
+int take_packed_ov(PackedOv v) { return v.i; }
+
+// CIR: cir.func{{.*}} @take_packed_ov(%arg0: !cir.ptr<!rec_PackedOv> {llvm.align = 8 : i64, llvm.byval = !rec_PackedOv, llvm.noalias, llvm.noundef}{{.*}}) -> !s32i
+// LLVM-CIR: define dso_local i32 @take_packed_ov(ptr noalias noundef byval(%struct.PackedOv) align 8 %{{.+}})
+// LLVM-OGCG: define dso_local i32 @take_packed_ov(ptr noundef byval(%struct.PackedOv) align 8 %{{.+}})
+
+// A packed member reaches the classifier through an enclosing record and
+// through an array element, neither of which is packed itself.  The member
+// decides the outcome, so both routes are covered on each side of it.
+int take_nest_packed(NestPacked v) { return v.ci.i; }
+int take_arr_packed(ArrPacked v) { return v.a[1].i; }
+int take_nest_nine(NestNine v) { return v.n.b; }
+int take_arr_nine(ArrNine v) { return v.n[0].b; }
+
+// CIR: cir.func{{.*}} @take_nest_packed(%arg0: !cir.ptr<!rec_NestPacked> {llvm.align = 8 : i64, llvm.byval = !rec_NestPacked, llvm.noalias, llvm.noundef}{{.*}}) -> !s32i
+// CIR: cir.func{{.*}} @take_arr_packed(%arg0: !cir.ptr<!rec_ArrPacked> {llvm.align = 8 : i64, llvm.byval = !rec_ArrPacked, llvm.noalias, llvm.noundef}{{.*}}) -> !s32i
+// CIR: cir.func{{.*}} @take_nest_nine(%arg0: !u64i{{.*}}, %arg1: !s8i{{.*}}) -> !s32i
+// CIR: cir.func{{.*}} @take_arr_nine(%arg0: !u64i{{.*}}, %arg1: !s8i{{.*}}) -> !s32i
+// LLVM-CIR: define dso_local i32 @take_nest_packed(ptr noalias noundef byval(%struct.NestPacked) align 8 %{{.+}})
+// LLVM-OGCG: define dso_local i32 @take_nest_packed(ptr noundef byval(%struct.NestPacked) align 8 %{{.+}})
+// LLVM-CIR: define dso_local i32 @take_arr_packed(ptr noalias noundef byval(%struct.ArrPacked) align 8 %{{.+}})
+// LLVM-OGCG: define dso_local i32 @take_arr_packed(ptr noundef byval(%struct.ArrPacked) align 8 %{{.+}})
+// LLVM: define dso_local i32 @take_nest_nine(i64 %{{.+}}, i8 %{{.+}})
+// LLVM: define dso_local i32 @take_arr_nine(i64 %{{.+}}, i8 %{{.+}})
+
+// A union's members all start at offset zero, so packing never misaligns one
+// and the five-byte union stays in a register.
+int take_upacked(UPacked v) { return v.i; }
+
+// CIR: cir.func{{.*}} @take_upacked(%arg0: !cir.int<u, 40>{{.*}}) -> !s32i
+// LLVM: define dso_local i32 @take_upacked(i40 %{{.+}})
+
+// #pragma pack reaches the same layout as the attribute.
+int take_pragma_packed(PragmaPacked v) { return v.i; }
+
+// CIR: cir.func{{.*}} @take_pragma_packed(%arg0: !cir.ptr<!rec_PragmaPacked> {llvm.align = 8 : i64, llvm.byval = !rec_PragmaPacked, llvm.noalias, llvm.noundef}{{.*}}) -> !s32i
+// LLVM-CIR: define dso_local i32 @take_pragma_packed(ptr noalias noundef byval(%struct.PragmaPacked) align 8 %{{.+}})
+// LLVM-OGCG: define dso_local i32 @take_pragma_packed(ptr noundef byval(%struct.PragmaPacked) 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 2322608a80768..74abbdb0de626 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
@@ -5,13 +5,14 @@
 !s32i = !cir.int<s, 32>
 !u8i = !cir.int<u, 8>
 !u32i = !cir.int<u, 32>
-!rec_UPacked = !cir.union<"UPacked" packed {data !s32i, data !cir.array<!s8i x 5>}, padding = {!u8i}>
 !rec_UOverAligned = !cir.union<"UOverAligned" {data !s32i}, padding = {!cir.array<!u8i x 12>}>
 !rec_UShortStorage = !cir.union<"UShortStorage" {data !s16i, data !cir.array<!s8i x 3>}, padding = {!cir.array<!u8i x 2>}>
 !rec_UByteBlobs = !cir.union<"UByteBlobs" {data !u8i, data !u8i}, padding = {!cir.array<!u8i x 3>}>
 !rec_SWrapsOverAligned = !cir.struct<"SWrapsOverAligned" {data !cir.double, data !rec_UOverAligned}>
 !rec_UEmptyLarge = !cir.union<"UEmptyLarge" {}, padding = {!cir.array<!u8i x 32>}>
-!rec_P = !cir.struct<"P" packed {data !s8i, data !s32i}>
+!rec_Poly = !cir.struct<"Poly" packed {data !cir.vptr, data !s32i, pad !cir.array<!u8i x 4>}>
+!rec_BFPad = !cir.struct<"BFPad" packed {bitfield !u8i, data !s32i, pad !cir.array<!u8i x 3>}>
+!rec_ZeroLenArr = !cir.struct<"ZeroLenArr" packed {data !s8i, empty !cir.array<!s32i x 0>}>
 !rec_NamedPlusZeroWidth =
     !cir.struct<"NamedPlusZeroWidth" {bitfield !u8i, pad !cir.array<!u8i x 3>,
                                       bitfield !cir.array<!s32i x 0>,
@@ -41,14 +42,6 @@ module attributes {
     #dlti.dl_entry<f64, dense<64>: vector<2xi64>>>
 } {
 
-  // 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<"UPacked" packed
-
   // 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.
@@ -93,12 +86,33 @@ module attributes {
 
   // 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) {
+  // CIR marks a polymorphic class packed as a layout fallback, so accepting
+  // packed records does not make this one classifiable: the vptr member has no
+  // mapping to an ABI type.
+  cir.func @take_poly(%arg0: !rec_Poly) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for type '!cir.struct<"Poly" packed
+
+  // The unit-width rule keys on the padding rather than the packing, so a
+  // packed record still hits it once a pad member gives classic a gap to read
+  // the declared type through.  Classic sends this one to memory, so the
+  // rejection is conservative rather than a divergence avoided.
+  cir.func @take_bf_pad(%arg0: !rec_BFPad) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for type '!cir.struct<"BFPad" packed
+
+  // A zero-sized member is dropped before classification, so its offset never
+  // reaches the rule sending a record with a member off its own alignment to
+  // memory.  Only packing can put one at such an offset.
+  cir.func @take_zero_len_arr(%arg0: !rec_ZeroLenArr) {
     cir.return
   }
 
-  // CHECK: not yet implemented for type '!cir.struct<"P" packed
+  // 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

diff  --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-record-packed.cir b/clang/test/CIR/Transforms/abi-lowering/x86_64-record-packed.cir
new file mode 100644
index 0000000000000..e0b162b781ba4
--- /dev/null
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-record-packed.cir
@@ -0,0 +1,90 @@
+// RUN: cir-opt %s -cir-call-conv-lowering=target=x86_64 | FileCheck %s
+
+!s8i = !cir.int<s, 8>
+!s16i = !cir.int<s, 16>
+!s32i = !cir.int<s, 32>
+!u8i = !cir.int<u, 8>
+!u64i = !cir.int<u, 64>
+!rec_P = !cir.struct<"P" packed {data !s8i, data !s32i}>
+!rec_Nine = !cir.struct<"Nine" packed {data !s32i, data !s32i, data !s8i}>
+!rec_FiveShort = !cir.struct<"FiveShort" packed {data !s16i, data !s16i, data !s8i}>
+!rec_UPacked = !cir.union<"UPacked" packed {data !s32i, data !cir.array<!s8i x 5>}, padding = {!u8i}>
+
+module attributes {
+  cir.triple = "x86_64-unknown-linux-gnu",
+  cir.record_layouts = {
+    FiveShort = #cir.record_layout<arg_passing_kind = can_pass_in_regs,
+                                   has_trivial_dtor = true, record_align = 1>,
+    Nine = #cir.record_layout<arg_passing_kind = can_pass_in_regs,
+                              has_trivial_dtor = true, record_align = 1>,
+    P = #cir.record_layout<arg_passing_kind = cannot_pass_in_regs,
+                           has_trivial_dtor = false, record_align = 1>,
+    UPacked = #cir.record_layout<arg_passing_kind = can_pass_in_regs,
+                                 has_trivial_dtor = true, record_align = 1>
+  },
+  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>>>
+} {
+
+  // The coerce type is as wide as the whole record here, so the slot is
+  // allocated as the coerce type and the record is read through a bitcast of
+  // it.  That is the opposite direction from a padded record, where the slot
+  // has to be the wider record or the reload would run past it.
+  cir.func @take_five_short(%arg0: !rec_FiveShort) {
+    cir.return
+  }
+
+  // CHECK-LABEL: cir.func{{.*}} @take_five_short(%arg0: !cir.int<u, 40>)
+  // CHECK:   %[[SLOT:.*]] = cir.alloca "coerce"{{.*}}: !cir.ptr<!cir.int<u, 40>>
+  // CHECK:   cir.store %arg0, %[[SLOT]] : !cir.int<u, 40>, !cir.ptr<!cir.int<u, 40>>
+  // CHECK:   %[[VIEW:.*]] = cir.cast bitcast %[[SLOT]] : !cir.ptr<!cir.int<u, 40>> -> !cir.ptr<!rec_FiveShort>
+  // CHECK:   %{{.+}} = cir.load %[[VIEW]] : !cir.ptr<!rec_FiveShort>, !rec_FiveShort
+
+  // The caller allocates the slot the other way round, as the record, and
+  // reads the register out through a view of it.
+  cir.func @call_five_short(%arg0: !rec_FiveShort) {
+    cir.call @take_five_short(%arg0) : (!rec_FiveShort) -> ()
+    cir.return
+  }
+
+  // CHECK-LABEL: cir.func{{.*}} @call_five_short(%arg0: !cir.int<u, 40>)
+  // CHECK:   %[[OUT:.*]] = cir.alloca "coerce"{{.*}}: !cir.ptr<!rec_FiveShort>
+  // CHECK:   cir.store %{{.+}}, %[[OUT]] : !rec_FiveShort, !cir.ptr<!rec_FiveShort>
+  // CHECK:   %[[OUTVIEW:.*]] = cir.cast bitcast %[[OUT]] : !cir.ptr<!rec_FiveShort> -> !cir.ptr<!cir.int<u, 40>>
+  // CHECK:   %[[REG:.*]] = cir.load %[[OUTVIEW]] : !cir.ptr<!cir.int<u, 40>>, !cir.int<u, 40>
+  // CHECK:   cir.call @take_five_short(%[[REG]]) : (!cir.int<u, 40>) -> ()
+
+  // Two eightbytes flatten into two wire arguments, so the record is rebuilt a
+  // register at a time through the coercion tuple rather than by one bitcast.
+  cir.func @take_nine(%arg0: !rec_Nine) {
+    cir.return
+  }
+
+  // CHECK-LABEL: cir.func{{.*}} @take_nine(%arg0: !u64i, %arg1: !s8i)
+  // CHECK:   %[[RECSLOT:.*]] = cir.alloca "coerce"{{.*}}: !cir.ptr<!rec_anon_struct>
+  // CHECK:   %[[TUPLE:.*]] = cir.alloca "coerce"{{.*}}: !cir.ptr<!rec_anon_struct>
+  // CHECK:   %[[LO:.*]] = cir.get_member %[[TUPLE]][0]{{.*}}-> !cir.ptr<!u64i>
+  // CHECK:   cir.store %arg0, %[[LO]] : !u64i, !cir.ptr<!u64i>
+  // CHECK:   %[[HI:.*]] = cir.get_member %[[TUPLE]][1]{{.*}}-> !cir.ptr<!s8i>
+  // CHECK:   cir.store %arg1, %[[HI]] : !s8i, !cir.ptr<!s8i>
+  // CHECK:   %{{.+}} = cir.cast bitcast %[[RECSLOT]] : !cir.ptr<!rec_anon_struct> -> !cir.ptr<!rec_Nine>
+
+  // A packed record the caller must destroy takes byref rather than byval, and
+  // byref keeps the record's declared alignment where byval would raise it to
+  // the ABI minimum of 8.
+  cir.func @take_p(%arg0: !rec_P) {
+    cir.return
+  }
+
+  // CHECK-LABEL: cir.func{{.*}} @take_p(%arg0: !cir.ptr<!rec_P> {llvm.align = 1 : i64, llvm.byref = !rec_P})
+
+  // A union coerces through the same one-register path as FiveShort.
+  cir.func @take_upacked(%arg0: !rec_UPacked) {
+    cir.return
+  }
+
+  // CHECK-LABEL: cir.func{{.*}} @take_upacked(%arg0: !cir.int<u, 40>)
+}


        


More information about the cfe-commits mailing list