From 994a94d9fdc840bb3c44e8b75a4810b62324df21 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 31 Mar 2026 09:00:38 +0100 Subject: [PATCH 1/2] Wasm: add support for `swifttailcc` calling convention Merged upstream as https://github.com/llvm/llvm-project/pull/188296 --- .../WebAssemblyFixFunctionBitcasts.cpp | 8 +- .../WebAssembly/WebAssemblyISelLowering.cpp | 39 +++-- .../WebAssemblyMachineFunctionInfo.cpp | 18 ++- .../WebAssembly/swiftasync-coroutine.ll | 41 +++++ .../swifttailcc-musttail-no-tailcall.ll | 10 ++ .../WebAssembly/swifttailcc-no-tailcall.ll | 19 +++ llvm/test/CodeGen/WebAssembly/swifttailcc.ll | 148 ++++++++++++++++++ 7 files changed, 263 insertions(+), 20 deletions(-) create mode 100644 llvm/test/CodeGen/WebAssembly/swiftasync-coroutine.ll create mode 100644 llvm/test/CodeGen/WebAssembly/swifttailcc-musttail-no-tailcall.ll create mode 100644 llvm/test/CodeGen/WebAssembly/swifttailcc-no-tailcall.ll create mode 100644 llvm/test/CodeGen/WebAssembly/swifttailcc.ll diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFixFunctionBitcasts.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFixFunctionBitcasts.cpp index 37a34573bb339..8176a0bcad928 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyFixFunctionBitcasts.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyFixFunctionBitcasts.cpp @@ -236,9 +236,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 difference 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 difference 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 229812dce17b8..6a76b343c9d59 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp @@ -1187,7 +1187,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 @@ -1274,12 +1274,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.isSwiftCoro()) fail(DL, DAG, "WebAssembly hasn't implemented swiftcoro arguments"); if (Out.Flags.isNest()) @@ -1312,11 +1314,12 @@ 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++; ISD::OutputArg Arg; @@ -1333,6 +1336,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. @@ -1528,9 +1540,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()) @@ -1550,18 +1564,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 }> +@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 +} +@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 +} From 86883f274a5304fc9c9a47a4f12414ada40eb9d6 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 1 Apr 2026 10:21:53 +0100 Subject: [PATCH 2/2] clang/lib/Basic/Targets/WebAssembly.h: fix `checkCallingConvention` --- clang/lib/Basic/Targets/WebAssembly.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/Basic/Targets/WebAssembly.h b/clang/lib/Basic/Targets/WebAssembly.h index 57b366cb9c750..cab0124b2816c 100644 --- a/clang/lib/Basic/Targets/WebAssembly.h +++ b/clang/lib/Basic/Targets/WebAssembly.h @@ -166,7 +166,7 @@ class LLVM_LIBRARY_VISIBILITY WebAssemblyTargetInfo : public TargetInfo { case CC_Swift: return CCCR_OK; case CC_SwiftAsync: - return CCCR_Error; + return HasTailCall ? CCCR_OK : CCCR_Error; default: return CCCR_Warning; }