Author: adams381
Date: 2026-07-21T10:51:01-05:00
New Revision: d534912565bd46fe4c031c46c5204db3ec03dbed

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

LOG: [CIR] Add x86_64 aggregate calling-convention lowering (#210528)

The x86_64 SysV calling-convention bridge in CallConvLowering so far handles 
only scalar arguments and returns.  A function with a struct or array parameter 
is reported NYI.  This teaches the bridge to classify struct and array 
aggregates.

A struct is mapped to an `llvm::abi` record built from the DataLayout field 
offsets and the CanPassInRegisters flag on the module's `cir.record_layouts` 
metadata, and an array maps to an `llvm::abi` array.  The library's classifier 
then produces the ArgInfo, either Direct with a coerced register type the 
existing rewriter flattens, or Indirect via sret, byval, or byref.  
CIRABIRewriteContext already applies all of these, so this only feeds it the 
aggregate classifications and leaves the scalar path untouched.

Aggregate shapes the bridge does not yet classify stay errorNYI, so an 
unsupported signature fails cleanly instead of being misclassified.  Those are 
unions (whose register coercion needs a widen fixup), packed and over-aligned 
records, empty-for-ABI records, and `_BitInt`.  All-float aggregates such as a 
two-`float` struct or `float[2]` are included too.  Their SSE class coerces to 
a `<2 x float>` vector the bridge cannot yet represent, so instead of passing 
the aggregate through unchanged it reports the coercion as NYI.

The byval and sret argument attributes still carry the CIR record type.  
Converting that to the LLVM type in LowerToLLVM is a separate change, so the 
byval and sret tests check the CIR output only, while the direct and flatten 
tests also check the lowered LLVM IR.

Added: 
    clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir
    clang/test/CIR/Transforms/abi-lowering/x86_64-struct-direct.cir
    clang/test/CIR/Transforms/abi-lowering/x86_64-struct-indirect.cir

Modified: 
    clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp

Removed: 
    


################################################################################
diff  --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp 
b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
index bb5b194ee0433..e1414d3b311e2 100644
--- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
@@ -62,21 +62,43 @@ namespace mlir {
 namespace {
 
 
//===----------------------------------------------------------------------===//
-// x86_64 System V classifier bridge (scalar types)
+// x86_64 System V classifier bridge (scalar and struct/array types)
 //
-// Maps scalar CIR types to llvm::abi::Type, runs the LLVM ABI Lowering
-// Library's SysV x86_64 classifier, and converts the result back into the
+// Maps CIR types to llvm::abi::Type, runs the LLVM ABI Lowering Library's
+// SysV x86_64 classifier, and converts the result back into the
 // dialect-agnostic mlir::abi::FunctionClassification that CIRABIRewriteContext
-// consumes.  Only integer / pointer / bool / f32 / f64 signatures are handled;
-// aggregates and other leaf types are reported NYI by classifyX86_64Function
-// so an unsupported signature fails the pass instead of being misclassified.
+// consumes.  Integer / pointer / bool / f32 / f64 scalars and struct / array
+// aggregates are handled; unions, `_BitInt`, `_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.
 
//===----------------------------------------------------------------------===//
 
-/// The scalar CIR types the x86_64 bridge handles.  A regular integer up to
-/// 64 bits, pointer, bool, void, f32, or f64 is a single-register Direct or
-/// Extend argument.  `_BitInt`, `__int128`, and wider/other types need 
coercion
-/// or indirect passing, which this scalar bridge does not do.
-static bool isSupportedScalarType(mlir::Type ty) {
+/// Whether a struct's declared argument-passing kind (from the module's
+/// record-layout metadata) allows it to be passed in registers.  A record with
+/// 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);
+  if (!layout)
+    return true;
+  return layout.getArgPassingKind() == cir::ArgPassingKind::CanPassInRegs;
+}
+
+/// The CIR types the x86_64 bridge handles.  Scalars: a regular integer up to
+/// 64 bits, pointer, bool, void, f32, or f64.  Aggregates: a complete struct
+/// whose fields are all themselves supported, or an array of a supported
+/// element type.  `_BitInt`, `__int128`, 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) {
   // 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.
@@ -87,6 +109,23 @@ static bool isSupportedScalarType(mlir::Type ty) {
     return true;
   if (auto intTy = dyn_cast<cir::IntType>(ty))
     return !intTy.getIsBitInt() && intTy.getWidth() <= 64;
+  if (auto arrTy = dyn_cast<cir::ArrayType>(ty))
+    return isSupportedType(arrTy.getElementType());
+  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.  Empty-for-ABI records classify as Ignore, which is also
+    // deferred: a C empty struct is a zero-field record, and CIRGen lays out
+    // an empty C++ class as a single padded byte (caught by the padded check).
+    // 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() || recTy.getMembers().empty())
+      return false;
+    return llvm::all_of(recTy.getMembers(),
+                        [](mlir::Type m) { return isSupportedType(m); });
+  }
   return false;
 }
 
@@ -107,15 +146,28 @@ static mlir::Type abiTypeToCIR(const llvm::abi::Type *ty, 
MLIRContext *ctx) {
       .Case([&](const llvm::abi::PointerType *) {
         return cir::PointerType::get(cir::VoidType::get(ctx));
       })
+      .Case([&](const llvm::abi::RecordType *recTy) -> mlir::Type {
+        SmallVector<mlir::Type> fieldTypes;
+        fieldTypes.reserve(recTy->getFields().size());
+        for (const auto &field : recTy->getFields()) {
+          mlir::Type fieldCIR = abiTypeToCIR(field.FieldType, ctx);
+          if (!fieldCIR)
+            return nullptr;
+          fieldTypes.push_back(fieldCIR);
+        }
+        // Coercion types are plain register tuples, not the source record.
+        return cir::StructType::get(ctx, fieldTypes, /*packed=*/false,
+                                    /*padded=*/false, /*is_class=*/false);
+      })
       .Default([](const llvm::abi::Type *) -> mlir::Type { return nullptr; });
 }
 
-/// Map a scalar CIR type to an llvm::abi::Type.  classifyX86_64Function
-/// pre-filters the signature, so only the scalar types handled here can
+/// Map a CIR type to an llvm::abi::Type.  classifyX86_64Function pre-filters
+/// the signature, so only the scalar and struct/array types handled here can
 /// reach this function.
 static const llvm::abi::Type *mapCIRType(mlir::Type type,
                                          mlir::abi::ABITypeMapper &typeMapper,
-                                         const DataLayout &dl) {
+                                         const DataLayout &dl, ModuleOp modOp) 
{
   llvm::abi::TypeBuilder &tb = typeMapper.getTypeBuilder();
   return llvm::TypeSwitch<mlir::Type, const llvm::abi::Type *>(type)
       .Case([&](cir::IntType intTy) {
@@ -147,17 +199,54 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type,
         return tb.getFloatType(llvm::APFloat::IEEEdouble(),
                                llvm::Align(dl.getTypeABIAlignment(type)));
       })
+      .Case([&](cir::ArrayType arrTy) {
+        const llvm::abi::Type *elemAbi =
+            mapCIRType(arrTy.getElementType(), typeMapper, dl, modOp);
+        return tb.getArrayType(elemAbi, arrTy.getSize(),
+                               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.
+        SmallVector<llvm::abi::FieldInfo> fields;
+        fields.reserve(recTy.getMembers().size());
+        uint64_t offsetBits = 0;
+        for (mlir::Type fieldTy : recTy.getMembers()) {
+          const llvm::abi::Type *mappedField =
+              mapCIRType(fieldTy, typeMapper, dl, modOp);
+          offsetBits =
+              llvm::alignTo(offsetBits, dl.getTypeABIAlignment(fieldTy) * 8);
+          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);
+      })
       .Default([](mlir::Type) -> const llvm::abi::Type * {
         llvm_unreachable(
             "mapCIRType: type not pre-filtered by classifyX86_64Function");
       });
 }
 
-/// Convert an llvm::abi::ArgInfo for a scalar type into the ArgClassification
-/// consumed by CIRABIRewriteContext.
+/// Convert an llvm::abi::ArgInfo into the ArgClassification consumed by
+/// CIRABIRewriteContext.
 ///
-/// Direct: every scalar this bridge maps passes as-is, so no coercion type
-/// is needed (nullptr means "same as the original CIR type").
+/// Direct: a scalar passes as-is (nullptr coercion means "same as the
+/// original CIR type").  A struct or array is coerced to a register-friendly
+/// type; getDirect keeps canFlatten set so the rewriter can split a
+/// multi-field coerced struct into individual wire arguments.  If the
+/// classifier picks a coercion this bridge cannot represent (e.g. an SSE
+/// <2 x float> vector), std::nullopt is returned so the caller reports NYI
+/// rather than silently passing the aggregate unchanged.
 ///
 /// Extend: bool or a sub-register integer needs a signext/zeroext attribute.
 /// Every ArgInfo::getExtend() call site in the x86_64 classifier
@@ -165,20 +254,27 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type,
 /// so a non-integer, non-bool origTy here would mean the classifier
 /// disagreed with its own source -- asserted rather than silently handled.
 ///
-/// Indirect: needed once records/_BitInt/vectors are supported (sret,
-/// byval, and large _BitInt all classify Indirect), but
-/// X86_64TargetInfo::getIndirectResult()/getIndirectReturnResult() only
-/// return Indirect for aggregates or _BitInt, neither of which the
-/// scalar-only type set this bridge accepts can produce.  Unreachable until
-/// a later PR adds those types and the record/vector/complex/int type gate
-/// that belongs here.
+/// Indirect: an aggregate that does not fit in registers is passed via a
+/// pointer (sret for returns, byval for arguments).
 ///
-/// Ignore: a void return has no register or stack slot.
-static ArgClassification convertABIArgInfo(const llvm::abi::ArgInfo &info,
-                                           MLIRContext *ctx,
-                                           mlir::Type origTy) {
-  if (info.isDirect())
-    return ArgClassification::getDirect(nullptr);
+/// Ignore: a void return has no register or stack slot.  (Empty-for-ABI
+/// records are rejected by isSupportedType, so they never reach here.)
+static std::optional<ArgClassification>
+convertABIArgInfo(const llvm::abi::ArgInfo &info, MLIRContext *ctx,
+                  mlir::Type origTy) {
+  if (info.isDirect()) {
+    // A scalar passes as-is; only an aggregate carries a coercion type.
+    if (!origTy || !isa<cir::RecordType, cir::ArrayType>(origTy))
+      return ArgClassification::getDirect(nullptr);
+    // An aggregate must coerce to a type this bridge can represent.  A coerce
+    // this bridge cannot map (an SSE vector, or a nested type it does not
+    // handle) yields a null type; report that as NYI instead of leaving the
+    // aggregate as an unchanged by-value record.
+    mlir::Type coerced = abiTypeToCIR(info.getCoerceToType(), ctx);
+    if (!coerced)
+      return std::nullopt;
+    return ArgClassification::getDirect(coerced);
+  }
   if (info.isExtend()) {
     if (origTy && isa<cir::BoolType>(origTy))
       return ArgClassification::getExtend(nullptr, info.isSignExt());
@@ -187,17 +283,21 @@ static ArgClassification convertABIArgInfo(const 
llvm::abi::ArgInfo &info,
     mlir::Type extendedTy = abiTypeToCIR(info.getCoerceToType(), ctx);
     return ArgClassification::getExtend(extendedTy, info.isSignExt());
   }
+  if (info.isIndirect())
+    return ArgClassification::getIndirect(info.getIndirectAlign(),
+                                          info.getIndirectByVal());
   assert(info.isIgnore() && "Unexpected classification");
   return ArgClassification::getIgnore();
 }
 
 /// Classify a cir.func for x86_64 SysV using the LLVM ABI library.  Returns
-/// std::nullopt and emits an NYI error if the signature uses a type the scalar
-/// bridge does not handle yet.
+/// std::nullopt and emits an NYI error if the signature uses a type the bridge
+/// does not handle yet.
 static std::optional<FunctionClassification>
 classifyX86_64Function(cir::FuncOp func, const DataLayout &dl,
                        mlir::abi::ABITypeMapper &typeMapper,
-                       const llvm::abi::TargetInfo &targetInfo) {
+                       const llvm::abi::TargetInfo &targetInfo,
+                       ModuleOp modOp) {
   MLIRContext *ctx = func->getContext();
   cir::FuncType fnTy = func.getFunctionType();
   mlir::Type retCIR = fnTy.getReturnType();
@@ -205,7 +305,7 @@ classifyX86_64Function(cir::FuncOp func, const DataLayout 
&dl,
   bool voidRet = isa<cir::VoidType>(retCIR);
 
   auto reject = [&](mlir::Type t) -> bool {
-    if (isSupportedScalarType(t))
+    if (isSupportedType(t))
       return false;
     func.emitOpError()
         << "x86_64 calling-convention lowering not yet implemented for type "
@@ -220,23 +320,43 @@ classifyX86_64Function(cir::FuncOp func, const DataLayout 
&dl,
 
   const llvm::abi::Type *retAbi =
       voidRet ? typeMapper.getTypeBuilder().getVoidType()
-              : mapCIRType(retCIR, typeMapper, dl);
+              : mapCIRType(retCIR, typeMapper, dl, modOp);
   SmallVector<const llvm::abi::Type *> argAbi;
   for (mlir::Type a : fnTy.getInputs())
-    argAbi.push_back(mapCIRType(a, typeMapper, dl));
+    argAbi.push_back(mapCIRType(a, typeMapper, dl, modOp));
 
   std::unique_ptr<llvm::abi::FunctionInfo> fi =
       llvm::abi::FunctionInfo::create(llvm::CallingConv::C, retAbi, argAbi);
   targetInfo.computeInfo(*fi);
 
+  // convertABIArgInfo returns nullopt when the classifier picks a coercion
+  // this bridge cannot represent (e.g. an SSE vector coerce for an all-float
+  // aggregate).  Report it as NYI rather than emitting a wrong signature.
+  auto nyiCoercion = [&](mlir::Type t) {
+    func.emitOpError() << "x86_64 calling-convention lowering not yet "
+                          "implemented for the ABI coercion of type "
+                       << t;
+  };
+
   FunctionClassification fc;
   mlir::Type origRet = voidRet ? mlir::Type() : retCIR;
-  fc.returnInfo = convertABIArgInfo(fi->getReturnInfo(), ctx, origRet);
+  std::optional<ArgClassification> retAc =
+      convertABIArgInfo(fi->getReturnInfo(), ctx, origRet);
+  if (!retAc) {
+    nyiCoercion(retCIR);
+    return std::nullopt;
+  }
+  fc.returnInfo = *retAc;
   auto inputs = fnTy.getInputs();
   for (unsigned i = 0, e = fi->arg_size(); i < e; ++i) {
     mlir::Type origArg = i < inputs.size() ? inputs[i] : mlir::Type();
-    fc.argInfos.push_back(
-        convertABIArgInfo(fi->getArgInfo(i).Info, ctx, origArg));
+    std::optional<ArgClassification> ac =
+        convertABIArgInfo(fi->getArgInfo(i).Info, ctx, origArg);
+    if (!ac) {
+      nyiCoercion(origArg);
+      return std::nullopt;
+    }
+    fc.argInfos.push_back(*ac);
   }
   return fc;
 }
@@ -347,7 +467,7 @@ void CallConvLoweringPass::runOnOperation() {
   moduleOp.walk([&](cir::FuncOp f) {
     std::optional<FunctionClassification> fc;
     if (x86Target)
-      fc = classifyX86_64Function(f, dl, *x86TypeMapper, *x86Target);
+      fc = classifyX86_64Function(f, dl, *x86TypeMapper, *x86Target, moduleOp);
     else
       fc = classifyFunction(f, dl, target, classificationAttr);
     if (!fc) {

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
new file mode 100644
index 0000000000000..87692e1a3e5a2
--- /dev/null
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir
@@ -0,0 +1,92 @@
+// RUN: not cir-opt %s -cir-call-conv-lowering=target=x86_64 2>&1 | FileCheck 
%s
+
+!s8i = !cir.int<s, 8>
+!s32i = !cir.int<s, 32>
+!u8i = !cir.int<u, 8>
+!u32i = !cir.int<u, 32>
+!rec_U = !cir.union<"U" {!s32i, !u32i}>
+!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}>
+!rec_E0 = !cir.struct<"E0" {}>
+!rec_FF = !cir.struct<"FF" {!cir.float, !cir.float}>
+!rec_RetFF = !cir.struct<"RetFF" {!cir.float, !cir.float}>
+
+module attributes {
+  dlti.dl_spec = #dlti.dl_spec<
+    #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>>>
+} {
+
+  // A union is rejected: its register coercion needs a widen fixup.
+  cir.func @take_union(%arg0: !rec_U) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for type '!cir.union<"U"
+
+  // A packed struct is rejected: it needs pad-aware classification.
+  cir.func @take_packed(%arg0: !rec_P) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for type '!cir.struct<"P" packed
+
+  // A padded (over-aligned) struct is rejected: it needs pad-aware
+  // classification.
+  cir.func @take_padded(%arg0: !rec_Ov) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for type '!cir.struct<"Ov" padded
+
+  // An empty C++ class is laid out as a single padded byte, so it is rejected
+  // by the padded check; its Ignore classification is deferred.
+  cir.func @take_empty(%arg0: !rec_E) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for type '!cir.struct<"E" padded
+
+  // A zero-field record (a C empty struct) also classifies as Ignore, which is
+  // deferred, so it is rejected rather than passed with the argument dropped.
+  cir.func @take_e0(%arg0: !rec_E0) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for type '!cir.struct<"E0"
+
+  // An all-float struct classifies to an SSE vector coerce this bridge does
+  // not represent, so it is reported NYI rather than passed unchanged.
+  cir.func @take_ff(%arg0: !rec_FF) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for the ABI coercion of type '!cir.struct<"FF"
+
+  // The same holds for an all-float array.
+  cir.func @take_farr(%arg0: !cir.array<!cir.float x 2>) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for the ABI coercion of type 
'!cir.array<!cir.float x 2>
+
+  // A three-float struct coerces to a record with a vector field; the
+  // unmappable field propagates out as NYI too.
+  cir.func @take_f3(%arg0: !cir.struct<"F3" {!cir.float, !cir.float, 
!cir.float}>) {
+    cir.return
+  }
+
+  // CHECK: not yet implemented for the ABI coercion of type '!cir.struct<"F3"
+
+  // The unmappable-coercion check also covers the return value.
+  cir.func @ret_ff() -> !rec_RetFF {
+    %0 = cir.alloca "r" align(4) : !cir.ptr<!rec_RetFF>
+    %1 = cir.load %0 : !cir.ptr<!rec_RetFF>, !rec_RetFF
+    cir.return %1 : !rec_RetFF
+  }
+
+  // CHECK: not yet implemented for the ABI coercion of type 
'!cir.struct<"RetFF"
+}

diff  --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-struct-direct.cir 
b/clang/test/CIR/Transforms/abi-lowering/x86_64-struct-direct.cir
new file mode 100644
index 0000000000000..a5b56cda7fe5f
--- /dev/null
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-struct-direct.cir
@@ -0,0 +1,158 @@
+// 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>
+!rec_Pair = !cir.struct<"Pair" {!s32i, !s32i}>
+!rec_Two = !cir.struct<"Two" {!s64i, !s64i}>
+!rec_Mixed = !cir.struct<"Mixed" {!s8i, !s32i}>
+!rec_CharBuf = !cir.struct<"CharBuf" {!cir.array<!s8i x 4>}>
+!rec_Char1 = !cir.struct<"Char1" {!cir.array<!s8i x 1>}>
+
+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<i32, dense<32>: vector<2xi64>>,
+    #dlti.dl_entry<i64, dense<64>: vector<2xi64>>>
+} {
+
+  // A two-int struct (8 bytes) is coerced to a single i64 register.  The body
+  // reads a field, so the prologue that reconstitutes the record from the
+  // register is visible: the i64 is stored to a coerce slot, the slot is
+  // bitcast to the record pointer, and the record is loaded back.
+  cir.func @take_pair(%arg0: !rec_Pair) -> !s32i {
+    %0 = cir.alloca "p" align(4) : !cir.ptr<!rec_Pair>
+    cir.store %arg0, %0 : !rec_Pair, !cir.ptr<!rec_Pair>
+    %1 = cir.get_member %0[0] {name = "a"} : !cir.ptr<!rec_Pair> -> 
!cir.ptr<!s32i>
+    %2 = cir.load %1 : !cir.ptr<!s32i>, !s32i
+    cir.return %2 : !s32i
+  }
+
+  // CHECK: cir.func{{.*}} @take_pair(%arg0: !u64i) -> !s32i
+  // CHECK:   %[[SLOT:.*]] = cir.alloca "coerce" {{.*}} : !cir.ptr<!u64i>
+  // CHECK:   cir.store %arg0, %[[SLOT]] : !u64i, !cir.ptr<!u64i>
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %[[SLOT]] : !cir.ptr<!u64i> -> 
!cir.ptr<!rec_Pair>
+  // CHECK:   %[[REC:.*]] = cir.load %[[CAST]] : !cir.ptr<!rec_Pair>, !rec_Pair
+  // CHECK:   %[[LOCAL:.*]] = cir.alloca "p" {{.*}} : !cir.ptr<!rec_Pair>
+  // CHECK:   cir.store %[[REC]], %[[LOCAL]] : !rec_Pair, !cir.ptr<!rec_Pair>
+  // CHECK:   %[[FLD:.*]] = cir.get_member %[[LOCAL]][0] {name = "a"} : 
!cir.ptr<!rec_Pair> -> !cir.ptr<!s32i>
+  // CHECK:   %{{.*}} = cir.load %[[FLD]] : !cir.ptr<!s32i>, !s32i
+
+  // A two-i64 struct (16 bytes) is passed as two i64 registers (flattened).
+  // The body reads the second field: the two registers are written into a
+  // flattened coerce slot, reloaded as the record, then indexed.
+  cir.func @take_two(%arg0: !rec_Two) -> !s64i {
+    %0 = cir.alloca "t" align(8) : !cir.ptr<!rec_Two>
+    cir.store %arg0, %0 : !rec_Two, !cir.ptr<!rec_Two>
+    %1 = cir.get_member %0[1] {name = "b"} : !cir.ptr<!rec_Two> -> 
!cir.ptr<!s64i>
+    %2 = cir.load %1 : !cir.ptr<!s64i>, !s64i
+    cir.return %2 : !s64i
+  }
+
+  // CHECK: cir.func{{.*}} @take_two(%arg0: !s64i, %arg1: !s64i) -> !s64i
+  // CHECK:   cir.alloca "coerce" {{.*}} : !cir.ptr<!rec_anon_struct>
+  // CHECK:   %[[FLAT:.*]] = cir.alloca "coerce" {{.*}} : 
!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<!s64i>
+  // CHECK:   cir.store %arg1, %[[E1]] : !s64i, !cir.ptr<!s64i>
+  // CHECK:   %{{.*}} = cir.cast bitcast %{{.*}} : !cir.ptr<!rec_anon_struct> 
-> !cir.ptr<!rec_Two>
+  // CHECK:   %{{.*}} = cir.get_member %{{.*}}[1] {name = "b"} : 
!cir.ptr<!rec_Two> -> !cir.ptr<!s64i>
+
+  // Mixed-alignment fields (i8 then i32) still fit one eightbyte: the i32 is
+  // placed at its aligned offset and the struct coerces to a single i64.
+  cir.func @take_mixed(%arg0: !rec_Mixed) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_mixed(%arg0: !u64i)
+  // CHECK:   %[[SLOT:.*]] = cir.alloca "coerce" {{.*}} : !cir.ptr<!u64i>
+  // CHECK:   cir.store %arg0, %[[SLOT]] : !u64i, !cir.ptr<!u64i>
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %[[SLOT]] : !cir.ptr<!u64i> -> 
!cir.ptr<!rec_Mixed>
+  // CHECK:   %{{.*}} = cir.load %[[CAST]] : !cir.ptr<!rec_Mixed>, !rec_Mixed
+
+  // A struct wrapping a small char array is data, not an empty record: the
+  // 4-byte struct coerces to a single i32.
+  cir.func @take_charbuf(%arg0: !rec_CharBuf) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_charbuf(%arg0: !u32i)
+  // CHECK:   %[[SLOT:.*]] = cir.alloca "coerce" {{.*}} : !cir.ptr<!u32i>
+  // CHECK:   cir.store %arg0, %[[SLOT]] : !u32i, !cir.ptr<!u32i>
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %[[SLOT]] : !cir.ptr<!u32i> -> 
!cir.ptr<!rec_CharBuf>
+  // CHECK:   %{{.*}} = cir.load %[[CAST]] : !cir.ptr<!rec_CharBuf>, 
!rec_CharBuf
+
+  // A one-byte struct wrapping a single-element char array is data, not an
+  // empty class: it coerces to a single i8.
+  cir.func @take_char1(%arg0: !rec_Char1) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_char1(%arg0: !s8i)
+  // CHECK:   %[[SLOT:.*]] = cir.alloca "coerce" {{.*}} : !cir.ptr<!s8i>
+  // CHECK:   cir.store %arg0, %[[SLOT]] : !s8i, !cir.ptr<!s8i>
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %[[SLOT]] : !cir.ptr<!s8i> -> 
!cir.ptr<!rec_Char1>
+  // CHECK:   %{{.*}} = cir.load %[[CAST]] : !cir.ptr<!rec_Char1>, !rec_Char1
+
+  // A 12-byte array argument is passed as two eightbytes (i64 then i32).  The
+  // two registers are written into a flattened coerce slot, then reloaded as
+  // the array.
+  cir.func @take_arr3(%arg0: !cir.array<!s32i x 3>) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_arr3(%arg0: !u64i, %arg1: !s32i)
+  // CHECK:   cir.alloca "coerce" {{.*}} : !cir.ptr<!rec_anon_struct1>
+  // CHECK:   %[[FLAT:.*]] = cir.alloca "coerce" {{.*}} : 
!cir.ptr<!rec_anon_struct1>
+  // CHECK:   %[[E0:.*]] = cir.get_member %[[FLAT]][0] {{.*}} : 
!cir.ptr<!rec_anon_struct1> -> !cir.ptr<!u64i>
+  // CHECK:   cir.store %arg0, %[[E0]] : !u64i, !cir.ptr<!u64i>
+  // CHECK:   %[[E1:.*]] = cir.get_member %[[FLAT]][1] {{.*}} : 
!cir.ptr<!rec_anon_struct1> -> !cir.ptr<!s32i>
+  // CHECK:   cir.store %arg1, %[[E1]] : !s32i, !cir.ptr<!s32i>
+  // CHECK:   %{{.*}} = cir.cast bitcast %{{.*}} : !cir.ptr<!rec_anon_struct1> 
-> !cir.ptr<!cir.array<!s32i x 3>>
+
+  // An anonymous struct has no record-layout entry, so it defaults to
+  // can-pass-in-registers and coerces like its named counterpart.
+  cir.func @take_anon(%arg0: !cir.struct<{!s32i, !s32i}>) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_anon(%arg0: !u64i)
+  // CHECK:   %[[SLOT:.*]] = cir.alloca "coerce" {{.*}} : !cir.ptr<!u64i>
+  // CHECK:   cir.store %arg0, %[[SLOT]] : !u64i, !cir.ptr<!u64i>
+  // CHECK:   %[[CAST:.*]] = cir.cast bitcast %[[SLOT]] : !cir.ptr<!u64i> -> 
!cir.ptr<!rec_anon_struct2>
+  // CHECK:   %{{.*}} = cir.load %[[CAST]] : !cir.ptr<!rec_anon_struct2>, 
!rec_anon_struct2
+
+  // The call site round-trips the record through the coercion type: the
+  // incoming i64 is reloaded as the record, then stored and reloaded as the
+  // i64 the callee expects.
+  cir.func @call_pair(%arg0: !rec_Pair) {
+    %0 = cir.call @take_pair(%arg0) : (!rec_Pair) -> !s32i
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @call_pair(%arg0: !u64i)
+  // CHECK:   %[[S0:.*]] = cir.alloca "coerce" {{.*}} : !cir.ptr<!u64i>
+  // CHECK:   cir.store %arg0, %[[S0]] : !u64i, !cir.ptr<!u64i>
+  // CHECK:   %[[C0:.*]] = cir.cast bitcast %[[S0]] : !cir.ptr<!u64i> -> 
!cir.ptr<!rec_Pair>
+  // CHECK:   %[[REC:.*]] = cir.load %[[C0]] : !cir.ptr<!rec_Pair>, !rec_Pair
+  // CHECK:   %[[S1:.*]] = cir.alloca "coerce" {{.*}} : !cir.ptr<!rec_Pair>
+  // CHECK:   cir.store %[[REC]], %[[S1]] : !rec_Pair, !cir.ptr<!rec_Pair>
+  // CHECK:   %[[C1:.*]] = cir.cast bitcast %[[S1]] : !cir.ptr<!rec_Pair> -> 
!cir.ptr<!u64i>
+  // CHECK:   %[[U:.*]] = cir.load %[[C1]] : !cir.ptr<!u64i>, !u64i
+  // CHECK:   %{{.*}} = cir.call @take_pair(%[[U]]) : (!u64i) -> !s32i
+}
+
+// LLVM: define i32 @take_pair(i64 %{{.+}})
+// LLVM: define i64 @take_two(i64 %{{.+}}, i64 %{{.+}})
+// LLVM: define void @take_mixed(i64 %{{.+}})
+// LLVM: define void @take_charbuf(i32 %{{.+}})
+// LLVM: define void @take_char1(i8 %{{.+}})
+// LLVM: define void @take_arr3(i64 %{{.+}}, i32 %{{.+}})
+// LLVM: define void @take_anon(i64 %{{.+}})
+// LLVM: define void @call_pair(i64 %{{.+}})
+// LLVM:   %{{.+}} = call i32 @take_pair(i64 %{{.+}})

diff  --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-struct-indirect.cir 
b/clang/test/CIR/Transforms/abi-lowering/x86_64-struct-indirect.cir
new file mode 100644
index 0000000000000..373e47b41b9fe
--- /dev/null
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-struct-indirect.cir
@@ -0,0 +1,51 @@
+// RUN: cir-opt %s -cir-call-conv-lowering=target=x86_64 | FileCheck %s
+
+!s32i = !cir.int<s, 32>
+!s64i = !cir.int<s, 64>
+!rec_Big = !cir.struct<"Big" {!s64i, !s64i, !s64i}>
+!rec_NoRegs = !cir.struct<"NoRegs" {!s32i, !s32i}>
+
+module attributes {
+  cir.record_layouts = {NoRegs = #cir.record_layout<
+    arg_passing_kind = cannot_pass_in_regs, has_trivial_dtor = false,
+    record_align = 4>},
+  dlti.dl_spec = #dlti.dl_spec<
+    #dlti.dl_entry<i32, dense<32>: vector<2xi64>>,
+    #dlti.dl_entry<i64, dense<64>: vector<2xi64>>>
+} {
+
+  // A 24-byte struct does not fit in registers: passed byval.  The body reads
+  // a field, so the load the rewriter inserts at entry (turning the byval
+  // pointer back into the record value) is visible.
+  cir.func @take_big(%arg0: !rec_Big) -> !s64i {
+    %0 = cir.alloca "b" align(8) : !cir.ptr<!rec_Big>
+    cir.store %arg0, %0 : !rec_Big, !cir.ptr<!rec_Big>
+    %1 = cir.get_member %0[2] {name = "c"} : !cir.ptr<!rec_Big> -> 
!cir.ptr<!s64i>
+    %2 = cir.load %1 : !cir.ptr<!s64i>, !s64i
+    cir.return %2 : !s64i
+  }
+
+  // CHECK: cir.func{{.*}} @take_big(%arg0: !cir.ptr<!rec_Big> {llvm.align = 8 
: i64, llvm.byval = !rec_Big, llvm.noalias, llvm.noundef}) -> !s64i
+  // CHECK:   %[[VAL:.*]] = cir.load %arg0 : !cir.ptr<!rec_Big>, !rec_Big
+  // CHECK:   %[[LOCAL:.*]] = cir.alloca "b" {{.*}} : !cir.ptr<!rec_Big>
+  // CHECK:   cir.store %[[VAL]], %[[LOCAL]] : !rec_Big, !cir.ptr<!rec_Big>
+  // CHECK:   %[[FLD:.*]] = cir.get_member %[[LOCAL]][2] {name = "c"} : 
!cir.ptr<!rec_Big> -> !cir.ptr<!s64i>
+  // CHECK:   %{{.*}} = cir.load %[[FLD]] : !cir.ptr<!s64i>, !s64i
+
+  // A 24-byte struct return uses an sret pointer argument.
+  cir.func @ret_big() -> !rec_Big {
+    %0 = cir.alloca "r" align(8) : !cir.ptr<!rec_Big>
+    %1 = cir.load %0 : !cir.ptr<!rec_Big>, !rec_Big
+    cir.return %1 : !rec_Big
+  }
+
+  // CHECK: cir.func{{.*}} @ret_big(%arg0: !cir.ptr<!rec_Big> {llvm.align = 8 
: i64, llvm.dead_on_unwind, llvm.noalias, llvm.sret = !rec_Big, llvm.writable})
+
+  // A small struct that cannot pass in registers (non-trivial for ABI) is
+  // passed indirectly even though its size would otherwise fit a register.
+  cir.func @take_noregs(%arg0: !rec_NoRegs) {
+    cir.return
+  }
+
+  // CHECK: cir.func{{.*}} @take_noregs(%arg0: !cir.ptr<!rec_NoRegs> 
{llvm.align = 4 : i64, llvm.byref = !rec_NoRegs})
+}


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

Reply via email to