[clang] b979a11 - [CIR] Lower indirect calls in CallConvLowering (#211636)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 29 10:51:45 PDT 2026
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}) -> ()
+}
More information about the cfe-commits
mailing list