Skip to content

[WebAssembly][GlobalISel] CallLowering lowerFormalArguments#180263

Open
QuantumSegfault wants to merge 3 commits intollvm:mainfrom
QuantumSegfault:wasm-gisel-calllowering-args
Open

[WebAssembly][GlobalISel] CallLowering lowerFormalArguments#180263
QuantumSegfault wants to merge 3 commits intollvm:mainfrom
QuantumSegfault:wasm-gisel-calllowering-args

Conversation

@QuantumSegfault
Copy link
Contributor

Implements WebAssemblyCallLowering::lowerFormalArguments

Split from #157161

@llvmbot
Copy link
Member

llvmbot commented Feb 6, 2026

@llvm/pr-subscribers-backend-webassembly

@llvm/pr-subscribers-llvm-globalisel

Author: Demetrius Kanios (QuantumSegfault)

Changes

Implements WebAssemblyCallLowering::lowerFormalArguments

Split from #157161


Patch is 31.59 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/180263.diff

4 Files Affected:

  • (modified) llvm/lib/Target/WebAssembly/GISel/WebAssemblyCallLowering.cpp (+206-3)
  • (added) llvm/test/CodeGen/WebAssembly/GlobalISel/irtranslator/args-simd.ll (+171)
  • (added) llvm/test/CodeGen/WebAssembly/GlobalISel/irtranslator/args-swiftcc.ll (+73)
  • (added) llvm/test/CodeGen/WebAssembly/GlobalISel/irtranslator/args.ll (+209)
diff --git a/llvm/lib/Target/WebAssembly/GISel/WebAssemblyCallLowering.cpp b/llvm/lib/Target/WebAssembly/GISel/WebAssemblyCallLowering.cpp
index 3d0bbcc9a1c7b..b90c5cc43467c 100644
--- a/llvm/lib/Target/WebAssembly/GISel/WebAssemblyCallLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/GISel/WebAssemblyCallLowering.cpp
@@ -19,6 +19,8 @@
 #include "WebAssemblySubtarget.h"
 #include "WebAssemblyUtilities.h"
 #include "llvm/CodeGen/Analysis.h"
+#include "llvm/CodeGen/FunctionLoweringInfo.h"
+#include "llvm/CodeGen/GlobalISel/MachineIRBuilder.h"
 #include "llvm/IR/DataLayout.h"
 #include "llvm/IR/DebugLoc.h"
 #include "llvm/IR/Value.h"
@@ -27,6 +29,21 @@
 
 using namespace llvm;
 
+// Test whether the given calling convention is supported.
+static bool callingConvSupported(CallingConv::ID CallConv) {
+  // We currently support the language-independent target-independent
+  // conventions. We don't yet have a way to annotate calls with properties like
+  // "cold", and we don't have any call-clobbered registers, so these are mostly
+  // all handled the same.
+  return CallConv == CallingConv::C || CallConv == CallingConv::Fast ||
+         CallConv == CallingConv::Cold ||
+         CallConv == CallingConv::PreserveMost ||
+         CallConv == CallingConv::PreserveAll ||
+         CallConv == CallingConv::CXX_FAST_TLS ||
+         CallConv == CallingConv::WASM_EmscriptenInvoke ||
+         CallConv == CallingConv::Swift;
+}
+
 WebAssemblyCallLowering::WebAssemblyCallLowering(
     const WebAssemblyTargetLowering &TLI)
     : CallLowering(&TLI) {}
@@ -50,13 +67,199 @@ bool WebAssemblyCallLowering::lowerReturn(MachineIRBuilder &MIRBuilder,
   return false;
 }
 
+static unsigned getWASMArgumentOpcode(MVT ArgType) {
+  switch (ArgType.SimpleTy) {
+  case MVT::i32:
+    return WebAssembly::ARGUMENT_i32;
+  case MVT::i64:
+    return WebAssembly::ARGUMENT_i64;
+  case MVT::f32:
+    return WebAssembly::ARGUMENT_f32;
+  case MVT::f64:
+    return WebAssembly::ARGUMENT_f64;
+
+  case MVT::funcref:
+    return WebAssembly::ARGUMENT_funcref;
+  case MVT::externref:
+    return WebAssembly::ARGUMENT_externref;
+  case MVT::exnref:
+    return WebAssembly::ARGUMENT_exnref;
+
+  case MVT::v16i8:
+    return WebAssembly::ARGUMENT_v16i8;
+  case MVT::v8i16:
+    return WebAssembly::ARGUMENT_v8i16;
+  case MVT::v4i32:
+    return WebAssembly::ARGUMENT_v4i32;
+  case MVT::v2i64:
+    return WebAssembly::ARGUMENT_v2i64;
+  case MVT::v8f16:
+    return WebAssembly::ARGUMENT_v8f16;
+  case MVT::v4f32:
+    return WebAssembly::ARGUMENT_v4f32;
+  case MVT::v2f64:
+    return WebAssembly::ARGUMENT_v2f64;
+  default:
+    break;
+  }
+  llvm_unreachable("Found unexpected type for WASM argument");
+}
+
 bool WebAssemblyCallLowering::lowerFormalArguments(
     MachineIRBuilder &MIRBuilder, const Function &F,
     ArrayRef<ArrayRef<Register>> VRegs, FunctionLoweringInfo &FLI) const {
-  if (VRegs.empty())
-    return true; // allow only empty signatures for now
+  MachineFunction &MF = MIRBuilder.getMF();
+  MachineRegisterInfo &MRI = MF.getRegInfo();
+  WebAssemblyFunctionInfo *MFI = MF.getInfo<WebAssemblyFunctionInfo>();
+  const DataLayout &DL = F.getDataLayout();
+  auto &TLI = *getTLI<WebAssemblyTargetLowering>();
+  auto &Subtarget = MF.getSubtarget<WebAssemblySubtarget>();
+  auto &TRI = *Subtarget.getRegisterInfo();
+  auto &TII = *Subtarget.getInstrInfo();
+  auto &RBI = *Subtarget.getRegBankInfo();
 
-  return false;
+  LLVMContext &Ctx = MIRBuilder.getContext();
+  const CallingConv::ID CallConv = F.getCallingConv();
+
+  if (!callingConvSupported(CallConv)) {
+    return false;
+  }
+
+  MF.getRegInfo().addLiveIn(WebAssembly::ARGUMENTS);
+  MF.front().addLiveIn(WebAssembly::ARGUMENTS);
+
+  SmallVector<ArgInfo, 8> SplitArgs;
+
+  if (!FLI.CanLowerReturn) {
+    insertSRetIncomingArgument(F, SplitArgs, FLI.DemoteRegister, MRI, DL);
+  }
+
+  unsigned ArgIdx = 0;
+  bool HasSwiftErrorArg = false;
+  bool HasSwiftSelfArg = false;
+  for (const auto &Arg : F.args()) {
+    ArgInfo OrigArg{VRegs[ArgIdx], Arg.getType(), ArgIdx};
+    setArgFlags(OrigArg, ArgIdx + AttributeList::FirstArgIndex, DL, F);
+
+    HasSwiftSelfArg |= Arg.hasSwiftSelfAttr();
+    HasSwiftErrorArg |= Arg.hasSwiftErrorAttr();
+    if (Arg.hasInAllocaAttr()) {
+      return false;
+    }
+    if (Arg.hasNestAttr()) {
+      return false;
+    }
+    splitToValueTypes(OrigArg, SplitArgs, DL, F.getCallingConv());
+    ++ArgIdx;
+  }
+
+  unsigned FinalArgIdx = 0;
+  for (auto &Arg : SplitArgs) {
+    EVT OrigVT = TLI.getValueType(DL, Arg.Ty);
+    MVT NewVT = TLI.getRegisterTypeForCallingConv(Ctx, CallConv, OrigVT);
+    LLT OrigLLT = getLLTForType(*Arg.Ty, DL);
+    LLT NewLLT = getLLTForMVT(NewVT);
+
+    // If we need to split the type over multiple regs, check it's a scenario
+    // we currently support.
+    unsigned NumParts =
+        TLI.getNumRegistersForCallingConv(Ctx, CallConv, OrigVT);
+
+    ISD::ArgFlagsTy OrigFlags = Arg.Flags[0];
+    Arg.Flags.clear();
+
+    for (unsigned Part = 0; Part < NumParts; ++Part) {
+      ISD::ArgFlagsTy Flags = OrigFlags;
+      if (Part == 0) {
+        Flags.setSplit();
+      } else {
+        Flags.setOrigAlign(Align(1));
+        if (Part == NumParts - 1)
+          Flags.setSplitEnd();
+      }
+
+      Arg.Flags.push_back(Flags);
+    }
+
+    Arg.OrigRegs.assign(Arg.Regs.begin(), Arg.Regs.end());
+    if (NumParts != 1 || OrigVT != NewVT) {
+      // If we can't directly assign the register, we need one or more
+      // intermediate values.
+      Arg.Regs.resize(NumParts);
+
+      // For each split register, create and assign a vreg that will store
+      // the incoming component of the larger value. These will later be
+      // merged to form the final vreg.
+      for (unsigned Part = 0; Part < NumParts; ++Part) {
+        Arg.Regs[Part] = MRI.createGenericVirtualRegister(NewLLT);
+      }
+    }
+
+    for (unsigned Part = 0; Part < NumParts; ++Part) {
+      auto ArgInst = MIRBuilder.buildInstr(getWASMArgumentOpcode(NewVT))
+                         .addDef(Arg.Regs[Part])
+                         .addImm(FinalArgIdx);
+
+      constrainOperandRegClass(MF, TRI, MRI, TII, RBI, *ArgInst,
+                               ArgInst->getDesc(), ArgInst->getOperand(0), 0);
+      MFI->addParam(NewVT);
+      ++FinalArgIdx;
+    }
+
+    if (NumParts != 1 || OrigVT != NewVT) {
+      buildCopyFromRegs(MIRBuilder, Arg.OrigRegs, Arg.Regs, OrigLLT, NewLLT,
+                        Arg.Flags[0]);
+    }
+  }
+
+  // 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.
+  auto PtrVT = TLI.getPointerTy(DL);
+  if (CallConv == CallingConv::Swift) {
+    if (!HasSwiftSelfArg) {
+      MFI->addParam(PtrVT);
+    }
+    if (!HasSwiftErrorArg) {
+      MFI->addParam(PtrVT);
+    }
+  }
+
+  // Varargs are copied into a buffer allocated by the caller, and a pointer to
+  // the buffer is passed as an argument.
+  if (F.isVarArg()) {
+    auto PtrVT = TLI.getPointerTy(DL, 0);
+    auto PtrLLT = LLT::pointer(0, DL.getPointerSizeInBits(0));
+    Register VarargVreg = MF.getRegInfo().createGenericVirtualRegister(PtrLLT);
+
+    MFI->setVarargBufferVreg(VarargVreg);
+
+    auto ArgInst = MIRBuilder.buildInstr(getWASMArgumentOpcode(PtrVT))
+                       .addDef(VarargVreg)
+                       .addImm(FinalArgIdx);
+
+    constrainOperandRegClass(MF, TRI, MRI, TII, RBI, *ArgInst,
+                             ArgInst->getDesc(), ArgInst->getOperand(0), 0);
+
+    MFI->addParam(PtrVT);
+    ++FinalArgIdx;
+  }
+
+  // Record the number and types of arguments and results.
+  SmallVector<MVT, 4> Params;
+  SmallVector<MVT, 4> Results;
+  computeSignatureVTs(MF.getFunction().getFunctionType(), &MF.getFunction(),
+                      MF.getFunction(), MF.getTarget(), Params, Results);
+  for (MVT VT : Results)
+    MFI->addResult(VT);
+
+  // TODO: Use signatures in WebAssemblyMachineFunctionInfo too and unify
+  // the param logic here with ComputeSignatureVTs
+  assert(MFI->getParams().size() == Params.size() &&
+         std::equal(MFI->getParams().begin(), MFI->getParams().end(),
+                    Params.begin()));
+  return true;
 }
 
 bool WebAssemblyCallLowering::lowerCall(MachineIRBuilder &MIRBuilder,
diff --git a/llvm/test/CodeGen/WebAssembly/GlobalISel/irtranslator/args-simd.ll b/llvm/test/CodeGen/WebAssembly/GlobalISel/irtranslator/args-simd.ll
new file mode 100644
index 0000000000000..fb167796bec68
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/GlobalISel/irtranslator/args-simd.ll
@@ -0,0 +1,171 @@
+; NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -mtriple=wasm32 -mattr=-simd128 -global-isel -stop-after=irtranslator -verify-machineinstrs < %s | FileCheck %s -check-prefixes=NO-SIMD
+; RUN: llc -mtriple=wasm32 -mattr=+simd128,-fp16 -global-isel -stop-after=irtranslator -verify-machineinstrs < %s | FileCheck %s -check-prefixes=SIMD,SIMD-NO-F16
+; RUN: llc -mtriple=wasm32 -mattr=+simd128,+fp16 -global-isel -stop-after=irtranslator -verify-machineinstrs < %s | FileCheck %s -check-prefixes=SIMD,SIMD-F16
+
+define void @test_v16i8_arg(<16 x i8> %arg) {
+  ; NO-SIMD-LABEL: name: test_v16i8_arg
+  ; NO-SIMD: bb.1 (%ir-block.0):
+  ; NO-SIMD-NEXT:   liveins: $arguments
+  ; NO-SIMD-NEXT: {{  $}}
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_:%[0-9]+]]:i32(s32) = ARGUMENT_i32 0, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_1:%[0-9]+]]:i32(s32) = ARGUMENT_i32 1, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_2:%[0-9]+]]:i32(s32) = ARGUMENT_i32 2, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_3:%[0-9]+]]:i32(s32) = ARGUMENT_i32 3, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_4:%[0-9]+]]:i32(s32) = ARGUMENT_i32 4, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_5:%[0-9]+]]:i32(s32) = ARGUMENT_i32 5, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_6:%[0-9]+]]:i32(s32) = ARGUMENT_i32 6, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_7:%[0-9]+]]:i32(s32) = ARGUMENT_i32 7, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_8:%[0-9]+]]:i32(s32) = ARGUMENT_i32 8, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_9:%[0-9]+]]:i32(s32) = ARGUMENT_i32 9, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_10:%[0-9]+]]:i32(s32) = ARGUMENT_i32 10, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_11:%[0-9]+]]:i32(s32) = ARGUMENT_i32 11, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_12:%[0-9]+]]:i32(s32) = ARGUMENT_i32 12, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_13:%[0-9]+]]:i32(s32) = ARGUMENT_i32 13, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_14:%[0-9]+]]:i32(s32) = ARGUMENT_i32 14, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_15:%[0-9]+]]:i32(s32) = ARGUMENT_i32 15, implicit $arguments
+  ; NO-SIMD-NEXT:   [[BUILD_VECTOR:%[0-9]+]]:_(<16 x s32>) = G_BUILD_VECTOR [[ARGUMENT_i32_]](s32), [[ARGUMENT_i32_1]](s32), [[ARGUMENT_i32_2]](s32), [[ARGUMENT_i32_3]](s32), [[ARGUMENT_i32_4]](s32), [[ARGUMENT_i32_5]](s32), [[ARGUMENT_i32_6]](s32), [[ARGUMENT_i32_7]](s32), [[ARGUMENT_i32_8]](s32), [[ARGUMENT_i32_9]](s32), [[ARGUMENT_i32_10]](s32), [[ARGUMENT_i32_11]](s32), [[ARGUMENT_i32_12]](s32), [[ARGUMENT_i32_13]](s32), [[ARGUMENT_i32_14]](s32), [[ARGUMENT_i32_15]](s32)
+  ; NO-SIMD-NEXT:   [[TRUNC:%[0-9]+]]:_(<16 x s8>) = G_TRUNC [[BUILD_VECTOR]](<16 x s32>)
+  ;
+  ; SIMD-LABEL: name: test_v16i8_arg
+  ; SIMD: bb.1 (%ir-block.0):
+  ; SIMD-NEXT:   liveins: $arguments
+  ; SIMD-NEXT: {{  $}}
+  ; SIMD-NEXT:   [[ARGUMENT_v16i8_:%[0-9]+]]:v128(<16 x s8>) = ARGUMENT_v16i8 0, implicit $arguments
+  ret void
+}
+
+define void @test_v8i16_arg(<8 x i16> %arg) {
+  ; NO-SIMD-LABEL: name: test_v8i16_arg
+  ; NO-SIMD: bb.1 (%ir-block.0):
+  ; NO-SIMD-NEXT:   liveins: $arguments
+  ; NO-SIMD-NEXT: {{  $}}
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_:%[0-9]+]]:i32(s32) = ARGUMENT_i32 0, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_1:%[0-9]+]]:i32(s32) = ARGUMENT_i32 1, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_2:%[0-9]+]]:i32(s32) = ARGUMENT_i32 2, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_3:%[0-9]+]]:i32(s32) = ARGUMENT_i32 3, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_4:%[0-9]+]]:i32(s32) = ARGUMENT_i32 4, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_5:%[0-9]+]]:i32(s32) = ARGUMENT_i32 5, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_6:%[0-9]+]]:i32(s32) = ARGUMENT_i32 6, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_7:%[0-9]+]]:i32(s32) = ARGUMENT_i32 7, implicit $arguments
+  ; NO-SIMD-NEXT:   [[BUILD_VECTOR:%[0-9]+]]:_(<8 x s32>) = G_BUILD_VECTOR [[ARGUMENT_i32_]](s32), [[ARGUMENT_i32_1]](s32), [[ARGUMENT_i32_2]](s32), [[ARGUMENT_i32_3]](s32), [[ARGUMENT_i32_4]](s32), [[ARGUMENT_i32_5]](s32), [[ARGUMENT_i32_6]](s32), [[ARGUMENT_i32_7]](s32)
+  ; NO-SIMD-NEXT:   [[TRUNC:%[0-9]+]]:_(<8 x s16>) = G_TRUNC [[BUILD_VECTOR]](<8 x s32>)
+  ;
+  ; SIMD-LABEL: name: test_v8i16_arg
+  ; SIMD: bb.1 (%ir-block.0):
+  ; SIMD-NEXT:   liveins: $arguments
+  ; SIMD-NEXT: {{  $}}
+  ; SIMD-NEXT:   [[ARGUMENT_v8i16_:%[0-9]+]]:v128(<8 x s16>) = ARGUMENT_v8i16 0, implicit $arguments
+  ret void
+}
+
+define void @test_v4i32_arg(<4 x i32> %arg) {
+  ; NO-SIMD-LABEL: name: test_v4i32_arg
+  ; NO-SIMD: bb.1 (%ir-block.0):
+  ; NO-SIMD-NEXT:   liveins: $arguments
+  ; NO-SIMD-NEXT: {{  $}}
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_:%[0-9]+]]:i32(s32) = ARGUMENT_i32 0, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_1:%[0-9]+]]:i32(s32) = ARGUMENT_i32 1, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_2:%[0-9]+]]:i32(s32) = ARGUMENT_i32 2, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_3:%[0-9]+]]:i32(s32) = ARGUMENT_i32 3, implicit $arguments
+  ; NO-SIMD-NEXT:   [[BUILD_VECTOR:%[0-9]+]]:_(<4 x s32>) = G_BUILD_VECTOR [[ARGUMENT_i32_]](s32), [[ARGUMENT_i32_1]](s32), [[ARGUMENT_i32_2]](s32), [[ARGUMENT_i32_3]](s32)
+  ;
+  ; SIMD-LABEL: name: test_v4i32_arg
+  ; SIMD: bb.1 (%ir-block.0):
+  ; SIMD-NEXT:   liveins: $arguments
+  ; SIMD-NEXT: {{  $}}
+  ; SIMD-NEXT:   [[ARGUMENT_v4i32_:%[0-9]+]]:v128(<4 x s32>) = ARGUMENT_v4i32 0, implicit $arguments
+  ret void
+}
+
+define void @test_v2i64_arg(<2 x i64> %arg) {
+  ; NO-SIMD-LABEL: name: test_v2i64_arg
+  ; NO-SIMD: bb.1 (%ir-block.0):
+  ; NO-SIMD-NEXT:   liveins: $arguments
+  ; NO-SIMD-NEXT: {{  $}}
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i64_:%[0-9]+]]:i64(s64) = ARGUMENT_i64 0, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i64_1:%[0-9]+]]:i64(s64) = ARGUMENT_i64 1, implicit $arguments
+  ; NO-SIMD-NEXT:   [[BUILD_VECTOR:%[0-9]+]]:_(<2 x s64>) = G_BUILD_VECTOR [[ARGUMENT_i64_]](s64), [[ARGUMENT_i64_1]](s64)
+  ;
+  ; SIMD-LABEL: name: test_v2i64_arg
+  ; SIMD: bb.1 (%ir-block.0):
+  ; SIMD-NEXT:   liveins: $arguments
+  ; SIMD-NEXT: {{  $}}
+  ; SIMD-NEXT:   [[ARGUMENT_v2i64_:%[0-9]+]]:v128(<2 x s64>) = ARGUMENT_v2i64 0, implicit $arguments
+  ret void
+}
+
+define void @test_v8f16_arg(<8 x half> %arg) {
+  ; NO-SIMD-LABEL: name: test_v8f16_arg
+  ; NO-SIMD: bb.1 (%ir-block.0):
+  ; NO-SIMD-NEXT:   liveins: $arguments
+  ; NO-SIMD-NEXT: {{  $}}
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_:%[0-9]+]]:i32(s32) = ARGUMENT_i32 0, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_1:%[0-9]+]]:i32(s32) = ARGUMENT_i32 1, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_2:%[0-9]+]]:i32(s32) = ARGUMENT_i32 2, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_3:%[0-9]+]]:i32(s32) = ARGUMENT_i32 3, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_4:%[0-9]+]]:i32(s32) = ARGUMENT_i32 4, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_5:%[0-9]+]]:i32(s32) = ARGUMENT_i32 5, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_6:%[0-9]+]]:i32(s32) = ARGUMENT_i32 6, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_i32_7:%[0-9]+]]:i32(s32) = ARGUMENT_i32 7, implicit $arguments
+  ; NO-SIMD-NEXT:   [[BUILD_VECTOR:%[0-9]+]]:_(<8 x s32>) = G_BUILD_VECTOR [[ARGUMENT_i32_]](s32), [[ARGUMENT_i32_1]](s32), [[ARGUMENT_i32_2]](s32), [[ARGUMENT_i32_3]](s32), [[ARGUMENT_i32_4]](s32), [[ARGUMENT_i32_5]](s32), [[ARGUMENT_i32_6]](s32), [[ARGUMENT_i32_7]](s32)
+  ; NO-SIMD-NEXT:   [[TRUNC:%[0-9]+]]:_(<8 x s16>) = G_TRUNC [[BUILD_VECTOR]](<8 x s32>)
+  ;
+  ; SIMD-NO-F16-LABEL: name: test_v8f16_arg
+  ; SIMD-NO-F16: bb.1 (%ir-block.0):
+  ; SIMD-NO-F16-NEXT:   liveins: $arguments
+  ; SIMD-NO-F16-NEXT: {{  $}}
+  ; SIMD-NO-F16-NEXT:   [[ARGUMENT_i32_:%[0-9]+]]:i32(s32) = ARGUMENT_i32 0, implicit $arguments
+  ; SIMD-NO-F16-NEXT:   [[ARGUMENT_i32_1:%[0-9]+]]:i32(s32) = ARGUMENT_i32 1, implicit $arguments
+  ; SIMD-NO-F16-NEXT:   [[ARGUMENT_i32_2:%[0-9]+]]:i32(s32) = ARGUMENT_i32 2, implicit $arguments
+  ; SIMD-NO-F16-NEXT:   [[ARGUMENT_i32_3:%[0-9]+]]:i32(s32) = ARGUMENT_i32 3, implicit $arguments
+  ; SIMD-NO-F16-NEXT:   [[ARGUMENT_i32_4:%[0-9]+]]:i32(s32) = ARGUMENT_i32 4, implicit $arguments
+  ; SIMD-NO-F16-NEXT:   [[ARGUMENT_i32_5:%[0-9]+]]:i32(s32) = ARGUMENT_i32 5, implicit $arguments
+  ; SIMD-NO-F16-NEXT:   [[ARGUMENT_i32_6:%[0-9]+]]:i32(s32) = ARGUMENT_i32 6, implicit $arguments
+  ; SIMD-NO-F16-NEXT:   [[ARGUMENT_i32_7:%[0-9]+]]:i32(s32) = ARGUMENT_i32 7, implicit $arguments
+  ; SIMD-NO-F16-NEXT:   [[BUILD_VECTOR:%[0-9]+]]:_(<8 x s32>) = G_BUILD_VECTOR [[ARGUMENT_i32_]](s32), [[ARGUMENT_i32_1]](s32), [[ARGUMENT_i32_2]](s32), [[ARGUMENT_i32_3]](s32), [[ARGUMENT_i32_4]](s32), [[ARGUMENT_i32_5]](s32), [[ARGUMENT_i32_6]](s32), [[ARGUMENT_i32_7]](s32)
+  ; SIMD-NO-F16-NEXT:   [[TRUNC:%[0-9]+]]:_(<8 x s16>) = G_TRUNC [[BUILD_VECTOR]](<8 x s32>)
+  ;
+  ; SIMD-F16-LABEL: name: test_v8f16_arg
+  ; SIMD-F16: bb.1 (%ir-block.0):
+  ; SIMD-F16-NEXT:   liveins: $arguments
+  ; SIMD-F16-NEXT: {{  $}}
+  ; SIMD-F16-NEXT:   [[ARGUMENT_v8f16_:%[0-9]+]]:v128(<8 x s16>) = ARGUMENT_v8f16 0, implicit $arguments
+  ret void
+}
+
+define void @test_v4f32_arg(<4 x float> %arg) {
+  ; NO-SIMD-LABEL: name: test_v4f32_arg
+  ; NO-SIMD: bb.1 (%ir-block.0):
+  ; NO-SIMD-NEXT:   liveins: $arguments
+  ; NO-SIMD-NEXT: {{  $}}
+  ; NO-SIMD-NEXT:   [[ARGUMENT_f32_:%[0-9]+]]:f32(s32) = ARGUMENT_f32 0, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_f32_1:%[0-9]+]]:f32(s32) = ARGUMENT_f32 1, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_f32_2:%[0-9]+]]:f32(s32) = ARGUMENT_f32 2, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_f32_3:%[0-9]+]]:f32(s32) = ARGUMENT_f32 3, implicit $arguments
+  ; NO-SIMD-NEXT:   [[BUILD_VECTOR:%[0-9]+]]:_(<4 x s32>) = G_BUILD_VECTOR [[ARGUMENT_f32_]](s32), [[ARGUMENT_f32_1]](s32), [[ARGUMENT_f32_2]](s32), [[ARGUMENT_f32_3]](s32)
+  ;
+  ; SIMD-LABEL: name: test_v4f32_arg
+  ; SIMD: bb.1 (%ir-block.0):
+  ; SIMD-NEXT:   liveins: $arguments
+  ; SIMD-NEXT: {{  $}}
+  ; SIMD-NEXT:   [[ARGUMENT_v4f32_:%[0-9]+]]:v128(<4 x s32>) = ARGUMENT_v4f32 0, implicit $arguments
+  ret void
+}
+
+define void @test_v2f64_arg(<2 x double> %arg) {
+  ; NO-SIMD-LABEL: name: test_v2f64_arg
+  ; NO-SIMD: bb.1 (%ir-block.0):
+  ; NO-SIMD-NEXT:   liveins: $arguments
+  ; NO-SIMD-NEXT: {{  $}}
+  ; NO-SIMD-NEXT:   [[ARGUMENT_f64_:%[0-9]+]]:f64(s64) = ARGUMENT_f64 0, implicit $arguments
+  ; NO-SIMD-NEXT:   [[ARGUMENT_f64_1:%[0-9]+]]:f64(s64) = ARGUMENT_f64 1, implicit $arguments
+  ; NO-SIMD-NEXT:   [[BUILD_VECTOR:%[0-9]+]]:_(<2 x s64>) = G_BUILD_VECTOR [[ARGUMENT_f64_]](s64), [[ARGUMENT_f64_1]](s64)
+  ;
+  ; SIMD-LABEL: name: test_v2f64_arg
+  ; SIMD: bb.1 (%ir-block.0):
+  ; SIMD-NEXT:   liveins: $arguments
+  ; SIMD-NEXT: {{  $}}
+  ; SIMD-NEXT:   [[ARGUMENT_v2f64_:%[0-9]+]]:v128(<2 x s64>) = ARGUMENT_v2f64 0, implicit $arguments
+  ret void
+}
diff --git a/llvm/test/CodeGen/WebAssembly/GlobalISel/irtranslator/args-swiftcc.ll b/llvm/test/CodeGen/WebAssembly/GlobalISel/irtranslator/args-swiftcc.ll
new file mode 100644
index 0000000000000..1971e1dc89c9e
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/GlobalISel/irtranslator/args-swiftcc.ll
@@ -0,0 +1,73 @@
+; RUN: llc -mtriple=wasm32 -global-isel -stop-after=irtranslator -verify-machineinstrs < %s | FileCheck %s -check-prefixes=CHECK,WASM32
+; RUN: llc -mtriple=wasm64 -global-isel -stop-after=irtranslator -verify-machineinstrs < %s | FileCheck %s -check-prefixes=CHECK,WASM64
+
+define swift...
[truncated]

@QuantumSegfault
Copy link
Contributor Author

Requesting review

@dschuff @arsenm

return false;
}

static unsigned getWASMArgumentOpcode(MVT ArgType) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing this was copied from somewhere else. Either share it, or you shouldn't need it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, not copied. ThoughWebAssemblyFastISel::fastLowerArguments does something similar.

// Varargs are copied into a buffer allocated by the caller, and a pointer to
// the buffer is passed as an argument.
if (F.isVarArg()) {
auto PtrVT = TLI.getPointerTy(DL, 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No auto. Also you should hardly ever need to call getPointerTy. Especially here since it returns an MVT

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need an MVT. Both to determine the correct argument opcode, but also to encode into the WASM function signature (MFI->addParam(PtrVT);). The hidden VarArg buffer argument is going to be a pointer, thus either i32 vs i64 on wasm32 vs wasm64.

Is there a better way to do this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even in the DAG, getPointerTy should be used sparingly. In the vast majority of situations, the correct type to use is implied by the original operation. getPointerTy (and other TargetLowering getType functions) are only necessary when synthesizing operations out of thin air

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well...then it seems we have a legit use case here then. Cause we are injecting hidden parameters

Comment on lines +146 to +151
if (Arg.hasInAllocaAttr()) {
return false;
}
if (Arg.hasNestAttr()) {
return false;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Untested? What about sret and byval?

Copy link
Contributor Author

@QuantumSegfault QuantumSegfault Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, untested...I could add some tests to make sure it fails (which in -global-isel-abort=2 fallback to SelectionDAG, and provide an explicit error message about these not being supported in Wasm).

I can test sret as part of the next PR (lowerReturn), cause otherwise there's nothing special about it; it's just any other pointer. As far as byval, on the lowerFormalArugments side it's nothing special...just a pointer argument. The copy is made on the callee side.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually sret testing has to be done alongside lowerCall. Within the callee it's just a pointer to be written into as any other.


MFI->setVarargBufferVreg(VarargVreg);

auto ArgInst = MIRBuilder.buildInstr(getWASMArgumentOpcode(PtrVT))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't this using IncomingValueHandler?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can re-investigate. But when I started out, it seemed overkill, as there is never stack spilling, nor are there any actual physical registers to copy into. Things remain in vreg end-to-end, so it seemed to make more sense to take the necessary bits only.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. I looked into again and it's just not the right fit. We don't have the typical CCAssignFn generated by tablegen. And even if I wrote a custom one, my options are to assign values into physical registers (MCRegister), memory, or "custom" which bypasses all the automatic handling and puts me right back where I started with manually splits and copies.

As far VarArg specifically, it's just passed as a hidden pointer argument, and only manipulated with vararg related instructions/intrinsics, so there's no unpacking to do for anything

; NO-SIMD-NEXT: [[ARGUMENT_i32_12:%[0-9]+]]:i32(s32) = ARGUMENT_i32 12, implicit $arguments
; NO-SIMD-NEXT: [[ARGUMENT_i32_13:%[0-9]+]]:i32(s32) = ARGUMENT_i32 13, implicit $arguments
; NO-SIMD-NEXT: [[ARGUMENT_i32_14:%[0-9]+]]:i32(s32) = ARGUMENT_i32 14, implicit $arguments
; NO-SIMD-NEXT: [[ARGUMENT_i32_15:%[0-9]+]]:i32(s32) = ARGUMENT_i32 15, implicit $arguments
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do arguments always use these ARGUMENT_ instructions, or do you ever have cases with overflow stack arguments?

Copy link
Contributor Author

@QuantumSegfault QuantumSegfault Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I know, all LLVM arguments are passed directly through individual Wasm-side arguments (ARGUMENT_xxxx eventually lowers to just a local.get). Aggregates are recursively split into individual arguments.

There is no stack-spilling. The closest there is to that is the handling for varargs, which are written onto the stack and passed as a single pointer, by necessity.


This is partially why I avoided IncomingValueHandler

// 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 = TLI.getPointerTy(DL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't need getPointerTy

Copy link
Contributor Author

@QuantumSegfault QuantumSegfault Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question as before? Is there a better way to get MVT::i32 vs MVT::i64 to give to MFI->addParam

@QuantumSegfault
Copy link
Contributor Author

For more context, see WebAssemblyTargetLowering::LowerFormalArguments

https://github.com/QuantumSegfault/llvm-project/blob/66b6420d6dade8da22b9424895ca2f78d44a8cbb/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp#L1589-L1668


You can see that in my WebAssemblyCallLowering::lowerFormalArguments, lines 124-129, 144-151, and 215-261 are translations of WebAssemblyTargetLowering::LowerFormalArguments

Lines 156-213 are a stripped down version of what would normally be handled by determineAssignments, handleAssignments, and the ValueAssigners and ValueHandlers. I saw no point is using those when Wasm is much more straightforward than most backends/architectures in that respect.

The rest is adapted from the commonalities of the target-specific implementations of XXXXXXCallLowering::lowerFormalArguments


Does that clear some things up?

@github-actions
Copy link

github-actions bot commented Feb 8, 2026

✅ With the latest revision this PR passed the C/C++ code formatter.

@QuantumSegfault QuantumSegfault force-pushed the wasm-gisel-calllowering-args branch from 81f6675 to 7d13abe Compare February 8, 2026 09:13
@ppenzin ppenzin self-requested a review February 13, 2026 23:14
@QuantumSegfault
Copy link
Contributor Author

Ping

@dschuff @arsenm

@QuantumSegfault
Copy link
Contributor Author

Ping

@dschuff @arsenm @ppenzin

@QuantumSegfault QuantumSegfault changed the title [WebAssembly][GlobalISel] Part 2 - CallLowering lowerFormalArguments [WebAssembly][GlobalISel] CallLowering lowerFormalArguments Feb 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants