[llvm] [WebAssembly] Implement addrspacecast to funcref (PR #166820)

Demetrius Kanios via llvm-commits llvm-commits at lists.llvm.org
Thu Dec 4 09:53:07 PST 2025


https://github.com/QuantumSegfault updated https://github.com/llvm/llvm-project/pull/166820

>From 237533c393f3668d3129415bf8f1376af8759441 Mon Sep 17 00:00:00 2001
From: Demetrius Kanios <demetrius at kanios.net>
Date: Thu, 6 Nov 2025 10:28:45 -0800
Subject: [PATCH 1/3] Implement addrspacecast 0 -> 20

---
 .../WebAssembly/WebAssemblyISelLowering.cpp   | 58 +++++++++++++++++++
 .../WebAssembly/WebAssemblyISelLowering.h     |  1 +
 .../Target/WebAssembly/WebAssemblyInstrRef.td |  7 ++-
 .../WebAssembly/addrspacecast-funcref.ll      | 55 ++++++++++++++++++
 4 files changed, 120 insertions(+), 1 deletion(-)
 create mode 100644 llvm/test/CodeGen/WebAssembly/addrspacecast-funcref.ll

diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
index af322982d5355..782b878350b42 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
@@ -409,6 +409,10 @@ WebAssemblyTargetLowering::WebAssemblyTargetLowering(
   setOperationAction(ISD::INTRINSIC_W_CHAIN, MVT::Other, Custom);
   setOperationAction(ISD::INTRINSIC_VOID, MVT::Other, Custom);
 
+  // Allow converting function ptrs in address space 0 to WASM funcref (address
+  // space 20)
+  setOperationAction(ISD::ADDRSPACECAST, MVT::funcref, Custom);
+
   setMaxAtomicSizeInBitsSupported(64);
 
   // Always convert switches to br_tables unless there is only one case, which
@@ -1733,6 +1737,8 @@ SDValue WebAssemblyTargetLowering::LowerOperation(SDValue Op,
     return LowerMUL_LOHI(Op, DAG);
   case ISD::UADDO:
     return LowerUADDO(Op, DAG);
+  case ISD::ADDRSPACECAST:
+    return LowerADDRSPACECAST(Op, DAG);
   }
 }
 
@@ -1876,6 +1882,58 @@ SDValue WebAssemblyTargetLowering::LowerUADDO(SDValue Op,
   return DAG.getMergeValues(Ops, DL);
 }
 
+SDValue WebAssemblyTargetLowering::LowerADDRSPACECAST(SDValue Op,
+                                                      SelectionDAG &DAG) const {
+  SDLoc DL(Op);
+
+  AddrSpaceCastSDNode *ACN = cast<AddrSpaceCastSDNode>(Op.getNode());
+
+  if (ACN->getSrcAddressSpace() !=
+          WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_DEFAULT ||
+      ACN->getDestAddressSpace() !=
+          WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_FUNCREF)
+    return Op;
+
+  if (ACN->getValueType(0) != MVT::funcref) {
+    reportFatalInternalError("Cannot addrspacecast to funcref addrspace with "
+                             "results other than MVT::funcref");
+  }
+
+  SDValue Src = ACN->getOperand(0);
+
+  // Lower addrspacecasts of direct/constant function ptrs to ref.func
+  if (auto *GA = dyn_cast<GlobalAddressSDNode>(
+          Src->getOpcode() == WebAssemblyISD::Wrapper ? Src->getOperand(0)
+                                                      : Src)) {
+    auto *GV = GA->getGlobal();
+
+    if (const Function *F = dyn_cast<Function>(GV)) {
+      SDValue FnAddress = DAG.getTargetGlobalAddress(F, DL, MVT::i32);
+
+      SDValue RefFuncNode =
+          DAG.getNode(WebAssemblyISD::REF_FUNC, DL, MVT::funcref, FnAddress);
+      return RefFuncNode;
+    }
+  }
+
+  // Lower everything else to a table.get from the indirect function table
+  const MachineFunction &MF = DAG.getMachineFunction();
+
+  MVT PtrVT = getPointerTy(MF.getDataLayout());
+
+  MCSymbolWasm *Table =
+      WebAssembly::getOrCreateFunctionTableSymbol(MF.getContext(), Subtarget);
+  SDValue TableSym = DAG.getMCSymbol(Table, PtrVT);
+
+  SDValue TableSlot = Op.getOperand(0);
+
+  SDValue Result(DAG.getMachineNode(WebAssembly::TABLE_GET_FUNCREF, DL,
+                                    MVT::funcref, TableSym, TableSlot),
+                 0);
+
+  return Result;
+}
+
 SDValue WebAssemblyTargetLowering::Replace128Op(SDNode *N,
                                                 SelectionDAG &DAG) const {
   assert(Subtarget->hasWideArithmetic());
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.h b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.h
index f7052989b3c75..c3cca072f1958 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.h
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.h
@@ -121,6 +121,7 @@ class WebAssemblyTargetLowering final : public TargetLowering {
   SDValue LowerMUL_LOHI(SDValue Op, SelectionDAG &DAG) const;
   SDValue Replace128Op(SDNode *N, SelectionDAG &DAG) const;
   SDValue LowerUADDO(SDValue Op, SelectionDAG &DAG) const;
+  SDValue LowerADDRSPACECAST(SDValue Op, SelectionDAG &DAG) const;
 
   // Custom DAG combine hooks
   SDValue
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td b/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td
index 304c4f3fcb028..2589ab758638c 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td
@@ -11,6 +11,11 @@
 ///
 //===----------------------------------------------------------------------===//
 
+def WebAssemblyRefFunc_t : SDTypeProfile<1, 1, [SDTCisVT<0, funcref>, SDTCisPtrTy<1>]>;
+def WebAssemblyRefFunc :
+    SDNode<"WebAssemblyISD::REF_FUNC", WebAssemblyRefFunc_t,
+           []>;
+
 multiclass REF_I<WebAssemblyRegClass rc, ValueType vt, string ht> {
   defm REF_NULL_#rc : I<(outs rc:$dst), (ins),
                         (outs), (ins),
@@ -42,7 +47,7 @@ defm REF_TEST_FUNCREF : I<(outs I32:$res), (ins TypeIndex:$type, FUNCREF:$ref),
                         Requires<[HasGC]>;
 
 defm REF_FUNC : I<(outs FUNCREF:$res), (ins function32_op:$func),
-                    (outs), (ins function32_op:$func), [],
+                    (outs), (ins function32_op:$func), [(set FUNCREF:$res, (WebAssemblyRefFunc tglobaladdr:$func))],
                     "ref.func\t$func", "ref.func $func", 0xd2>,
                 Requires<[HasReferenceTypes]>;
 
diff --git a/llvm/test/CodeGen/WebAssembly/addrspacecast-funcref.ll b/llvm/test/CodeGen/WebAssembly/addrspacecast-funcref.ll
new file mode 100644
index 0000000000000..1ae676f1c99c8
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/addrspacecast-funcref.ll
@@ -0,0 +1,55 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -mtriple=wasm32-unknown-unknown -mattr=+reference-types < %s | FileCheck -check-prefixes=CHECK,WASM32 %s
+; RUN: llc -mtriple=wasm64-unknown-unknown -mattr=+reference-types < %s | FileCheck -check-prefixes=CHECK,WASM64 %s
+
+%funcref = type ptr addrspace(20) ;; addrspace 20 is nonintegral
+
+declare void @foo();
+
+ at global_var = local_unnamed_addr global i32 undef
+
+define %funcref @cast_const_funcptr() {
+; CHECK-LABEL: cast_const_funcptr:
+; CHECK:         .functype cast_const_funcptr () -> (funcref)
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    ref.func foo
+; CHECK-NEXT:    # fallthrough-return
+  %result = addrspacecast ptr @foo to ptr addrspace(20)
+  ret %funcref %result
+}
+
+define %funcref @cast_const_not_funcptr() {
+; WASM32-LABEL: cast_const_not_funcptr:
+; WASM32:         .functype cast_const_not_funcptr () -> (funcref)
+; WASM32-NEXT:  # %bb.0:
+; WASM32-NEXT:    i32.const global_var
+; WASM32-NEXT:    table.get __indirect_function_table
+; WASM32-NEXT:    # fallthrough-return
+;
+; WASM64-LABEL: cast_const_not_funcptr:
+; WASM64:         .functype cast_const_not_funcptr () -> (funcref)
+; WASM64-NEXT:  # %bb.0:
+; WASM64-NEXT:    i64.const global_var
+; WASM64-NEXT:    table.get __indirect_function_table
+; WASM64-NEXT:    # fallthrough-return
+  %result = addrspacecast ptr @global_var to ptr addrspace(20)
+  ret %funcref %result
+}
+
+define %funcref @cast_param_funcptr(ptr %funcptr) {
+; WASM32-LABEL: cast_param_funcptr:
+; WASM32:         .functype cast_param_funcptr (i32) -> (funcref)
+; WASM32-NEXT:  # %bb.0:
+; WASM32-NEXT:    local.get 0
+; WASM32-NEXT:    table.get __indirect_function_table
+; WASM32-NEXT:    # fallthrough-return
+;
+; WASM64-LABEL: cast_param_funcptr:
+; WASM64:         .functype cast_param_funcptr (i64) -> (funcref)
+; WASM64-NEXT:  # %bb.0:
+; WASM64-NEXT:    local.get 0
+; WASM64-NEXT:    table.get __indirect_function_table
+; WASM64-NEXT:    # fallthrough-return
+  %result = addrspacecast ptr %funcptr to ptr addrspace(20)
+  ret %funcref %result
+}

>From b8d4767ee3b4a20c8d361e120a44b44a9fc820e6 Mon Sep 17 00:00:00 2001
From: Demetrius Kanios <demetrius at kanios.net>
Date: Thu, 6 Nov 2025 10:37:38 -0800
Subject: [PATCH 2/3] Replace undef in test

---
 llvm/test/CodeGen/WebAssembly/addrspacecast-funcref.ll | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/test/CodeGen/WebAssembly/addrspacecast-funcref.ll b/llvm/test/CodeGen/WebAssembly/addrspacecast-funcref.ll
index 1ae676f1c99c8..a7dfdd08d9030 100644
--- a/llvm/test/CodeGen/WebAssembly/addrspacecast-funcref.ll
+++ b/llvm/test/CodeGen/WebAssembly/addrspacecast-funcref.ll
@@ -6,7 +6,7 @@
 
 declare void @foo();
 
- at global_var = local_unnamed_addr global i32 undef
+ at global_var = local_unnamed_addr global i32 0
 
 define %funcref @cast_const_funcptr() {
 ; CHECK-LABEL: cast_const_funcptr:

>From ae52e7a149b18c0e8923a70c150f85aa74619c9f Mon Sep 17 00:00:00 2001
From: Demetrius Kanios <demetrius at kanios.net>
Date: Thu, 4 Dec 2025 09:52:56 -0800
Subject: [PATCH 3/3] Minor touchups

---
 llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp | 4 ++--
 llvm/test/CodeGen/WebAssembly/addrspacecast-funcref.ll  | 6 +++---
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
index 782b878350b42..d30b54535c4b0 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
@@ -409,7 +409,7 @@ WebAssemblyTargetLowering::WebAssemblyTargetLowering(
   setOperationAction(ISD::INTRINSIC_W_CHAIN, MVT::Other, Custom);
   setOperationAction(ISD::INTRINSIC_VOID, MVT::Other, Custom);
 
-  // Allow converting function ptrs in address space 0 to WASM funcref (address
+  // Allow converting function ptrs in address space 0 to Wasm funcref (address
   // space 20)
   setOperationAction(ISD::ADDRSPACECAST, MVT::funcref, Custom);
 
@@ -1892,7 +1892,7 @@ SDValue WebAssemblyTargetLowering::LowerADDRSPACECAST(SDValue Op,
           WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_DEFAULT ||
       ACN->getDestAddressSpace() !=
           WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_FUNCREF)
-    return Op;
+    return SDValue();
 
   if (ACN->getValueType(0) != MVT::funcref) {
     reportFatalInternalError("Cannot addrspacecast to funcref addrspace with "
diff --git a/llvm/test/CodeGen/WebAssembly/addrspacecast-funcref.ll b/llvm/test/CodeGen/WebAssembly/addrspacecast-funcref.ll
index a7dfdd08d9030..f0a5989a4c811 100644
--- a/llvm/test/CodeGen/WebAssembly/addrspacecast-funcref.ll
+++ b/llvm/test/CodeGen/WebAssembly/addrspacecast-funcref.ll
@@ -14,7 +14,7 @@ define %funcref @cast_const_funcptr() {
 ; CHECK-NEXT:  # %bb.0:
 ; CHECK-NEXT:    ref.func foo
 ; CHECK-NEXT:    # fallthrough-return
-  %result = addrspacecast ptr @foo to ptr addrspace(20)
+  %result = addrspacecast ptr @foo to %funcref
   ret %funcref %result
 }
 
@@ -32,7 +32,7 @@ define %funcref @cast_const_not_funcptr() {
 ; WASM64-NEXT:    i64.const global_var
 ; WASM64-NEXT:    table.get __indirect_function_table
 ; WASM64-NEXT:    # fallthrough-return
-  %result = addrspacecast ptr @global_var to ptr addrspace(20)
+  %result = addrspacecast ptr @global_var to %funcref
   ret %funcref %result
 }
 
@@ -50,6 +50,6 @@ define %funcref @cast_param_funcptr(ptr %funcptr) {
 ; WASM64-NEXT:    local.get 0
 ; WASM64-NEXT:    table.get __indirect_function_table
 ; WASM64-NEXT:    # fallthrough-return
-  %result = addrspacecast ptr %funcptr to ptr addrspace(20)
+  %result = addrspacecast ptr %funcptr to %funcref
   ret %funcref %result
 }



More information about the llvm-commits mailing list