[llvm] [WebAssembly] Implement lowering calls through funcref to call_ref when available (PR #162227)
Demetrius Kanios via llvm-commits
llvm-commits at lists.llvm.org
Wed Oct 8 23:12:08 PDT 2025
https://github.com/QuantumSegfault updated https://github.com/llvm/llvm-project/pull/162227
>From aea6ad2dbec1cc8882be01bd55a9604669c4c743 Mon Sep 17 00:00:00 2001
From: Demetrius Kanios <demetrius at kanios.net>
Date: Mon, 6 Oct 2025 14:39:52 -0700
Subject: [PATCH 1/3] Call ref pre-tests
---
llvm/test/CodeGen/WebAssembly/call-ref.ll | 45 +++++++++++++++++++++++
1 file changed, 45 insertions(+)
create mode 100644 llvm/test/CodeGen/WebAssembly/call-ref.ll
diff --git a/llvm/test/CodeGen/WebAssembly/call-ref.ll b/llvm/test/CodeGen/WebAssembly/call-ref.ll
new file mode 100644
index 0000000000000..8c296010d07c6
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/call-ref.ll
@@ -0,0 +1,45 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc < %s -mattr=+reference-types,+gc | FileCheck %s
+
+; Test that calls through funcref lower to call_ref when GC is available
+
+target triple = "wasm32-unknown-unknown"
+
+%funcref = type ptr addrspace(20);
+
+define void @call_ref_void(%funcref %callee) {
+; CHECK-LABEL: call_ref_void:
+; CHECK: .functype call_ref_void (funcref) -> ()
+; CHECK-NEXT: # %bb.0:
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: table.set __funcref_call_table
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: call_indirect __funcref_call_table, () -> ()
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: ref.null_func
+; CHECK-NEXT: table.set __funcref_call_table
+; CHECK-NEXT: # fallthrough-return
+ call addrspace(20) void %callee()
+ ret void
+}
+
+define void @call_ref_with_args_and_ret(%funcref %callee) {
+; CHECK-LABEL: call_ref_with_args_and_ret:
+; CHECK: .functype call_ref_with_args_and_ret (funcref) -> ()
+; CHECK-NEXT: # %bb.0:
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: table.set __funcref_call_table
+; CHECK-NEXT: i32.const 1
+; CHECK-NEXT: f64.const 0x1p1
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: call_indirect __funcref_call_table, (i32, f64) -> (i32)
+; CHECK-NEXT: drop
+; CHECK-NEXT: i32.const 0
+; CHECK-NEXT: ref.null_func
+; CHECK-NEXT: table.set __funcref_call_table
+; CHECK-NEXT: # fallthrough-return
+ %result = call addrspace(20) i32 %callee(i32 1, double 2.0)
+ ret void
+}
>From 022e0b27c6dfb057157d131a2f607799221c0bb7 Mon Sep 17 00:00:00 2001
From: Demetrius Kanios <demetrius at kanios.net>
Date: Mon, 6 Oct 2025 21:53:58 -0700
Subject: [PATCH 2/3] Implement lowering of funcref calls to call_ref instead
of call_indirect
---
.../MCTargetDesc/WebAssemblyMCTargetDesc.h | 12 ++
.../WebAssembly/WebAssemblyISelDAGToDAG.cpp | 57 +------
.../WebAssembly/WebAssemblyISelLowering.cpp | 153 +++++++++++++++---
.../WebAssembly/WebAssemblyISelLowering.h | 5 +
.../WebAssembly/WebAssemblyInstrCall.td | 20 +++
.../Target/WebAssembly/WebAssemblyInstrRef.td | 5 +
.../WebAssembly/WebAssemblyMCInstLower.cpp | 16 +-
llvm/test/CodeGen/WebAssembly/call-ref.ll | 38 +++--
8 files changed, 203 insertions(+), 103 deletions(-)
diff --git a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h
index fe9a4bada2430..db4d9edb152ce 100644
--- a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h
+++ b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h
@@ -435,6 +435,18 @@ inline bool isCallIndirect(unsigned Opc) {
}
}
+inline bool isCallRef(unsigned Opc) {
+ switch (Opc) {
+ case WebAssembly::CALL_REF:
+ case WebAssembly::CALL_REF_S:
+ case WebAssembly::RET_CALL_REF:
+ case WebAssembly::RET_CALL_REF_S:
+ return true;
+ default:
+ return false;
+ }
+}
+
inline bool isBrTable(unsigned Opc) {
switch (Opc) {
case WebAssembly::BR_TABLE_I32:
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
index 2541b0433ab59..03c90c7160a68 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
@@ -120,60 +120,6 @@ static SDValue getTagSymNode(int Tag, SelectionDAG *DAG) {
return DAG->getTargetExternalSymbol(SymName, PtrVT);
}
-static APInt encodeFunctionSignature(SelectionDAG *DAG, SDLoc &DL,
- SmallVector<MVT, 4> &Returns,
- SmallVector<MVT, 4> &Params) {
- auto toWasmValType = [](MVT VT) {
- if (VT == MVT::i32) {
- return wasm::ValType::I32;
- }
- if (VT == MVT::i64) {
- return wasm::ValType::I64;
- }
- if (VT == MVT::f32) {
- return wasm::ValType::F32;
- }
- if (VT == MVT::f64) {
- return wasm::ValType::F64;
- }
- if (VT == MVT::externref) {
- return wasm::ValType::EXTERNREF;
- }
- if (VT == MVT::funcref) {
- return wasm::ValType::FUNCREF;
- }
- if (VT == MVT::exnref) {
- return wasm::ValType::EXNREF;
- }
- LLVM_DEBUG(errs() << "Unhandled type for llvm.wasm.ref.test.func: " << VT
- << "\n");
- llvm_unreachable("Unhandled type for llvm.wasm.ref.test.func");
- };
- auto NParams = Params.size();
- auto NReturns = Returns.size();
- auto BitWidth = (NParams + NReturns + 2) * 64;
- auto Sig = APInt(BitWidth, 0);
-
- // Annoying special case: if getSignificantBits() <= 64 then InstrEmitter will
- // emit an Imm instead of a CImm. It simplifies WebAssemblyMCInstLower if we
- // always emit a CImm. So xor NParams with 0x7ffffff to ensure
- // getSignificantBits() > 64
- Sig |= NReturns ^ 0x7ffffff;
- for (auto &Return : Returns) {
- auto V = toWasmValType(Return);
- Sig <<= 64;
- Sig |= (int64_t)V;
- }
- Sig <<= 64;
- Sig |= NParams;
- for (auto &Param : Params) {
- auto V = toWasmValType(Param);
- Sig <<= 64;
- Sig |= (int64_t)V;
- }
- return Sig;
-}
-
void WebAssemblyDAGToDAGISel::Select(SDNode *Node) {
// If we have a custom node, we already have selected!
if (Node->isMachineOpcode()) {
@@ -288,7 +234,8 @@ void WebAssemblyDAGToDAGISel::Select(SDNode *Node) {
Returns.push_back(VT);
}
}
- auto Sig = encodeFunctionSignature(CurDAG, DL, Returns, Params);
+ auto Sig =
+ WebAssembly::encodeFunctionSignature(CurDAG, DL, Returns, Params);
auto SigOp = CurDAG->getTargetConstant(
Sig, DL, EVT::getIntegerVT(*CurDAG->getContext(), Sig.getBitWidth()));
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
index 163bf9ba5b089..bd0733c73f7ed 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
@@ -723,6 +723,7 @@ LowerCallResults(MachineInstr &CallResults, DebugLoc DL, MachineBasicBlock *BB,
bool IsIndirect =
CallParams.getOperand(0).isReg() || CallParams.getOperand(0).isFI();
bool IsRetCall = CallResults.getOpcode() == WebAssembly::RET_CALL_RESULTS;
+ bool IsCallRef = false;
bool IsFuncrefCall = false;
if (IsIndirect && CallParams.getOperand(0).isReg()) {
@@ -732,10 +733,19 @@ LowerCallResults(MachineInstr &CallResults, DebugLoc DL, MachineBasicBlock *BB,
const TargetRegisterClass *TRC = MRI.getRegClass(Reg);
IsFuncrefCall = (TRC == &WebAssembly::FUNCREFRegClass);
assert(!IsFuncrefCall || Subtarget->hasReferenceTypes());
+
+ if (IsFuncrefCall && Subtarget->hasGC()) {
+ IsIndirect = false;
+ IsCallRef = true;
+ }
}
unsigned CallOp;
- if (IsIndirect && IsRetCall) {
+ if (IsCallRef && IsRetCall) {
+ CallOp = WebAssembly::RET_CALL_REF;
+ } else if (IsCallRef) {
+ CallOp = WebAssembly::CALL_REF;
+ } else if (IsIndirect && IsRetCall) {
CallOp = WebAssembly::RET_CALL_INDIRECT;
} else if (IsIndirect) {
CallOp = WebAssembly::CALL_INDIRECT;
@@ -771,6 +781,14 @@ LowerCallResults(MachineInstr &CallResults, DebugLoc DL, MachineBasicBlock *BB,
CallParams.addOperand(FnPtr);
}
+ // Move the function pointer to the end of the arguments for funcref calls
+ if (IsCallRef) {
+ auto FnRef = CallParams.getOperand(0);
+ CallParams.removeOperand(0);
+
+ CallParams.addOperand(FnRef);
+ }
+
for (auto Def : CallResults.defs())
MIB.add(Def);
@@ -795,6 +813,12 @@ LowerCallResults(MachineInstr &CallResults, DebugLoc DL, MachineBasicBlock *BB,
}
}
+ if (IsCallRef) {
+ // Placeholder for the type index.
+ // This gets replaced with the correct value in WebAssemblyMCInstLower.cpp
+ MIB.addImm(0);
+ }
+
for (auto Use : CallParams.uses())
MIB.add(Use);
@@ -1173,6 +1197,60 @@ static bool callingConvSupported(CallingConv::ID CallConv) {
CallConv == CallingConv::Swift;
}
+APInt WebAssembly::encodeFunctionSignature(SelectionDAG *DAG, SDLoc &DL,
+ SmallVector<MVT, 4> &Returns,
+ SmallVector<MVT, 4> &Params) {
+ auto toWasmValType = [](MVT VT) {
+ if (VT == MVT::i32) {
+ return wasm::ValType::I32;
+ }
+ if (VT == MVT::i64) {
+ return wasm::ValType::I64;
+ }
+ if (VT == MVT::f32) {
+ return wasm::ValType::F32;
+ }
+ if (VT == MVT::f64) {
+ return wasm::ValType::F64;
+ }
+ if (VT == MVT::externref) {
+ return wasm::ValType::EXTERNREF;
+ }
+ if (VT == MVT::funcref) {
+ return wasm::ValType::FUNCREF;
+ }
+ if (VT == MVT::exnref) {
+ return wasm::ValType::EXNREF;
+ }
+ LLVM_DEBUG(errs() << "Unhandled type for llvm.wasm.ref.test.func: " << VT
+ << "\n");
+ llvm_unreachable("Unhandled type for llvm.wasm.ref.test.func");
+ };
+ auto NParams = Params.size();
+ auto NReturns = Returns.size();
+ auto BitWidth = (NParams + NReturns + 2) * 64;
+ auto Sig = APInt(BitWidth, 0);
+
+ // Annoying special case: if getSignificantBits() <= 64 then InstrEmitter will
+ // emit an Imm instead of a CImm. It simplifies WebAssemblyMCInstLower if we
+ // always emit a CImm. So xor NParams with 0x7ffffff to ensure
+ // getSignificantBits() > 64
+ Sig |= NReturns ^ 0x7ffffff;
+ for (auto &Return : Returns) {
+ auto V = toWasmValType(Return);
+ Sig <<= 64;
+ Sig |= (int64_t)V;
+ }
+ Sig <<= 64;
+ Sig |= NParams;
+ for (auto &Param : Params) {
+ auto V = toWasmValType(Param);
+ Sig <<= 64;
+ Sig |= (int64_t)V;
+ }
+ return Sig;
+}
+
SDValue
WebAssemblyTargetLowering::LowerCall(CallLoweringInfo &CLI,
SmallVectorImpl<SDValue> &InVals) const {
@@ -1412,33 +1490,58 @@ WebAssemblyTargetLowering::LowerCall(CallLoweringInfo &CLI,
InTys.push_back(In.VT);
}
- // Lastly, if this is a call to a funcref we need to add an instruction
- // table.set to the chain and transform the call.
+ // Lastly, if this is a call to a funcref we need to insert an instruction
+ // to either cast the funcref to a typed funcref for call_ref, or place it
+ // into a table for call_indirect
if (CLI.CB && WebAssembly::isWebAssemblyFuncrefType(
CLI.CB->getCalledOperand()->getType())) {
- // In the absence of function references proposal where a funcref call is
- // lowered to call_ref, using reference types we generate a table.set to set
- // the funcref to a special table used solely for this purpose, followed by
- // a call_indirect. Here we just generate the table set, and return the
- // SDValue of the table.set so that LowerCall can finalize the lowering by
- // generating the call_indirect.
- SDValue Chain = Ops[0];
+ if (Subtarget->hasGC()) {
+ // Since LLVM doesn't directly support typed function references, we take
+ // the untyped funcref and ref.cast it into a typed funcref.
+ SmallVector<MVT, 4> Params;
+ SmallVector<MVT, 4> Returns;
+
+ for (const auto &Out : Outs) {
+ Params.push_back(Out.VT);
+ }
+ for (const auto &In : Ins) {
+ Returns.push_back(In.VT);
+ }
- MCSymbolWasm *Table = WebAssembly::getOrCreateFuncrefCallTableSymbol(
- MF.getContext(), Subtarget);
- SDValue Sym = DAG.getMCSymbol(Table, PtrVT);
- SDValue TableSlot = DAG.getConstant(0, DL, MVT::i32);
- SDValue TableSetOps[] = {Chain, Sym, TableSlot, Callee};
- SDValue TableSet = DAG.getMemIntrinsicNode(
- WebAssemblyISD::TABLE_SET, DL, DAG.getVTList(MVT::Other), TableSetOps,
- MVT::funcref,
- // Machine Mem Operand args
- MachinePointerInfo(
- WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_FUNCREF),
- CLI.CB->getCalledOperand()->getPointerAlignment(DAG.getDataLayout()),
- MachineMemOperand::MOStore);
-
- Ops[0] = TableSet; // The new chain is the TableSet itself
+ auto Sig =
+ WebAssembly::encodeFunctionSignature(&DAG, DL, Returns, Params);
+
+ auto SigOp = DAG.getTargetConstant(
+ Sig, DL, EVT::getIntegerVT(*DAG.getContext(), Sig.getBitWidth()));
+ MachineSDNode *RefCastNode = DAG.getMachineNode(
+ WebAssembly::REF_CAST_FUNCREF, DL, MVT::funcref, {SigOp, Callee});
+
+ Ops[1] = SDValue(RefCastNode, 0);
+ } else {
+ // In the absence of function references proposal where a funcref call is
+ // lowered to call_ref, using reference types we generate a table.set to
+ // set the funcref to a special table used solely for this purpose,
+ // followed by a call_indirect. Here we just generate the table set, and
+ // return the SDValue of the table.set so that LowerCall can finalize the
+ // lowering by generating the call_indirect.
+ SDValue Chain = Ops[0];
+
+ MCSymbolWasm *Table = WebAssembly::getOrCreateFuncrefCallTableSymbol(
+ MF.getContext(), Subtarget);
+ SDValue Sym = DAG.getMCSymbol(Table, PtrVT);
+ SDValue TableSlot = DAG.getConstant(0, DL, MVT::i32);
+ SDValue TableSetOps[] = {Chain, Sym, TableSlot, Callee};
+ SDValue TableSet = DAG.getMemIntrinsicNode(
+ WebAssemblyISD::TABLE_SET, DL, DAG.getVTList(MVT::Other), TableSetOps,
+ MVT::funcref,
+ // Machine Mem Operand args
+ MachinePointerInfo(
+ WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_FUNCREF),
+ CLI.CB->getCalledOperand()->getPointerAlignment(DAG.getDataLayout()),
+ MachineMemOperand::MOStore);
+
+ Ops[0] = TableSet; // The new chain is the TableSet itself
+ }
}
if (CLI.IsTailCall) {
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.h b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.h
index b33a8530310be..7d2194132f293 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.h
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.h
@@ -141,6 +141,11 @@ class WebAssemblyTargetLowering final : public TargetLowering {
namespace WebAssembly {
FastISel *createFastISel(FunctionLoweringInfo &funcInfo,
const TargetLibraryInfo *libInfo);
+
+APInt encodeFunctionSignature(SelectionDAG *DAG, SDLoc &DL,
+ SmallVector<MVT, 4> &Returns,
+ SmallVector<MVT, 4> &Params);
+
} // end namespace WebAssembly
} // end namespace llvm
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyInstrCall.td b/llvm/lib/Target/WebAssembly/WebAssemblyInstrCall.td
index ca9a5ef9dda1c..81b62f6a682ec 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyInstrCall.td
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyInstrCall.td
@@ -66,6 +66,16 @@ defm CALL_INDIRECT :
[],
"call_indirect", "call_indirect\t$type, $table", 0x11>;
+let variadicOpsAreDefs = 1 in
+defm CALL_REF :
+ I<(outs),
+ (ins TypeIndex:$type, variable_ops),
+ (outs),
+ (ins TypeIndex:$type),
+ [],
+ "call_ref", "call_ref\t$type", 0x14>,
+ Requires<[HasGC]>;
+
let isReturn = 1, isTerminator = 1, hasCtrlDep = 1, isBarrier = 1 in
defm RET_CALL :
I<(outs), (ins function32_op:$callee, variable_ops),
@@ -81,4 +91,14 @@ defm RET_CALL_INDIRECT :
0x13>,
Requires<[HasTailCall]>;
+let isReturn = 1, isTerminator = 1, hasCtrlDep = 1, isBarrier = 1 in
+defm RET_CALL_REF :
+ I<(outs),
+ (ins TypeIndex:$type, variable_ops),
+ (outs),
+ (ins TypeIndex:$type),
+ [],
+ "return_call_ref", "return_call_ref\t$type", 0x15>,
+ Requires<[HasTailCall, HasGC]>;
+
} // Uses = [SP32,SP64], isCall = 1
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td b/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td
index fc82e5b4a61da..6fa6ed897d647 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td
@@ -41,6 +41,11 @@ defm REF_TEST_FUNCREF : I<(outs I32:$res), (ins TypeIndex:$type, FUNCREF:$ref),
"ref.test\t$type, $ref", "ref.test $type", 0xfb14>,
Requires<[HasGC]>;
+defm REF_CAST_FUNCREF : I<(outs FUNCREF:$res), (ins TypeIndex:$type, FUNCREF:$ref),
+ (outs), (ins TypeIndex:$type), [],
+ "ref.cast\t$type, $ref", "ref.cast $type", 0xfb16>,
+ Requires<[HasGC]>;
+
defm "" : REF_I<FUNCREF, funcref, "func">;
defm "" : REF_I<EXTERNREF, externref, "extern">;
defm "" : REF_I<EXNREF, exnref, "exn">;
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp
index e48283aadb437..1ed15967c01fe 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp
@@ -230,7 +230,7 @@ void WebAssemblyMCInstLower::lower(const MachineInstr *MI,
break;
}
case llvm::MachineOperand::MO_CImmediate: {
- // Lower type index placeholder for ref.test
+ // Lower type index placeholder for ref.test and ref.cast
// Currently this is the only way that CImmediates show up so panic if we
// get confused.
unsigned DescIndex = I - NumVariadicDefs;
@@ -266,14 +266,16 @@ void WebAssemblyMCInstLower::lower(const MachineInstr *MI,
Params.push_back(WebAssembly::regClassToValType(
MRI.getRegClass(MO.getReg())->getID()));
- // call_indirect instructions have a callee operand at the end which
- // doesn't count as a param.
- if (WebAssembly::isCallIndirect(MI->getOpcode()))
+ // call_indirect and call_ref instructions have a callee operand at
+ // the end which doesn't count as a param.
+ if (WebAssembly::isCallIndirect(MI->getOpcode()) ||
+ WebAssembly::isCallRef(MI->getOpcode()))
Params.pop_back();
- // return_call_indirect instructions have the return type of the
- // caller
- if (MI->getOpcode() == WebAssembly::RET_CALL_INDIRECT)
+ // return_call_indirect and return_call_ref instructions have the
+ // return type of the caller
+ if (MI->getOpcode() == WebAssembly::RET_CALL_INDIRECT ||
+ MI->getOpcode() == WebAssembly::RET_CALL_REF)
getFunctionReturns(MI, Returns);
MCOp = lowerTypeIndexOperand(std::move(Returns), std::move(Params));
diff --git a/llvm/test/CodeGen/WebAssembly/call-ref.ll b/llvm/test/CodeGen/WebAssembly/call-ref.ll
index 8c296010d07c6..25fc7440ac64c 100644
--- a/llvm/test/CodeGen/WebAssembly/call-ref.ll
+++ b/llvm/test/CodeGen/WebAssembly/call-ref.ll
@@ -1,5 +1,6 @@
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
-; RUN: llc < %s -mattr=+reference-types,+gc | FileCheck %s
+; RUN: llc < %s -mattr=+reference-types,-gc | FileCheck --check-prefixes=CHECK,NOGC %s
+; RUN: llc < %s -mattr=+reference-types,+gc | FileCheck --check-prefixes=CHECK,GC %s
; Test that calls through funcref lower to call_ref when GC is available
@@ -11,14 +12,16 @@ define void @call_ref_void(%funcref %callee) {
; CHECK-LABEL: call_ref_void:
; CHECK: .functype call_ref_void (funcref) -> ()
; CHECK-NEXT: # %bb.0:
-; CHECK-NEXT: i32.const 0
+; NOGC-NEXT: i32.const 0
; CHECK-NEXT: local.get 0
-; CHECK-NEXT: table.set __funcref_call_table
-; CHECK-NEXT: i32.const 0
-; CHECK-NEXT: call_indirect __funcref_call_table, () -> ()
-; CHECK-NEXT: i32.const 0
-; CHECK-NEXT: ref.null_func
-; CHECK-NEXT: table.set __funcref_call_table
+; NOGC-NEXT: table.set __funcref_call_table
+; NOGC-NEXT: i32.const 0
+; NOGC-NEXT: call_indirect __funcref_call_table, () -> ()
+; NOGC-NEXT: i32.const 0
+; NOGC-NEXT: ref.null_func
+; NOGC-NEXT: table.set __funcref_call_table
+; GC-NEXT: ref.cast () -> ()
+; GC-NEXT: call_ref () -> ()
; CHECK-NEXT: # fallthrough-return
call addrspace(20) void %callee()
ret void
@@ -28,17 +31,20 @@ define void @call_ref_with_args_and_ret(%funcref %callee) {
; CHECK-LABEL: call_ref_with_args_and_ret:
; CHECK: .functype call_ref_with_args_and_ret (funcref) -> ()
; CHECK-NEXT: # %bb.0:
-; CHECK-NEXT: i32.const 0
-; CHECK-NEXT: local.get 0
-; CHECK-NEXT: table.set __funcref_call_table
+; NOGC-NEXT: i32.const 0
+; NOGC-NEXT: local.get 0
+; NOGC-NEXT: table.set __funcref_call_table
; CHECK-NEXT: i32.const 1
; CHECK-NEXT: f64.const 0x1p1
-; CHECK-NEXT: i32.const 0
-; CHECK-NEXT: call_indirect __funcref_call_table, (i32, f64) -> (i32)
+; NOGC-NEXT: i32.const 0
+; NOGC-NEXT: call_indirect __funcref_call_table, (i32, f64) -> (i32)
+; GC-NEXT: local.get 0
+; GC-NEXT: ref.cast (i32, f64) -> (i32)
+; GC-NEXT: call_ref (i32, f64) -> (i32)
; CHECK-NEXT: drop
-; CHECK-NEXT: i32.const 0
-; CHECK-NEXT: ref.null_func
-; CHECK-NEXT: table.set __funcref_call_table
+; NOGC-NEXT: i32.const 0
+; NOGC-NEXT: ref.null_func
+; NOGC-NEXT: table.set __funcref_call_table
; CHECK-NEXT: # fallthrough-return
%result = call addrspace(20) i32 %callee(i32 1, double 2.0)
ret void
>From eec2961aee2865af4c8cae73b0aa8a93e3b07f96 Mon Sep 17 00:00:00 2001
From: Demetrius Kanios <demetrius at kanios.net>
Date: Wed, 8 Oct 2025 23:11:58 -0700
Subject: [PATCH 3/3] Add ref.cast AsmParser support and test. Change
SmallVector params to ArrayRef
---
.../AsmParser/WebAssemblyAsmParser.cpp | 4 ++++
.../WebAssembly/WebAssemblyISelLowering.cpp | 4 ++--
.../WebAssembly/WebAssemblyISelLowering.h | 4 ++--
llvm/test/MC/WebAssembly/reference-types.s | 19 +++++++++++++++++++
4 files changed, 27 insertions(+), 4 deletions(-)
diff --git a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
index 45bbf128ce0b7..2b3b6e74e8aa2 100644
--- a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
+++ b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
@@ -672,6 +672,10 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
// When we get support for wasm-gc types, this should become
// ExpectRefType.
ExpectFuncType = true;
+ } else if (Name == "ref.cast") {
+ // When we get support for wasm-gc types, this should become
+ // ExpectRefType.
+ ExpectFuncType = true;
}
// Returns true if the next tokens are a catch clause
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
index bd0733c73f7ed..ab7e48c54e53f 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
@@ -1198,8 +1198,8 @@ static bool callingConvSupported(CallingConv::ID CallConv) {
}
APInt WebAssembly::encodeFunctionSignature(SelectionDAG *DAG, SDLoc &DL,
- SmallVector<MVT, 4> &Returns,
- SmallVector<MVT, 4> &Params) {
+ ArrayRef<MVT> Returns,
+ ArrayRef<MVT> Params) {
auto toWasmValType = [](MVT VT) {
if (VT == MVT::i32) {
return wasm::ValType::I32;
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.h b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.h
index 7d2194132f293..61ccaec49b8d0 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.h
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.h
@@ -143,8 +143,8 @@ FastISel *createFastISel(FunctionLoweringInfo &funcInfo,
const TargetLibraryInfo *libInfo);
APInt encodeFunctionSignature(SelectionDAG *DAG, SDLoc &DL,
- SmallVector<MVT, 4> &Returns,
- SmallVector<MVT, 4> &Params);
+ ArrayRef<MVT> Returns,
+ ArrayRef<MVT> Params);
} // end namespace WebAssembly
diff --git a/llvm/test/MC/WebAssembly/reference-types.s b/llvm/test/MC/WebAssembly/reference-types.s
index 7a838fc519493..89d5058858192 100644
--- a/llvm/test/MC/WebAssembly/reference-types.s
+++ b/llvm/test/MC/WebAssembly/reference-types.s
@@ -42,6 +42,25 @@ ref_test_test:
ref.test () -> (i32)
end_function
+ # CHECK-LABEL: ref_cast_test:
+ # CHECK: ref.null_func # encoding: [0xd0,0x70]
+ # CHECK: ref.cast () -> () # encoding: [0xfb,0x16,0x80'A',0x80'A',0x80'A',0x80'A',A]
+ # CHECK: # fixup A - offset: 2, value: .Ltypeindex2 at TYPEINDEX, kind: fixup_uleb128_i32
+ # CHECK: drop # encoding: [0x1a]
+ # CHECK: ref.null_func # encoding: [0xd0,0x70]
+ # CHECK: ref.cast () -> (i32) # encoding: [0xfb,0x16,0x80'A',0x80'A',0x80'A',0x80'A',A]
+ # CHECK: # fixup A - offset: 2, value: .Ltypeindex3 at TYPEINDEX, kind: fixup_uleb128_i32
+ # CHECK: drop # encoding: [0x1a]
+ ref_cast_test:
+ .functype ref_cast_test () -> ()
+ ref.null_func
+ ref.cast () -> ()
+ drop
+ ref.null_func
+ ref.cast () -> (i32)
+ drop
+ end_function
+
# CHECK-LABEL: ref_sig_test_funcref:
# CHECK-NEXT: .functype ref_sig_test_funcref (funcref) -> (funcref)
ref_sig_test_funcref:
More information about the llvm-commits
mailing list