[llvm] dbad8a1 - Wasm: add support for `swifttailcc` calling convention (#188296)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Mar 31 01:00:43 PDT 2026
Author: Max Desiatov
Date: 2026-03-31T09:00:38+01:00
New Revision: dbad8a1e24c9e63041d91c8d5551225024b983eb
URL: https://github.com/llvm/llvm-project/commit/dbad8a1e24c9e63041d91c8d5551225024b983eb
DIFF: https://github.com/llvm/llvm-project/commit/dbad8a1e24c9e63041d91c8d5551225024b983eb.diff
LOG: Wasm: add support for `swifttailcc` calling convention (#188296)
Wasm backend already supports tail calls where available, we only need
to enable corresponding branches for this calling convention.
Added:
llvm/test/CodeGen/WebAssembly/swiftasync-coroutine.ll
llvm/test/CodeGen/WebAssembly/swifttailcc-musttail-no-tailcall.ll
llvm/test/CodeGen/WebAssembly/swifttailcc-no-tailcall.ll
llvm/test/CodeGen/WebAssembly/swifttailcc.ll
Modified:
llvm/lib/Target/WebAssembly/WebAssemblyFixFunctionBitcasts.cpp
llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
llvm/lib/Target/WebAssembly/WebAssemblyMachineFunctionInfo.cpp
Removed:
################################################################################
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFixFunctionBitcasts.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFixFunctionBitcasts.cpp
index 67cf4f5bd055c..f4fe3ccd4bf9e 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFixFunctionBitcasts.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFixFunctionBitcasts.cpp
@@ -229,9 +229,11 @@ bool FixFunctionBitcasts::runOnModule(Module &M) {
// Collect all the places that need wrappers.
for (Function &F : M) {
- // Skip to fix when the function is swiftcc because swiftcc allows
- // bitcast type
diff erence for swiftself and swifterror.
- if (F.getCallingConv() == CallingConv::Swift)
+ // Skip to fix when the function is swiftcc or swifttailcc because these
+ // calling conventions allow bitcast type
diff erence for swiftself,
+ // swifterror, and swiftasync.
+ if (F.getCallingConv() == CallingConv::Swift ||
+ F.getCallingConv() == CallingConv::SwiftTail)
continue;
findUses(&F, F, Uses);
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
index a0c5e2f5d2639..47de46a6f7070 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
@@ -1272,7 +1272,7 @@ static bool callingConvSupported(CallingConv::ID CallConv) {
CallConv == CallingConv::PreserveAll ||
CallConv == CallingConv::CXX_FAST_TLS ||
CallConv == CallingConv::WASM_EmscriptenInvoke ||
- CallConv == CallingConv::Swift;
+ CallConv == CallingConv::Swift || CallConv == CallingConv::SwiftTail;
}
SDValue
@@ -1359,12 +1359,14 @@ WebAssemblyTargetLowering::LowerCall(CallLoweringInfo &CLI,
bool HasSwiftSelfArg = false;
bool HasSwiftErrorArg = false;
+ bool HasSwiftAsyncArg = false;
unsigned NumFixedArgs = 0;
for (unsigned I = 0; I < Outs.size(); ++I) {
const ISD::OutputArg &Out = Outs[I];
SDValue &OutVal = OutVals[I];
HasSwiftSelfArg |= Out.Flags.isSwiftSelf();
HasSwiftErrorArg |= Out.Flags.isSwiftError();
+ HasSwiftAsyncArg |= Out.Flags.isSwiftAsync();
if (Out.Flags.isNest())
fail(DL, DAG, "WebAssembly hasn't implemented nest arguments");
if (Out.Flags.isInAlloca())
@@ -1395,11 +1397,11 @@ WebAssemblyTargetLowering::LowerCall(CallLoweringInfo &CLI,
bool IsVarArg = CLI.IsVarArg;
auto PtrVT = getPointerTy(Layout);
- // For swiftcc, emit additional swiftself and swifterror arguments
- // if there aren't. These additional arguments are also added for callee
- // signature They are necessary to match callee and caller signature for
- // indirect call.
- if (CallConv == CallingConv::Swift) {
+ // For swiftcc and swifttailcc, emit additional swiftself, swifterror, and
+ // (for swifttailcc) swiftasync arguments if there aren't. These additional
+ // arguments are also added for callee signature. They are necessary to match
+ // callee and caller signature for indirect call.
+ if (CallConv == CallingConv::Swift || CallConv == CallingConv::SwiftTail) {
Type *PtrTy = PointerType::getUnqual(*DAG.getContext());
if (!HasSwiftSelfArg) {
NumFixedArgs++;
@@ -1419,6 +1421,15 @@ WebAssemblyTargetLowering::LowerCall(CallLoweringInfo &CLI,
SDValue ArgVal = DAG.getUNDEF(PtrVT);
CLI.OutVals.push_back(ArgVal);
}
+ if (CallConv == CallingConv::SwiftTail && !HasSwiftAsyncArg) {
+ NumFixedArgs++;
+ ISD::ArgFlagsTy Flags;
+ Flags.setSwiftAsync();
+ ISD::OutputArg Arg(Flags, PtrVT, EVT(PtrVT), PtrTy, 0, 0);
+ CLI.Outs.push_back(Arg);
+ SDValue ArgVal = DAG.getUNDEF(PtrVT);
+ CLI.OutVals.push_back(ArgVal);
+ }
}
// Analyze operands of the call, assigning locations to each operand.
@@ -1614,9 +1625,11 @@ SDValue WebAssemblyTargetLowering::LowerFormalArguments(
bool HasSwiftErrorArg = false;
bool HasSwiftSelfArg = false;
+ bool HasSwiftAsyncArg = false;
for (const ISD::InputArg &In : Ins) {
HasSwiftSelfArg |= In.Flags.isSwiftSelf();
HasSwiftErrorArg |= In.Flags.isSwiftError();
+ HasSwiftAsyncArg |= In.Flags.isSwiftAsync();
if (In.Flags.isInAlloca())
fail(DL, DAG, "WebAssembly hasn't implemented inalloca arguments");
if (In.Flags.isNest())
@@ -1636,18 +1649,21 @@ SDValue WebAssemblyTargetLowering::LowerFormalArguments(
MFI->addParam(In.VT);
}
- // For swiftcc, emit additional swiftself and swifterror arguments
- // if there aren't. These additional arguments are also added for callee
- // signature They are necessary to match callee and caller signature for
- // indirect call.
+ // For swiftcc and swifttailcc, emit additional swiftself, swifterror, and
+ // (for swifttailcc) swiftasync arguments if there aren't. These additional
+ // arguments are also added for callee signature. They are necessary to match
+ // callee and caller signature for indirect call.
auto PtrVT = getPointerTy(MF.getDataLayout());
- if (CallConv == CallingConv::Swift) {
+ if (CallConv == CallingConv::Swift || CallConv == CallingConv::SwiftTail) {
if (!HasSwiftSelfArg) {
MFI->addParam(PtrVT);
}
if (!HasSwiftErrorArg) {
MFI->addParam(PtrVT);
}
+ if (CallConv == CallingConv::SwiftTail && !HasSwiftAsyncArg) {
+ MFI->addParam(PtrVT);
+ }
}
// Varargs are copied into a buffer allocated by the caller, and a pointer to
// the buffer is passed as an argument.
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyMachineFunctionInfo.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyMachineFunctionInfo.cpp
index f7b216e698241..d1f4894c4a5af 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyMachineFunctionInfo.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyMachineFunctionInfo.cpp
@@ -86,22 +86,28 @@ void llvm::computeSignatureVTs(const FunctionType *Ty,
if (Ty->isVarArg())
Params.push_back(PtrVT);
- // For swiftcc, emit additional swiftself and swifterror parameters
- // if there aren't. These additional parameters are also passed for caller.
- // They are necessary to match callee and caller signature for indirect
- // call.
+ // For swiftcc and swifttailcc, emit additional swiftself, swifterror, and
+ // (for swifttailcc) swiftasync parameters if there aren't. These additional
+ // parameters are also passed for caller. They are necessary to match callee
+ // and caller signature for indirect call.
- if (TargetFunc && TargetFunc->getCallingConv() == CallingConv::Swift) {
+ if (TargetFunc && (TargetFunc->getCallingConv() == CallingConv::Swift ||
+ TargetFunc->getCallingConv() == CallingConv::SwiftTail)) {
MVT PtrVT = MVT::getIntegerVT(TM.createDataLayout().getPointerSizeInBits());
bool HasSwiftErrorArg = false;
bool HasSwiftSelfArg = false;
+ bool HasSwiftAsyncArg = false;
for (const auto &Arg : TargetFunc->args()) {
HasSwiftErrorArg |= Arg.hasAttribute(Attribute::SwiftError);
HasSwiftSelfArg |= Arg.hasAttribute(Attribute::SwiftSelf);
+ HasSwiftAsyncArg |= Arg.hasAttribute(Attribute::SwiftAsync);
}
+ if (!HasSwiftSelfArg)
+ Params.push_back(PtrVT);
if (!HasSwiftErrorArg)
Params.push_back(PtrVT);
- if (!HasSwiftSelfArg)
+ if (TargetFunc->getCallingConv() == CallingConv::SwiftTail &&
+ !HasSwiftAsyncArg)
Params.push_back(PtrVT);
}
}
diff --git a/llvm/test/CodeGen/WebAssembly/swiftasync-coroutine.ll b/llvm/test/CodeGen/WebAssembly/swiftasync-coroutine.ll
new file mode 100644
index 0000000000000..34e89a348ab13
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/swiftasync-coroutine.ll
@@ -0,0 +1,41 @@
+; RUN: opt < %s -passes='module(coro-early),cgscc(coro-split),module(coro-cleanup)' -S -mtriple=wasm32-unknown-unknown -mattr=+tail-call | FileCheck --check-prefix=IR %s
+; RUN: opt < %s -passes='module(coro-early),cgscc(coro-split),module(coro-cleanup)' -mtriple=wasm32-unknown-unknown -mattr=+tail-call | \
+; RUN: llc -mtriple=wasm32-unknown-unknown -verify-machineinstrs \
+; RUN: -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals \
+; RUN: -wasm-keep-registers -mattr=+tail-call | FileCheck --check-prefix=ASM %s
+
+; End-to-end test: verify that async coroutine splitting with swifttailcc
+; produces musttail calls (at the IR level) that lower to return_call (at the
+; assembly level) when the tail-call feature is enabled.
+
+%swift.async_func_pointer = type <{ i32, i32 }>
+ at checkTu = global %swift.async_func_pointer <{ i32 ptrtoint (ptr @check to i32), i32 8 }>
+
+define swifttailcc void @check(ptr swiftasync %0) {
+; IR-LABEL: define swifttailcc void @check(
+; IR: musttail call swifttailcc void @check.0()
+;
+; Coroutine splitting generates a resume function with swifttailcc and swiftasync.
+; IR-LABEL: define internal swifttailcc void @checkTQ0_(ptr swiftasync
+;
+; ASM-LABEL: check:
+; ASM: return_call check.0
+entry:
+ %1 = call token @llvm.coro.id.async(i32 0, i32 0, i32 0, ptr @checkTu)
+ %2 = call ptr @llvm.coro.begin(token %1, ptr null)
+ %3 = call ptr @llvm.coro.async.resume()
+ store ptr %3, ptr %0, align 4
+ %4 = call { ptr, i32 } (i32, ptr, ptr, ...) @llvm.coro.suspend.async.sl_p0i32s(i32 0, ptr %3, ptr @__swift_async_resume_project_context, ptr @check.0, ptr null, ptr null)
+ ret void
+}
+
+declare swifttailcc void @check.0()
+declare { ptr, i32 } @llvm.coro.suspend.async.sl_p0i32s(i32, ptr, ptr, ...)
+declare token @llvm.coro.id.async(i32, i32, i32, ptr)
+declare ptr @llvm.coro.begin(token, ptr writeonly)
+declare ptr @llvm.coro.async.resume()
+
+define ptr @__swift_async_resume_project_context(ptr %0) {
+entry:
+ ret ptr null
+}
diff --git a/llvm/test/CodeGen/WebAssembly/swifttailcc-musttail-no-tailcall.ll b/llvm/test/CodeGen/WebAssembly/swifttailcc-musttail-no-tailcall.ll
new file mode 100644
index 0000000000000..4060e59fd6d22
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/swifttailcc-musttail-no-tailcall.ll
@@ -0,0 +1,10 @@
+; RUN: not llc < %s -mtriple=wasm32-unknown-unknown -verify-machineinstrs 2>&1 | FileCheck %s
+
+; musttail with swifttailcc requires the +tail-call feature.
+; CHECK: error:
+; CHECK-SAME: WebAssembly 'tail-call' feature not enabled
+
+define swifttailcc void @musttail_no_tailcall(ptr swiftasync %ctx) {
+ musttail call swifttailcc void @musttail_no_tailcall(ptr swiftasync %ctx)
+ ret void
+}
diff --git a/llvm/test/CodeGen/WebAssembly/swifttailcc-no-tailcall.ll b/llvm/test/CodeGen/WebAssembly/swifttailcc-no-tailcall.ll
new file mode 100644
index 0000000000000..730505b68fb92
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/swifttailcc-no-tailcall.ll
@@ -0,0 +1,19 @@
+; RUN: llc < %s -mtriple=wasm32-unknown-unknown -verify-machineinstrs -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers | FileCheck %s
+
+; Without +tail-call, advisory `tail` calls silently fall back to regular calls.
+
+define swifttailcc void @basic_no_tailcall(ptr swiftasync %ctx) {
+; CHECK-LABEL: basic_no_tailcall:
+; CHECK: .functype basic_no_tailcall (i32, i32, i32) -> ()
+; CHECK: return
+ ret void
+}
+
+; Advisory tail call without +tail-call: falls back to regular call.
+define swifttailcc void @tail_no_tailcall(ptr swiftasync %ctx) {
+; CHECK-LABEL: tail_no_tailcall:
+; CHECK-NOT: return_call
+; CHECK: call tail_no_tailcall
+ tail call swifttailcc void @tail_no_tailcall(ptr swiftasync %ctx)
+ ret void
+}
diff --git a/llvm/test/CodeGen/WebAssembly/swifttailcc.ll b/llvm/test/CodeGen/WebAssembly/swifttailcc.ll
new file mode 100644
index 0000000000000..2d1cbe27417fd
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/swifttailcc.ll
@@ -0,0 +1,148 @@
+; RUN: llc < %s -mtriple=wasm32-unknown-unknown -verify-machineinstrs -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers -mattr=+tail-call | FileCheck -DPTR=i32 %s
+; RUN: llc < %s -mtriple=wasm64-unknown-unknown -verify-machineinstrs -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers -mattr=+tail-call | FileCheck -DPTR=i64 %s
+
+; Test swifttailcc (SwiftTail calling convention) support for WebAssembly.
+; Requires the tail-call feature for return_call / return_call_indirect.
+
+; Basic swifttailcc function definition with swiftasync parameter.
+; Missing swiftself and swifterror are padded automatically.
+define swifttailcc void @basic_swifttailcc(ptr swiftasync %ctx) {
+; CHECK-LABEL: basic_swifttailcc:
+; CHECK: .functype basic_swifttailcc ([[PTR]], [[PTR]], [[PTR]]) -> ()
+ ret void
+}
+
+; All Swift parameter attributes together — no padding needed.
+define swifttailcc void @full_swift_params(i32 %x, ptr swiftasync %ctx, ptr swiftself %self, ptr swifterror %err) {
+; CHECK-LABEL: full_swift_params:
+; CHECK: .functype full_swift_params (i32, [[PTR]], [[PTR]], [[PTR]]) -> ()
+ ret void
+}
+
+; Direct musttail call produces return_call.
+define swifttailcc void @direct_tail_call(ptr swiftasync %ctx) {
+; CHECK-LABEL: direct_tail_call:
+; CHECK: return_call direct_tail_call
+ musttail call swifttailcc void @direct_tail_call(ptr swiftasync %ctx)
+ ret void
+}
+
+; Indirect musttail call produces return_call_indirect.
+define swifttailcc void @indirect_tail_call(ptr swiftasync %ctx, ptr %fn) {
+; CHECK-LABEL: indirect_tail_call:
+; CHECK: return_call_indirect
+ musttail call swifttailcc void %fn(ptr swiftasync %ctx)
+ ret void
+}
+
+; Tail call with swiftasync and swiftself parameters.
+; Note: swifterror is not allowed in swifttailcc musttail caller/callee.
+; Errors are handled through the async context rather than through the
+; swifterror register convention.
+declare swifttailcc void @callee_swift_params(i32, ptr swiftasync, ptr swiftself)
+
+define swifttailcc void @tail_call_swift_params(i32 %x, ptr swiftasync %ctx, ptr swiftself %self) {
+; CHECK-LABEL: tail_call_swift_params:
+; CHECK: return_call callee_swift_params
+ musttail call swifttailcc void @callee_swift_params(i32 %x, ptr swiftasync %ctx, ptr swiftself %self)
+ ret void
+}
+
+; Non-tail call: regular call instruction, not return_call.
+declare swifttailcc void @other_func(ptr swiftasync)
+declare void @side_effect()
+
+define swifttailcc void @regular_call(ptr swiftasync %ctx) {
+; CHECK-LABEL: regular_call:
+; CHECK: call other_func
+; CHECK: call side_effect
+; CHECK: return
+ call swifttailcc void @other_func(ptr swiftasync %ctx)
+ call void @side_effect()
+ ret void
+}
+
+; Tail call with return value.
+declare swifttailcc i32 @callee_i32(ptr swiftasync, i32)
+
+define swifttailcc i32 @with_return(ptr swiftasync %ctx, i32 %x) {
+; CHECK-LABEL: with_return:
+; CHECK: return_call callee_i32
+ %r = musttail call swifttailcc i32 @callee_i32(ptr swiftasync %ctx, i32 %x)
+ ret i32 %r
+}
+
+; Mixed calling conventions: swifttailcc function calling a C function.
+declare void @c_function(i32)
+
+define swifttailcc void @mixed_cc(ptr swiftasync %ctx) {
+; CHECK-LABEL: mixed_cc:
+; CHECK: call c_function
+ call void @c_function(i32 42)
+ ret void
+}
+
+; Signature padding: a swifttailcc function with no Swift attributes gets
+; swiftself, swifterror, and swiftasync dummy params padded automatically.
+define swifttailcc void @no_swift_params(i32 %x) {
+; CHECK-LABEL: no_swift_params:
+; CHECK: .functype no_swift_params (i32, [[PTR]], [[PTR]], [[PTR]]) -> ()
+ ret void
+}
+
+; Return type mismatch: advisory tail call falls back to regular call.
+declare swifttailcc i32 @returns_i32_callee(ptr swiftasync)
+
+define swifttailcc void @return_type_mismatch(ptr swiftasync %ctx) {
+; CHECK-LABEL: return_type_mismatch:
+; CHECK-NOT: return_call
+; CHECK: call $drop=, returns_i32_callee
+ tail call swifttailcc i32 @returns_i32_callee(ptr swiftasync %ctx)
+ ret void
+}
+
+; Varargs callee: advisory tail call falls back to regular call.
+declare swifttailcc void @varargs_callee(ptr, ...)
+
+define swifttailcc void @varargs_tail(ptr swiftasync %ctx) {
+; CHECK-LABEL: varargs_tail:
+; CHECK-NOT: return_call
+; CHECK: call varargs_callee
+ tail call swifttailcc void @varargs_callee(ptr swiftasync %ctx)
+ ret void
+}
+
+; Indirect call signature consistency: all indirect calls to a swifttailcc
+; function must produce the same call_indirect signature, regardless of which
+; swift parameter attributes are present at the call site. This also verifies
+; that FixFunctionBitcasts skips swifttailcc (the IR type doesn't match the
+; padded Wasm type, so without the skip a wrapper would break this).
+define swifttailcc void @indirect_target(i32, i32) {
+; CHECK-LABEL: indirect_target:
+; CHECK: .functype indirect_target (i32, i32, [[PTR]], [[PTR]], [[PTR]]) -> ()
+ ret void
+}
+ at fn_ptr = global ptr @indirect_target
+
+define swifttailcc void @test_indirect_consistency() {
+; CHECK-LABEL: test_indirect_consistency:
+ %p = load ptr, ptr @fn_ptr
+
+ ; No swift attrs — swiftself, swifterror, swiftasync all padded.
+; CHECK: call_indirect __indirect_function_table, (i32, i32, [[PTR]], [[PTR]], [[PTR]]) -> ()
+ call swifttailcc void %p(i32 1, i32 2)
+
+ ; swiftasync present — swiftself and swifterror padded.
+; CHECK: call_indirect __indirect_function_table, (i32, i32, [[PTR]], [[PTR]], [[PTR]]) -> ()
+ call swifttailcc void %p(i32 1, i32 2, ptr swiftasync null)
+
+ ; swiftself present — swifterror and swiftasync padded.
+; CHECK: call_indirect __indirect_function_table, (i32, i32, [[PTR]], [[PTR]], [[PTR]]) -> ()
+ call swifttailcc void %p(i32 1, i32 2, ptr swiftself null)
+
+ ; swiftasync + swiftself present — swifterror padded.
+; CHECK: call_indirect __indirect_function_table, (i32, i32, [[PTR]], [[PTR]], [[PTR]]) -> ()
+ call swifttailcc void %p(i32 1, i32 2, ptr swiftasync null, ptr swiftself null)
+
+ ret void
+}
More information about the llvm-commits
mailing list