Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 206 additions & 3 deletions llvm/lib/Target/WebAssembly/GISel/WebAssemblyCallLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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) {}
Expand All @@ -50,13 +67,199 @@ bool WebAssemblyCallLowering::lowerReturn(MachineIRBuilder &MIRBuilder,
return false;
}

static unsigned getWASMArgumentOpcode(MVT ArgType) {
Comment thread
QuantumSegfault marked this conversation as resolved.
Comment thread
QuantumSegfault marked this conversation as resolved.
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;
}
Comment on lines +169 to +174

Copy link
Copy Markdown
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?

@QuantumSegfault QuantumSegfault Feb 7, 2026

Copy link
Copy Markdown
Contributor Author

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
Copy Markdown
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.

splitToValueTypes(OrigArg, SplitArgs, DL, F.getCallingConv());
++ArgIdx;
}

unsigned FinalArgIdx = 0;
for (auto &Arg : SplitArgs) {
Comment thread
QuantumSegfault marked this conversation as resolved.
Outdated
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);
Comment thread
QuantumSegfault marked this conversation as resolved.
Outdated
if (CallConv == CallingConv::Swift) {
if (!HasSwiftSelfArg) {
MFI->addParam(PtrVT);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ideally should keep this in terms of LLT

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

So...like?:

    const LLT PtrLLT = LLT::pointer(0, DL.getPointerSizeInBits(0));
    const MVT PtrVT = getMVTForLLT(PtrLLT);

    if (!HasSwiftSelfArg) {
      MFI->addParam(PtrVT);

Again, MFI->addParam is a Wasm specific extension to MachineFunctionInfo marking the Wasm-level signature of the function. We need an MVT. The only question is how we obtain it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I mean in the perfect future, the MFI would be in terms of LLT. As in, the LLT:MVT compatibility decisions would be made inside of MFI and would pass in LLT

@dschuff dschuff Mar 10, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not at all familiar with LLTs or how they should be used in GISel, but if most type logic in GISel should use LLTs, maybe we need to extend the infrastructure for handling wasm signatures to use LLTs too. Either reimplement it, or have some way to convert or reconcile them. Signatures are fairly fundamental to wasm, but the existing code that reasons about signatures was added after most of the backend was already there, so it may well the case that more code in the existing backend should handle them explicitly anyway.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Tricky...so now SelectionDAG (and FastISel?) needs to be reworked to accept LLT... I can look into it (unless you meant a separate overload?).

What's the reason for having MFI->addParam take in an LLT instead?

@arsenm arsenm Apr 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LLTs do now express float vs. int. Eventually all MVT usage should be replaced with LLT

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Oh! So even ISelDAG and FastISel should be moving towards LLT?

Wait... FINALLY! float vs int. Does that mean all those ugly heuristics to guess whether a select, PHI, load, etc. is floating or integer are no longer necessary?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

That probably won't happen. I more meant there should be GIsel native infrastructure instead of reusing the DAG pieces

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

And mostly yes. There's also still migration work to use the float typed LLTs (IIRC there's still a target hook to opt in for the moment)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Looks like support got reverted cause of build errors on old GCC :/

Reland hasn't been merged yet #188502

}
if (!HasSwiftErrorArg) {
Comment thread
QuantumSegfault marked this conversation as resolved.
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);
Comment thread
QuantumSegfault marked this conversation as resolved.
Outdated
auto PtrLLT = LLT::pointer(0, DL.getPointerSizeInBits(0));
Register VarargVreg = MF.getRegInfo().createGenericVirtualRegister(PtrLLT);

MFI->setVarargBufferVreg(VarargVreg);

auto ArgInst = MIRBuilder.buildInstr(getWASMArgumentOpcode(PtrVT))
Comment thread
QuantumSegfault marked this conversation as resolved.
Outdated
.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,
Expand Down
Loading