[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