Author: Adam Smith Date: 2026-07-29T12:51:38-05:00 New Revision: b979a119274a13fdee769478702148006219fc5b
URL: https://github.com/llvm/llvm-project/commit/b979a119274a13fdee769478702148006219fc5b DIFF: https://github.com/llvm/llvm-project/commit/b979a119274a13fdee769478702148006219fc5b.diff LOG: [CIR] Lower indirect calls in CallConvLowering (#211636) Indirect calls were unsupported by CallConvLowering. `rewriteCallSite` bailed with "indirect call not yet implemented", and the pass driver rejected any module that both needed call-site rewriting and contained an indirect call. A function pointer whose signature required ABI reshaping (a by-value struct argument, or an sret return) could not be compiled at all, even though a direct call to the same signature lowered fine. The callee is opaque, but its type is a pointer to a `FuncType` that carries the full signature. Classification is now driven from that pointee signature through the same LLVM ABI path as the direct case: `classifyX86_64Function` is split into `classifyX86_64Signature`, taking a return type, an argument `TypeRange`, and an `emitError` callback. The driver collects indirect calls, classifies each callee signature (x86_64, or the test target used by the pass tests), and runs `rewriteCallSite`. `prependIndirectCallee` re-adds the callee as operand 0, bitcasting the function pointer to the coerced signature when it differs so the rebuilt call matches its reshaped return. The `classification-attr` driver mode injects a per-function classification that cannot describe a callee resolved at run time, so an indirect call is a diagnosed error there rather than being left silently unrewritten while direct calls are coerced. Added: clang/test/CIR/CodeGen/call-conv-lowering-x86_64-indirect.c clang/test/CIR/Transforms/abi-lowering/indirect-call-classification-attr.cir clang/test/CIR/Transforms/abi-lowering/indirect-call-test-target.cir clang/test/CIR/Transforms/abi-lowering/indirect-call.cir Modified: clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp Removed: clang/test/CIR/Transforms/abi-lowering/indirect-call-nyi.cir ################################################################################ diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp index 2e385d480f4f1..f3e42ac5e73b7 100644 --- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp +++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp @@ -299,31 +299,30 @@ convertABIArgInfo(const llvm::abi::ArgInfo &info, MLIRContext *ctx, 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 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, - ModuleOp modOp) { - MLIRContext *ctx = func->getContext(); - cir::FuncType fnTy = func.getFunctionType(); - mlir::Type retCIR = fnTy.getReturnType(); - assert(retCIR && "FuncType::getReturnType() never returns null"); +/// Classify an x86_64 SysV signature (return type + argument types) using the +/// LLVM ABI library. Shared by the cir.func path and the indirect-call path +/// (which classifies from the callee function pointer's pointee FuncType). +/// Returns std::nullopt and emits an NYI error via \p emitError if the +/// signature uses a type the bridge does not handle yet. +static std::optional<FunctionClassification> classifyX86_64Signature( + mlir::Type retCIR, mlir::TypeRange inputs, MLIRContext *ctx, + const DataLayout &dl, mlir::abi::ABITypeMapper &typeMapper, + const llvm::abi::TargetInfo &targetInfo, ModuleOp modOp, + llvm::function_ref<mlir::InFlightDiagnostic()> emitError) { + assert(retCIR && "signature return type must be non-null"); bool voidRet = isa<cir::VoidType>(retCIR); auto reject = [&](mlir::Type t) -> bool { if (isSupportedType(t)) return false; - func.emitOpError() + emitError() << "x86_64 calling-convention lowering not yet implemented for type " << t; return true; }; if (!voidRet && reject(retCIR)) return std::nullopt; - for (mlir::Type a : fnTy.getInputs()) + for (mlir::Type a : inputs) if (reject(a)) return std::nullopt; @@ -331,7 +330,7 @@ classifyX86_64Function(cir::FuncOp func, const DataLayout &dl, voidRet ? typeMapper.getTypeBuilder().getVoidType() : mapCIRType(retCIR, typeMapper, dl, modOp); SmallVector<const llvm::abi::Type *> argAbi; - for (mlir::Type a : fnTy.getInputs()) + for (mlir::Type a : inputs) argAbi.push_back(mapCIRType(a, typeMapper, dl, modOp)); std::unique_ptr<llvm::abi::FunctionInfo> fi = @@ -342,9 +341,9 @@ classifyX86_64Function(cir::FuncOp func, const DataLayout &dl, // 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; + emitError() << "x86_64 calling-convention lowering not yet " + "implemented for the ABI coercion of type " + << t; }; FunctionClassification fc; @@ -356,7 +355,6 @@ classifyX86_64Function(cir::FuncOp func, const DataLayout &dl, 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(); std::optional<ArgClassification> ac = @@ -370,13 +368,18 @@ classifyX86_64Function(cir::FuncOp func, const DataLayout &dl, return fc; } -bool needsRewrite(const FunctionClassification &fc) { - if ((fc.returnInfo.kind != ArgKind::Direct) || fc.returnInfo.coercedType) - return true; - for (const ArgClassification &ac : fc.argInfos) - if ((ac.kind != ArgKind::Direct) || ac.coercedType) - return true; - return false; +/// 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 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, + ModuleOp modOp) { + cir::FuncType fnTy = func.getFunctionType(); + return classifyX86_64Signature(fnTy.getReturnType(), fnTy.getInputs(), + func->getContext(), dl, typeMapper, targetInfo, + modOp, [&]() { return func.emitOpError(); }); } struct CallConvLoweringPass @@ -529,24 +532,44 @@ void CallConvLoweringPass::runOnOperation() { } } - // Reject indirect calls when the module contains any ABI rewrite that - // would need call-site lowering. We cannot strip or coerce operands - // without a resolved callee symbol. - const FunctionClassification *rewriteFc = nullptr; - for (auto &kv : classifications) { - if (needsRewrite(kv.second)) { - rewriteFc = &kv.second; - break; + // Rewrite indirect call sites. The callee is opaque, so classify from the + // function pointer's pointee FuncType and let rewriteCallSite retype the + // callee pointer to match the coerced signature. Collect the calls first: + // when an sret rewrite reuses a single-use store's destination as the return + // slot it erases that store, which is the operation a live walk has already + // cached as the next one to visit. + SmallVector<cir::CallOp> indirectCalls; + moduleOp.walk([&](cir::CallOp c) { + if (c.isIndirect()) + indirectCalls.push_back(c); + }); + for (cir::CallOp c : indirectCalls) { + // classification-attr mode injects a per-function classification, which + // cannot describe a callee resolved at run time. Report it rather than + // leave the indirect call unrewritten while direct calls are coerced. + if (!classificationAttr.empty()) { + c.emitOpError() << "indirect call cannot be classified in the " + "'classification-attr' driver mode"; + signalPassFailure(); + return; } - } - if (rewriteFc) { - moduleOp.walk([&](cir::CallOp c) { - if (!c.isIndirect()) - return; - if (failed(rewriteCtx.rewriteCallSite(c, *rewriteFc, builder))) - anyFailed = true; - }); - if (anyFailed) { + // The CallOp verifier guarantees an indirect callee is a pointer to a + // function type. + auto ptrTy = cast<cir::PointerType>(c.getIndirectCall().getType()); + auto funcTy = cast<cir::FuncType>(ptrTy.getPointee()); + std::optional<FunctionClassification> fc; + if (x86Target) + fc = classifyX86_64Signature(funcTy.getReturnType(), funcTy.getInputs(), + ctx, dl, *x86TypeMapper, *x86Target, + moduleOp, [&]() { return c.emitOpError(); }); + else + fc = mlir::abi::test::classify(funcTy.getInputs(), funcTy.getReturnType(), + dl); + if (!fc) { + signalPassFailure(); + return; + } + if (failed(rewriteCtx.rewriteCallSite(c, *fc, builder))) { signalPassFailure(); return; } diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp index d4c76fd266a22..e87ae2326d524 100644 --- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp +++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp @@ -799,6 +799,32 @@ void applySretSlotAttrs(cir::CallOp newCall, mlir::ArrayAttr argAttrs, newCall->setAttr("arg_attrs", mlir::ArrayAttr::get(ctx, newArgAttrs)); } +/// For an indirect call, prepend the callee function pointer as operand 0 so +/// CallOp::create rebuilds it as an indirect call, bitcasting it to a function +/// pointer whose signature matches the rewritten operands and return type. +/// No-op for direct calls. +static void prependIndirectCallee(cir::CallOp call, + SmallVectorImpl<mlir::Value> &args, + mlir::Type retTy, mlir::OpBuilder &builder) { + if (!call.isIndirect()) + return; + mlir::Value calleePtr = call.getIndirectCall(); + SmallVector<mlir::Type> paramTypes; + paramTypes.reserve(args.size()); + llvm::transform(args, std::back_inserter(paramTypes), + [](mlir::Value v) { return v.getType(); }); + // Lowering builds an indirect call's LLVM function type from the callee + // pointer's pointee and takes the call's result from that type, so the + // pointee's return type has to track the rewrite: an sret return would + // leave a result the call no longer produces, and a coerced return one of + // the wrong type. + auto newPtrTy = cir::PointerType::get(cir::FuncType::get(paramTypes, retTy)); + if (calleePtr.getType() != newPtrTy) + calleePtr = cir::CastOp::create(builder, call.getLoc(), newPtrTy, + cir::CastKind::bitcast, calleePtr); + args.insert(args.begin(), calleePtr); +} + /// Rewrite an indirect-return (sret) call site: prepend a return-slot /// pointer as operand 0, make the call return void, and either reuse a /// dominating single-use store destination as the slot (so construction @@ -853,6 +879,7 @@ void rewriteIndirectReturnCall(cir::CallOp call, sretArgs.append(newArgs.begin(), newArgs.end()); mlir::Type sretVoidTy = cir::VoidType::get(ctx); + prependIndirectCallee(call, sretArgs, sretVoidTy, builder); auto newCall = cir::CallOp::create( builder, call.getLoc(), call.getCalleeAttr(), sretVoidTy, sretArgs); for (mlir::NamedAttribute attr : call->getAttrs()) @@ -1082,10 +1109,6 @@ CIRABIRewriteContext::rewriteCallSite(mlir::Operation *callOp, << "TryCallOp not yet implemented in CallConvLowering"; auto call = mlir::cast<cir::CallOp>(callOp); - if (call.isIndirect()) - return call.emitOpError() - << "indirect call not yet implemented in CallConvLowering"; - mlir::MLIRContext *ctx = callOp->getContext(); auto enclosingFunc = call->getParentOfType<mlir::FunctionOpInterface>(); @@ -1196,6 +1219,7 @@ CIRABIRewriteContext::rewriteCallSite(mlir::Operation *callOp, callRetTy = fc.returnInfo.coercedType; builder.setInsertionPoint(call); + prependIndirectCallee(call, newArgs, callRetTy, builder); auto newCall = cir::CallOp::create(builder, call.getLoc(), call.getCalleeAttr(), callRetTy, newArgs); for (mlir::NamedAttribute attr : call->getAttrs()) diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-indirect.c b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-indirect.c new file mode 100644 index 0000000000000..b6c22f0f0077d --- /dev/null +++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-indirect.c @@ -0,0 +1,49 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -clangir-enable-call-conv-lowering -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 -clangir-enable-call-conv-lowering -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 { long a, b, c, d; } Big; +typedef struct { int a, b; } Pair; + +// Scalar indirect call: no ABI reshaping, the callee pointer is called as-is. +int call_scalar(int (*fp)(int), int x) { return fp(x); } + +// CIR: cir.func {{.*}}@call_scalar(%arg0: !cir.ptr<!cir.func<(!s32i) -> !s32i>> {{.*}}, %arg1: !s32i {{.*}}) -> !s32i +// CIR: %{{.+}} = cir.call %{{.+}}(%{{.+}}) : (!cir.ptr<!cir.func<(!s32i) -> !s32i>>, !s32i {llvm.noundef}) -> !s32i +// LLVM: define dso_local i32 @call_scalar(ptr noundef %{{.+}}, i32 noundef %{{.+}}) +// LLVM: call i32 %{{.+}}(i32 noundef %{{.+}}) + +// Indirect call whose small-struct argument and return are coerced to a +// register: the callee pointer is bitcast so its pointee tracks both. +Pair call_coerce(Pair (*fp)(Pair), Pair p) { return fp(p); } + +// CIR: cir.func {{.*}}@call_coerce(%arg0: !cir.ptr<!cir.func<(!rec_Pair) -> !rec_Pair>> {{.*}}, %arg1: !u64i{{.*}}) -> !u64i +// CIR: %[[PCAST:.*]] = cir.cast bitcast %{{.+}} : !cir.ptr<!cir.func<(!rec_Pair) -> !rec_Pair>> -> !cir.ptr<!cir.func<(!u64i) -> !u64i>> +// CIR: %{{.+}} = cir.call %[[PCAST]](%{{.+}}) : (!cir.ptr<!cir.func<(!u64i) -> !u64i>>, !u64i) -> !u64i +// LLVM: define dso_local i64 @call_coerce(ptr noundef %{{.+}}, i64 %{{.+}}) +// LLVM: call i64 %{{.+}}(i64 %{{.+}}) + +// Indirect call with a byval struct argument: the argument is spilled to a +// stack slot and the callee pointer is bitcast to the coerced signature. +long call_byval(long (*fp)(Big), Big b) { return fp(b); } + +// CIR: cir.func {{.*}}@call_byval(%arg0: !cir.ptr<!cir.func<(!rec_Big) -> !s64i>> {{.*}}, %arg1: !cir.ptr<!rec_Big> {{.*}}llvm.byval = !rec_Big{{.*}}) -> !s64i +// CIR: %[[CAST:.*]] = cir.cast bitcast %{{.+}} : !cir.ptr<!cir.func<(!rec_Big) -> !s64i>> -> !cir.ptr<!cir.func<(!cir.ptr<!rec_Big>) -> !s64i>> +// CIR: %{{.+}} = cir.call %[[CAST]](%{{.+}}) : (!cir.ptr<!cir.func<(!cir.ptr<!rec_Big>) -> !s64i>>, !cir.ptr<!rec_Big> {llvm.align = 8 : i64, llvm.byval = !rec_Big, llvm.noalias, llvm.noundef}) -> !s64i +// LLVM-CIR: define dso_local i64 @call_byval(ptr noundef %{{.+}}, ptr noalias noundef byval(%struct.Big) align 8 %{{.+}}) +// LLVM-OGCG: define dso_local i64 @call_byval(ptr noundef %{{.+}}, ptr noundef byval(%struct.Big) align 8 %{{.+}}) +// LLVM-CIR: call i64 %{{.+}}(ptr noalias noundef byval(%struct.Big) align 8 %{{.+}}) +// LLVM-OGCG: call i64 %{{.+}}(ptr noundef byval(%struct.Big) align 8 %{{.+}}) + +// Indirect call returning a large struct: an sret pointer slot is prepended +// and the callee is bitcast to the void-returning sret signature. +Big call_sret(Big (*fp)(void)) { return fp(); } + +// CIR: cir.func {{.*}}@call_sret(%arg0: !cir.ptr<!rec_Big> {{.*}}llvm.sret = !rec_Big{{.*}}, %arg1: !cir.ptr<!cir.func<() -> !rec_Big>> {{.*}}) +// CIR: %[[SCAST:.*]] = cir.cast bitcast %{{.+}} : !cir.ptr<!cir.func<() -> !rec_Big>> -> !cir.ptr<!cir.func<(!cir.ptr<!rec_Big>)>> +// CIR: cir.call %[[SCAST]](%{{.+}}) : (!cir.ptr<!cir.func<(!cir.ptr<!rec_Big>)>>, !cir.ptr<!rec_Big> {llvm.align = 8 : i64, llvm.dead_on_unwind, llvm.sret = !rec_Big, llvm.writable}) -> () +// LLVM: define dso_local void @call_sret(ptr dead_on_unwind noalias writable sret(%struct.Big) align 8 %{{.+}}, ptr noundef %{{.+}}) +// LLVM: call void %{{.+}}(ptr dead_on_unwind writable sret(%struct.Big) align 8 %{{.+}}) diff --git a/clang/test/CIR/Transforms/abi-lowering/indirect-call-nyi.cir b/clang/test/CIR/Transforms/abi-lowering/indirect-call-classification-attr.cir similarity index 59% rename from clang/test/CIR/Transforms/abi-lowering/indirect-call-nyi.cir rename to clang/test/CIR/Transforms/abi-lowering/indirect-call-classification-attr.cir index e4bd17588bd57..590e9eb21b1a7 100644 --- a/clang/test/CIR/Transforms/abi-lowering/indirect-call-nyi.cir +++ b/clang/test/CIR/Transforms/abi-lowering/indirect-call-classification-attr.cir @@ -3,25 +3,18 @@ !s32i = !cir.int<s, 32> -#ignore_first_arg = { - return = { kind = "direct" }, - args = [ { kind = "ignore" }, { kind = "direct" } ] -} - #passthrough = { return = { kind = "direct" }, - args = [ { kind = "direct" }, { kind = "direct" }, { kind = "direct" } ] + args = [ { kind = "direct" }, { kind = "direct" } ] } module attributes { dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<i32, dense<32>: vector<2xi64>>> } { - cir.func @callee(%arg0: !s32i, %arg1: !s32i) -> !s32i - attributes { test_classify = #ignore_first_arg } { - cir.return %arg1 : !s32i - } - + // classification-attr injects a per-function classification, which cannot + // describe a callee resolved at run time, so an indirect call is a hard + // error in this driver mode rather than being left silently unrewritten. cir.func @caller(%fp: !cir.ptr<!cir.func<(!s32i, !s32i) -> !s32i>>, %arg0: !s32i, %arg1: !s32i) -> !s32i attributes { test_classify = #passthrough } { @@ -32,4 +25,4 @@ module attributes { } -// CHECK: error: 'cir.call' op indirect call not yet implemented in CallConvLowering +// CHECK: error: 'cir.call' op indirect call cannot be classified in the 'classification-attr' driver mode diff --git a/clang/test/CIR/Transforms/abi-lowering/indirect-call-test-target.cir b/clang/test/CIR/Transforms/abi-lowering/indirect-call-test-target.cir new file mode 100644 index 0000000000000..794487e756ffe --- /dev/null +++ b/clang/test/CIR/Transforms/abi-lowering/indirect-call-test-target.cir @@ -0,0 +1,41 @@ +// RUN: cir-opt %s -cir-call-conv-lowering=target=test | FileCheck %s + +!s32i = !cir.int<s, 32> +!s64i = !cir.int<s, 64> +!rec_Big = !cir.struct<"Big" {!s64i, !s64i, !s64i, !s64i}> + +module attributes { + dlti.dl_spec = #dlti.dl_spec< + #dlti.dl_entry<i32, dense<32>: vector<2xi64>>, + #dlti.dl_entry<i64, dense<64>: vector<2xi64>>> +} { + + // Scalar indirect call under the test target: the callee's pointee FuncType + // classifies to Direct passthrough, so signature and call site are unchanged. + cir.func @call_scalar(%fp: !cir.ptr<!cir.func<(!s32i) -> !s32i>>, %x: !s32i) -> !s32i { + %0 = cir.call %fp(%x) : (!cir.ptr<!cir.func<(!s32i) -> !s32i>>, !s32i) -> !s32i + cir.return %0 : !s32i + } + + // CHECK: cir.func{{.*}} @call_scalar(%[[FP:.*]]: !cir.ptr<!cir.func<(!s32i) -> !s32i>>, %[[X:.*]]: !s32i) -> !s32i + // CHECK-NEXT: %[[RES:.*]] = cir.call %[[FP]](%[[X]]) : (!cir.ptr<!cir.func<(!s32i) -> !s32i>>, !s32i) -> !s32i + // CHECK-NEXT: cir.return %[[RES]] : !s32i + + // Indirect call whose struct argument classifies to Indirect (byval) under + // the test target: the argument spills to a stack slot and the callee + // pointer is bitcast to the coerced signature before the call. + cir.func @call_byval(%fp: !cir.ptr<!cir.func<(!rec_Big) -> !s64i>>, %b: !rec_Big) -> !s64i { + %0 = cir.call %fp(%b) : (!cir.ptr<!cir.func<(!rec_Big) -> !s64i>>, !rec_Big) -> !s64i + cir.return %0 : !s64i + } + + // CHECK: cir.func{{.*}} @call_byval(%[[FP:.*]]: !cir.ptr<!cir.func<(!rec_Big) -> !s64i>>, %{{.*}}: !cir.ptr<!rec_Big> + // CHECK-SAME: llvm.byval = !rec_Big + // CHECK: %[[SLOT:.*]] = cir.alloca "byval" align(8) : !cir.ptr<!rec_Big> + // CHECK: %[[CAST:.*]] = cir.cast bitcast %[[FP]] : !cir.ptr<!cir.func<(!rec_Big) -> !s64i>> -> !cir.ptr<!cir.func<(!cir.ptr<!rec_Big>) -> !s64i>> + // CHECK: %{{.*}} = cir.call %[[CAST]](%[[SLOT]]) : (!cir.ptr<!cir.func<(!cir.ptr<!rec_Big>) -> !s64i>>, !cir.ptr<!rec_Big> + // CHECK-SAME: llvm.byval = !rec_Big + // CHECK-SAME: llvm.noalias + // CHECK-SAME: llvm.noundef + +} diff --git a/clang/test/CIR/Transforms/abi-lowering/indirect-call.cir b/clang/test/CIR/Transforms/abi-lowering/indirect-call.cir new file mode 100644 index 0000000000000..6a6f7fe7a5a0b --- /dev/null +++ b/clang/test/CIR/Transforms/abi-lowering/indirect-call.cir @@ -0,0 +1,105 @@ +// RUN: cir-opt %s -cir-call-conv-lowering=target=x86_64 | FileCheck %s + +!s16i = !cir.int<s, 16> +!s32i = !cir.int<s, 32> +!s64i = !cir.int<s, 64> +!rec_Big = !cir.struct<"Big" {!s64i, !s64i, !s64i}> +!rec_Pair = !cir.struct<"Pair" {!s32i, !s32i}> +!rec_Empty = !cir.struct<"Empty" {}> + +module attributes { + dlti.dl_spec = #dlti.dl_spec< + #dlti.dl_entry<i16, dense<16>: vector<2xi64>>, + #dlti.dl_entry<i32, dense<32>: vector<2xi64>>, + #dlti.dl_entry<i64, dense<64>: vector<2xi64>>> +} { + + // Scalar indirect call: no ABI reshaping, so it passes through unchanged + // (and is no longer rejected as NYI). + cir.func @call_scalar(%fp: !cir.ptr<!cir.func<(!s32i) -> !s32i>>, + %arg0: !s32i) -> !s32i { + %0 = cir.call %fp(%arg0) + : (!cir.ptr<!cir.func<(!s32i) -> !s32i>>, !s32i) -> !s32i + cir.return %0 : !s32i + } + + // CHECK: cir.func{{.*}} @call_scalar + // CHECK: cir.call %{{.+}}(%{{.+}}) : (!cir.ptr<!cir.func<(!s32i) -> !s32i>>, !s32i) -> !s32i + + // Indirect call whose sub-32-bit int arg/return is Extend: the signext + // attribute is applied at the call site and the callee pointer keeps its + // signature (Extend does not change the value type, so no bitcast). + cir.func @call_ext(%fp: !cir.ptr<!cir.func<(!s16i) -> !s16i>>, + %arg0: !s16i) -> !s16i { + %0 = cir.call %fp(%arg0) + : (!cir.ptr<!cir.func<(!s16i) -> !s16i>>, !s16i) -> !s16i + cir.return %0 : !s16i + } + + // CHECK: cir.func{{.*}} @call_ext + // CHECK-NOT: cir.cast bitcast + // CHECK: cir.call %{{.+}}(%{{.+}}) : (!cir.ptr<!cir.func<(!s16i) -> !s16i>>, !s16i {llvm.signext}) -> (!s16i {llvm.signext}) + + // Indirect call whose small-struct argument and return are Direct with a + // coerce type: the callee pointer is bitcast so its pointee tracks both the + // coerced parameter and the coerced result. + cir.func @call_coerce(%fp: !cir.ptr<!cir.func<(!rec_Pair) -> !rec_Pair>>, + %out: !cir.ptr<!rec_Pair>) { + %0 = cir.alloca "p" align(4) : !cir.ptr<!rec_Pair> + %1 = cir.load %0 : !cir.ptr<!rec_Pair>, !rec_Pair + %2 = cir.call %fp(%1) + : (!cir.ptr<!cir.func<(!rec_Pair) -> !rec_Pair>>, + !rec_Pair) -> !rec_Pair + cir.store %2, %out : !rec_Pair, !cir.ptr<!rec_Pair> + cir.return + } + + // CHECK: cir.func{{.*}} @call_coerce(%arg0: !cir.ptr<!cir.func<(!rec_Pair) -> !rec_Pair>> + // CHECK: %[[CCAST:.*]] = cir.cast bitcast %arg0 : !cir.ptr<!cir.func<(!rec_Pair) -> !rec_Pair>> -> !cir.ptr<!cir.func<(!u64i) -> !u64i>> + // CHECK: cir.call %[[CCAST]](%{{.+}}) : (!cir.ptr<!cir.func<(!u64i) -> !u64i>>, !u64i) -> !u64i + + // Indirect call with an Ignore-classified empty-record argument: the operand + // is dropped and the callee pointer loses that parameter. + cir.func @call_ignore(%fp: !cir.ptr<!cir.func<(!rec_Empty, !s32i) -> !s32i>>, + %x: !s32i) -> !s32i { + %0 = cir.alloca "e" align(1) : !cir.ptr<!rec_Empty> + %1 = cir.load %0 : !cir.ptr<!rec_Empty>, !rec_Empty + %2 = cir.call %fp(%1, %x) + : (!cir.ptr<!cir.func<(!rec_Empty, !s32i) -> !s32i>>, + !rec_Empty, !s32i) -> !s32i + cir.return %2 : !s32i + } + + // CHECK: cir.func{{.*}} @call_ignore(%arg0: !cir.ptr<!cir.func<(!rec_Empty, !s32i) -> !s32i>> + // CHECK: %[[ICAST:.*]] = cir.cast bitcast %arg0 : !cir.ptr<!cir.func<(!rec_Empty, !s32i) -> !s32i>> -> !cir.ptr<!cir.func<(!s32i) -> !s32i>> + // CHECK: cir.call %[[ICAST]](%arg1) : (!cir.ptr<!cir.func<(!s32i) -> !s32i>>, !s32i) -> !s32i + + // Indirect call with a by-value (byval) struct argument: the argument is + // spilled to a stack slot and the callee pointer is bitcast to the coerced + // signature before the call is rebuilt. + cir.func @call_byval(%fp: !cir.ptr<!cir.func<(!rec_Big) -> !s64i>>) -> !s64i { + %0 = cir.alloca "b" align(8) : !cir.ptr<!rec_Big> + %1 = cir.load %0 : !cir.ptr<!rec_Big>, !rec_Big + %2 = cir.call %fp(%1) + : (!cir.ptr<!cir.func<(!rec_Big) -> !s64i>>, !rec_Big) -> !s64i + cir.return %2 : !s64i + } + + // CHECK: cir.func{{.*}} @call_byval(%arg0: !cir.ptr<!cir.func<(!rec_Big) -> !s64i>>) + // CHECK: %[[SLOT:.*]] = cir.alloca "byval" align(8) : !cir.ptr<!rec_Big> + // CHECK: cir.store %{{.+}}, %[[SLOT]] : !rec_Big, !cir.ptr<!rec_Big> + // CHECK: %[[CAST:.*]] = cir.cast bitcast %arg0 : !cir.ptr<!cir.func<(!rec_Big) -> !s64i>> -> !cir.ptr<!cir.func<(!cir.ptr<!rec_Big>) -> !s64i>> + // CHECK: cir.call %[[CAST]](%[[SLOT]]) : (!cir.ptr<!cir.func<(!cir.ptr<!rec_Big>) -> !s64i>>, !cir.ptr<!rec_Big> {llvm.align = 8 : i64, llvm.byval = !rec_Big, llvm.noalias, llvm.noundef}) -> !s64i + + // Indirect call returning a large struct: an sret pointer slot is prepended + // and the callee is bitcast to the void-returning sret signature. + cir.func @call_sret(%fp: !cir.ptr<!cir.func<() -> !rec_Big>>) { + %0 = cir.call %fp() : (!cir.ptr<!cir.func<() -> !rec_Big>>) -> !rec_Big + cir.return + } + + // CHECK: cir.func{{.*}} @call_sret(%arg0: !cir.ptr<!cir.func<() -> !rec_Big>>) + // CHECK: %[[SRET:.*]] = cir.alloca "sret" align(8) : !cir.ptr<!rec_Big> + // CHECK: %[[SCAST:.*]] = cir.cast bitcast %arg0 : !cir.ptr<!cir.func<() -> !rec_Big>> -> !cir.ptr<!cir.func<(!cir.ptr<!rec_Big>)>> + // CHECK: cir.call %[[SCAST]](%[[SRET]]) : (!cir.ptr<!cir.func<(!cir.ptr<!rec_Big>)>>, !cir.ptr<!rec_Big> {llvm.align = 8 : i64, llvm.dead_on_unwind, llvm.sret = !rec_Big, llvm.writable}) -> () +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
