https://github.com/adams381 created https://github.com/llvm/llvm-project/pull/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. >From c0ca1b3bcd09f491d23a3efd42f4433f08788061 Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Sat, 18 Jul 2026 10:47:04 -0700 Subject: [PATCH] [CIR] Add x86_64 aggregate calling-convention lowering Extend the x86_64 SysV classifier bridge in CallConvLowering to handle struct and array aggregates alongside the scalars it already supports. A struct maps to an llvm::abi record (field offsets and the CanPassInRegisters flag from the module's record-layout metadata) and an array maps to an llvm::abi array. The resulting ArgInfo is Direct (a register-friendly coerced type the rewriter flattens) or Indirect (sret for returns, byval or byref for arguments). CIRABIRewriteContext already performs these rewrites, so this only feeds it the aggregate classifications. Unions, packed and over-aligned records, empty-for-ABI records, and _BitInt stay on the errorNYI path, so an unsupported signature fails cleanly rather than being misclassified. All-float aggregates are included too. Their SysV class coerces to an SSE vector the bridge does not yet map, so rather than pass the aggregate unchanged the classifier 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. --- .../Transforms/CallConvLoweringPass.cpp | 213 ++++++++++++++---- .../abi-lowering/x86_64-aggregate-nyi.cir | 82 +++++++ .../abi-lowering/x86_64-struct-direct.cir | 87 +++++++ .../abi-lowering/x86_64-struct-indirect.cir | 40 ++++ 4 files changed, 382 insertions(+), 40 deletions(-) create mode 100644 clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir create mode 100644 clang/test/CIR/Transforms/abi-lowering/x86_64-struct-direct.cir create mode 100644 clang/test/CIR/Transforms/abi-lowering/x86_64-struct-indirect.cir diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp index bb5b194ee0433..2d2b630bf8c74 100644 --- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp +++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp @@ -62,21 +62,60 @@ 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 +// consumes. Integer / pointer / bool / f32 / f64 scalars and struct / array +// aggregates are handled; unions, `_BitInt`, `_Complex`, vectors, wider +// floats, and empty-for-ABI 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) { +/// Proxy for the AST notion of an empty record (a C++ empty class): CIRGen +/// materializes an empty class as a single padding byte, so a record whose +/// members are all single-byte i8 arrays is empty for ABI purposes. A real +/// `char` array of size > 1 is data, not padding. The bridge only uses this +/// to reject empty records -- classifying them as Ignore is reported NYI in +/// classifyX86_64Function. A struct that merely contains an empty record is +/// rejected too, but through the member recursion in isSupportedType. +static bool recordIsEmptyForABI(cir::RecordType recTy) { + for (mlir::Type m : recTy.getMembers()) { + auto arr = dyn_cast<cir::ArrayType>(m); + if (!arr) + return false; + auto elt = dyn_cast<cir::IntType>(arr.getElementType()); + if (!elt || elt.getWidth() != 8 || arr.getSize() != 1) + return false; + } + return true; +} + +/// 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 module, cir::RecordType recTy) { + mlir::StringAttr name = recTy.getName(); + if (!name) + return true; + auto dict = module->getAttrOfType<DictionaryAttr>( + cir::CIRDialect::getRecordLayoutsAttrName()); + auto layout = + dict ? dict.getAs<cir::RecordLayoutAttr>(name) : cir::RecordLayoutAttr(); + 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 empty-for-ABI 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 +126,19 @@ 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, packed / padded records, and empty-for-ABI records each need + // classification this bridge does not implement (a union widen fixup, + // pad-aware eightbyte classification, and Ignore respectively), so reject + // them here and report NYI rather than misclassify. + if (recTy.isUnion() || !recTy.isComplete() || recTy.getPacked() || + recTy.getPadded() || recordIsEmptyForABI(recTy)) + return false; + return llvm::all_of(recTy.getMembers(), + [](mlir::Type m) { return isSupportedType(m); }); + } return false; } @@ -107,15 +159,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); + } + 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 module) { llvm::abi::TypeBuilder &tb = typeMapper.getTypeBuilder(); return llvm::TypeSwitch<mlir::Type, const llvm::abi::Type *>(type) .Case([&](cir::IntType intTy) { @@ -147,17 +212,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, module); + 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, module); + 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(module, 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 +267,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 +296,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 module) { MLIRContext *ctx = func->getContext(); cir::FuncType fnTy = func.getFunctionType(); mlir::Type retCIR = fnTy.getReturnType(); @@ -205,7 +318,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 +333,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, module); 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, module)); 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 +480,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..b67fa211afbce --- /dev/null +++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir @@ -0,0 +1,82 @@ +// 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" {!cir.array<!u8i x 1>}> +!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-DAG: 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-DAG: 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-DAG: not yet implemented for type '!cir.struct<"Ov" padded + + // An empty-for-ABI record is rejected: it needs Ignore classification. + cir.func @take_empty(%arg0: !rec_E) { + cir.return + } + + // CHECK-DAG: not yet implemented for type '!cir.struct<"E" + + // 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-DAG: 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-DAG: 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-DAG: 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-DAG: 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..57433552bfdea --- /dev/null +++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-struct-direct.cir @@ -0,0 +1,87 @@ +// 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>}> + +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. + cir.func @take_pair(%arg0: !rec_Pair) { + cir.return + } + + // CHECK: cir.func{{.*}} @take_pair(%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_Pair> + + // A two-i64 struct (16 bytes) is passed as two i64 registers (flattened). + cir.func @take_two(%arg0: !rec_Two) { + cir.return + } + + // CHECK: cir.func{{.*}} @take_two(%arg0: !s64i, %arg1: !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) + + // 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) + + // A 12-byte array argument is passed as two eightbytes (i64 then i32). + cir.func @take_arr3(%arg0: !cir.array<!s32i x 3>) { + cir.return + } + + // CHECK: cir.func{{.*}} @take_arr3(%arg0: !u64i, %arg1: !s32i) + + // 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) + + // The call site coerces the record operand to the same i64. + cir.func @call_pair(%arg0: !rec_Pair) { + cir.call @take_pair(%arg0) : (!rec_Pair) -> () + cir.return + } + + // CHECK: cir.func{{.*}} @call_pair(%arg0: !u64i) + // CHECK: cir.call @take_pair(%{{.*}}) : (!u64i) -> () +} + +// LLVM: define void @take_pair(i64 %{{.+}}) +// LLVM: define void @take_two(i64 %{{.+}}, i64 %{{.+}}) +// LLVM: define void @take_mixed(i64 %{{.+}}) +// LLVM: define void @take_charbuf(i32 %{{.+}}) +// LLVM: define void @take_arr3(i64 %{{.+}}, i32 %{{.+}}) +// LLVM: define void @take_anon(i64 %{{.+}}) +// LLVM: define void @call_pair(i64 %{{.+}}) +// LLVM: call void @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..a2d5987863d49 --- /dev/null +++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-struct-indirect.cir @@ -0,0 +1,40 @@ +// 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. + cir.func @take_big(%arg0: !rec_Big) { + cir.return + } + + // CHECK: cir.func{{.*}} @take_big(%arg0: !cir.ptr<!rec_Big> {llvm.align = 8 : i64, llvm.byval = !rec_Big, llvm.noalias, llvm.noundef}) + + // 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
