[clang] [llvm] [WebAssembly] Represent reference types as TargetExtType (PR #203165)
Hood Chatham via cfe-commits
cfe-commits at lists.llvm.org
Thu Jun 11 12:08:39 PDT 2026
https://github.com/hoodmane updated https://github.com/llvm/llvm-project/pull/203165
>From 475bcb6fe19776b6a724648d16f29ebe3250f255 Mon Sep 17 00:00:00 2001
From: Hood Chatham <roberthoodchatham at gmail.com>
Date: Wed, 10 Jun 2026 20:36:28 -0700
Subject: [PATCH 1/9] [WebAssembly] Represent reference types as TargetExtType
Originally #71540 by Paolo Matos, I picked it up and finished it.
Model WebAssembly externref and funcref as target("wasm.externref") /
target("wasm.funcref") TargetExtTypes instead of pointers in non-integral
address spaces 10 and 20.
The entire WebAssemblyLowerRefTypesIntPtrConv can be removed.
This breaks the GlobalISel handling for reference types, I just disabled
GlobalISel handling for functions that use them.
I added intrinsics for `wasm.ptr.to_funcref` and `wasm.funcref.to_ptr`.
ptr.to_funcref does a table.get from the indirect function pointer table. As a
special case, 0 is converted to the null funcref rather than doing table.get on
0. `wasm.funcref.to_ptr` is only handled when we call it immediately, otherwise
it will fail to lower. We could dynamically put the funcref into the table to
make it work but that would require a stack of spilled funcrefs and isn't worth
the effort.
In the process of looking into this, I noticed that clang used to allow casting
from funcref to function pointer but if the cast result escaped it would hit
`Cannot select: FUNCREF_TO_PTR` in the backend. I added a diagnostic that says
"a funcref can only be converted to a pointer to be directly called" to make
this a little cleaner.
Co-authored-by: Hood Chatham <roberthoodchatham at gmail.com>
Co-authored-by: Paulo Matos <pmatos at igalia.com>
---
clang/lib/CodeGen/CGCall.cpp | 13 ++
clang/lib/CodeGen/CGExprScalar.cpp | 43 +++-
clang/lib/CodeGen/CodeGenTypes.cpp | 4 +
.../WebAssembly/builtins-table-externref.c | 28 +--
.../WebAssembly/builtins-table-funcref.c | 16 +-
.../WebAssembly/builtins-test-fp-sig.c | 4 +-
.../test/CodeGen/WebAssembly/wasm-externref.c | 8 +-
.../WebAssembly/wasm-funcref-to-ptr-error.c | 21 ++
clang/test/CodeGen/WebAssembly/wasm-funcref.c | 69 ++++--
clang/test/CodeGen/builtins-wasm.c | 4 +-
llvm/include/llvm/IR/Intrinsics.h | 2 +
llvm/include/llvm/IR/IntrinsicsWebAssembly.td | 11 +
.../CodeGen/SelectionDAG/SelectionDAGISel.cpp | 23 +-
llvm/lib/CodeGen/ValueTypes.cpp | 4 +
llvm/lib/IR/Intrinsics.cpp | 17 +-
llvm/lib/IR/Type.cpp | 12 +-
llvm/lib/Target/WebAssembly/CMakeLists.txt | 1 -
.../GISel/WebAssemblyCallLowering.cpp | 53 +++--
.../WebAssembly/Utils/WasmAddressSpaces.h | 6 +-
.../Utils/WebAssemblyTypeUtilities.h | 10 +-
llvm/lib/Target/WebAssembly/WebAssembly.h | 2 -
.../WebAssembly/WebAssemblyISelDAGToDAG.cpp | 21 ++
.../WebAssembly/WebAssemblyISelLowering.cpp | 49 +++--
.../WebAssembly/WebAssemblyISelLowering.h | 3 -
.../WebAssemblyLowerRefTypesIntPtrConv.cpp | 85 --------
.../WebAssemblyRefTypeMem2Local.cpp | 2 +
.../WebAssembly/WebAssemblyTargetMachine.cpp | 2 -
.../GlobalISel/irtranslator/args.ll | 24 +--
.../GlobalISel/irtranslator/call-basics.ll | 56 +----
.../GlobalISel/irtranslator/ret-basics.ll | 30 +--
.../GlobalISel/reference-types-fallback.ll | 40 ++++
.../WebAssembly/externref-globalget.ll | 2 +-
.../WebAssembly/externref-globalset.ll | 2 +-
.../CodeGen/WebAssembly/externref-inttoptr.ll | 12 +-
.../CodeGen/WebAssembly/externref-ptrtoint.ll | 11 +-
.../CodeGen/WebAssembly/externref-tableget.ll | 2 +-
.../CodeGen/WebAssembly/externref-tableset.ll | 2 +-
.../WebAssembly/externref-unsized-load.ll | 2 +-
.../WebAssembly/externref-unsized-store.ll | 2 +-
llvm/test/CodeGen/WebAssembly/funcref-call.ll | 10 +-
.../CodeGen/WebAssembly/funcref-globalget.ll | 2 +-
.../CodeGen/WebAssembly/funcref-globalset.ll | 2 +-
.../WebAssembly/funcref-ptr-conversion.ll | 28 +++
.../CodeGen/WebAssembly/funcref-table_call.ll | 6 +-
.../CodeGen/WebAssembly/funcref-tableget.ll | 2 +-
.../CodeGen/WebAssembly/funcref-tableset.ll | 2 +-
.../WebAssembly/funcref-to-ptr-error.ll | 30 +++
llvm/test/CodeGen/WebAssembly/ref-null.ll | 4 +-
.../test/CodeGen/WebAssembly/ref-test-func.ll | 201 ++++++++++++------
.../CodeGen/WebAssembly/ref-type-mem2local.ll | 44 ++--
.../CodeGen/WebAssembly/select-reftype.ll | 24 +--
llvm/test/CodeGen/WebAssembly/table-copy.ll | 2 +-
llvm/test/CodeGen/WebAssembly/table-fill.ll | 2 +-
llvm/test/CodeGen/WebAssembly/table-grow.ll | 2 +-
llvm/test/CodeGen/WebAssembly/table-size.ll | 2 +-
llvm/test/CodeGen/WebAssembly/table-types.ll | 4 +-
.../llvm/lib/Target/WebAssembly/BUILD.gn | 1 -
57 files changed, 616 insertions(+), 450 deletions(-)
create mode 100644 clang/test/CodeGen/WebAssembly/wasm-funcref-to-ptr-error.c
delete mode 100644 llvm/lib/Target/WebAssembly/WebAssemblyLowerRefTypesIntPtrConv.cpp
create mode 100644 llvm/test/CodeGen/WebAssembly/GlobalISel/reference-types-fallback.ll
create mode 100644 llvm/test/CodeGen/WebAssembly/funcref-ptr-conversion.ll
create mode 100644 llvm/test/CodeGen/WebAssembly/funcref-to-ptr-error.ll
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 40cc275d40273..3b91a210248ff 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -49,6 +49,7 @@
#include "llvm/IR/InlineAsm.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Intrinsics.h"
+#include "llvm/IR/IntrinsicsWebAssembly.h"
#include "llvm/IR/Type.h"
#include "llvm/Transforms/Utils/Local.h"
#include <optional>
@@ -5972,6 +5973,18 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
const CGCallee &ConcreteCallee = Callee.prepareConcreteCallee(*this);
llvm::Value *CalleePtr = ConcreteCallee.getFunctionPointer();
+ // A WebAssembly funcref is an opaque reference type and llvm only accepts
+ // function pointers as the call target. To make an indirect call through a
+ // reference type, first use the llvm.wasm.funcref.to_ptr intrinsic to make a
+ // fake function pointer to it. The backend lowers the resulting indirect call
+ // to a table.set into a single element dummy table + call_indirect 0.
+ if (auto *TET = dyn_cast<llvm::TargetExtType>(CalleePtr->getType());
+ TET && TET->getName() == "wasm.funcref") {
+ llvm::Function *ToPtr =
+ CGM.getIntrinsic(llvm::Intrinsic::wasm_funcref_to_ptr);
+ CalleePtr = Builder.CreateCall(ToPtr, {CalleePtr});
+ }
+
// If we're using inalloca, set up that argument.
if (ArgMemory.isValid()) {
llvm::Value *Arg = ArgMemory.getPointer();
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index 3a3dff7bec347..18ed6570730f4 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -47,6 +47,7 @@
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/IntrinsicsPowerPC.h"
+#include "llvm/IR/IntrinsicsWebAssembly.h"
#include "llvm/IR/MatrixBuilder.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/TypeSize.h"
@@ -2839,6 +2840,43 @@ Value *ScalarExprEmitter::VisitCastExpr(CastExpr *CE) {
return CGF.authPointerToPointerCast(Result, E->getType(), DestTy);
}
case CK_AddressSpaceConversion: {
+ llvm::Type *DestLTy = ConvertType(DestTy);
+ // WebAssembly reference types are opaque target extension types so an
+ // "address space conversion" involving them is not a real pointer cast.
+ auto IsWasmFuncref = [](llvm::Type *T) {
+ auto *TET = dyn_cast<llvm::TargetExtType>(T);
+ return TET && TET->getName() == "wasm.funcref";
+ };
+ bool SrcIsFuncref = IsWasmFuncref(ConvertType(E->getType()));
+ bool DestIsFuncref = IsWasmFuncref(DestLTy);
+ if (SrcIsFuncref && DestIsFuncref) {
+ // funcref -> funcref (e.g. between differently-typed funcrefs) is the
+ // identity on the opaque reference value.
+ return Visit(E);
+ }
+ if (SrcIsFuncref && !DestIsFuncref) {
+ // funcref -> pointer: use wasm_funcref_to_ptr. This will probably crash
+ // later in codegen since we haven't implemented a way to actually get a
+ // function pointer from a funcref.
+ llvm::Function *ToPtr =
+ CGF.CGM.getIntrinsic(llvm::Intrinsic::wasm_funcref_to_ptr);
+ return CGF.Builder.CreateCall(ToPtr, {Visit(E)});
+ }
+ if (!SrcIsFuncref && DestIsFuncref) {
+ // A null function pointer converts to a null funcref (ref.null func),
+ // rather than a table lookup at index 0.
+ Expr::EvalResult NullResult;
+ if (E->EvaluateAsRValue(NullResult, CGF.getContext()) &&
+ NullResult.Val.isNullPointer()) {
+ if (NullResult.HasSideEffects)
+ Visit(E);
+ return llvm::Constant::getNullValue(DestLTy);
+ }
+ // pointer -> funcref: do a table.get from the indirect function table.
+ llvm::Function *ToFuncref =
+ CGF.CGM.getIntrinsic(llvm::Intrinsic::wasm_ptr_to_funcref);
+ return CGF.Builder.CreateCall(ToFuncref, {Visit(E)});
+ }
Expr::EvalResult Result;
if (E->EvaluateAsRValue(Result, CGF.getContext()) &&
Result.Val.isNullPointer()) {
@@ -2847,12 +2885,11 @@ Value *ScalarExprEmitter::VisitCastExpr(CastExpr *CE) {
// eliminate the useless instructions emitted during translating E.
if (Result.HasSideEffects)
Visit(E);
- return CGF.CGM.getNullPointer(cast<llvm::PointerType>(
- ConvertType(DestTy)), DestTy);
+ return CGF.CGM.getNullPointer(cast<llvm::PointerType>(DestLTy), DestTy);
}
// Since target may map different address spaces in AST to the same address
// space, an address space conversion may end up as a bitcast.
- return CGF.performAddrSpaceCast(Visit(E), ConvertType(DestTy));
+ return CGF.performAddrSpaceCast(Visit(E), DestLTy);
}
case CK_AtomicToNonAtomic:
case CK_NonAtomicToAtomic:
diff --git a/clang/lib/CodeGen/CodeGenTypes.cpp b/clang/lib/CodeGen/CodeGenTypes.cpp
index b28a0eb82f302..3de3bad6affb5 100644
--- a/clang/lib/CodeGen/CodeGenTypes.cpp
+++ b/clang/lib/CodeGen/CodeGenTypes.cpp
@@ -645,6 +645,10 @@ llvm::Type *CodeGenTypes::ConvertType(QualType T) {
case Type::Pointer: {
const PointerType *PTy = cast<PointerType>(Ty);
QualType ETy = PTy->getPointeeType();
+ if (ETy.getAddressSpace() == LangAS::wasm_funcref) {
+ ResultType = CGM.getTargetCodeGenInfo().getWasmFuncrefReferenceType();
+ break;
+ }
unsigned AS = getTargetAddressSpace(ETy);
ResultType = llvm::PointerType::get(getLLVMContext(), AS);
break;
diff --git a/clang/test/CodeGen/WebAssembly/builtins-table-externref.c b/clang/test/CodeGen/WebAssembly/builtins-table-externref.c
index 7600a53ba3aa2..454fc31a5f53f 100644
--- a/clang/test/CodeGen/WebAssembly/builtins-table-externref.c
+++ b/clang/test/CodeGen/WebAssembly/builtins-table-externref.c
@@ -8,8 +8,8 @@ static const __externref_t const_table[0];
// CHECK-LABEL: define {{[^@]+}}@test_builtin_wasm_table_get
// CHECK-SAME: (i32 noundef [[INDEX:%.*]]) #[[ATTR0:[0-9]+]] {
// CHECK-NEXT: entry:
-// CHECK-NEXT: [[TMP0:%.*]] = call ptr addrspace(10) @llvm.wasm.table.get.externref(ptr addrspace(1) @table, i32 [[INDEX]])
-// CHECK-NEXT: ret ptr addrspace(10) [[TMP0]]
+// CHECK-NEXT: [[TMP0:%.*]] = call target("wasm.externref") @llvm.wasm.table.get.externref(ptr addrspace(1) @table, i32 [[INDEX]])
+// CHECK-NEXT: ret target("wasm.externref") [[TMP0]]
//
__externref_t test_builtin_wasm_table_get(int index) {
return __builtin_wasm_table_get(table, index);
@@ -18,18 +18,18 @@ __externref_t test_builtin_wasm_table_get(int index) {
// CHECK-LABEL: define {{[^@]+}}@test_builtin_wasm_table_get_const
// CHECK-SAME: (i32 noundef [[INDEX:%.*]]) #[[ATTR0]] {
// CHECK-NEXT: entry:
-// CHECK-NEXT: [[TMP0:%.*]] = call ptr addrspace(10) @llvm.wasm.table.get.externref(ptr addrspace(1) @table, i32 [[INDEX]])
-// CHECK-NEXT: ret ptr addrspace(10) [[TMP0]]
+// CHECK-NEXT: [[TMP0:%.*]] = call target("wasm.externref") @llvm.wasm.table.get.externref(ptr addrspace(1) @table, i32 [[INDEX]])
+// CHECK-NEXT: ret target("wasm.externref") [[TMP0]]
//
__externref_t test_builtin_wasm_table_get_const(const int index) {
return __builtin_wasm_table_get(table, index);
}
// CHECK-LABEL: define {{[^@]+}}@test_builtin_wasm_table_set
-// CHECK-SAME: (i32 noundef [[INDEX:%.*]], ptr addrspace(10) [[REF:%.*]]) #[[ATTR0]] {
+// CHECK-SAME: (i32 noundef [[INDEX:%.*]], target("wasm.externref") [[REF:%.*]]) #[[ATTR0]] {
// CHECK-NEXT: entry:
-// CHECK-NEXT: call void @llvm.wasm.table.set.externref(ptr addrspace(1) @const_table, i32 [[INDEX]], ptr addrspace(10) [[REF]])
-// CHECK-NEXT: call void @llvm.wasm.table.set.externref(ptr addrspace(1) @table, i32 [[INDEX]], ptr addrspace(10) [[REF]])
+// CHECK-NEXT: call void @llvm.wasm.table.set.externref(ptr addrspace(1) @const_table, i32 [[INDEX]], target("wasm.externref") [[REF]])
+// CHECK-NEXT: call void @llvm.wasm.table.set.externref(ptr addrspace(1) @table, i32 [[INDEX]], target("wasm.externref") [[REF]])
// CHECK-NEXT: ret void
//
void test_builtin_wasm_table_set(const int index, __externref_t ref) {
@@ -38,10 +38,10 @@ void test_builtin_wasm_table_set(const int index, __externref_t ref) {
}
// CHECK-LABEL: define {{[^@]+}}@test_builtin_wasm_table_set_const
-// CHECK-SAME: (i32 noundef [[INDEX:%.*]], ptr addrspace(10) [[REF:%.*]]) #[[ATTR0]] {
+// CHECK-SAME: (i32 noundef [[INDEX:%.*]], target("wasm.externref") [[REF:%.*]]) #[[ATTR0]] {
// CHECK-NEXT: entry:
-// CHECK-NEXT: call void @llvm.wasm.table.set.externref(ptr addrspace(1) @table, i32 [[INDEX]], ptr addrspace(10) [[REF]])
-// CHECK-NEXT: call void @llvm.wasm.table.set.externref(ptr addrspace(1) @const_table, i32 [[INDEX]], ptr addrspace(10) [[REF]])
+// CHECK-NEXT: call void @llvm.wasm.table.set.externref(ptr addrspace(1) @table, i32 [[INDEX]], target("wasm.externref") [[REF]])
+// CHECK-NEXT: call void @llvm.wasm.table.set.externref(ptr addrspace(1) @const_table, i32 [[INDEX]], target("wasm.externref") [[REF]])
// CHECK-NEXT: ret void
//
void test_builtin_wasm_table_set_const(const int index, const __externref_t ref) {
@@ -60,9 +60,9 @@ int test_builtin_wasm_table_size() {
}
// CHECK-LABEL: define {{[^@]+}}@test_builtin_wasm_table_grow
-// CHECK-SAME: (ptr addrspace(10) [[REF:%.*]], i32 noundef [[NELEM:%.*]]) #[[ATTR0]] {
+// CHECK-SAME: (target("wasm.externref") [[REF:%.*]], i32 noundef [[NELEM:%.*]]) #[[ATTR0]] {
// CHECK-NEXT: entry:
-// CHECK-NEXT: [[TMP0:%.*]] = call i32 @llvm.wasm.table.grow.externref(ptr addrspace(1) @table, ptr addrspace(10) [[REF]], i32 [[NELEM]])
+// CHECK-NEXT: [[TMP0:%.*]] = call i32 @llvm.wasm.table.grow.externref(ptr addrspace(1) @table, target("wasm.externref") [[REF]], i32 [[NELEM]])
// CHECK-NEXT: ret i32 [[TMP0]]
//
int test_builtin_wasm_table_grow(__externref_t ref, int nelem) {
@@ -70,9 +70,9 @@ int test_builtin_wasm_table_grow(__externref_t ref, int nelem) {
}
// CHECK-LABEL: define {{[^@]+}}@test_builtin_wasm_table_fill
-// CHECK-SAME: (i32 noundef [[INDEX:%.*]], ptr addrspace(10) [[REF:%.*]], i32 noundef [[NELEM:%.*]]) #[[ATTR0]] {
+// CHECK-SAME: (i32 noundef [[INDEX:%.*]], target("wasm.externref") [[REF:%.*]], i32 noundef [[NELEM:%.*]]) #[[ATTR0]] {
// CHECK-NEXT: entry:
-// CHECK-NEXT: call void @llvm.wasm.table.fill.externref(ptr addrspace(1) @table, i32 [[INDEX]], ptr addrspace(10) [[REF]], i32 [[NELEM]])
+// CHECK-NEXT: call void @llvm.wasm.table.fill.externref(ptr addrspace(1) @table, i32 [[INDEX]], target("wasm.externref") [[REF]], i32 [[NELEM]])
// CHECK-NEXT: ret void
//
void test_builtin_wasm_table_fill(int index, __externref_t ref, int nelem) {
diff --git a/clang/test/CodeGen/WebAssembly/builtins-table-funcref.c b/clang/test/CodeGen/WebAssembly/builtins-table-funcref.c
index b4f729669a795..f80e9b10c4941 100644
--- a/clang/test/CodeGen/WebAssembly/builtins-table-funcref.c
+++ b/clang/test/CodeGen/WebAssembly/builtins-table-funcref.c
@@ -8,17 +8,17 @@ static funcref_t table[0];
// CHECK-LABEL: define {{[^@]+}}@test_builtin_wasm_table_get
// CHECK-SAME: (i32 noundef [[INDEX:%.*]]) #[[ATTR0:[0-9]+]] {
// CHECK-NEXT: entry:
-// CHECK-NEXT: [[TMP0:%.*]] = call ptr addrspace(20) @llvm.wasm.table.get.funcref(ptr addrspace(1) @table, i32 [[INDEX]])
-// CHECK-NEXT: ret ptr addrspace(20) [[TMP0]]
+// CHECK-NEXT: [[TMP0:%.*]] = call target("wasm.funcref") @llvm.wasm.table.get.funcref(ptr addrspace(1) @table, i32 [[INDEX]])
+// CHECK-NEXT: ret target("wasm.funcref") [[TMP0]]
//
funcref_t test_builtin_wasm_table_get(int index) {
return __builtin_wasm_table_get(table, index);
}
// CHECK-LABEL: define {{[^@]+}}@test_builtin_wasm_table_set
-// CHECK-SAME: (i32 noundef [[INDEX:%.*]], ptr addrspace(20) noundef [[REF:%.*]]) #[[ATTR0]] {
+// CHECK-SAME: (i32 noundef [[INDEX:%.*]], target("wasm.funcref") noundef [[REF:%.*]]) #[[ATTR0]] {
// CHECK-NEXT: entry:
-// CHECK-NEXT: call void @llvm.wasm.table.set.funcref(ptr addrspace(1) @table, i32 [[INDEX]], ptr addrspace(20) [[REF]])
+// CHECK-NEXT: call void @llvm.wasm.table.set.funcref(ptr addrspace(1) @table, i32 [[INDEX]], target("wasm.funcref") [[REF]])
// CHECK-NEXT: ret void
//
void test_builtin_wasm_table_set(int index, funcref_t ref) {
@@ -37,9 +37,9 @@ int test_builtin_wasm_table_size() {
// CHECK-LABEL: define {{[^@]+}}@test_builtin_wasm_table_grow
-// CHECK-SAME: (ptr addrspace(20) noundef [[REF:%.*]], i32 noundef [[NELEM:%.*]]) #[[ATTR0]] {
+// CHECK-SAME: (target("wasm.funcref") noundef [[REF:%.*]], i32 noundef [[NELEM:%.*]]) #[[ATTR0]] {
// CHECK-NEXT: entry:
-// CHECK-NEXT: [[TMP0:%.*]] = call i32 @llvm.wasm.table.grow.funcref(ptr addrspace(1) @table, ptr addrspace(20) [[REF]], i32 [[NELEM]])
+// CHECK-NEXT: [[TMP0:%.*]] = call i32 @llvm.wasm.table.grow.funcref(ptr addrspace(1) @table, target("wasm.funcref") [[REF]], i32 [[NELEM]])
// CHECK-NEXT: ret i32 [[TMP0]]
//
int test_builtin_wasm_table_grow(funcref_t ref, int nelem) {
@@ -47,9 +47,9 @@ int test_builtin_wasm_table_grow(funcref_t ref, int nelem) {
}
// CHECK-LABEL: define {{[^@]+}}@test_builtin_wasm_table_fill
-// CHECK-SAME: (i32 noundef [[INDEX:%.*]], ptr addrspace(20) noundef [[REF:%.*]], i32 noundef [[NELEM:%.*]]) #[[ATTR0]] {
+// CHECK-SAME: (i32 noundef [[INDEX:%.*]], target("wasm.funcref") noundef [[REF:%.*]], i32 noundef [[NELEM:%.*]]) #[[ATTR0]] {
// CHECK-NEXT: entry:
-// CHECK-NEXT: call void @llvm.wasm.table.fill.funcref(ptr addrspace(1) @table, i32 [[INDEX]], ptr addrspace(20) [[REF]], i32 [[NELEM]])
+// CHECK-NEXT: call void @llvm.wasm.table.fill.funcref(ptr addrspace(1) @table, i32 [[INDEX]], target("wasm.funcref") [[REF]], i32 [[NELEM]])
// CHECK-NEXT: ret void
//
void test_builtin_wasm_table_fill(int index, funcref_t ref, int nelem) {
diff --git a/clang/test/CodeGen/WebAssembly/builtins-test-fp-sig.c b/clang/test/CodeGen/WebAssembly/builtins-test-fp-sig.c
index 88447f7fa232d..c8825028e1789 100644
--- a/clang/test/CodeGen/WebAssembly/builtins-test-fp-sig.c
+++ b/clang/test/CodeGen/WebAssembly/builtins-test-fp-sig.c
@@ -32,13 +32,13 @@ void test_function_pointer_signature_varargs(FVarArgs func) {
typedef __externref_t (*FExternRef)(__externref_t, __externref_t);
void test_function_pointer_externref(FExternRef func) {
- // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, ptr addrspace(10) poison, token poison, ptr addrspace(10) poison, ptr addrspace(10) poison)
+ // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, target("wasm.externref") poison, token poison, target("wasm.externref") poison, target("wasm.externref") poison)
use(__builtin_wasm_test_function_pointer_signature(func));
}
typedef __funcref Fpointers (*FFuncRef)(__funcref Fvoid, __funcref Ffloats);
void test_function_pointer_funcref(FFuncRef func) {
- // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, ptr addrspace(20) poison, token poison, ptr addrspace(20) poison, ptr addrspace(20) poison)
+ // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, target("wasm.funcref") poison, token poison, target("wasm.funcref") poison, target("wasm.funcref") poison)
use(__builtin_wasm_test_function_pointer_signature(func));
}
diff --git a/clang/test/CodeGen/WebAssembly/wasm-externref.c b/clang/test/CodeGen/WebAssembly/wasm-externref.c
index 788438bb4a86a..d226c51b7fd4e 100644
--- a/clang/test/CodeGen/WebAssembly/wasm-externref.c
+++ b/clang/test/CodeGen/WebAssembly/wasm-externref.c
@@ -7,10 +7,10 @@ void helper(externref_t);
// CHECK-LABEL: @handle(
// CHECK-NEXT: entry:
-// CHECK-NEXT: [[OBJ_ADDR:%.*]] = alloca ptr addrspace(10), align 1
-// CHECK-NEXT: store ptr addrspace(10) [[OBJ:%.*]], ptr [[OBJ_ADDR]], align 1
-// CHECK-NEXT: [[TMP0:%.*]] = load ptr addrspace(10), ptr [[OBJ_ADDR]], align 1
-// CHECK-NEXT: call void @helper(ptr addrspace(10) [[TMP0]])
+// CHECK-NEXT: [[OBJ_ADDR:%.*]] = alloca target("wasm.externref"), align 1
+// CHECK-NEXT: store target("wasm.externref") [[OBJ:%.*]], ptr [[OBJ_ADDR]], align 1
+// CHECK-NEXT: [[TMP0:%.*]] = load target("wasm.externref"), ptr [[OBJ_ADDR]], align 1
+// CHECK-NEXT: call void @helper(target("wasm.externref") [[TMP0]])
// CHECK-NEXT: ret void
//
void handle(externref_t obj) {
diff --git a/clang/test/CodeGen/WebAssembly/wasm-funcref-to-ptr-error.c b/clang/test/CodeGen/WebAssembly/wasm-funcref-to-ptr-error.c
new file mode 100644
index 0000000000000..ce663a70dd3b1
--- /dev/null
+++ b/clang/test/CodeGen/WebAssembly/wasm-funcref-to-ptr-error.c
@@ -0,0 +1,21 @@
+// RUN: not %clang_cc1 -triple wasm32 -target-feature +reference-types -S -o /dev/null %s 2>&1 | FileCheck %s
+// RUN: not %clang_cc1 -triple wasm64 -target-feature +reference-types -S -o /dev/null %s 2>&1 | FileCheck %s
+
+// We haven't implemented a way of converting a funcref to a function pointer.
+// We can generate code for it if the result is immediately called, which avoids
+// the need for creating a function pointer. If the resulting pointer escapes,
+// we haven't implemented codegen for that. Diagnose it in the front end rather
+// than crashing in the backend.
+
+typedef void (*__funcref funcref_t)(void);
+typedef void (*fn_t)(void);
+
+// CHECK: error: a funcref can only be converted to a pointer to be directly called; the resulting pointer cannot otherwise be used
+void store_funcref_as_ptr(funcref_t f, fn_t *out) {
+ *out = (fn_t)f;
+}
+
+// CHECK: error: a funcref can only be converted to a pointer to be directly called; the resulting pointer cannot otherwise be used
+fn_t return_funcref_as_ptr(funcref_t f) {
+ return (fn_t)f;
+}
diff --git a/clang/test/CodeGen/WebAssembly/wasm-funcref.c b/clang/test/CodeGen/WebAssembly/wasm-funcref.c
index f01af0db321dd..63e9099b08a15 100644
--- a/clang/test/CodeGen/WebAssembly/wasm-funcref.c
+++ b/clang/test/CodeGen/WebAssembly/wasm-funcref.c
@@ -8,8 +8,8 @@ typedef int (*fn_t)(int);
// Null funcref builtin call
// CHECK-LABEL: @get_null(
// CHECK-NEXT: entry:
-// CHECK-NEXT: [[TMP0:%.*]] = call ptr addrspace(20) @llvm.wasm.ref.null.func()
-// CHECK-NEXT: ret ptr addrspace(20) [[TMP0]]
+// CHECK-NEXT: [[TMP0:%.*]] = call target("wasm.funcref") @llvm.wasm.ref.null.func()
+// CHECK-NEXT: ret target("wasm.funcref") [[TMP0]]
//
funcref_t get_null() {
return __builtin_wasm_ref_null_func();
@@ -19,8 +19,8 @@ funcref_t get_null() {
// default return value for builtin is a funcref with function type () -> ().
// CHECK-LABEL: @get_null_ii(
// CHECK-NEXT: entry:
-// CHECK-NEXT: [[TMP0:%.*]] = call ptr addrspace(20) @llvm.wasm.ref.null.func()
-// CHECK-NEXT: ret ptr addrspace(20) [[TMP0]]
+// CHECK-NEXT: [[TMP0:%.*]] = call target("wasm.funcref") @llvm.wasm.ref.null.func()
+// CHECK-NEXT: ret target("wasm.funcref") [[TMP0]]
//
fn_funcref_t get_null_ii() {
return (fn_funcref_t) __builtin_wasm_ref_null_func();
@@ -29,10 +29,10 @@ fn_funcref_t get_null_ii() {
// Identity function for funcref.
// CHECK-LABEL: @identity(
// CHECK-NEXT: entry:
-// CHECK-NEXT: [[FN_ADDR:%.*]] = alloca ptr addrspace(20), align 4
-// CHECK-NEXT: store ptr addrspace(20) [[FN:%.*]], ptr [[FN_ADDR]], align 4
-// CHECK-NEXT: [[TMP0:%.*]] = load ptr addrspace(20), ptr [[FN_ADDR]], align 4
-// CHECK-NEXT: ret ptr addrspace(20) [[TMP0]]
+// CHECK-NEXT: [[FN_ADDR:%.*]] = alloca target("wasm.funcref"), align 4
+// CHECK-NEXT: store target("wasm.funcref") [[FN:%.*]], ptr [[FN_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load target("wasm.funcref"), ptr [[FN_ADDR]], align 4
+// CHECK-NEXT: ret target("wasm.funcref") [[TMP0]]
//
funcref_t identity(funcref_t fn) {
return fn;
@@ -43,10 +43,10 @@ void helper(funcref_t);
// Pass funcref ref as an argument to a helper function.
// CHECK-LABEL: @handle(
// CHECK-NEXT: entry:
-// CHECK-NEXT: [[FN_ADDR:%.*]] = alloca ptr addrspace(20), align 4
-// CHECK-NEXT: store ptr addrspace(20) [[FN:%.*]], ptr [[FN_ADDR]], align 4
-// CHECK-NEXT: [[TMP0:%.*]] = load ptr addrspace(20), ptr [[FN_ADDR]], align 4
-// CHECK-NEXT: call void @helper(ptr addrspace(20) noundef [[TMP0]])
+// CHECK-NEXT: [[FN_ADDR:%.*]] = alloca target("wasm.funcref"), align 4
+// CHECK-NEXT: store target("wasm.funcref") [[FN:%.*]], ptr [[FN_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load target("wasm.funcref"), ptr [[FN_ADDR]], align 4
+// CHECK-NEXT: call void @helper(target("wasm.funcref") noundef [[TMP0]])
// CHECK-NEXT: ret i32 0
//
int handle(funcref_t fn) {
@@ -60,29 +60,58 @@ int handle(funcref_t fn) {
// CHECK-NEXT: [[FNPTR_ADDR:%.*]] = alloca ptr, align 4
// CHECK-NEXT: store ptr [[FNPTR:%.*]], ptr [[FNPTR_ADDR]], align 4
// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[FNPTR_ADDR]], align 4
-// CHECK-NEXT: [[TMP1:%.*]] = addrspacecast ptr [[TMP0]] to ptr addrspace(20)
-// CHECK-NEXT: ret ptr addrspace(20) [[TMP1]]
+// CHECK-NEXT: [[TMP1:%.*]] = call target("wasm.funcref") @llvm.wasm.ptr.to_funcref(ptr [[TMP0]])
+// CHECK-NEXT: ret target("wasm.funcref") [[TMP1]]
//
fn_funcref_t get_ref(fn_t fnptr) {
return (fn_funcref_t) fnptr;
}
+// Casting a null function pointer to a funcref yields a null funcref
+// (ref.null func), not a lookup of index 0 in the indirect function table.
+// CHECK-LABEL: @get_null_from_fnptr(
+// CHECK-NEXT: entry:
+// CHECK-NEXT: ret target("wasm.funcref") zeroinitializer
+//
+fn_funcref_t get_null_from_fnptr() {
+ return (fn_funcref_t)(fn_t)0;
+}
+
// Call funcref
// CHECK-LABEL: @call_fn(
// CHECK-NEXT: entry:
-// CHECK-NEXT: [[REF_ADDR:%.*]] = alloca ptr addrspace(20), align 4
+// CHECK-NEXT: [[REF_ADDR:%.*]] = alloca target("wasm.funcref"), align 4
// CHECK-NEXT: [[X_ADDR:%.*]] = alloca i32, align 4
-// CHECK-NEXT: store ptr addrspace(20) [[REF:%.*]], ptr [[REF_ADDR]], align 4
+// CHECK-NEXT: store target("wasm.funcref") [[REF:%.*]], ptr [[REF_ADDR]], align 4
// CHECK-NEXT: store i32 [[X:%.*]], ptr [[X_ADDR]], align 4
-// CHECK-NEXT: [[TMP0:%.*]] = load ptr addrspace(20), ptr [[REF_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load target("wasm.funcref"), ptr [[REF_ADDR]], align 4
// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[X_ADDR]], align 4
-// CHECK-NEXT: [[CALL:%.*]] = call addrspace(20) i32 [[TMP0]](i32 noundef [[TMP1]])
+// CHECK-NEXT: [[TMP2:%.*]] = call ptr @llvm.wasm.funcref.to_ptr(target("wasm.funcref") [[TMP0]])
+// CHECK-NEXT: [[CALL:%.*]] = call i32 [[TMP2]](i32 noundef [[TMP1]])
// CHECK-NEXT: ret i32 [[CALL]]
//
int call_fn(fn_funcref_t ref, int x) {
return ref(x);
}
+// Explicitly casting a funcref to a plain function pointer and calling it
+// immediately is allowed: the conversion feeds directly into the indirect call.
+// CHECK-LABEL: @call_fn_via_cast(
+// CHECK-NEXT: entry:
+// CHECK-NEXT: [[REF_ADDR:%.*]] = alloca target("wasm.funcref"), align 4
+// CHECK-NEXT: [[X_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store target("wasm.funcref") [[REF:%.*]], ptr [[REF_ADDR]], align 4
+// CHECK-NEXT: store i32 [[X:%.*]], ptr [[X_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load target("wasm.funcref"), ptr [[REF_ADDR]], align 4
+// CHECK-NEXT: [[TMP1:%.*]] = call ptr @llvm.wasm.funcref.to_ptr(target("wasm.funcref") [[TMP0]])
+// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[X_ADDR]], align 4
+// CHECK-NEXT: [[CALL:%.*]] = call i32 [[TMP1]](i32 noundef [[TMP2]])
+// CHECK-NEXT: ret i32 [[CALL]]
+//
+int call_fn_via_cast(fn_funcref_t ref, int x) {
+ return ((fn_t)ref)(x);
+}
+
typedef fn_funcref_t (*builtin_refnull_t)();
// Calling ref.null through a function pointer.
@@ -91,8 +120,8 @@ typedef fn_funcref_t (*builtin_refnull_t)();
// CHECK-NEXT: [[REFNULL_ADDR:%.*]] = alloca ptr, align 4
// CHECK-NEXT: store ptr [[REFNULL:%.*]], ptr [[REFNULL_ADDR]], align 4
// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[REFNULL_ADDR]], align 4
-// CHECK-NEXT: [[CALL:%.*]] = call ptr addrspace(20) [[TMP0]]()
-// CHECK-NEXT: ret ptr addrspace(20) [[CALL]]
+// CHECK-NEXT: [[CALL:%.*]] = call target("wasm.funcref") [[TMP0]]()
+// CHECK-NEXT: ret target("wasm.funcref") [[CALL]]
//
fn_funcref_t get_null_fptr(builtin_refnull_t refnull) {
return refnull();
diff --git a/clang/test/CodeGen/builtins-wasm.c b/clang/test/CodeGen/builtins-wasm.c
index 375664b852636..40788b0afeb45 100644
--- a/clang/test/CodeGen/builtins-wasm.c
+++ b/clang/test/CodeGen/builtins-wasm.c
@@ -737,13 +737,13 @@ f16x8 pmax_f16x8(f16x8 a, f16x8 b) {
}
__externref_t externref_null() {
return __builtin_wasm_ref_null_extern();
- // WEBASSEMBLY: tail call ptr addrspace(10) @llvm.wasm.ref.null.extern()
+ // WEBASSEMBLY: tail call target("wasm.externref") @llvm.wasm.ref.null.extern()
// WEBASSEMBLY-NEXT: ret
}
int externref_is_null(__externref_t arg) {
return __builtin_wasm_ref_is_null_extern(arg);
- // WEBASSEMBLY: tail call i32 @llvm.wasm.ref.is_null.extern(ptr addrspace(10) %arg)
+ // WEBASSEMBLY: tail call i32 @llvm.wasm.ref.is_null.extern(target("wasm.externref") %arg)
// WEBASSEMBLY-NEXT: ret
}
diff --git a/llvm/include/llvm/IR/Intrinsics.h b/llvm/include/llvm/IR/Intrinsics.h
index 5ccb18d7281bc..d78a66e45a3ad 100644
--- a/llvm/include/llvm/IR/Intrinsics.h
+++ b/llvm/include/llvm/IR/Intrinsics.h
@@ -177,6 +177,8 @@ struct IITDescriptor {
AMX,
PPCQuad,
AArch64Svcount,
+ WasmExternref,
+ WasmFuncref,
// Overloaded type.
Overloaded, // AnyKind and overload index in OverloadInfo.
diff --git a/llvm/include/llvm/IR/IntrinsicsWebAssembly.td b/llvm/include/llvm/IR/IntrinsicsWebAssembly.td
index c1e4b97e96bc8..a0e83cee9f055 100644
--- a/llvm/include/llvm/IR/IntrinsicsWebAssembly.td
+++ b/llvm/include/llvm/IR/IntrinsicsWebAssembly.td
@@ -47,6 +47,17 @@ def int_wasm_ref_test_func
: DefaultAttrsIntrinsic<[llvm_i32_ty], [llvm_ptr_ty, llvm_vararg_ty],
[IntrNoMem]>;
+//===----------------------------------------------------------------------===//
+// funcref <--> pointer conversion intrinsics
+//===----------------------------------------------------------------------===//
+def int_wasm_funcref_to_ptr :
+ DefaultAttrsIntrinsic<[llvm_ptr_ty], [llvm_funcref_ty],
+ [IntrNoMem], "llvm.wasm.funcref.to_ptr">;
+
+def int_wasm_ptr_to_funcref :
+ DefaultAttrsIntrinsic<[llvm_funcref_ty], [llvm_ptr_ty],
+ [IntrNoMem], "llvm.wasm.ptr.to_funcref">;
+
//===----------------------------------------------------------------------===//
// Table intrinsics
//===----------------------------------------------------------------------===//
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
index 5ae52cae771fb..1b479c2b36058 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
@@ -230,9 +230,26 @@ static bool dontUseFastISelFor(const Function &Fn) {
// Debug info on those is reliant on good Argument lowering, and FastISel is
// not capable of lowering the entire function. Mixing the two selectors tend
// to result in poor lowering of Arguments.
- return any_of(Fn.args(), [](const Argument &Arg) {
- return Arg.hasAttribute(Attribute::AttrKind::SwiftAsync);
- });
+ if (any_of(Fn.args(), [](const Argument &Arg) {
+ return Arg.hasAttribute(Attribute::AttrKind::SwiftAsync);
+ }))
+ return true;
+
+ // A WebAssembly funcref call is expressed in IR as a call through the pointer
+ // produced by the llvm.wasm.funcref.to_ptr intrinsic. SelectionDAG fuses the
+ // intrinsic and the call into a table.set + call_indirect through the
+ // dedicated __funcref_call_table. FastISel selects each call as its own
+ // single-instruction block and therefore cannot perform this fusion, so fall
+ // back to SelectionDAG for the whole function when the intrinsic is present.
+ if (Fn.getParent()->getTargetTriple().isWasm()) {
+ for (const BasicBlock &BB : Fn)
+ for (const Instruction &I : BB)
+ if (const auto *II = dyn_cast<IntrinsicInst>(&I))
+ if (II->getIntrinsicID() == Intrinsic::wasm_funcref_to_ptr)
+ return true;
+ }
+
+ return false;
}
static bool maintainPGOProfile(const TargetMachine &TM,
diff --git a/llvm/lib/CodeGen/ValueTypes.cpp b/llvm/lib/CodeGen/ValueTypes.cpp
index e74068e22f4cd..a8eb5f801a280 100644
--- a/llvm/lib/CodeGen/ValueTypes.cpp
+++ b/llvm/lib/CodeGen/ValueTypes.cpp
@@ -268,6 +268,10 @@ MVT MVT::getVT(Type *Ty, bool HandleUnknown){
TargetExtType *TargetExtTy = cast<TargetExtType>(Ty);
if (TargetExtTy->getName() == "aarch64.svcount")
return MVT(MVT::aarch64svcount);
+ else if (TargetExtTy->getName() == "wasm.externref")
+ return MVT(MVT::externref);
+ else if (TargetExtTy->getName() == "wasm.funcref")
+ return MVT(MVT::funcref);
else if (TargetExtTy->getName().starts_with("spirv."))
return MVT(MVT::spirvbuiltin);
if (TargetExtTy->getName() == "riscv.vector.tuple") {
diff --git a/llvm/lib/IR/Intrinsics.cpp b/llvm/lib/IR/Intrinsics.cpp
index b0bad878f0c6f..ed4d37df33e7d 100644
--- a/llvm/lib/IR/Intrinsics.cpp
+++ b/llvm/lib/IR/Intrinsics.cpp
@@ -358,10 +358,10 @@ DecodeIITType(unsigned &NextElt, ArrayRef<unsigned char> Infos,
DecodeIITType(NextElt, Infos, OutputTable);
return;
case IIT_EXTERNREF:
- OutputTable.push_back(IITDescriptor::get(IITDescriptor::Pointer, 10));
+ OutputTable.push_back(IITDescriptor::get(IITDescriptor::WasmExternref, 0));
return;
case IIT_FUNCREF:
- OutputTable.push_back(IITDescriptor::get(IITDescriptor::Pointer, 20));
+ OutputTable.push_back(IITDescriptor::get(IITDescriptor::WasmFuncref, 0));
return;
case IIT_PTR:
OutputTable.push_back(IITDescriptor::get(IITDescriptor::Pointer, 0));
@@ -556,7 +556,10 @@ static Type *DecodeFixedType(ArrayRef<Intrinsic::IITDescriptor> &Infos,
return Type::getPPC_FP128Ty(Context);
case IITDescriptor::AArch64Svcount:
return TargetExtType::get(Context, "aarch64.svcount");
-
+ case IITDescriptor::WasmExternref:
+ return TargetExtType::get(Context, "wasm.externref");
+ case IITDescriptor::WasmFuncref:
+ return TargetExtType::get(Context, "wasm.funcref");
case IITDescriptor::Integer:
return IntegerType::get(Context, D.IntegerWidth);
case IITDescriptor::Vector:
@@ -1029,6 +1032,14 @@ matchIntrinsicType(Type *Ty, ArrayRef<Intrinsic::IITDescriptor> &Infos,
return PrintMsg(isa<TargetExtType>(Ty) &&
cast<TargetExtType>(Ty)->getName() == "aarch64.svcount",
"aarch64.svcount");
+ case IITDescriptor::WasmExternref:
+ return PrintMsg(isa<TargetExtType>(Ty) &&
+ cast<TargetExtType>(Ty)->getName() == "wasm.externref",
+ "wasm.externref");
+ case IITDescriptor::WasmFuncref:
+ return PrintMsg(isa<TargetExtType>(Ty) &&
+ cast<TargetExtType>(Ty)->getName() == "wasm.funcref",
+ "wasm.funcref");
case IITDescriptor::Vector: {
VectorType *VT = dyn_cast<VectorType>(Ty);
StringRef Scalable = D.VectorWidth.isScalable() ? "vscale " : "";
diff --git a/llvm/lib/IR/Type.cpp b/llvm/lib/IR/Type.cpp
index 47b230d44285b..d5f4671487acb 100644
--- a/llvm/lib/IR/Type.cpp
+++ b/llvm/lib/IR/Type.cpp
@@ -334,13 +334,11 @@ Type *Type::getByteFromIntType(Type *Ty) {
}
Type *Type::getWasm_ExternrefTy(LLVMContext &C) {
- // opaque pointer in addrspace(10)
- return PointerType::get(C, 10);
+ return TargetExtType::get(C, "wasm.externref", {}, {});
}
Type *Type::getWasm_FuncrefTy(LLVMContext &C) {
- // opaque pointer in addrspace(20)
- return PointerType::get(C, 20);
+ return TargetExtType::get(C, "wasm.funcref", {}, {});
}
//===----------------------------------------------------------------------===//
@@ -1134,6 +1132,12 @@ static TargetTypeInfo getTargetTypeInfo(const TargetExtType *Ty) {
TargetExtType::CanBeVectorElement);
}
+ // Opaque types in the WebAssembly name space.
+ if (Name == "wasm.funcref" || Name == "wasm.externref")
+ return TargetTypeInfo(PointerType::getUnqual(C), TargetExtType::HasZeroInit,
+ TargetExtType::CanBeGlobal,
+ TargetExtType::CanBeLocal);
+
return TargetTypeInfo(Type::getVoidTy(C));
}
diff --git a/llvm/lib/Target/WebAssembly/CMakeLists.txt b/llvm/lib/Target/WebAssembly/CMakeLists.txt
index ef5f7d11e7e49..a024a354890d2 100644
--- a/llvm/lib/Target/WebAssembly/CMakeLists.txt
+++ b/llvm/lib/Target/WebAssembly/CMakeLists.txt
@@ -51,7 +51,6 @@ add_llvm_target(WebAssemblyCodeGen
WebAssemblyInstrInfo.cpp
WebAssemblyLowerBrUnless.cpp
WebAssemblyLowerEmscriptenEHSjLj.cpp
- WebAssemblyLowerRefTypesIntPtrConv.cpp
WebAssemblyMachineFunctionInfo.cpp
WebAssemblyMCInstLower.cpp
WebAssemblyMCLowerPrePass.cpp
diff --git a/llvm/lib/Target/WebAssembly/GISel/WebAssemblyCallLowering.cpp b/llvm/lib/Target/WebAssembly/GISel/WebAssemblyCallLowering.cpp
index 9f3a1d1ba7fa2..3241e30789b97 100644
--- a/llvm/lib/Target/WebAssembly/GISel/WebAssemblyCallLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/GISel/WebAssemblyCallLowering.cpp
@@ -14,7 +14,7 @@
#include "WebAssemblyCallLowering.h"
#include "MCTargetDesc/WebAssemblyMCTargetDesc.h"
-#include "Utils/WasmAddressSpaces.h"
+#include "Utils/WebAssemblyTypeUtilities.h"
#include "WebAssemblyISelLowering.h"
#include "WebAssemblyMachineFunctionInfo.h"
#include "WebAssemblyRegisterInfo.h"
@@ -58,22 +58,18 @@ static unsigned extendOpFromFlags(ISD::ArgFlagsTy Flags) {
return TargetOpcode::G_ANYEXT;
}
-static LLT getLLTForWasmMVT(MVT Ty, const DataLayout &DL) {
- if (Ty == MVT::externref) {
- return LLT::pointer(
- WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_EXTERNREF,
- DL.getPointerSizeInBits(
- WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_EXTERNREF));
- }
-
- if (Ty == MVT::funcref) {
- return LLT::pointer(
- WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_FUNCREF,
- DL.getPointerSizeInBits(
- WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_FUNCREF));
- }
-
- return llvm::getLLTForMVT(Ty);
+// GlobalISel doesn't handle reference types. We bail out of GlobalISel for
+// functions passing/returning references and fall back to SDAG.
+static bool typeContainsReference(const Type *Ty) {
+ if (WebAssembly::isWebAssemblyReferenceType(Ty))
+ return true;
+ if (const auto *ArrTy = dyn_cast<ArrayType>(Ty))
+ return typeContainsReference(ArrTy->getElementType());
+ if (const auto *StructTy = dyn_cast<StructType>(Ty))
+ return llvm::any_of(StructTy->elements(), [](const Type *ElemTy) {
+ return typeContainsReference(ElemTy);
+ });
+ return false;
}
WebAssemblyCallLowering::WebAssemblyCallLowering(
@@ -99,6 +95,9 @@ bool WebAssemblyCallLowering::lowerReturn(MachineIRBuilder &MIRBuilder,
const WebAssemblyTargetLowering &TLI = *getTLI<WebAssemblyTargetLowering>();
const DataLayout &DL = F.getDataLayout();
+ if (Val && typeContainsReference(Val->getType()))
+ return false;
+
MachineInstrBuilder MIB = MIRBuilder.buildInstrNoInsert(WebAssembly::RETURN);
assert(((Val && !VRegs.empty()) || (!Val && VRegs.empty())) &&
@@ -134,7 +133,7 @@ bool WebAssemblyCallLowering::lowerReturn(MachineIRBuilder &MIRBuilder,
TLI.getRegisterTypeForCallingConv(Ctx, CallConv, OrigVT);
const LLT OrigLLT =
getLLTForType(*OrigVT.getTypeForEVT(F.getContext()), DL);
- const LLT NewLLT = getLLTForWasmMVT(NewVT, DL);
+ const LLT NewLLT = llvm::getLLTForMVT(NewVT);
const TargetRegisterClass &NewRegClass = *TLI.getRegClassFor(NewVT);
@@ -276,6 +275,12 @@ bool WebAssemblyCallLowering::lowerFormalArguments(
if (!callingConvSupported(CallConv))
return false;
+ if (typeContainsReference(F.getReturnType()))
+ return false;
+ for (const Argument &Arg : F.args())
+ if (typeContainsReference(Arg.getType()))
+ return false;
+
MF.getRegInfo().addLiveIn(WebAssembly::ARGUMENTS);
MF.front().addLiveIn(WebAssembly::ARGUMENTS);
@@ -308,7 +313,7 @@ bool WebAssemblyCallLowering::lowerFormalArguments(
const MVT NewVT = TLI.getRegisterTypeForCallingConv(Ctx, CallConv, OrigVT);
const LLT OrigLLT =
getLLTForType(*OrigVT.getTypeForEVT(F.getContext()), DL);
- const LLT NewLLT = getLLTForWasmMVT(NewVT, DL);
+ const LLT NewLLT = llvm::getLLTForMVT(NewVT);
// If we need to split the type over multiple regs, check it's a scenario
// we currently support.
@@ -410,6 +415,12 @@ bool WebAssemblyCallLowering::lowerCall(MachineIRBuilder &MIRBuilder,
if (!callingConvSupported(CallConv))
return false;
+ if (typeContainsReference(Info.OrigRet.Ty))
+ return false;
+ for (const ArgInfo &Arg : Info.OrigArgs)
+ if (typeContainsReference(Arg.Ty))
+ return false;
+
// TODO: tail calls
if (Info.IsMustTailCall)
return false;
@@ -456,7 +467,7 @@ bool WebAssemblyCallLowering::lowerCall(MachineIRBuilder &MIRBuilder,
const MVT NewVT = TLI.getRegisterTypeForCallingConv(Ctx, CallConv, OrigVT);
const LLT OrigLLT =
getLLTForType(*OrigVT.getTypeForEVT(F.getContext()), DL);
- const LLT NewLLT = getLLTForWasmMVT(NewVT, DL);
+ const LLT NewLLT = llvm::getLLTForMVT(NewVT);
const TargetRegisterClass &NewRegClass = *TLI.getRegClassFor(NewVT);
@@ -552,7 +563,7 @@ bool WebAssemblyCallLowering::lowerCall(MachineIRBuilder &MIRBuilder,
TLI.getRegisterTypeForCallingConv(Ctx, CallConv, OrigVT);
const LLT OrigLLT =
getLLTForType(*OrigVT.getTypeForEVT(F.getContext()), DL);
- const LLT NewLLT = getLLTForWasmMVT(NewVT, DL);
+ const LLT NewLLT = llvm::getLLTForMVT(NewVT);
const TargetRegisterClass &NewRegClass = *TLI.getRegClassFor(NewVT);
diff --git a/llvm/lib/Target/WebAssembly/Utils/WasmAddressSpaces.h b/llvm/lib/Target/WebAssembly/Utils/WasmAddressSpaces.h
index 2239badca69c3..d2ab2c0f9e777 100644
--- a/llvm/lib/Target/WebAssembly/Utils/WasmAddressSpaces.h
+++ b/llvm/lib/Target/WebAssembly/Utils/WasmAddressSpaces.h
@@ -24,11 +24,7 @@ enum WasmAddressSpace : unsigned {
// linear memory: WebAssembly globals or WebAssembly locals. Loads and stores
// to these pointers are lowered to global.get / global.set or local.get /
// local.set, as appropriate.
- WASM_ADDRESS_SPACE_VAR = 1,
- // A non-integral address space for externref values
- WASM_ADDRESS_SPACE_EXTERNREF = 10,
- // A non-integral address space for funcref values
- WASM_ADDRESS_SPACE_FUNCREF = 20,
+ WASM_ADDRESS_SPACE_VAR = 1
};
inline bool isDefaultAddressSpace(unsigned AS) {
diff --git a/llvm/lib/Target/WebAssembly/Utils/WebAssemblyTypeUtilities.h b/llvm/lib/Target/WebAssembly/Utils/WebAssemblyTypeUtilities.h
index c5e0dcfffef9c..47ba91df81161 100644
--- a/llvm/lib/Target/WebAssembly/Utils/WebAssemblyTypeUtilities.h
+++ b/llvm/lib/Target/WebAssembly/Utils/WebAssemblyTypeUtilities.h
@@ -28,16 +28,14 @@ namespace WebAssembly {
/// Return true if this is a WebAssembly Externref Type.
inline bool isWebAssemblyExternrefType(const Type *Ty) {
- return Ty->isPointerTy() &&
- Ty->getPointerAddressSpace() ==
- WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_EXTERNREF;
+ const TargetExtType *TargetTy = dyn_cast<TargetExtType>(Ty);
+ return TargetTy && TargetTy->getName() == "wasm.externref";
}
/// Return true if this is a WebAssembly Funcref Type.
inline bool isWebAssemblyFuncrefType(const Type *Ty) {
- return Ty->isPointerTy() &&
- Ty->getPointerAddressSpace() ==
- WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_FUNCREF;
+ const TargetExtType *TargetTy = dyn_cast<TargetExtType>(Ty);
+ return TargetTy && TargetTy->getName() == "wasm.funcref";
}
/// Return true if this is a WebAssembly Reference Type.
diff --git a/llvm/lib/Target/WebAssembly/WebAssembly.h b/llvm/lib/Target/WebAssembly/WebAssembly.h
index 101348501bece..6556cb5e25c0e 100644
--- a/llvm/lib/Target/WebAssembly/WebAssembly.h
+++ b/llvm/lib/Target/WebAssembly/WebAssembly.h
@@ -32,7 +32,6 @@ ModulePass *createWebAssemblyLowerEmscriptenEHSjLj();
ModulePass *createWebAssemblyAddMissingPrototypes();
ModulePass *createWebAssemblyFixFunctionBitcasts();
FunctionPass *createWebAssemblyOptimizeReturned();
-FunctionPass *createWebAssemblyLowerRefTypesIntPtrConv();
FunctionPass *createWebAssemblyRefTypeMem2Local();
FunctionPass *createWebAssemblyReduceToAnyAllTrue(WebAssemblyTargetMachine &TM);
@@ -94,7 +93,6 @@ void initializeWebAssemblyFixIrreducibleControlFlowPass(PassRegistry &);
void initializeWebAssemblyLateEHPreparePass(PassRegistry &);
void initializeWebAssemblyLowerBrUnlessPass(PassRegistry &);
void initializeWebAssemblyLowerEmscriptenEHSjLjPass(PassRegistry &);
-void initializeWebAssemblyLowerRefTypesIntPtrConvPass(PassRegistry &);
void initializeWebAssemblyMCLowerPrePassPass(PassRegistry &);
void initializeWebAssemblyMemIntrinsicResultsPass(PassRegistry &);
void initializeWebAssemblyNullifyDebugValueListsPass(PassRegistry &);
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
index c7b57588877b7..69e9e6b5a9717 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
@@ -275,6 +275,27 @@ void WebAssemblyDAGToDAGISel::Select(SDNode *Node) {
ReplaceNode(Node, TLSAlign);
return;
}
+ case Intrinsic::wasm_ptr_to_funcref: {
+ // Convert a function pointer to a funcref by reading the corresponding
+ // entry from the __indirect_function_table.
+ MachineFunction &MF = CurDAG->getMachineFunction();
+ auto PtrVT = MVT::getIntegerVT(MF.getDataLayout().getPointerSizeInBits());
+ MCSymbol *Table = WebAssembly::getOrCreateFunctionTableSymbol(
+ MF.getContext(), Subtarget);
+ SDValue TableSym = CurDAG->getMCSymbol(Table, PtrVT);
+ SDValue FuncPtr = Node->getOperand(1);
+ if (Subtarget->hasAddr64() && FuncPtr.getValueType() == MVT::i64) {
+ // table.get expects an i32 but on 64 bit platforms the function pointer
+ // is an i64. In that case, i32.wrap_i64 to convert.
+ FuncPtr = SDValue(CurDAG->getMachineNode(WebAssembly::I32_WRAP_I64, DL,
+ MVT::i32, FuncPtr),
+ 0);
+ }
+ MachineSDNode *FuncRef = CurDAG->getMachineNode(
+ WebAssembly::TABLE_GET_FUNCREF, DL, MVT::funcref, TableSym, FuncPtr);
+ ReplaceNode(Node, FuncRef);
+ return;
+ }
case Intrinsic::wasm_ref_test_func: {
// First emit the TABLE_GET instruction to convert function pointer ==>
// funcref
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
index bba3a34b08df8..7c0fc3fffea01 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
@@ -436,24 +436,6 @@ WebAssemblyTargetLowering::WebAssemblyTargetLowering(
setMinimumJumpTableEntries(2);
}
-MVT WebAssemblyTargetLowering::getPointerTy(const DataLayout &DL,
- uint32_t AS) const {
- if (AS == WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_EXTERNREF)
- return MVT::externref;
- if (AS == WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_FUNCREF)
- return MVT::funcref;
- return TargetLowering::getPointerTy(DL, AS);
-}
-
-MVT WebAssemblyTargetLowering::getPointerMemTy(const DataLayout &DL,
- uint32_t AS) const {
- if (AS == WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_EXTERNREF)
- return MVT::externref;
- if (AS == WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_FUNCREF)
- return MVT::funcref;
- return TargetLowering::getPointerMemTy(DL, AS);
-}
-
TargetLowering::AtomicExpansionKind
WebAssemblyTargetLowering::shouldExpandAtomicRMWInIR(
const AtomicRMWInst *AI) const {
@@ -1294,6 +1276,17 @@ WebAssemblyTargetLowering::LowerCall(CallLoweringInfo &CLI,
MachineFunction &MF = DAG.getMachineFunction();
auto Layout = MF.getDataLayout();
+ // A call through a funcref is expressed in IR as a call through the pointer
+ // produced by the llvm.wasm.funcref.to_ptr intrinsic. Detect this here and
+ // recover the underlying funcref value so the call can be lowered to a
+ // table.set + call_indirect through the dedicated __funcref_call_table.
+ bool IsFuncrefCall = false;
+ if (Callee.getOpcode() == ISD::INTRINSIC_WO_CHAIN &&
+ Callee.getConstantOperandVal(0) == Intrinsic::wasm_funcref_to_ptr) {
+ Callee = Callee.getOperand(1);
+ IsFuncrefCall = true;
+ }
+
CallingConv::ID CallConv = CLI.CallConv;
if (!callingConvSupported(CallConv))
fail(DL, DAG,
@@ -1537,8 +1530,7 @@ WebAssemblyTargetLowering::LowerCall(CallLoweringInfo &CLI,
// Lastly, if this is a call to a funcref we need to add an instruction
// table.set to the chain and transform the call.
- if (CLI.CB && WebAssembly::isWebAssemblyFuncrefType(
- CLI.CB->getCalledOperand()->getType())) {
+ if (IsFuncrefCall) {
// 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
@@ -1554,11 +1546,7 @@ WebAssemblyTargetLowering::LowerCall(CallLoweringInfo &CLI,
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()),
+ MVT::funcref, MachinePointerInfo(), Align(1),
MachineMemOperand::MOStore);
Ops[0] = TableSet; // The new chain is the TableSet itself
@@ -2277,6 +2265,17 @@ SDValue WebAssemblyTargetLowering::LowerIntrinsic(SDValue Op,
return DAG.getNode(WebAssemblyISD::SHUFFLE, DL, Op.getValueType(), Ops);
}
+ case Intrinsic::wasm_funcref_to_ptr: {
+ // llvm.wasm.funcref.to_ptr only has a defined lowering when its result
+ // feeds directly into an indirect call. Reaching here means the pointer
+ // escapes a direct call. We haven't implemented conversion of a funcref
+ // into a real function pointer so we crash if we get here.
+ fail(DL, DAG,
+ "a funcref can only be converted to a pointer to be directly called; "
+ "the resulting pointer cannot otherwise be used");
+ return DAG.getUNDEF(Op.getValueType());
+ }
+
case Intrinsic::thread_pointer: {
return SDValue(WebAssembly::getTLSBase(DAG, DL, Subtarget), 0);
}
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.h b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.h
index 42f047840e504..04e6d6f2d9367 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.h
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.h
@@ -26,9 +26,6 @@ class WebAssemblyTargetLowering final : public TargetLowering {
WebAssemblyTargetLowering(const TargetMachine &TM,
const WebAssemblySubtarget &STI);
- MVT getPointerTy(const DataLayout &DL, uint32_t AS = 0) const override;
- MVT getPointerMemTy(const DataLayout &DL, uint32_t AS = 0) const override;
-
private:
/// Keep a pointer to the WebAssemblySubtarget around so that we can make the
/// right decision when generating code for different targets.
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyLowerRefTypesIntPtrConv.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyLowerRefTypesIntPtrConv.cpp
deleted file mode 100644
index be500de67e320..0000000000000
--- a/llvm/lib/Target/WebAssembly/WebAssemblyLowerRefTypesIntPtrConv.cpp
+++ /dev/null
@@ -1,85 +0,0 @@
-//=== WebAssemblyLowerRefTypesIntPtrConv.cpp -
-// Lower IntToPtr and PtrToInt on Reference Types ---===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-///
-/// \file
-/// Lowers IntToPtr and PtrToInt instructions on reference types to
-/// Trap instructions since they have been allowed to operate
-/// on non-integral pointers.
-///
-//===----------------------------------------------------------------------===//
-
-#include "Utils/WebAssemblyTypeUtilities.h"
-#include "WebAssembly.h"
-#include "WebAssemblySubtarget.h"
-#include "llvm/IR/InstIterator.h"
-#include "llvm/Pass.h"
-#include <set>
-
-using namespace llvm;
-
-#define DEBUG_TYPE "wasm-lower-reftypes-intptr-conv"
-
-namespace {
-class WebAssemblyLowerRefTypesIntPtrConv final : public FunctionPass {
- StringRef getPassName() const override {
- return "WebAssembly Lower RefTypes Int-Ptr Conversions";
- }
-
- bool runOnFunction(Function &MF) override;
-
-public:
- static char ID; // Pass identification
- WebAssemblyLowerRefTypesIntPtrConv() : FunctionPass(ID) {}
-};
-} // end anonymous namespace
-
-char WebAssemblyLowerRefTypesIntPtrConv::ID = 0;
-INITIALIZE_PASS(WebAssemblyLowerRefTypesIntPtrConv, DEBUG_TYPE,
- "WebAssembly Lower RefTypes Int-Ptr Conversions", false, false)
-
-FunctionPass *llvm::createWebAssemblyLowerRefTypesIntPtrConv() {
- return new WebAssemblyLowerRefTypesIntPtrConv();
-}
-
-bool WebAssemblyLowerRefTypesIntPtrConv::runOnFunction(Function &F) {
- LLVM_DEBUG(dbgs() << "********** Lower RefTypes IntPtr Convs **********\n"
- "********** Function: "
- << F.getName() << '\n');
-
- // This function will check for uses of ptrtoint and inttoptr on reference
- // types and replace them with a trap instruction.
- //
- // We replace the instruction by a trap instruction
- // and its uses by null in the case of inttoptr and 0 in the
- // case of ptrtoint.
- std::set<Instruction *> worklist;
-
- for (inst_iterator I = inst_begin(F), E = inst_end(F); I != E; ++I) {
- PtrToIntInst *PTI = dyn_cast<PtrToIntInst>(&*I);
- IntToPtrInst *ITP = dyn_cast<IntToPtrInst>(&*I);
- if (!(PTI && WebAssembly::isWebAssemblyReferenceType(
- PTI->getPointerOperand()->getType())) &&
- !(ITP && WebAssembly::isWebAssemblyReferenceType(ITP->getDestTy())))
- continue;
-
- I->replaceAllUsesWith(PoisonValue::get(I->getType()));
-
- Function *TrapIntrin =
- Intrinsic::getOrInsertDeclaration(F.getParent(), Intrinsic::debugtrap);
- CallInst::Create(TrapIntrin, {}, "", I->getIterator());
-
- worklist.insert(&*I);
- }
-
- // erase each instruction replaced by trap
- for (Instruction *I : worklist)
- I->eraseFromParent();
-
- return !worklist.empty();
-}
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyRefTypeMem2Local.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyRefTypeMem2Local.cpp
index 04b4c7d78aabb..d2ff9b264d576 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyRefTypeMem2Local.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyRefTypeMem2Local.cpp
@@ -64,6 +64,8 @@ void WebAssemblyRefTypeMem2Local::visitAllocaInst(AllocaInst &AI) {
auto *NewAI = IRB.CreateAlloca(AI.getAllocatedType(),
WebAssembly::WASM_ADDRESS_SPACE_VAR, nullptr,
AI.getName() + ".var");
+ // Preserve the original alloca's alignment.
+ NewAI->setAlignment(AI.getAlign());
// The below is basically equivalent to AI.replaceAllUsesWith(NewAI), but we
// cannot use it because it requires the old and new types be the same,
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp
index 886ea0a8ab574..63bda89309dfc 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp
@@ -119,7 +119,6 @@ LLVMInitializeWebAssemblyTarget() {
initializeWebAssemblyDebugFixupPass(PR);
initializeWebAssemblyPeepholePass(PR);
initializeWebAssemblyMCLowerPrePassPass(PR);
- initializeWebAssemblyLowerRefTypesIntPtrConvPass(PR);
initializeWebAssemblyFixBrTableDefaultsPass(PR);
initializeWebAssemblyDAGToDAGISelLegacyPass(PR);
}
@@ -670,7 +669,6 @@ void WebAssemblyPassConfig::addPreEmitPass() {
bool WebAssemblyPassConfig::addPreISel() {
TargetPassConfig::addPreISel();
- addPass(createWebAssemblyLowerRefTypesIntPtrConv());
return false;
}
diff --git a/llvm/test/CodeGen/WebAssembly/GlobalISel/irtranslator/args.ll b/llvm/test/CodeGen/WebAssembly/GlobalISel/irtranslator/args.ll
index 152a607b14d5a..97413111536be 100644
--- a/llvm/test/CodeGen/WebAssembly/GlobalISel/irtranslator/args.ll
+++ b/llvm/test/CodeGen/WebAssembly/GlobalISel/irtranslator/args.ll
@@ -144,27 +144,9 @@ define void @test_f128_arg(fp128 %arg) {
ret void
}
-%externref = type ptr addrspace(10)
-define void @test_externref_arg(%externref %arg) {
- ; CHECK-LABEL: name: test_externref_arg
- ; CHECK: bb.1 (%ir-block.0):
- ; CHECK-NEXT: liveins: $arguments
- ; CHECK-NEXT: {{ $}}
- ; CHECK-NEXT: [[ARGUMENT_externref:%[0-9]+]]:externref(p10) = ARGUMENT_externref 0, implicit $arguments
- ; CHECK-NEXT: RETURN implicit-def $arguments
- ret void
-}
-
-%funcref = type ptr addrspace(20)
-define void @test_funcref_arg(%funcref %arg) {
- ; CHECK-LABEL: name: test_funcref_arg
- ; CHECK: bb.1 (%ir-block.0):
- ; CHECK-NEXT: liveins: $arguments
- ; CHECK-NEXT: {{ $}}
- ; CHECK-NEXT: [[ARGUMENT_funcref:%[0-9]+]]:funcref(p20) = ARGUMENT_funcref 0, implicit $arguments
- ; CHECK-NEXT: RETURN implicit-def $arguments
- ret void
-}
+; NOTE: Reference types (externref/funcref) are intentionally not tested here.
+; GlobalISel currently falls back to SelectionDAG for them; see
+; reference-types-fallback.ll.
define void @test_multiple_args(ptr %arg1, float %arg2, i1 %arg3) {
; WASM32-LABEL: name: test_multiple_args
diff --git a/llvm/test/CodeGen/WebAssembly/GlobalISel/irtranslator/call-basics.ll b/llvm/test/CodeGen/WebAssembly/GlobalISel/irtranslator/call-basics.ll
index d767ed5104dae..5a1a4fc8f3c68 100644
--- a/llvm/test/CodeGen/WebAssembly/GlobalISel/irtranslator/call-basics.ll
+++ b/llvm/test/CodeGen/WebAssembly/GlobalISel/irtranslator/call-basics.ll
@@ -5,8 +5,6 @@
; RUN: llc -mtriple=wasm64 -mattr=+simd128,+multivalue -target-abi=experimental-mv -global-isel -stop-after=irtranslator < %s | FileCheck %s -check-prefixes=CHECK,WASM64,MULTIVAL-SIMD,WASM64-MULTIVAL-SIMD
-%externref = type ptr addrspace(10)
-%funcref = type ptr addrspace(20)
declare void @ret_void_args_none()
define void @call_ret_void_args_none() {
@@ -87,29 +85,9 @@ define ptr @call_ret_ptr_args_none() {
ret ptr %ret
}
-declare %externref @ret_externref_args_none()
-define %externref @call_ret_externref_args_none() {
- ; CHECK-LABEL: name: call_ret_externref_args_none
- ; CHECK: bb.1 (%ir-block.0):
- ; CHECK-NEXT: liveins: $arguments
- ; CHECK-NEXT: {{ $}}
- ; CHECK-NEXT: [[CALL:%[0-9]+]]:externref(p10) = CALL @ret_externref_args_none, implicit-def $arguments, implicit $sp32, implicit $sp64
- ; CHECK-NEXT: RETURN [[CALL]](p10), implicit-def $arguments
- %ret = call %externref @ret_externref_args_none()
- ret %externref %ret
-}
-
-declare %funcref @ret_funcref_args_none()
-define %funcref @call_ret_funcref_args_none() {
- ; CHECK-LABEL: name: call_ret_funcref_args_none
- ; CHECK: bb.1 (%ir-block.0):
- ; CHECK-NEXT: liveins: $arguments
- ; CHECK-NEXT: {{ $}}
- ; CHECK-NEXT: [[CALL:%[0-9]+]]:funcref(p20) = CALL @ret_funcref_args_none, implicit-def $arguments, implicit $sp32, implicit $sp64
- ; CHECK-NEXT: RETURN [[CALL]](p20), implicit-def $arguments
- %ret = call %funcref @ret_funcref_args_none()
- ret %funcref %ret
-}
+; NOTE: Reference-type (externref/funcref) returns are intentionally not tested
+; here. GlobalISel currently falls back to SelectionDAG for them; see
+; reference-types-fallback.ll.
declare i128 @ret_i128_args_none()
define i128 @call_ret_i128_args_none() {
@@ -234,31 +212,9 @@ define void @call_ret_void_args_ptr(ptr %a) {
ret void
}
-declare void @ret_void_args_externref(%externref)
-define void @call_ret_void_args_externref(%externref %a) {
- ; CHECK-LABEL: name: call_ret_void_args_externref
- ; CHECK: bb.1 (%ir-block.0):
- ; CHECK-NEXT: liveins: $arguments
- ; CHECK-NEXT: {{ $}}
- ; CHECK-NEXT: [[ARGUMENT_externref:%[0-9]+]]:externref(p10) = ARGUMENT_externref 0, implicit $arguments
- ; CHECK-NEXT: CALL @ret_void_args_externref, [[ARGUMENT_externref]](p10), implicit-def $arguments, implicit $sp32, implicit $sp64
- ; CHECK-NEXT: RETURN implicit-def $arguments
- call void @ret_void_args_externref(%externref %a)
- ret void
-}
-
-declare void @ret_void_args_funcref(%funcref)
-define void @call_ret_void_args_funcref(%funcref %a) {
- ; CHECK-LABEL: name: call_ret_void_args_funcref
- ; CHECK: bb.1 (%ir-block.0):
- ; CHECK-NEXT: liveins: $arguments
- ; CHECK-NEXT: {{ $}}
- ; CHECK-NEXT: [[ARGUMENT_funcref:%[0-9]+]]:funcref(p20) = ARGUMENT_funcref 0, implicit $arguments
- ; CHECK-NEXT: CALL @ret_void_args_funcref, [[ARGUMENT_funcref]](p20), implicit-def $arguments, implicit $sp32, implicit $sp64
- ; CHECK-NEXT: RETURN implicit-def $arguments
- call void @ret_void_args_funcref(%funcref %a)
- ret void
-}
+; NOTE: Reference-type (externref/funcref) arguments are intentionally not
+; tested here. GlobalISel currently falls back to SelectionDAG for them; see
+; reference-types-fallback.ll.
declare void @ret_void_args_i128(i128)
define void @call_ret_void_args_i128(i128 %a) {
diff --git a/llvm/test/CodeGen/WebAssembly/GlobalISel/irtranslator/ret-basics.ll b/llvm/test/CodeGen/WebAssembly/GlobalISel/irtranslator/ret-basics.ll
index 21e066e78c515..3bc6aaaa3d48c 100644
--- a/llvm/test/CodeGen/WebAssembly/GlobalISel/irtranslator/ret-basics.ll
+++ b/llvm/test/CodeGen/WebAssembly/GlobalISel/irtranslator/ret-basics.ll
@@ -124,30 +124,6 @@ define double @test_ret_f64() {
ret double 0.0
}
-%externref = type ptr addrspace(10)
-define %externref @test_ret_externref() {
- ; CHECK-LABEL: name: test_ret_externref
- ; CHECK: bb.1 (%ir-block.0):
- ; CHECK-NEXT: liveins: $arguments
- ; CHECK-NEXT: {{ $}}
- ; CHECK-NEXT: [[FRAME_INDEX:%[0-9]+]]:_(p0) = G_FRAME_INDEX %stack.0.ref_ptr
- ; CHECK-NEXT: [[LOAD:%[0-9]+]]:_(p10) = G_LOAD [[FRAME_INDEX]](p0) :: (load (p10) from %ir.ref_ptr)
- ; CHECK-NEXT: RETURN [[LOAD]](p10), implicit-def $arguments
- %ref_ptr = alloca %externref
- %ref = load %externref, ptr %ref_ptr
- ret %externref %ref
-}
-
-%funcref = type ptr addrspace(20)
-define %funcref @test_ret_funcref() {
- ; CHECK-LABEL: name: test_ret_funcref
- ; CHECK: bb.1 (%ir-block.0):
- ; CHECK-NEXT: liveins: $arguments
- ; CHECK-NEXT: {{ $}}
- ; CHECK-NEXT: [[FRAME_INDEX:%[0-9]+]]:_(p0) = G_FRAME_INDEX %stack.0.ref_ptr
- ; CHECK-NEXT: [[LOAD:%[0-9]+]]:_(p20) = G_LOAD [[FRAME_INDEX]](p0) :: (load (p20) from %ir.ref_ptr)
- ; CHECK-NEXT: RETURN [[LOAD]](p20), implicit-def $arguments
- %ref_ptr = alloca %funcref
- %ref = load %funcref, ptr %ref_ptr
- ret %funcref %ref
-}
+; NOTE: Reference types (externref/funcref) are intentionally not tested here.
+; GlobalISel currently falls back to SelectionDAG for them; see
+; reference-types-fallback.ll.
diff --git a/llvm/test/CodeGen/WebAssembly/GlobalISel/reference-types-fallback.ll b/llvm/test/CodeGen/WebAssembly/GlobalISel/reference-types-fallback.ll
new file mode 100644
index 0000000000000..bce9127a8969e
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/GlobalISel/reference-types-fallback.ll
@@ -0,0 +1,40 @@
+; RUN: llc -mtriple=wasm32 -mattr=+reference-types -global-isel -global-isel-abort=2 -pass-remarks-missed='gisel*' %s -o - 2>&1 | FileCheck %s
+; RUN: llc -mtriple=wasm64 -mattr=+reference-types -global-isel -global-isel-abort=2 -pass-remarks-missed='gisel*' %s -o - 2>&1 | FileCheck %s
+
+; GlobalISel does not yet correctly model WebAssembly reference types
+; (externref/funcref): the generic getLLTForType derives an integer scalar LLT
+; from their pointer layout type, which would round-trip references through
+; invalid integer loads/bitcasts. Until that is fixed, the WebAssembly
+; CallLowering bails out for any function passing or returning a reference type,
+; so instruction selection falls back to SelectionDAG. This test verifies that
+; fallback (rather than emission of incorrect code).
+
+define target("wasm.externref") @ret_externref(target("wasm.externref") %a) {
+; CHECK: remark: {{.*}} unable to lower arguments{{.*}}wasm.externref
+; CHECK-LABEL: warning: Instruction selection used fallback path for ret_externref
+ ret target("wasm.externref") %a
+}
+
+define target("wasm.funcref") @ret_funcref(target("wasm.funcref") %a) {
+; CHECK: remark: {{.*}} unable to lower arguments{{.*}}wasm.funcref
+; CHECK-LABEL: warning: Instruction selection used fallback path for ret_funcref
+ ret target("wasm.funcref") %a
+}
+
+declare void @take_externref(target("wasm.externref"))
+
+define void @call_take_externref(target("wasm.externref") %a) {
+; CHECK: remark: {{.*}} unable to lower arguments{{.*}}wasm.externref
+; CHECK-LABEL: warning: Instruction selection used fallback path for call_take_externref
+ call void @take_externref(target("wasm.externref") %a)
+ ret void
+}
+
+declare target("wasm.externref") @produce_externref()
+
+define void @call_produce_externref() {
+; CHECK: remark: {{.*}} unable to {{.*}}
+; CHECK-LABEL: warning: Instruction selection used fallback path for call_produce_externref
+ %ref = call target("wasm.externref") @produce_externref()
+ ret void
+}
diff --git a/llvm/test/CodeGen/WebAssembly/externref-globalget.ll b/llvm/test/CodeGen/WebAssembly/externref-globalget.ll
index 79d7932486e22..8d377cbfdad30 100644
--- a/llvm/test/CodeGen/WebAssembly/externref-globalget.ll
+++ b/llvm/test/CodeGen/WebAssembly/externref-globalget.ll
@@ -3,7 +3,7 @@
; not error out.
; RUN: llc < %s --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types -print-after=finalize-isel | FileCheck %s
-%externref = type ptr addrspace(10) ;; addrspace 10 is nonintegral
+%externref = type target("wasm.externref")
@externref_global = local_unnamed_addr addrspace(1) global %externref undef
diff --git a/llvm/test/CodeGen/WebAssembly/externref-globalset.ll b/llvm/test/CodeGen/WebAssembly/externref-globalset.ll
index 5bfd673e89fa1..57cfffe82dee0 100644
--- a/llvm/test/CodeGen/WebAssembly/externref-globalset.ll
+++ b/llvm/test/CodeGen/WebAssembly/externref-globalset.ll
@@ -1,6 +1,6 @@
; RUN: llc --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types < %s | FileCheck %s
-%externref = type ptr addrspace(10) ;; addrspace 10 is nonintegral
+%externref = type target("wasm.externref")
@externref_global = local_unnamed_addr addrspace(1) global %externref undef
diff --git a/llvm/test/CodeGen/WebAssembly/externref-inttoptr.ll b/llvm/test/CodeGen/WebAssembly/externref-inttoptr.ll
index 64f955b6ed0f0..bdf04f73c54a7 100644
--- a/llvm/test/CodeGen/WebAssembly/externref-inttoptr.ll
+++ b/llvm/test/CodeGen/WebAssembly/externref-inttoptr.ll
@@ -1,16 +1,10 @@
-; RUN: llc < %s --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types 2>&1 | FileCheck %s
+; RUN: not llc --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types < %s 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR
-%externref = type ptr addrspace(10)
+%externref = type target("wasm.externref")
define %externref @int_to_externref(i32 %i) {
%ref = inttoptr i32 %i to %externref
ret %externref %ref
}
-
-; CHECK-LABEL: int_to_externref:
-; CHECK-NEXT: .functype int_to_externref (i32) -> (externref)
-; CHECK-NEXT: .local externref
-; CHECK-NEXT: unreachable
-; CHECK-NEXT: local.get 1
-; CHECK-NEXT: end_function
+# CHECK-ERROR: error: invalid cast opcode for cast from 'i32' to 'target("wasm.externref")'
diff --git a/llvm/test/CodeGen/WebAssembly/externref-ptrtoint.ll b/llvm/test/CodeGen/WebAssembly/externref-ptrtoint.ll
index 22558796f0624..f3cfe31445b60 100644
--- a/llvm/test/CodeGen/WebAssembly/externref-ptrtoint.ll
+++ b/llvm/test/CodeGen/WebAssembly/externref-ptrtoint.ll
@@ -1,15 +1,10 @@
-; RUN: llc < %s --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types 2>&1 | FileCheck %s
+; RUN: not llc --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types < %s 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR
-%externref = type ptr addrspace(10)
+%externref = type target("wasm.externref")
define i32 @externref_to_int(%externref %ref) {
%i = ptrtoint %externref %ref to i32
ret i32 %i
}
-; CHECK-LABEL: externref_to_int:
-; CHECK-NEXT: .functype externref_to_int (externref) -> (i32)
-; CHECK-NEXT: .local i32
-; CHECK-NEXT: unreachable
-; CHECK-NEXT: local.get 1
-; CHECK-NEXT: end_function
+# CHECK-ERROR: error: invalid cast opcode for cast from 'target("wasm.externref")' to 'i32'
diff --git a/llvm/test/CodeGen/WebAssembly/externref-tableget.ll b/llvm/test/CodeGen/WebAssembly/externref-tableget.ll
index d9ae7c8f6c9b1..1189fc3571902 100644
--- a/llvm/test/CodeGen/WebAssembly/externref-tableget.ll
+++ b/llvm/test/CodeGen/WebAssembly/externref-tableget.ll
@@ -1,6 +1,6 @@
; RUN: llc < %s --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types | FileCheck %s
-%externref = type ptr addrspace(10) ;; addrspace 10 is nonintegral
+%externref = type target("wasm.externref")
@externref_table = local_unnamed_addr addrspace(1) global [0 x %externref] undef
diff --git a/llvm/test/CodeGen/WebAssembly/externref-tableset.ll b/llvm/test/CodeGen/WebAssembly/externref-tableset.ll
index 37c663869428e..f84ae4dba68eb 100644
--- a/llvm/test/CodeGen/WebAssembly/externref-tableset.ll
+++ b/llvm/test/CodeGen/WebAssembly/externref-tableset.ll
@@ -1,6 +1,6 @@
; RUN: llc --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types < %s | FileCheck %s
-%externref = type ptr addrspace(10) ;; addrspace 10 is nonintegral
+%externref = type target("wasm.externref")
@externref_table = local_unnamed_addr addrspace(1) global [0 x %externref] undef
diff --git a/llvm/test/CodeGen/WebAssembly/externref-unsized-load.ll b/llvm/test/CodeGen/WebAssembly/externref-unsized-load.ll
index 1f8f4d5140c51..98a0e801af387 100644
--- a/llvm/test/CodeGen/WebAssembly/externref-unsized-load.ll
+++ b/llvm/test/CodeGen/WebAssembly/externref-unsized-load.ll
@@ -1,6 +1,6 @@
; RUN: not llc --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types < %s 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR
-%externref = type ptr addrspace(10)
+%externref = type target("wasm.externref")
define void @load_extern(%externref %ref) {
%e = load %extern, %externref %ref
diff --git a/llvm/test/CodeGen/WebAssembly/externref-unsized-store.ll b/llvm/test/CodeGen/WebAssembly/externref-unsized-store.ll
index c7e062d1b0526..f3d3ee3685230 100644
--- a/llvm/test/CodeGen/WebAssembly/externref-unsized-store.ll
+++ b/llvm/test/CodeGen/WebAssembly/externref-unsized-store.ll
@@ -1,6 +1,6 @@
; RUN: not llc --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types < %s 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR
-%externref = type ptr addrspace(10)
+%externref = type target("wasm.externref")
define void @store_extern(%externref %ref) {
store %extern undef, %externref %ref
diff --git a/llvm/test/CodeGen/WebAssembly/funcref-call.ll b/llvm/test/CodeGen/WebAssembly/funcref-call.ll
index 9904df2280e81..66692ac0eb0f0 100644
--- a/llvm/test/CodeGen/WebAssembly/funcref-call.ll
+++ b/llvm/test/CodeGen/WebAssembly/funcref-call.ll
@@ -2,7 +2,9 @@
; RUN: llc < %s --mtriple=wasm32-unknown-unknown -fast-isel=0 -mattr=+reference-types | FileCheck %s
; RUN: llc < %s --mtriple=wasm32-unknown-unknown -fast-isel=1 -mattr=+reference-types | FileCheck %s
-%funcref = type ptr addrspace(20) ;; addrspace 20 is nonintegral
+%funcref = type target("wasm.funcref")
+
+declare ptr @llvm.wasm.funcref.to_ptr(%funcref) nounwind
; CHECK: .tabletype __funcref_call_table, funcref, 1
@@ -19,7 +21,8 @@ define void @call_funcref(%funcref %ref) {
; CHECK-NEXT: ref.null_func
; CHECK-NEXT: table.set __funcref_call_table
; CHECK-NEXT: # fallthrough-return
- call addrspace(20) void %ref()
+ %refptr = call ptr @llvm.wasm.funcref.to_ptr(%funcref %ref)
+ call void %refptr()
ret void
}
@@ -41,6 +44,7 @@ define float @call_funcref_with_args(%funcref %ref) {
; CHECK-NEXT: table.set __funcref_call_table
; CHECK-NEXT: local.get 1
; CHECK-NEXT: # fallthrough-return
- %ret = call addrspace(20) float %ref(double 1.0, i32 2)
+ %refptr = call ptr @llvm.wasm.funcref.to_ptr(%funcref %ref)
+ %ret = call float %refptr(double 1.0, i32 2)
ret float %ret
}
diff --git a/llvm/test/CodeGen/WebAssembly/funcref-globalget.ll b/llvm/test/CodeGen/WebAssembly/funcref-globalget.ll
index 9aa7fdabfdea9..2634e5a6fc57e 100644
--- a/llvm/test/CodeGen/WebAssembly/funcref-globalget.ll
+++ b/llvm/test/CodeGen/WebAssembly/funcref-globalget.ll
@@ -1,6 +1,6 @@
; RUN: llc < %s --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types | FileCheck %s
-%funcref = type ptr addrspace(20) ;; addrspace 20 is nonintegral
+%funcref = type target("wasm.funcref")
@funcref_global = local_unnamed_addr addrspace(1) global %funcref undef
diff --git a/llvm/test/CodeGen/WebAssembly/funcref-globalset.ll b/llvm/test/CodeGen/WebAssembly/funcref-globalset.ll
index ca2feb6617996..791e8648537c0 100644
--- a/llvm/test/CodeGen/WebAssembly/funcref-globalset.ll
+++ b/llvm/test/CodeGen/WebAssembly/funcref-globalset.ll
@@ -1,6 +1,6 @@
; RUN: llc < %s --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types | FileCheck %s
-%funcref = type ptr addrspace(20) ;; addrspace 20 is nonintegral
+%funcref = type target("wasm.funcref")
@funcref_global = local_unnamed_addr addrspace(1) global %funcref undef
diff --git a/llvm/test/CodeGen/WebAssembly/funcref-ptr-conversion.ll b/llvm/test/CodeGen/WebAssembly/funcref-ptr-conversion.ll
new file mode 100644
index 0000000000000..b9792b661a742
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/funcref-ptr-conversion.ll
@@ -0,0 +1,28 @@
+; RUN: llc < %s --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types | FileCheck %s
+; RUN: llc < %s --mtriple=wasm64-unknown-unknown -asm-verbose=false -mattr=+reference-types | FileCheck %s --check-prefix=CHECK64
+
+%funcref = type target("wasm.funcref")
+
+declare %funcref @llvm.wasm.ptr.to_funcref(ptr) nounwind
+
+; CHECK: .tabletype __indirect_function_table, funcref
+
+; Converting a function pointer to a funcref is a table.get from the
+; __indirect_function_table.
+define %funcref @ptr_to_funcref(ptr %p) {
+; CHECK-LABEL: ptr_to_funcref:
+; CHECK: .functype ptr_to_funcref (i32) -> (funcref)
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: table.get __indirect_function_table
+; CHECK-NEXT: end_function
+;
+; On wasm64 the function pointer is an i64 and must be wrapped to i32 first.
+; CHECK64-LABEL: ptr_to_funcref:
+; CHECK64: .functype ptr_to_funcref (i64) -> (funcref)
+; CHECK64-NEXT: local.get 0
+; CHECK64-NEXT: i32.wrap_i64
+; CHECK64-NEXT: table.get __indirect_function_table
+; CHECK64-NEXT: end_function
+ %ref = call %funcref @llvm.wasm.ptr.to_funcref(ptr %p)
+ ret %funcref %ref
+}
diff --git a/llvm/test/CodeGen/WebAssembly/funcref-table_call.ll b/llvm/test/CodeGen/WebAssembly/funcref-table_call.ll
index 74bbc802ac077..e5343fa80d846 100644
--- a/llvm/test/CodeGen/WebAssembly/funcref-table_call.ll
+++ b/llvm/test/CodeGen/WebAssembly/funcref-table_call.ll
@@ -1,12 +1,13 @@
; RUN: llc < %s --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types | FileCheck %s
-%funcref = type ptr addrspace(20) ;; addrspace 20 is nonintegral
+%funcref = type target("wasm.funcref")
@funcref_table = local_unnamed_addr addrspace(1) global [0 x %funcref] undef
; CHECK: .tabletype __funcref_call_table, funcref, 1
declare %funcref @llvm.wasm.table.get.funcref(ptr addrspace(1), i32) nounwind
+declare ptr @llvm.wasm.funcref.to_ptr(%funcref) nounwind
define void @call_funcref_from_table(i32 %i) {
; CHECK-LABEL: call_funcref_from_table:
@@ -22,7 +23,8 @@ define void @call_funcref_from_table(i32 %i) {
; CHECK-NEXT: table.set __funcref_call_table
; CHECK-NEXT: end_function
%ref = call %funcref @llvm.wasm.table.get.funcref(ptr addrspace(1) @funcref_table, i32 %i)
- call addrspace(20) void %ref()
+ %refptr = call ptr @llvm.wasm.funcref.to_ptr(%funcref %ref)
+ call void %refptr()
ret void
}
diff --git a/llvm/test/CodeGen/WebAssembly/funcref-tableget.ll b/llvm/test/CodeGen/WebAssembly/funcref-tableget.ll
index 3df308c5ddf80..2621dcde6a79b 100644
--- a/llvm/test/CodeGen/WebAssembly/funcref-tableget.ll
+++ b/llvm/test/CodeGen/WebAssembly/funcref-tableget.ll
@@ -1,6 +1,6 @@
; RUN: llc < %s --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types | FileCheck %s
-%funcref = type ptr addrspace(20) ;; addrspace 20 is nonintegral
+%funcref = type target("wasm.funcref")
@funcref_table = local_unnamed_addr addrspace(1) global [0 x %funcref] undef
diff --git a/llvm/test/CodeGen/WebAssembly/funcref-tableset.ll b/llvm/test/CodeGen/WebAssembly/funcref-tableset.ll
index 98e1b55613d7d..e62eb8a6f2d20 100644
--- a/llvm/test/CodeGen/WebAssembly/funcref-tableset.ll
+++ b/llvm/test/CodeGen/WebAssembly/funcref-tableset.ll
@@ -1,6 +1,6 @@
; RUN: llc --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types < %s | FileCheck %s
-%funcref = type ptr addrspace(20) ;; addrspace 20 is nonintegral
+%funcref = type target("wasm.funcref")
@funcref_table = local_unnamed_addr addrspace(1) global [0 x %funcref] undef
diff --git a/llvm/test/CodeGen/WebAssembly/funcref-to-ptr-error.ll b/llvm/test/CodeGen/WebAssembly/funcref-to-ptr-error.ll
new file mode 100644
index 0000000000000..b756513ecbafa
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/funcref-to-ptr-error.ll
@@ -0,0 +1,30 @@
+; RUN: not llc < %s --mtriple=wasm32-unknown-unknown -mattr=+reference-types 2>&1 | FileCheck %s
+
+; We have only implemented a lowering for llvm.wasm.funcref.to_ptr its result
+; feeds directly into an indirect call. Check that we diagnose the case where we
+; spill the result rather than crashing in the backend.
+
+%funcref = type target("wasm.funcref")
+
+declare ptr @llvm.wasm.funcref.to_ptr(%funcref)
+declare void @sink(ptr)
+
+; CHECK: error: {{.*}}in function escape_via_store {{.*}}: a funcref can only be converted to a pointer to be directly called; the resulting pointer cannot otherwise be used
+define void @escape_via_store(%funcref %ref, ptr %dst) {
+ %p = call ptr @llvm.wasm.funcref.to_ptr(%funcref %ref)
+ store ptr %p, ptr %dst
+ ret void
+}
+
+; CHECK: error: {{.*}}in function escape_via_return {{.*}}: a funcref can only be converted to a pointer to be directly called; the resulting pointer cannot otherwise be used
+define ptr @escape_via_return(%funcref %ref) {
+ %p = call ptr @llvm.wasm.funcref.to_ptr(%funcref %ref)
+ ret ptr %p
+}
+
+; CHECK: error: {{.*}}in function escape_via_arg {{.*}}: a funcref can only be converted to a pointer to be directly called; the resulting pointer cannot otherwise be used
+define void @escape_via_arg(%funcref %ref) {
+ %p = call ptr @llvm.wasm.funcref.to_ptr(%funcref %ref)
+ call void @sink(ptr %p)
+ ret void
+}
diff --git a/llvm/test/CodeGen/WebAssembly/ref-null.ll b/llvm/test/CodeGen/WebAssembly/ref-null.ll
index af6ddfd8e0814..f2f2a0dad378b 100644
--- a/llvm/test/CodeGen/WebAssembly/ref-null.ll
+++ b/llvm/test/CodeGen/WebAssembly/ref-null.ll
@@ -1,8 +1,8 @@
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
; RUN: llc --mtriple=wasm32-unknown-unknown -mattr=+reference-types < %s | FileCheck %s
-%externref = type ptr addrspace(10) ;; addrspace 10 is nonintegral
-%funcref = type ptr addrspace(20) ;; addrspace 20 is nonintegral
+%externref = type target("wasm.externref")
+%funcref = type target("wasm.funcref")
declare %externref @llvm.wasm.ref.null.extern() nounwind
declare %funcref @llvm.wasm.ref.null.func() nounwind
diff --git a/llvm/test/CodeGen/WebAssembly/ref-test-func.ll b/llvm/test/CodeGen/WebAssembly/ref-test-func.ll
index 4fda253d39fe3..745f55a45f8d7 100644
--- a/llvm/test/CodeGen/WebAssembly/ref-test-func.ll
+++ b/llvm/test/CodeGen/WebAssembly/ref-test-func.ll
@@ -3,16 +3,24 @@
; RUN: llc < %s --mtriple=wasm64-unknown-unknown -mcpu=mvp -mattr=+reference-types -mattr=+gc -verify-machineinstrs | FileCheck --check-prefixes CHECK,CHK64 %s
define void @test_fpsig_void_void(ptr noundef %func) local_unnamed_addr #0 {
-; CHECK-LABEL: test_fpsig_void_void:
+; CHK32-LABEL: test_fpsig_void_void:
; CHK32: .functype test_fpsig_void_void (i32) -> ()
+; CHK32-NEXT: # %bb.0: # %entry
+; CHK32-NEXT: local.get 0
+; CHK32-NEXT: table.get __indirect_function_table
+; CHK32-NEXT: ref.test () -> ()
+; CHK32-NEXT: call use
+; CHK32-NEXT: # fallthrough-return
+;
+; CHK64-LABEL: test_fpsig_void_void:
; CHK64: .functype test_fpsig_void_void (i64) -> ()
-; CHECK-NEXT: # %bb.0: # %entry
-; CHECK-NEXT: local.get 0
+; CHK64-NEXT: # %bb.0: # %entry
+; CHK64-NEXT: local.get 0
; CHK64-NEXT: i32.wrap_i64
-; CHECK-NEXT: table.get __indirect_function_table
-; CHECK-NEXT: ref.test () -> ()
-; CHECK-NEXT: call use
-; CHECK-NEXT: # fallthrough-return
+; CHK64-NEXT: table.get __indirect_function_table
+; CHK64-NEXT: ref.test () -> ()
+; CHK64-NEXT: call use
+; CHK64-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func)
tail call void @use(i32 noundef %res) #3
@@ -20,16 +28,24 @@ entry:
}
define void @test_fpsig_return_i32(ptr noundef %func) local_unnamed_addr #0 {
-; CHECK-LABEL: test_fpsig_return_i32:
+; CHK32-LABEL: test_fpsig_return_i32:
; CHK32: .functype test_fpsig_return_i32 (i32) -> ()
+; CHK32-NEXT: # %bb.0: # %entry
+; CHK32-NEXT: local.get 0
+; CHK32-NEXT: table.get __indirect_function_table
+; CHK32-NEXT: ref.test () -> (i32)
+; CHK32-NEXT: call use
+; CHK32-NEXT: # fallthrough-return
+;
+; CHK64-LABEL: test_fpsig_return_i32:
; CHK64: .functype test_fpsig_return_i32 (i64) -> ()
-; CHECK-NEXT: # %bb.0: # %entry
-; CHECK-NEXT: local.get 0
+; CHK64-NEXT: # %bb.0: # %entry
+; CHK64-NEXT: local.get 0
; CHK64-NEXT: i32.wrap_i64
-; CHECK-NEXT: table.get __indirect_function_table
-; CHECK-NEXT: ref.test () -> (i32)
-; CHECK-NEXT: call use
-; CHECK-NEXT: # fallthrough-return
+; CHK64-NEXT: table.get __indirect_function_table
+; CHK64-NEXT: ref.test () -> (i32)
+; CHK64-NEXT: call use
+; CHK64-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i32 poison)
tail call void @use(i32 noundef %res) #3
@@ -37,16 +53,24 @@ entry:
}
define void @test_fpsig_return_i64(ptr noundef %func) local_unnamed_addr #0 {
-; CHECK-LABEL: test_fpsig_return_i64:
+; CHK32-LABEL: test_fpsig_return_i64:
; CHK32: .functype test_fpsig_return_i64 (i32) -> ()
+; CHK32-NEXT: # %bb.0: # %entry
+; CHK32-NEXT: local.get 0
+; CHK32-NEXT: table.get __indirect_function_table
+; CHK32-NEXT: ref.test () -> (i64)
+; CHK32-NEXT: call use
+; CHK32-NEXT: # fallthrough-return
+;
+; CHK64-LABEL: test_fpsig_return_i64:
; CHK64: .functype test_fpsig_return_i64 (i64) -> ()
-; CHECK-NEXT: # %bb.0: # %entry
-; CHECK-NEXT: local.get 0
+; CHK64-NEXT: # %bb.0: # %entry
+; CHK64-NEXT: local.get 0
; CHK64-NEXT: i32.wrap_i64
-; CHECK-NEXT: table.get __indirect_function_table
-; CHECK-NEXT: ref.test () -> (i64)
-; CHECK-NEXT: call use
-; CHECK-NEXT: # fallthrough-return
+; CHK64-NEXT: table.get __indirect_function_table
+; CHK64-NEXT: ref.test () -> (i64)
+; CHK64-NEXT: call use
+; CHK64-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i64 poison)
tail call void @use(i32 noundef %res) #3
@@ -54,16 +78,24 @@ entry:
}
define void @test_fpsig_return_f32(ptr noundef %func) local_unnamed_addr #0 {
-; CHECK-LABEL: test_fpsig_return_f32:
+; CHK32-LABEL: test_fpsig_return_f32:
; CHK32: .functype test_fpsig_return_f32 (i32) -> ()
+; CHK32-NEXT: # %bb.0: # %entry
+; CHK32-NEXT: local.get 0
+; CHK32-NEXT: table.get __indirect_function_table
+; CHK32-NEXT: ref.test () -> (f32)
+; CHK32-NEXT: call use
+; CHK32-NEXT: # fallthrough-return
+;
+; CHK64-LABEL: test_fpsig_return_f32:
; CHK64: .functype test_fpsig_return_f32 (i64) -> ()
-; CHECK-NEXT: # %bb.0: # %entry
-; CHECK-NEXT: local.get 0
+; CHK64-NEXT: # %bb.0: # %entry
+; CHK64-NEXT: local.get 0
; CHK64-NEXT: i32.wrap_i64
-; CHECK-NEXT: table.get __indirect_function_table
-; CHECK-NEXT: ref.test () -> (f32)
-; CHECK-NEXT: call use
-; CHECK-NEXT: # fallthrough-return
+; CHK64-NEXT: table.get __indirect_function_table
+; CHK64-NEXT: ref.test () -> (f32)
+; CHK64-NEXT: call use
+; CHK64-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, float poison)
tail call void @use(i32 noundef %res) #3
@@ -71,16 +103,24 @@ entry:
}
define void @test_fpsig_return_f64(ptr noundef %func) local_unnamed_addr #0 {
-; CHECK-LABEL: test_fpsig_return_f64:
+; CHK32-LABEL: test_fpsig_return_f64:
; CHK32: .functype test_fpsig_return_f64 (i32) -> ()
+; CHK32-NEXT: # %bb.0: # %entry
+; CHK32-NEXT: local.get 0
+; CHK32-NEXT: table.get __indirect_function_table
+; CHK32-NEXT: ref.test () -> (f64)
+; CHK32-NEXT: call use
+; CHK32-NEXT: # fallthrough-return
+;
+; CHK64-LABEL: test_fpsig_return_f64:
; CHK64: .functype test_fpsig_return_f64 (i64) -> ()
-; CHECK-NEXT: # %bb.0: # %entry
-; CHECK-NEXT: local.get 0
+; CHK64-NEXT: # %bb.0: # %entry
+; CHK64-NEXT: local.get 0
; CHK64-NEXT: i32.wrap_i64
-; CHECK-NEXT: table.get __indirect_function_table
-; CHECK-NEXT: ref.test () -> (f64)
-; CHECK-NEXT: call use
-; CHECK-NEXT: # fallthrough-return
+; CHK64-NEXT: table.get __indirect_function_table
+; CHK64-NEXT: ref.test () -> (f64)
+; CHK64-NEXT: call use
+; CHK64-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, double poison)
tail call void @use(i32 noundef %res) #3
@@ -89,16 +129,24 @@ entry:
define void @test_fpsig_param_i32(ptr noundef %func) local_unnamed_addr #0 {
-; CHECK-LABEL: test_fpsig_param_i32:
+; CHK32-LABEL: test_fpsig_param_i32:
; CHK32: .functype test_fpsig_param_i32 (i32) -> ()
+; CHK32-NEXT: # %bb.0: # %entry
+; CHK32-NEXT: local.get 0
+; CHK32-NEXT: table.get __indirect_function_table
+; CHK32-NEXT: ref.test (f64) -> ()
+; CHK32-NEXT: call use
+; CHK32-NEXT: # fallthrough-return
+;
+; CHK64-LABEL: test_fpsig_param_i32:
; CHK64: .functype test_fpsig_param_i32 (i64) -> ()
-; CHECK-NEXT: # %bb.0: # %entry
-; CHECK-NEXT: local.get 0
+; CHK64-NEXT: # %bb.0: # %entry
+; CHK64-NEXT: local.get 0
; CHK64-NEXT: i32.wrap_i64
-; CHECK-NEXT: table.get __indirect_function_table
-; CHECK-NEXT: ref.test (f64) -> ()
-; CHECK-NEXT: call use
-; CHECK-NEXT: # fallthrough-return
+; CHK64-NEXT: table.get __indirect_function_table
+; CHK64-NEXT: ref.test (f64) -> ()
+; CHK64-NEXT: call use
+; CHK64-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, double poison)
tail call void @use(i32 noundef %res) #3
@@ -107,16 +155,24 @@ entry:
define void @test_fpsig_multiple_params_and_returns(ptr noundef %func) local_unnamed_addr #0 {
-; CHECK-LABEL: test_fpsig_multiple_params_and_returns:
+; CHK32-LABEL: test_fpsig_multiple_params_and_returns:
; CHK32: .functype test_fpsig_multiple_params_and_returns (i32) -> ()
+; CHK32-NEXT: # %bb.0: # %entry
+; CHK32-NEXT: local.get 0
+; CHK32-NEXT: table.get __indirect_function_table
+; CHK32-NEXT: ref.test (i64, f32, i64) -> (i32, i64, f32, f64)
+; CHK32-NEXT: call use
+; CHK32-NEXT: # fallthrough-return
+;
+; CHK64-LABEL: test_fpsig_multiple_params_and_returns:
; CHK64: .functype test_fpsig_multiple_params_and_returns (i64) -> ()
-; CHECK-NEXT: # %bb.0: # %entry
-; CHECK-NEXT: local.get 0
+; CHK64-NEXT: # %bb.0: # %entry
+; CHK64-NEXT: local.get 0
; CHK64-NEXT: i32.wrap_i64
-; CHECK-NEXT: table.get __indirect_function_table
-; CHECK-NEXT: ref.test (i64, f32, i64) -> (i32, i64, f32, f64)
-; CHECK-NEXT: call use
-; CHECK-NEXT: # fallthrough-return
+; CHK64-NEXT: table.get __indirect_function_table
+; CHK64-NEXT: ref.test (i64, f32, i64) -> (i32, i64, f32, f64)
+; CHK64-NEXT: call use
+; CHK64-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i32 poison, i64 poison, float poison, double poison, token poison, i64 poison, float poison, i64 poison)
tail call void @use(i32 noundef %res) #3
@@ -125,17 +181,24 @@ entry:
define void @test_fpsig_ptrs(ptr noundef %func) local_unnamed_addr #0 {
-; CHECK-LABEL: test_fpsig_ptrs:
+; CHK32-LABEL: test_fpsig_ptrs:
; CHK32: .functype test_fpsig_ptrs (i32) -> ()
+; CHK32-NEXT: # %bb.0: # %entry
+; CHK32-NEXT: local.get 0
+; CHK32-NEXT: table.get __indirect_function_table
+; CHK32-NEXT: ref.test (i32, i32) -> (i32)
+; CHK32-NEXT: call use
+; CHK32-NEXT: # fallthrough-return
+;
+; CHK64-LABEL: test_fpsig_ptrs:
; CHK64: .functype test_fpsig_ptrs (i64) -> ()
-; CHECK-NEXT: # %bb.0: # %entry
-; CHECK-NEXT: local.get 0
+; CHK64-NEXT: # %bb.0: # %entry
+; CHK64-NEXT: local.get 0
; CHK64-NEXT: i32.wrap_i64
-; CHECK-NEXT: table.get __indirect_function_table
-; CHK32-NEXT: ref.test (i32, i32) -> (i32)
+; CHK64-NEXT: table.get __indirect_function_table
; CHK64-NEXT: ref.test (i64, i64) -> (i64)
-; CHECK-NEXT: call use
-; CHECK-NEXT: # fallthrough-return
+; CHK64-NEXT: call use
+; CHK64-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, ptr poison, token poison, ptr poison, ptr poison)
tail call void @use(i32 noundef %res) #3
@@ -143,20 +206,30 @@ entry:
}
define void @test_reference_types(ptr noundef %func) local_unnamed_addr #0 {
-; CHECK-LABEL: test_reference_types:
+; CHK32-LABEL: test_reference_types:
; CHK32: .functype test_reference_types (i32) -> ()
+; CHK32-NEXT: # %bb.0: # %entry
+; CHK32-NEXT: local.get 0
+; CHK32-NEXT: table.get __indirect_function_table
+; CHK32-NEXT: ref.test (funcref, externref) -> (externref)
+; CHK32-NEXT: call use
+; CHK32-NEXT: # fallthrough-return
+;
+; CHK64-LABEL: test_reference_types:
; CHK64: .functype test_reference_types (i64) -> ()
-; CHECK-NEXT: # %bb.0: # %entry
-; CHECK-NEXT: local.get 0
+; CHK64-NEXT: # %bb.0: # %entry
+; CHK64-NEXT: local.get 0
; CHK64-NEXT: i32.wrap_i64
-; CHECK-NEXT: table.get __indirect_function_table
-; CHECK-NEXT: ref.test (funcref, externref) -> (externref)
-; CHECK-NEXT: call use
-; CHECK-NEXT: # fallthrough-return
+; CHK64-NEXT: table.get __indirect_function_table
+; CHK64-NEXT: ref.test (funcref, externref) -> (externref)
+; CHK64-NEXT: call use
+; CHK64-NEXT: # fallthrough-return
entry:
- %res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, ptr addrspace(10) poison, token poison, ptr addrspace(20) poison, ptr addrspace(10) poison)
+ %res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, target("wasm.externref") poison, token poison, target("wasm.funcref") poison, target("wasm.externref") poison)
tail call void @use(i32 noundef %res) #3
ret void
}
declare void @use(i32 noundef) local_unnamed_addr #1
+;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
+; CHECK: {{.*}}
diff --git a/llvm/test/CodeGen/WebAssembly/ref-type-mem2local.ll b/llvm/test/CodeGen/WebAssembly/ref-type-mem2local.ll
index 911e5bb516a2f..75b3002b3be76 100644
--- a/llvm/test/CodeGen/WebAssembly/ref-type-mem2local.ll
+++ b/llvm/test/CodeGen/WebAssembly/ref-type-mem2local.ll
@@ -3,8 +3,8 @@
target triple = "wasm32-unknown-unknown"
-%externref = type ptr addrspace(10)
-%funcref = type ptr addrspace(20)
+%externref = type target("wasm.externref")
+%funcref = type target("wasm.funcref")
declare %externref @get_externref()
declare %funcref @get_funcref()
@@ -22,22 +22,22 @@ entry:
store %externref %eref, ptr %alloc.externref, align 1
%eref.loaded = load %externref, ptr %alloc.externref, align 1
call void @take_externref(%externref %eref.loaded)
- ; CHECK: %alloc.externref.var = alloca ptr addrspace(10), align 1, addrspace(1)
- ; CHECK-NEXT: %eref = call ptr addrspace(10) @get_externref()
- ; CHECK-NEXT: store ptr addrspace(10) %eref, ptr addrspace(1) %alloc.externref.var, align 1
- ; CHECK-NEXT: %eref.loaded = load ptr addrspace(10), ptr addrspace(1) %alloc.externref.var, align 1
- ; CHECK-NEXT: call void @take_externref(ptr addrspace(10) %eref.loaded)
+ ; CHECK: %alloc.externref.var = alloca target("wasm.externref"), align 1, addrspace(1)
+ ; CHECK-NEXT: %eref = call target("wasm.externref") @get_externref()
+ ; CHECK-NEXT: store target("wasm.externref") %eref, ptr addrspace(1) %alloc.externref.var, align 1
+ ; CHECK-NEXT: %eref.loaded = load target("wasm.externref"), ptr addrspace(1) %alloc.externref.var, align 1
+ ; CHECK-NEXT: call void @take_externref(target("wasm.externref") %eref.loaded)
%alloc.funcref = alloca %funcref, align 1
%fref = call %funcref @get_funcref()
store %funcref %fref, ptr %alloc.funcref, align 1
%fref.loaded = load %funcref, ptr %alloc.funcref, align 1
call void @take_funcref(%funcref %fref.loaded)
- ; CHECK-NEXT: %alloc.funcref.var = alloca ptr addrspace(20), align 1, addrspace(1)
- ; CHECK-NEXT: %fref = call ptr addrspace(20) @get_funcref()
- ; CHECK-NEXT: store ptr addrspace(20) %fref, ptr addrspace(1) %alloc.funcref.var, align 1
- ; CHECK-NEXT: %fref.loaded = load ptr addrspace(20), ptr addrspace(1) %alloc.funcref.var, align 1
- ; CHECK-NEXT: call void @take_funcref(ptr addrspace(20) %fref.loaded)
+ ; CHECK-NEXT: %alloc.funcref.var = alloca target("wasm.funcref"), align 1, addrspace(1)
+ ; CHECK-NEXT: %fref = call target("wasm.funcref") @get_funcref()
+ ; CHECK-NEXT: store target("wasm.funcref") %fref, ptr addrspace(1) %alloc.funcref.var, align 1
+ ; CHECK-NEXT: %fref.loaded = load target("wasm.funcref"), ptr addrspace(1) %alloc.funcref.var, align 1
+ ; CHECK-NEXT: call void @take_funcref(target("wasm.funcref") %fref.loaded)
ret void
}
@@ -68,22 +68,22 @@ entry:
store %externref %eref, ptr %alloc.externref, align 1
%eref.loaded = load %externref, ptr %alloc.externref, align 1
call void @take_externref(%externref %eref.loaded)
- ; ATTR: %alloc.externref.var = alloca ptr addrspace(10), align 1, addrspace(1)
- ; ATTR-NEXT: %eref = call ptr addrspace(10) @get_externref()
- ; ATTR-NEXT: store ptr addrspace(10) %eref, ptr addrspace(1) %alloc.externref.var, align 1
- ; ATTR-NEXT: %eref.loaded = load ptr addrspace(10), ptr addrspace(1) %alloc.externref.var, align 1
- ; ATTR-NEXT: call void @take_externref(ptr addrspace(10) %eref.loaded)
+ ; ATTR: %alloc.externref.var = alloca target("wasm.externref"), align 1, addrspace(1)
+ ; ATTR-NEXT: %eref = call target("wasm.externref") @get_externref()
+ ; ATTR-NEXT: store target("wasm.externref") %eref, ptr addrspace(1) %alloc.externref.var, align 1
+ ; ATTR-NEXT: %eref.loaded = load target("wasm.externref"), ptr addrspace(1) %alloc.externref.var, align 1
+ ; ATTR-NEXT: call void @take_externref(target("wasm.externref") %eref.loaded)
%alloc.funcref = alloca %funcref, align 1
%fref = call %funcref @get_funcref()
store %funcref %fref, ptr %alloc.funcref, align 1
%fref.loaded = load %funcref, ptr %alloc.funcref, align 1
call void @take_funcref(%funcref %fref.loaded)
- ; ATTR-NEXT: %alloc.funcref.var = alloca ptr addrspace(20), align 1, addrspace(1)
- ; ATTR-NEXT: %fref = call ptr addrspace(20) @get_funcref()
- ; ATTR-NEXT: store ptr addrspace(20) %fref, ptr addrspace(1) %alloc.funcref.var, align 1
- ; ATTR-NEXT: %fref.loaded = load ptr addrspace(20), ptr addrspace(1) %alloc.funcref.var, align 1
- ; ATTR-NEXT: call void @take_funcref(ptr addrspace(20) %fref.loaded)
+ ; ATTR-NEXT: %alloc.funcref.var = alloca target("wasm.funcref"), align 1, addrspace(1)
+ ; ATTR-NEXT: %fref = call target("wasm.funcref") @get_funcref()
+ ; ATTR-NEXT: store target("wasm.funcref") %fref, ptr addrspace(1) %alloc.funcref.var, align 1
+ ; ATTR-NEXT: %fref.loaded = load target("wasm.funcref"), ptr addrspace(1) %alloc.funcref.var, align 1
+ ; ATTR-NEXT: call void @take_funcref(target("wasm.funcref") %fref.loaded)
ret void
}
diff --git a/llvm/test/CodeGen/WebAssembly/select-reftype.ll b/llvm/test/CodeGen/WebAssembly/select-reftype.ll
index baca3ca6258a3..985796048fb47 100644
--- a/llvm/test/CodeGen/WebAssembly/select-reftype.ll
+++ b/llvm/test/CodeGen/WebAssembly/select-reftype.ll
@@ -5,46 +5,46 @@
target triple = "wasm32-unknown-unknown"
-define ptr addrspace(10) @select_externref_eq(i32 %a, ptr addrspace(10) %b, ptr addrspace(10) %c) {
+define target("wasm.externref") @select_externref_eq(i32 %a, target("wasm.externref") %b, target("wasm.externref") %c) {
; CHECK-LABEL: select_externref_eq:
; CHECK: .functype select_externref_eq (i32, externref, externref) -> (externref)
; CHECK-NEXT: # %bb.0:
; CHECK-NEXT: externref.select $push0=, $2, $1, $0
; CHECK-NEXT: return $pop0
%cmp = icmp eq i32 %a, 0
- %cond = select i1 %cmp, ptr addrspace(10) %b, ptr addrspace(10) %c
- ret ptr addrspace(10) %cond
+ %cond = select i1 %cmp, target("wasm.externref") %b, target("wasm.externref") %c
+ ret target("wasm.externref") %cond
}
-define ptr addrspace(10) @select_externref_ne(i32 %a, ptr addrspace(10) %b, ptr addrspace(10) %c) {
+define target("wasm.externref") @select_externref_ne(i32 %a, target("wasm.externref") %b, target("wasm.externref") %c) {
; CHECK-LABEL: select_externref_ne:
; CHECK: .functype select_externref_ne (i32, externref, externref) -> (externref)
; CHECK-NEXT: # %bb.0:
; CHECK-NEXT: externref.select $push0=, $1, $2, $0
; CHECK-NEXT: return $pop0
%cmp = icmp ne i32 %a, 0
- %cond = select i1 %cmp, ptr addrspace(10) %b, ptr addrspace(10) %c
- ret ptr addrspace(10) %cond
+ %cond = select i1 %cmp, target("wasm.externref") %b, target("wasm.externref") %c
+ ret target("wasm.externref") %cond
}
-define ptr addrspace(20) @select_funcref_eq(i32 %a, ptr addrspace(20) %b, ptr addrspace(20) %c) {
+define target("wasm.funcref") @select_funcref_eq(i32 %a, target("wasm.funcref") %b, target("wasm.funcref") %c) {
; CHECK-LABEL: select_funcref_eq:
; CHECK: .functype select_funcref_eq (i32, funcref, funcref) -> (funcref)
; CHECK-NEXT: # %bb.0:
; CHECK-NEXT: funcref.select $push0=, $2, $1, $0
; CHECK-NEXT: return $pop0
%cmp = icmp eq i32 %a, 0
- %cond = select i1 %cmp, ptr addrspace(20) %b, ptr addrspace(20) %c
- ret ptr addrspace(20) %cond
+ %cond = select i1 %cmp, target("wasm.funcref") %b, target("wasm.funcref") %c
+ ret target("wasm.funcref") %cond
}
-define ptr addrspace(20) @select_funcref_ne(i32 %a, ptr addrspace(20) %b, ptr addrspace(20) %c) {
+define target("wasm.funcref") @select_funcref_ne(i32 %a, target("wasm.funcref") %b, target("wasm.funcref") %c) {
; CHECK-LABEL: select_funcref_ne:
; CHECK: .functype select_funcref_ne (i32, funcref, funcref) -> (funcref)
; CHECK-NEXT: # %bb.0:
; CHECK-NEXT: funcref.select $push0=, $1, $2, $0
; CHECK-NEXT: return $pop0
%cmp = icmp ne i32 %a, 0
- %cond = select i1 %cmp, ptr addrspace(20) %b, ptr addrspace(20) %c
- ret ptr addrspace(20) %cond
+ %cond = select i1 %cmp, target("wasm.funcref") %b, target("wasm.funcref") %c
+ ret target("wasm.funcref") %cond
}
diff --git a/llvm/test/CodeGen/WebAssembly/table-copy.ll b/llvm/test/CodeGen/WebAssembly/table-copy.ll
index 5c0647ada4ab0..d1387428c4368 100644
--- a/llvm/test/CodeGen/WebAssembly/table-copy.ll
+++ b/llvm/test/CodeGen/WebAssembly/table-copy.ll
@@ -1,6 +1,6 @@
; RUN: llc --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types < %s | FileCheck %s
-%externref = type ptr addrspace(10) ;; addrspace 10 is nonintegral
+%externref = type target("wasm.externref")
@externref_table1 = local_unnamed_addr addrspace(1) global [0 x %externref] undef
@externref_table2 = local_unnamed_addr addrspace(1) global [0 x %externref] undef
diff --git a/llvm/test/CodeGen/WebAssembly/table-fill.ll b/llvm/test/CodeGen/WebAssembly/table-fill.ll
index 0b78124f038b1..7ed39361a1afd 100644
--- a/llvm/test/CodeGen/WebAssembly/table-fill.ll
+++ b/llvm/test/CodeGen/WebAssembly/table-fill.ll
@@ -1,6 +1,6 @@
; RUN: llc --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types < %s | FileCheck %s
-%externref = type ptr addrspace(10) ;; addrspace 10 is nonintegral
+%externref = type target("wasm.externref")
@externref_table = local_unnamed_addr addrspace(1) global [0 x %externref] undef
diff --git a/llvm/test/CodeGen/WebAssembly/table-grow.ll b/llvm/test/CodeGen/WebAssembly/table-grow.ll
index 614c3400a782b..2b5801ea2fa17 100644
--- a/llvm/test/CodeGen/WebAssembly/table-grow.ll
+++ b/llvm/test/CodeGen/WebAssembly/table-grow.ll
@@ -1,6 +1,6 @@
; RUN: llc --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types < %s | FileCheck %s
-%externref = type ptr addrspace(10) ;; addrspace 10 is nonintegral
+%externref = type target("wasm.externref")
@externref_table = local_unnamed_addr addrspace(1) global [0 x %externref] undef
diff --git a/llvm/test/CodeGen/WebAssembly/table-size.ll b/llvm/test/CodeGen/WebAssembly/table-size.ll
index 42cd2e8a909d7..2b8d0185bb049 100644
--- a/llvm/test/CodeGen/WebAssembly/table-size.ll
+++ b/llvm/test/CodeGen/WebAssembly/table-size.ll
@@ -1,6 +1,6 @@
; RUN: llc --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types < %s | FileCheck %s
-%externref = type ptr addrspace(10) ;; addrspace 10 is nonintegral
+%externref = type target("wasm.externref")
@externref_table = local_unnamed_addr addrspace(1) global [0 x %externref] undef
diff --git a/llvm/test/CodeGen/WebAssembly/table-types.ll b/llvm/test/CodeGen/WebAssembly/table-types.ll
index cb5e54e2af230..da04ba35dd58e 100644
--- a/llvm/test/CodeGen/WebAssembly/table-types.ll
+++ b/llvm/test/CodeGen/WebAssembly/table-types.ll
@@ -1,7 +1,7 @@
; RUN: llc < %s --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+reference-types | FileCheck %s
-%externref = type ptr addrspace(10) ;; addrspace 10 is nonintegral
-%funcref = type ptr addrspace(20) ;; addrspace 20 is nonintegral
+%externref = type target("wasm.externref")
+%funcref = type target("wasm.funcref")
; CHECK: .tabletype eref_table, externref
; CHECK-NEXT: .globl eref_table
diff --git a/llvm/utils/gn/secondary/llvm/lib/Target/WebAssembly/BUILD.gn b/llvm/utils/gn/secondary/llvm/lib/Target/WebAssembly/BUILD.gn
index f8bd3fa48a92d..67f9a904bb1e7 100644
--- a/llvm/utils/gn/secondary/llvm/lib/Target/WebAssembly/BUILD.gn
+++ b/llvm/utils/gn/secondary/llvm/lib/Target/WebAssembly/BUILD.gn
@@ -106,7 +106,6 @@ static_library("LLVMWebAssemblyCodeGen") {
"WebAssemblyLateEHPrepare.cpp",
"WebAssemblyLowerBrUnless.cpp",
"WebAssemblyLowerEmscriptenEHSjLj.cpp",
- "WebAssemblyLowerRefTypesIntPtrConv.cpp",
"WebAssemblyMCInstLower.cpp",
"WebAssemblyMCLowerPrePass.cpp",
"WebAssemblyMachineFunctionInfo.cpp",
>From 5ba79f5bfba7f6cf9311116d7718a4edcc6eaf05 Mon Sep 17 00:00:00 2001
From: Hood Chatham <roberthoodchatham at gmail.com>
Date: Thu, 11 Jun 2026 07:29:43 -0700
Subject: [PATCH 2/9] Remove unneeded llvm:: namespace qualifier
there is a using namespace llvm
---
.../WebAssembly/GISel/WebAssemblyCallLowering.cpp | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/llvm/lib/Target/WebAssembly/GISel/WebAssemblyCallLowering.cpp b/llvm/lib/Target/WebAssembly/GISel/WebAssemblyCallLowering.cpp
index 3241e30789b97..848796601b256 100644
--- a/llvm/lib/Target/WebAssembly/GISel/WebAssemblyCallLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/GISel/WebAssemblyCallLowering.cpp
@@ -66,7 +66,7 @@ static bool typeContainsReference(const Type *Ty) {
if (const auto *ArrTy = dyn_cast<ArrayType>(Ty))
return typeContainsReference(ArrTy->getElementType());
if (const auto *StructTy = dyn_cast<StructType>(Ty))
- return llvm::any_of(StructTy->elements(), [](const Type *ElemTy) {
+ return any_of(StructTy->elements(), [](const Type *ElemTy) {
return typeContainsReference(ElemTy);
});
return false;
@@ -133,7 +133,7 @@ bool WebAssemblyCallLowering::lowerReturn(MachineIRBuilder &MIRBuilder,
TLI.getRegisterTypeForCallingConv(Ctx, CallConv, OrigVT);
const LLT OrigLLT =
getLLTForType(*OrigVT.getTypeForEVT(F.getContext()), DL);
- const LLT NewLLT = llvm::getLLTForMVT(NewVT);
+ const LLT NewLLT = getLLTForMVT(NewVT);
const TargetRegisterClass &NewRegClass = *TLI.getRegClassFor(NewVT);
@@ -313,7 +313,7 @@ bool WebAssemblyCallLowering::lowerFormalArguments(
const MVT NewVT = TLI.getRegisterTypeForCallingConv(Ctx, CallConv, OrigVT);
const LLT OrigLLT =
getLLTForType(*OrigVT.getTypeForEVT(F.getContext()), DL);
- const LLT NewLLT = llvm::getLLTForMVT(NewVT);
+ const LLT NewLLT = getLLTForMVT(NewVT);
// If we need to split the type over multiple regs, check it's a scenario
// we currently support.
@@ -467,7 +467,7 @@ bool WebAssemblyCallLowering::lowerCall(MachineIRBuilder &MIRBuilder,
const MVT NewVT = TLI.getRegisterTypeForCallingConv(Ctx, CallConv, OrigVT);
const LLT OrigLLT =
getLLTForType(*OrigVT.getTypeForEVT(F.getContext()), DL);
- const LLT NewLLT = llvm::getLLTForMVT(NewVT);
+ const LLT NewLLT = getLLTForMVT(NewVT);
const TargetRegisterClass &NewRegClass = *TLI.getRegClassFor(NewVT);
@@ -563,7 +563,7 @@ bool WebAssemblyCallLowering::lowerCall(MachineIRBuilder &MIRBuilder,
TLI.getRegisterTypeForCallingConv(Ctx, CallConv, OrigVT);
const LLT OrigLLT =
getLLTForType(*OrigVT.getTypeForEVT(F.getContext()), DL);
- const LLT NewLLT = llvm::getLLTForMVT(NewVT);
+ const LLT NewLLT = getLLTForMVT(NewVT);
const TargetRegisterClass &NewRegClass = *TLI.getRegClassFor(NewVT);
>From 32f2c038d1e2b951bf6e9f322fb0f45d141d0368 Mon Sep 17 00:00:00 2001
From: Hood Chatham <roberthoodchatham at gmail.com>
Date: Thu, 11 Jun 2026 09:07:18 -0700
Subject: [PATCH 3/9] Implement zero initializers for externref and funcref
---
.../SelectionDAG/SelectionDAGBuilder.cpp | 10 +++
.../WebAssembly/ref-null-zeroinitializer.ll | 67 +++++++++++++++++++
2 files changed, 77 insertions(+)
create mode 100644 llvm/test/CodeGen/WebAssembly/ref-null-zeroinitializer.ll
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
index 52af36014c5b3..80d0bf5f2440c 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -1978,6 +1978,16 @@ SDValue SelectionDAGBuilder::getValueImpl(const Value *V) {
DAG.getConstant(0, getCurSDLoc(), MVT::getIntegerVT(8))));
}
+ if (VT == MVT::externref || VT == MVT::funcref) {
+ assert(C->isNullValue() && "Can only zero this target type!");
+ // The zero value of a WebAssembly reference type is the null reference,
+ // materialized with ref.null.
+ Intrinsic::ID IID = VT == MVT::externref ? Intrinsic::wasm_ref_null_extern
+ : Intrinsic::wasm_ref_null_func;
+ return DAG.getNode(ISD::INTRINSIC_WO_CHAIN, getCurSDLoc(), VT,
+ DAG.getTargetConstant(IID, getCurSDLoc(), MVT::i32));
+ }
+
VectorType *VecTy = cast<VectorType>(V->getType());
// Now that we know the number and type of the elements, get that number of
diff --git a/llvm/test/CodeGen/WebAssembly/ref-null-zeroinitializer.ll b/llvm/test/CodeGen/WebAssembly/ref-null-zeroinitializer.ll
new file mode 100644
index 0000000000000..7483a72d0b96c
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/ref-null-zeroinitializer.ll
@@ -0,0 +1,67 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc < %s --mtriple=wasm32-unknown-unknown -mattr=+reference-types | FileCheck %s
+
+; The zero value of a WebAssembly reference type is the null reference, so a
+; zeroinitializer (or null) reference constant must lower to ref.null rather
+; than crash instruction selection.
+
+%externref = type target("wasm.externref")
+%funcref = type target("wasm.funcref")
+
+declare void @take_externref(%externref)
+declare i32 @llvm.wasm.ref.is_null.extern(%externref)
+
+define %externref @ret_zero_externref() {
+; CHECK-LABEL: ret_zero_externref:
+; CHECK: .functype ret_zero_externref () -> (externref)
+; CHECK-NEXT: # %bb.0:
+; CHECK-NEXT: ref.null_extern
+; CHECK-NEXT: # fallthrough-return
+ ret %externref zeroinitializer
+}
+
+define %funcref @ret_zero_funcref() {
+; CHECK-LABEL: ret_zero_funcref:
+; CHECK: .functype ret_zero_funcref () -> (funcref)
+; CHECK-NEXT: # %bb.0:
+; CHECK-NEXT: ref.null_func
+; CHECK-NEXT: # fallthrough-return
+ ret %funcref zeroinitializer
+}
+
+define void @pass_zero_externref() {
+; CHECK-LABEL: pass_zero_externref:
+; CHECK: .functype pass_zero_externref () -> ()
+; CHECK-NEXT: # %bb.0:
+; CHECK-NEXT: ref.null_extern
+; CHECK-NEXT: call take_externref
+; CHECK-NEXT: # fallthrough-return
+ call void @take_externref(%externref zeroinitializer)
+ ret void
+}
+
+define %externref @select_zero_externref(i1 %c, %externref %x) {
+; CHECK-LABEL: select_zero_externref:
+; CHECK: .functype select_zero_externref (i32, externref) -> (externref)
+; CHECK-NEXT: # %bb.0:
+; CHECK-NEXT: local.get 1
+; CHECK-NEXT: ref.null_extern
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: i32.const 1
+; CHECK-NEXT: i32.and
+; CHECK-NEXT: externref.select
+; CHECK-NEXT: # fallthrough-return
+ %r = select i1 %c, %externref %x, %externref zeroinitializer
+ ret %externref %r
+}
+
+define i32 @is_null_zero_externref() {
+; CHECK-LABEL: is_null_zero_externref:
+; CHECK: .functype is_null_zero_externref () -> (i32)
+; CHECK-NEXT: # %bb.0:
+; CHECK-NEXT: ref.null_extern
+; CHECK-NEXT: ref.is_null
+; CHECK-NEXT: # fallthrough-return
+ %r = call i32 @llvm.wasm.ref.is_null.extern(%externref zeroinitializer)
+ ret i32 %r
+}
>From fdcf148db1df4e1d4a12df985998b0f6868ff8fb Mon Sep 17 00:00:00 2001
From: Hood Chatham <roberthoodchatham at gmail.com>
Date: Thu, 11 Jun 2026 10:47:44 -0700
Subject: [PATCH 4/9] Implement funcref calls in FastISel
---
.../CodeGen/SelectionDAG/SelectionDAGISel.cpp | 23 +-----
.../WebAssembly/WebAssemblyFastISel.cpp | 70 ++++++++++++++++---
llvm/test/CodeGen/WebAssembly/funcref-call.ll | 5 +-
3 files changed, 66 insertions(+), 32 deletions(-)
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
index 1b479c2b36058..5ae52cae771fb 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
@@ -230,26 +230,9 @@ static bool dontUseFastISelFor(const Function &Fn) {
// Debug info on those is reliant on good Argument lowering, and FastISel is
// not capable of lowering the entire function. Mixing the two selectors tend
// to result in poor lowering of Arguments.
- if (any_of(Fn.args(), [](const Argument &Arg) {
- return Arg.hasAttribute(Attribute::AttrKind::SwiftAsync);
- }))
- return true;
-
- // A WebAssembly funcref call is expressed in IR as a call through the pointer
- // produced by the llvm.wasm.funcref.to_ptr intrinsic. SelectionDAG fuses the
- // intrinsic and the call into a table.set + call_indirect through the
- // dedicated __funcref_call_table. FastISel selects each call as its own
- // single-instruction block and therefore cannot perform this fusion, so fall
- // back to SelectionDAG for the whole function when the intrinsic is present.
- if (Fn.getParent()->getTargetTriple().isWasm()) {
- for (const BasicBlock &BB : Fn)
- for (const Instruction &I : BB)
- if (const auto *II = dyn_cast<IntrinsicInst>(&I))
- if (II->getIntrinsicID() == Intrinsic::wasm_funcref_to_ptr)
- return true;
- }
-
- return false;
+ return any_of(Fn.args(), [](const Argument &Arg) {
+ return Arg.hasAttribute(Attribute::AttrKind::SwiftAsync);
+ });
}
static bool maintainPGOProfile(const TargetMachine &TM,
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp
index b107886a1f16e..95f4367f76cdf 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp
@@ -35,6 +35,7 @@
#include "llvm/IR/GetElementPtrTypeIterator.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/Instructions.h"
+#include "llvm/IR/IntrinsicsWebAssembly.h"
#include "llvm/IR/Operator.h"
using namespace llvm;
@@ -833,11 +834,6 @@ bool WebAssemblyFastISel::fastLowerArguments() {
bool WebAssemblyFastISel::selectCall(const Instruction *I) {
const auto *Call = cast<CallInst>(I);
- // FastISel does not support calls through funcref
- if (Call->getCalledOperand()->getType()->getPointerAddressSpace() !=
- WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_DEFAULT)
- return false;
-
// TODO: Support tail calls in FastISel
if (Call->isMustTailCall() || Call->isInlineAsm() ||
Call->getFunctionType()->isVarArg())
@@ -942,10 +938,49 @@ bool WebAssemblyFastISel::selectCall(const Instruction *I) {
}
unsigned CalleeReg = 0;
+ // A call through a funcref is expressed as a call through the pointer
+ // produced by llvm.wasm.funcref.to_ptr. Recover the funcref operand, place it
+ // into __funcref_call_table, and call it.
+ //
+ // TODO: Use call_ref if wasm-gc feature is available, would lead to simpler
+ // code here.
+ const Value *FuncrefArg = nullptr;
+ if (const auto *Conv = dyn_cast<CallInst>(Call->getCalledOperand()))
+ if (Conv->getIntrinsicID() == Intrinsic::wasm_funcref_to_ptr)
+ FuncrefArg = Conv->getArgOperand(0);
+
+ const bool IsFuncrefCall = FuncrefArg != nullptr;
+ MCSymbolWasm *Table = nullptr;
+
if (!IsDirect) {
- CalleeReg = getRegForValue(Call->getCalledOperand());
- if (!CalleeReg)
- return false;
+ if (!IsFuncrefCall) {
+ // Table is ___indirect_function_table
+ Table = WebAssembly::getOrCreateFunctionTableSymbol(MF->getContext(),
+ Subtarget);
+ CalleeReg = getRegForValue(Call->getCalledOperand());
+ if (!CalleeReg)
+ return false;
+ } else {
+ // Table is __funcref_call_table
+ Table = WebAssembly::getOrCreateFuncrefCallTableSymbol(MF->getContext(),
+ Subtarget);
+ CalleeReg = getRegForValue(FuncrefArg);
+ // Put the funcref in slot 0 of __funcref_call_table
+ unsigned ZeroReg = createResultReg(&WebAssembly::I32RegClass);
+ BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD,
+ TII.get(WebAssembly::CONST_I32), ZeroReg)
+ .addImm(0);
+ BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD,
+ TII.get(WebAssembly::TABLE_SET_FUNCREF))
+ .addSym(Table)
+ .addReg(ZeroReg)
+ .addReg(CalleeReg);
+ // Set CalleeReg to an immediate 0
+ CalleeReg = createResultReg(&WebAssembly::I32RegClass);
+ BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD,
+ TII.get(WebAssembly::CONST_I32), CalleeReg)
+ .addImm(0);
+ }
}
auto MIB = BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD, TII.get(Opc));
@@ -958,9 +993,6 @@ bool WebAssemblyFastISel::selectCall(const Instruction *I) {
} else {
// Placeholder for the type index.
MIB.addImm(0);
- // The table into which this call_indirect indexes.
- MCSymbolWasm *Table = WebAssembly::getOrCreateFunctionTableSymbol(
- MF->getContext(), Subtarget);
if (Subtarget->hasCallIndirectOverlong()) {
MIB.addSym(Table);
} else {
@@ -978,6 +1010,22 @@ bool WebAssemblyFastISel::selectCall(const Instruction *I) {
if (!IsDirect)
MIB.addReg(CalleeReg);
+ if (IsFuncrefCall) {
+ // Clear slot 0 of the funcref call table after the call.
+ unsigned ZeroReg = createResultReg(&WebAssembly::I32RegClass);
+ BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD,
+ TII.get(WebAssembly::CONST_I32), ZeroReg)
+ .addImm(0);
+ unsigned NullReg = createResultReg(&WebAssembly::FUNCREFRegClass);
+ BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD,
+ TII.get(WebAssembly::REF_NULL_FUNCREF), NullReg);
+ BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD,
+ TII.get(WebAssembly::TABLE_SET_FUNCREF))
+ .addSym(Table)
+ .addReg(ZeroReg)
+ .addReg(NullReg);
+ }
+
if (!IsVoid)
updateValueMap(Call, ResultReg);
diff --git a/llvm/test/CodeGen/WebAssembly/funcref-call.ll b/llvm/test/CodeGen/WebAssembly/funcref-call.ll
index 66692ac0eb0f0..d8433d61a5db9 100644
--- a/llvm/test/CodeGen/WebAssembly/funcref-call.ll
+++ b/llvm/test/CodeGen/WebAssembly/funcref-call.ll
@@ -1,6 +1,9 @@
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
; RUN: llc < %s --mtriple=wasm32-unknown-unknown -fast-isel=0 -mattr=+reference-types | FileCheck %s
-; RUN: llc < %s --mtriple=wasm32-unknown-unknown -fast-isel=1 -mattr=+reference-types | FileCheck %s
+
+; Use -fast-isel-abort=3 (never fall back to SelectionDAG) to make sure that the
+; funcref call is selected by FastISel
+; RUN: llc < %s --mtriple=wasm32-unknown-unknown -fast-isel=1 -fast-isel-abort=3 -mattr=+reference-types | FileCheck %s
%funcref = type target("wasm.funcref")
>From e01a7807e89e090a6a702b3670ebedfea30f7fc7 Mon Sep 17 00:00:00 2001
From: Hood Chatham <roberthoodchatham at gmail.com>
Date: Thu, 11 Jun 2026 10:54:09 -0700
Subject: [PATCH 5/9] Trim ref-test-func back down
---
.../test/CodeGen/WebAssembly/ref-test-func.ll | 200 ++++++------------
1 file changed, 63 insertions(+), 137 deletions(-)
diff --git a/llvm/test/CodeGen/WebAssembly/ref-test-func.ll b/llvm/test/CodeGen/WebAssembly/ref-test-func.ll
index 745f55a45f8d7..f74c968abdadc 100644
--- a/llvm/test/CodeGen/WebAssembly/ref-test-func.ll
+++ b/llvm/test/CodeGen/WebAssembly/ref-test-func.ll
@@ -1,26 +1,17 @@
-; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
; RUN: llc < %s --mtriple=wasm32-unknown-unknown -mcpu=mvp -mattr=+reference-types -mattr=+gc -verify-machineinstrs | FileCheck --check-prefixes CHECK,CHK32 %s
; RUN: llc < %s --mtriple=wasm64-unknown-unknown -mcpu=mvp -mattr=+reference-types -mattr=+gc -verify-machineinstrs | FileCheck --check-prefixes CHECK,CHK64 %s
define void @test_fpsig_void_void(ptr noundef %func) local_unnamed_addr #0 {
-; CHK32-LABEL: test_fpsig_void_void:
+; CHECK-LABEL: test_fpsig_void_void:
; CHK32: .functype test_fpsig_void_void (i32) -> ()
-; CHK32-NEXT: # %bb.0: # %entry
-; CHK32-NEXT: local.get 0
-; CHK32-NEXT: table.get __indirect_function_table
-; CHK32-NEXT: ref.test () -> ()
-; CHK32-NEXT: call use
-; CHK32-NEXT: # fallthrough-return
-;
-; CHK64-LABEL: test_fpsig_void_void:
; CHK64: .functype test_fpsig_void_void (i64) -> ()
-; CHK64-NEXT: # %bb.0: # %entry
-; CHK64-NEXT: local.get 0
+; CHECK-NEXT: # %bb.0: # %entry
+; CHECK-NEXT: local.get 0
; CHK64-NEXT: i32.wrap_i64
-; CHK64-NEXT: table.get __indirect_function_table
-; CHK64-NEXT: ref.test () -> ()
-; CHK64-NEXT: call use
-; CHK64-NEXT: # fallthrough-return
+; CHECK-NEXT: table.get __indirect_function_table
+; CHECK-NEXT: ref.test () -> ()
+; CHECK-NEXT: call use
+; CHECK-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func)
tail call void @use(i32 noundef %res) #3
@@ -28,24 +19,16 @@ entry:
}
define void @test_fpsig_return_i32(ptr noundef %func) local_unnamed_addr #0 {
-; CHK32-LABEL: test_fpsig_return_i32:
+; CHECK-LABEL: test_fpsig_return_i32:
; CHK32: .functype test_fpsig_return_i32 (i32) -> ()
-; CHK32-NEXT: # %bb.0: # %entry
-; CHK32-NEXT: local.get 0
-; CHK32-NEXT: table.get __indirect_function_table
-; CHK32-NEXT: ref.test () -> (i32)
-; CHK32-NEXT: call use
-; CHK32-NEXT: # fallthrough-return
-;
-; CHK64-LABEL: test_fpsig_return_i32:
; CHK64: .functype test_fpsig_return_i32 (i64) -> ()
-; CHK64-NEXT: # %bb.0: # %entry
-; CHK64-NEXT: local.get 0
+; CHECK-NEXT: # %bb.0: # %entry
+; CHECK-NEXT: local.get 0
; CHK64-NEXT: i32.wrap_i64
-; CHK64-NEXT: table.get __indirect_function_table
-; CHK64-NEXT: ref.test () -> (i32)
-; CHK64-NEXT: call use
-; CHK64-NEXT: # fallthrough-return
+; CHECK-NEXT: table.get __indirect_function_table
+; CHECK-NEXT: ref.test () -> (i32)
+; CHECK-NEXT: call use
+; CHECK-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i32 poison)
tail call void @use(i32 noundef %res) #3
@@ -53,24 +36,16 @@ entry:
}
define void @test_fpsig_return_i64(ptr noundef %func) local_unnamed_addr #0 {
-; CHK32-LABEL: test_fpsig_return_i64:
+; CHECK-LABEL: test_fpsig_return_i64:
; CHK32: .functype test_fpsig_return_i64 (i32) -> ()
-; CHK32-NEXT: # %bb.0: # %entry
-; CHK32-NEXT: local.get 0
-; CHK32-NEXT: table.get __indirect_function_table
-; CHK32-NEXT: ref.test () -> (i64)
-; CHK32-NEXT: call use
-; CHK32-NEXT: # fallthrough-return
-;
-; CHK64-LABEL: test_fpsig_return_i64:
; CHK64: .functype test_fpsig_return_i64 (i64) -> ()
-; CHK64-NEXT: # %bb.0: # %entry
-; CHK64-NEXT: local.get 0
+; CHECK-NEXT: # %bb.0: # %entry
+; CHECK-NEXT: local.get 0
; CHK64-NEXT: i32.wrap_i64
-; CHK64-NEXT: table.get __indirect_function_table
-; CHK64-NEXT: ref.test () -> (i64)
-; CHK64-NEXT: call use
-; CHK64-NEXT: # fallthrough-return
+; CHECK-NEXT: table.get __indirect_function_table
+; CHECK-NEXT: ref.test () -> (i64)
+; CHECK-NEXT: call use
+; CHECK-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i64 poison)
tail call void @use(i32 noundef %res) #3
@@ -78,24 +53,16 @@ entry:
}
define void @test_fpsig_return_f32(ptr noundef %func) local_unnamed_addr #0 {
-; CHK32-LABEL: test_fpsig_return_f32:
+; CHECK-LABEL: test_fpsig_return_f32:
; CHK32: .functype test_fpsig_return_f32 (i32) -> ()
-; CHK32-NEXT: # %bb.0: # %entry
-; CHK32-NEXT: local.get 0
-; CHK32-NEXT: table.get __indirect_function_table
-; CHK32-NEXT: ref.test () -> (f32)
-; CHK32-NEXT: call use
-; CHK32-NEXT: # fallthrough-return
-;
-; CHK64-LABEL: test_fpsig_return_f32:
; CHK64: .functype test_fpsig_return_f32 (i64) -> ()
-; CHK64-NEXT: # %bb.0: # %entry
-; CHK64-NEXT: local.get 0
+; CHECK-NEXT: # %bb.0: # %entry
+; CHECK-NEXT: local.get 0
; CHK64-NEXT: i32.wrap_i64
-; CHK64-NEXT: table.get __indirect_function_table
-; CHK64-NEXT: ref.test () -> (f32)
-; CHK64-NEXT: call use
-; CHK64-NEXT: # fallthrough-return
+; CHECK-NEXT: table.get __indirect_function_table
+; CHECK-NEXT: ref.test () -> (f32)
+; CHECK-NEXT: call use
+; CHECK-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, float poison)
tail call void @use(i32 noundef %res) #3
@@ -103,24 +70,16 @@ entry:
}
define void @test_fpsig_return_f64(ptr noundef %func) local_unnamed_addr #0 {
-; CHK32-LABEL: test_fpsig_return_f64:
+; CHECK-LABEL: test_fpsig_return_f64:
; CHK32: .functype test_fpsig_return_f64 (i32) -> ()
-; CHK32-NEXT: # %bb.0: # %entry
-; CHK32-NEXT: local.get 0
-; CHK32-NEXT: table.get __indirect_function_table
-; CHK32-NEXT: ref.test () -> (f64)
-; CHK32-NEXT: call use
-; CHK32-NEXT: # fallthrough-return
-;
-; CHK64-LABEL: test_fpsig_return_f64:
; CHK64: .functype test_fpsig_return_f64 (i64) -> ()
-; CHK64-NEXT: # %bb.0: # %entry
-; CHK64-NEXT: local.get 0
+; CHECK-NEXT: # %bb.0: # %entry
+; CHECK-NEXT: local.get 0
; CHK64-NEXT: i32.wrap_i64
-; CHK64-NEXT: table.get __indirect_function_table
-; CHK64-NEXT: ref.test () -> (f64)
-; CHK64-NEXT: call use
-; CHK64-NEXT: # fallthrough-return
+; CHECK-NEXT: table.get __indirect_function_table
+; CHECK-NEXT: ref.test () -> (f64)
+; CHECK-NEXT: call use
+; CHECK-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, double poison)
tail call void @use(i32 noundef %res) #3
@@ -129,24 +88,16 @@ entry:
define void @test_fpsig_param_i32(ptr noundef %func) local_unnamed_addr #0 {
-; CHK32-LABEL: test_fpsig_param_i32:
+; CHECK-LABEL: test_fpsig_param_i32:
; CHK32: .functype test_fpsig_param_i32 (i32) -> ()
-; CHK32-NEXT: # %bb.0: # %entry
-; CHK32-NEXT: local.get 0
-; CHK32-NEXT: table.get __indirect_function_table
-; CHK32-NEXT: ref.test (f64) -> ()
-; CHK32-NEXT: call use
-; CHK32-NEXT: # fallthrough-return
-;
-; CHK64-LABEL: test_fpsig_param_i32:
; CHK64: .functype test_fpsig_param_i32 (i64) -> ()
-; CHK64-NEXT: # %bb.0: # %entry
-; CHK64-NEXT: local.get 0
+; CHECK-NEXT: # %bb.0: # %entry
+; CHECK-NEXT: local.get 0
; CHK64-NEXT: i32.wrap_i64
-; CHK64-NEXT: table.get __indirect_function_table
-; CHK64-NEXT: ref.test (f64) -> ()
-; CHK64-NEXT: call use
-; CHK64-NEXT: # fallthrough-return
+; CHECK-NEXT: table.get __indirect_function_table
+; CHECK-NEXT: ref.test (f64) -> ()
+; CHECK-NEXT: call use
+; CHECK-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, double poison)
tail call void @use(i32 noundef %res) #3
@@ -155,24 +106,16 @@ entry:
define void @test_fpsig_multiple_params_and_returns(ptr noundef %func) local_unnamed_addr #0 {
-; CHK32-LABEL: test_fpsig_multiple_params_and_returns:
+; CHECK-LABEL: test_fpsig_multiple_params_and_returns:
; CHK32: .functype test_fpsig_multiple_params_and_returns (i32) -> ()
-; CHK32-NEXT: # %bb.0: # %entry
-; CHK32-NEXT: local.get 0
-; CHK32-NEXT: table.get __indirect_function_table
-; CHK32-NEXT: ref.test (i64, f32, i64) -> (i32, i64, f32, f64)
-; CHK32-NEXT: call use
-; CHK32-NEXT: # fallthrough-return
-;
-; CHK64-LABEL: test_fpsig_multiple_params_and_returns:
; CHK64: .functype test_fpsig_multiple_params_and_returns (i64) -> ()
-; CHK64-NEXT: # %bb.0: # %entry
-; CHK64-NEXT: local.get 0
+; CHECK-NEXT: # %bb.0: # %entry
+; CHECK-NEXT: local.get 0
; CHK64-NEXT: i32.wrap_i64
-; CHK64-NEXT: table.get __indirect_function_table
-; CHK64-NEXT: ref.test (i64, f32, i64) -> (i32, i64, f32, f64)
-; CHK64-NEXT: call use
-; CHK64-NEXT: # fallthrough-return
+; CHECK-NEXT: table.get __indirect_function_table
+; CHECK-NEXT: ref.test (i64, f32, i64) -> (i32, i64, f32, f64)
+; CHECK-NEXT: call use
+; CHECK-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i32 poison, i64 poison, float poison, double poison, token poison, i64 poison, float poison, i64 poison)
tail call void @use(i32 noundef %res) #3
@@ -181,24 +124,17 @@ entry:
define void @test_fpsig_ptrs(ptr noundef %func) local_unnamed_addr #0 {
-; CHK32-LABEL: test_fpsig_ptrs:
+; CHECK-LABEL: test_fpsig_ptrs:
; CHK32: .functype test_fpsig_ptrs (i32) -> ()
-; CHK32-NEXT: # %bb.0: # %entry
-; CHK32-NEXT: local.get 0
-; CHK32-NEXT: table.get __indirect_function_table
-; CHK32-NEXT: ref.test (i32, i32) -> (i32)
-; CHK32-NEXT: call use
-; CHK32-NEXT: # fallthrough-return
-;
-; CHK64-LABEL: test_fpsig_ptrs:
; CHK64: .functype test_fpsig_ptrs (i64) -> ()
-; CHK64-NEXT: # %bb.0: # %entry
-; CHK64-NEXT: local.get 0
+; CHECK-NEXT: # %bb.0: # %entry
+; CHECK-NEXT: local.get 0
; CHK64-NEXT: i32.wrap_i64
-; CHK64-NEXT: table.get __indirect_function_table
+; CHECK-NEXT: table.get __indirect_function_table
+; CHK32-NEXT: ref.test (i32, i32) -> (i32)
; CHK64-NEXT: ref.test (i64, i64) -> (i64)
-; CHK64-NEXT: call use
-; CHK64-NEXT: # fallthrough-return
+; CHECK-NEXT: call use
+; CHECK-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, ptr poison, token poison, ptr poison, ptr poison)
tail call void @use(i32 noundef %res) #3
@@ -206,24 +142,16 @@ entry:
}
define void @test_reference_types(ptr noundef %func) local_unnamed_addr #0 {
-; CHK32-LABEL: test_reference_types:
+; CHECK-LABEL: test_reference_types:
; CHK32: .functype test_reference_types (i32) -> ()
-; CHK32-NEXT: # %bb.0: # %entry
-; CHK32-NEXT: local.get 0
-; CHK32-NEXT: table.get __indirect_function_table
-; CHK32-NEXT: ref.test (funcref, externref) -> (externref)
-; CHK32-NEXT: call use
-; CHK32-NEXT: # fallthrough-return
-;
-; CHK64-LABEL: test_reference_types:
; CHK64: .functype test_reference_types (i64) -> ()
-; CHK64-NEXT: # %bb.0: # %entry
-; CHK64-NEXT: local.get 0
+; CHECK-NEXT: # %bb.0: # %entry
+; CHECK-NEXT: local.get 0
; CHK64-NEXT: i32.wrap_i64
-; CHK64-NEXT: table.get __indirect_function_table
-; CHK64-NEXT: ref.test (funcref, externref) -> (externref)
-; CHK64-NEXT: call use
-; CHK64-NEXT: # fallthrough-return
+; CHECK-NEXT: table.get __indirect_function_table
+; CHECK-NEXT: ref.test (funcref, externref) -> (externref)
+; CHECK-NEXT: call use
+; CHECK-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, target("wasm.externref") poison, token poison, target("wasm.funcref") poison, target("wasm.externref") poison)
tail call void @use(i32 noundef %res) #3
@@ -231,5 +159,3 @@ entry:
}
declare void @use(i32 noundef) local_unnamed_addr #1
-;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
-; CHECK: {{.*}}
>From ed6aab11c0e0b2c21e5ac1da6394cf3ed4d08232 Mon Sep 17 00:00:00 2001
From: Hood Chatham <roberthoodchatham at gmail.com>
Date: Thu, 11 Jun 2026 11:09:34 -0700
Subject: [PATCH 6/9] Revert "Implement funcref calls in FastISel"
This reverts commit fdcf148db1df4e1d4a12df985998b0f6868ff8fb.
---
.../CodeGen/SelectionDAG/SelectionDAGISel.cpp | 23 +++++-
.../WebAssembly/WebAssemblyFastISel.cpp | 70 +++----------------
llvm/test/CodeGen/WebAssembly/funcref-call.ll | 5 +-
3 files changed, 32 insertions(+), 66 deletions(-)
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
index 5ae52cae771fb..1b479c2b36058 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
@@ -230,9 +230,26 @@ static bool dontUseFastISelFor(const Function &Fn) {
// Debug info on those is reliant on good Argument lowering, and FastISel is
// not capable of lowering the entire function. Mixing the two selectors tend
// to result in poor lowering of Arguments.
- return any_of(Fn.args(), [](const Argument &Arg) {
- return Arg.hasAttribute(Attribute::AttrKind::SwiftAsync);
- });
+ if (any_of(Fn.args(), [](const Argument &Arg) {
+ return Arg.hasAttribute(Attribute::AttrKind::SwiftAsync);
+ }))
+ return true;
+
+ // A WebAssembly funcref call is expressed in IR as a call through the pointer
+ // produced by the llvm.wasm.funcref.to_ptr intrinsic. SelectionDAG fuses the
+ // intrinsic and the call into a table.set + call_indirect through the
+ // dedicated __funcref_call_table. FastISel selects each call as its own
+ // single-instruction block and therefore cannot perform this fusion, so fall
+ // back to SelectionDAG for the whole function when the intrinsic is present.
+ if (Fn.getParent()->getTargetTriple().isWasm()) {
+ for (const BasicBlock &BB : Fn)
+ for (const Instruction &I : BB)
+ if (const auto *II = dyn_cast<IntrinsicInst>(&I))
+ if (II->getIntrinsicID() == Intrinsic::wasm_funcref_to_ptr)
+ return true;
+ }
+
+ return false;
}
static bool maintainPGOProfile(const TargetMachine &TM,
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp
index 95f4367f76cdf..b107886a1f16e 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp
@@ -35,7 +35,6 @@
#include "llvm/IR/GetElementPtrTypeIterator.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/Instructions.h"
-#include "llvm/IR/IntrinsicsWebAssembly.h"
#include "llvm/IR/Operator.h"
using namespace llvm;
@@ -834,6 +833,11 @@ bool WebAssemblyFastISel::fastLowerArguments() {
bool WebAssemblyFastISel::selectCall(const Instruction *I) {
const auto *Call = cast<CallInst>(I);
+ // FastISel does not support calls through funcref
+ if (Call->getCalledOperand()->getType()->getPointerAddressSpace() !=
+ WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_DEFAULT)
+ return false;
+
// TODO: Support tail calls in FastISel
if (Call->isMustTailCall() || Call->isInlineAsm() ||
Call->getFunctionType()->isVarArg())
@@ -938,49 +942,10 @@ bool WebAssemblyFastISel::selectCall(const Instruction *I) {
}
unsigned CalleeReg = 0;
- // A call through a funcref is expressed as a call through the pointer
- // produced by llvm.wasm.funcref.to_ptr. Recover the funcref operand, place it
- // into __funcref_call_table, and call it.
- //
- // TODO: Use call_ref if wasm-gc feature is available, would lead to simpler
- // code here.
- const Value *FuncrefArg = nullptr;
- if (const auto *Conv = dyn_cast<CallInst>(Call->getCalledOperand()))
- if (Conv->getIntrinsicID() == Intrinsic::wasm_funcref_to_ptr)
- FuncrefArg = Conv->getArgOperand(0);
-
- const bool IsFuncrefCall = FuncrefArg != nullptr;
- MCSymbolWasm *Table = nullptr;
-
if (!IsDirect) {
- if (!IsFuncrefCall) {
- // Table is ___indirect_function_table
- Table = WebAssembly::getOrCreateFunctionTableSymbol(MF->getContext(),
- Subtarget);
- CalleeReg = getRegForValue(Call->getCalledOperand());
- if (!CalleeReg)
- return false;
- } else {
- // Table is __funcref_call_table
- Table = WebAssembly::getOrCreateFuncrefCallTableSymbol(MF->getContext(),
- Subtarget);
- CalleeReg = getRegForValue(FuncrefArg);
- // Put the funcref in slot 0 of __funcref_call_table
- unsigned ZeroReg = createResultReg(&WebAssembly::I32RegClass);
- BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD,
- TII.get(WebAssembly::CONST_I32), ZeroReg)
- .addImm(0);
- BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD,
- TII.get(WebAssembly::TABLE_SET_FUNCREF))
- .addSym(Table)
- .addReg(ZeroReg)
- .addReg(CalleeReg);
- // Set CalleeReg to an immediate 0
- CalleeReg = createResultReg(&WebAssembly::I32RegClass);
- BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD,
- TII.get(WebAssembly::CONST_I32), CalleeReg)
- .addImm(0);
- }
+ CalleeReg = getRegForValue(Call->getCalledOperand());
+ if (!CalleeReg)
+ return false;
}
auto MIB = BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD, TII.get(Opc));
@@ -993,6 +958,9 @@ bool WebAssemblyFastISel::selectCall(const Instruction *I) {
} else {
// Placeholder for the type index.
MIB.addImm(0);
+ // The table into which this call_indirect indexes.
+ MCSymbolWasm *Table = WebAssembly::getOrCreateFunctionTableSymbol(
+ MF->getContext(), Subtarget);
if (Subtarget->hasCallIndirectOverlong()) {
MIB.addSym(Table);
} else {
@@ -1010,22 +978,6 @@ bool WebAssemblyFastISel::selectCall(const Instruction *I) {
if (!IsDirect)
MIB.addReg(CalleeReg);
- if (IsFuncrefCall) {
- // Clear slot 0 of the funcref call table after the call.
- unsigned ZeroReg = createResultReg(&WebAssembly::I32RegClass);
- BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD,
- TII.get(WebAssembly::CONST_I32), ZeroReg)
- .addImm(0);
- unsigned NullReg = createResultReg(&WebAssembly::FUNCREFRegClass);
- BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD,
- TII.get(WebAssembly::REF_NULL_FUNCREF), NullReg);
- BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD,
- TII.get(WebAssembly::TABLE_SET_FUNCREF))
- .addSym(Table)
- .addReg(ZeroReg)
- .addReg(NullReg);
- }
-
if (!IsVoid)
updateValueMap(Call, ResultReg);
diff --git a/llvm/test/CodeGen/WebAssembly/funcref-call.ll b/llvm/test/CodeGen/WebAssembly/funcref-call.ll
index d8433d61a5db9..66692ac0eb0f0 100644
--- a/llvm/test/CodeGen/WebAssembly/funcref-call.ll
+++ b/llvm/test/CodeGen/WebAssembly/funcref-call.ll
@@ -1,9 +1,6 @@
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
; RUN: llc < %s --mtriple=wasm32-unknown-unknown -fast-isel=0 -mattr=+reference-types | FileCheck %s
-
-; Use -fast-isel-abort=3 (never fall back to SelectionDAG) to make sure that the
-; funcref call is selected by FastISel
-; RUN: llc < %s --mtriple=wasm32-unknown-unknown -fast-isel=1 -fast-isel-abort=3 -mattr=+reference-types | FileCheck %s
+; RUN: llc < %s --mtriple=wasm32-unknown-unknown -fast-isel=1 -mattr=+reference-types | FileCheck %s
%funcref = type target("wasm.funcref")
>From 52981958249975eb74a5edde4cdbf16fc1fac647 Mon Sep 17 00:00:00 2001
From: Hood Chatham <roberthoodchatham at gmail.com>
Date: Thu, 11 Jun 2026 11:10:47 -0700
Subject: [PATCH 7/9] Update guard to bail out of FastISel when we have a
funcref call
---
.../CodeGen/SelectionDAG/SelectionDAGISel.cpp | 23 +++----------------
.../WebAssembly/WebAssemblyFastISel.cpp | 15 ++++++++----
2 files changed, 13 insertions(+), 25 deletions(-)
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
index 1b479c2b36058..5ae52cae771fb 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
@@ -230,26 +230,9 @@ static bool dontUseFastISelFor(const Function &Fn) {
// Debug info on those is reliant on good Argument lowering, and FastISel is
// not capable of lowering the entire function. Mixing the two selectors tend
// to result in poor lowering of Arguments.
- if (any_of(Fn.args(), [](const Argument &Arg) {
- return Arg.hasAttribute(Attribute::AttrKind::SwiftAsync);
- }))
- return true;
-
- // A WebAssembly funcref call is expressed in IR as a call through the pointer
- // produced by the llvm.wasm.funcref.to_ptr intrinsic. SelectionDAG fuses the
- // intrinsic and the call into a table.set + call_indirect through the
- // dedicated __funcref_call_table. FastISel selects each call as its own
- // single-instruction block and therefore cannot perform this fusion, so fall
- // back to SelectionDAG for the whole function when the intrinsic is present.
- if (Fn.getParent()->getTargetTriple().isWasm()) {
- for (const BasicBlock &BB : Fn)
- for (const Instruction &I : BB)
- if (const auto *II = dyn_cast<IntrinsicInst>(&I))
- if (II->getIntrinsicID() == Intrinsic::wasm_funcref_to_ptr)
- return true;
- }
-
- return false;
+ return any_of(Fn.args(), [](const Argument &Arg) {
+ return Arg.hasAttribute(Attribute::AttrKind::SwiftAsync);
+ });
}
static bool maintainPGOProfile(const TargetMachine &TM,
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp
index b107886a1f16e..4dde9dd9ee391 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp
@@ -35,6 +35,7 @@
#include "llvm/IR/GetElementPtrTypeIterator.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/Instructions.h"
+#include "llvm/IR/IntrinsicsWebAssembly.h"
#include "llvm/IR/Operator.h"
using namespace llvm;
@@ -833,11 +834,6 @@ bool WebAssemblyFastISel::fastLowerArguments() {
bool WebAssemblyFastISel::selectCall(const Instruction *I) {
const auto *Call = cast<CallInst>(I);
- // FastISel does not support calls through funcref
- if (Call->getCalledOperand()->getType()->getPointerAddressSpace() !=
- WebAssembly::WasmAddressSpace::WASM_ADDRESS_SPACE_DEFAULT)
- return false;
-
// TODO: Support tail calls in FastISel
if (Call->isMustTailCall() || Call->isInlineAsm() ||
Call->getFunctionType()->isVarArg())
@@ -941,6 +937,15 @@ bool WebAssemblyFastISel::selectCall(const Instruction *I) {
Args.push_back(Reg);
}
+ // A call through a funcref is expressed as a call through the pointer
+ // produced by llvm.wasm.funcref.to_ptr. We don't currently handle these in
+ // FastISel; bail out so the call is lowered by SelectionDAG.
+ //
+ // TODO: Handle this in FastISel.
+ if (const auto *Conv = dyn_cast<CallInst>(Call->getCalledOperand()))
+ if (Conv->getIntrinsicID() == Intrinsic::wasm_funcref_to_ptr)
+ return false;
+
unsigned CalleeReg = 0;
if (!IsDirect) {
CalleeReg = getRegForValue(Call->getCalledOperand());
>From 927f205e1a1c9eaec2de31890b8be51f68bbbf07 Mon Sep 17 00:00:00 2001
From: Hood Chatham <roberthoodchatham at gmail.com>
Date: Thu, 11 Jun 2026 11:41:13 -0700
Subject: [PATCH 8/9] Add regression test for issue #69894
---
.../WebAssembly/externref-no-vectorize.ll | 47 +++++++++++++++++++
1 file changed, 47 insertions(+)
create mode 100644 llvm/test/Transforms/SLPVectorizer/WebAssembly/externref-no-vectorize.ll
diff --git a/llvm/test/Transforms/SLPVectorizer/WebAssembly/externref-no-vectorize.ll b/llvm/test/Transforms/SLPVectorizer/WebAssembly/externref-no-vectorize.ll
new file mode 100644
index 0000000000000..f9320eb723f27
--- /dev/null
+++ b/llvm/test/Transforms/SLPVectorizer/WebAssembly/externref-no-vectorize.ll
@@ -0,0 +1,47 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=slp-vectorizer -mtriple=wasm32-unknown-unknown -mattr=+reference-types -S | FileCheck %s
+
+; The SLP vectorizer used to try to vectorize the two externref PHIs
+; below into a <2 x externref> and then crashed.
+; Check that they are left alone and the vectorizer doesn't crash.
+
+target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20"
+target triple = "wasm32-unknown-unknown"
+
+%externref = type target("wasm.externref")
+
+declare %externref @foo()
+declare void @bar(%externref)
+
+define void @test(i32 %flag, %externref %ref1, %externref %ref2) {
+; CHECK-LABEL: @test(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[CMP:%.*]] = icmp ne i32 [[FLAG:%.*]], 0
+; CHECK-NEXT: br i1 [[CMP]], label [[IF_THEN:%.*]], label [[IF_END:%.*]]
+; CHECK: if.then:
+; CHECK-NEXT: [[CALL:%.*]] = call target("wasm.externref") @foo()
+; CHECK-NEXT: [[CALL1:%.*]] = call target("wasm.externref") @foo()
+; CHECK-NEXT: br label [[IF_END]]
+; CHECK: if.end:
+; CHECK-NEXT: [[R1:%.*]] = phi target("wasm.externref") [ [[CALL]], [[IF_THEN]] ], [ [[REF1:%.*]], [[ENTRY:%.*]] ]
+; CHECK-NEXT: [[R2:%.*]] = phi target("wasm.externref") [ [[CALL1]], [[IF_THEN]] ], [ [[REF2:%.*]], [[ENTRY]] ]
+; CHECK-NEXT: call void @bar(target("wasm.externref") [[R1]])
+; CHECK-NEXT: call void @bar(target("wasm.externref") [[R2]])
+; CHECK-NEXT: ret void
+;
+entry:
+ %cmp = icmp ne i32 %flag, 0
+ br i1 %cmp, label %if.then, label %if.end
+
+if.then:
+ %call = call %externref @foo()
+ %call1 = call %externref @foo()
+ br label %if.end
+
+if.end:
+ %r1 = phi %externref [ %call, %if.then ], [ %ref1, %entry ]
+ %r2 = phi %externref [ %call1, %if.then ], [ %ref2, %entry ]
+ call void @bar(%externref %r1)
+ call void @bar(%externref %r2)
+ ret void
+}
>From 587499b4bb55eced1e50af22124e8e0fafacf2f3 Mon Sep 17 00:00:00 2001
From: Hood Chatham <roberthoodchatham at gmail.com>
Date: Thu, 11 Jun 2026 12:07:51 -0700
Subject: [PATCH 9/9] Go back to skipping FastISel with dontUseFastISelFor
---
.../CodeGen/SelectionDAG/SelectionDAGISel.cpp | 20 ++++++++++++++++---
.../WebAssembly/WebAssemblyFastISel.cpp | 10 ----------
2 files changed, 17 insertions(+), 13 deletions(-)
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
index 5ae52cae771fb..1ec05b6a5cfbe 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
@@ -230,9 +230,23 @@ static bool dontUseFastISelFor(const Function &Fn) {
// Debug info on those is reliant on good Argument lowering, and FastISel is
// not capable of lowering the entire function. Mixing the two selectors tend
// to result in poor lowering of Arguments.
- return any_of(Fn.args(), [](const Argument &Arg) {
- return Arg.hasAttribute(Attribute::AttrKind::SwiftAsync);
- });
+ if (any_of(Fn.args(), [](const Argument &Arg) {
+ return Arg.hasAttribute(Attribute::AttrKind::SwiftAsync);
+ }))
+ return true;
+
+ // Fall back to SelectionDAG for the whole function when the
+ // wasm_funcref_to_ptr intrinsic is present.
+ // TODO: implement FastISel for funcref calls
+ if (Fn.getParent()->getTargetTriple().isWasm()) {
+ for (const BasicBlock &BB : Fn)
+ for (const Instruction &I : BB)
+ if (const auto *II = dyn_cast<IntrinsicInst>(&I))
+ if (II->getIntrinsicID() == Intrinsic::wasm_funcref_to_ptr)
+ return true;
+ }
+
+ return false;
}
static bool maintainPGOProfile(const TargetMachine &TM,
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp
index 4dde9dd9ee391..25f48b94540f6 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp
@@ -35,7 +35,6 @@
#include "llvm/IR/GetElementPtrTypeIterator.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/Instructions.h"
-#include "llvm/IR/IntrinsicsWebAssembly.h"
#include "llvm/IR/Operator.h"
using namespace llvm;
@@ -937,15 +936,6 @@ bool WebAssemblyFastISel::selectCall(const Instruction *I) {
Args.push_back(Reg);
}
- // A call through a funcref is expressed as a call through the pointer
- // produced by llvm.wasm.funcref.to_ptr. We don't currently handle these in
- // FastISel; bail out so the call is lowered by SelectionDAG.
- //
- // TODO: Handle this in FastISel.
- if (const auto *Conv = dyn_cast<CallInst>(Call->getCalledOperand()))
- if (Conv->getIntrinsicID() == Intrinsic::wasm_funcref_to_ptr)
- return false;
-
unsigned CalleeReg = 0;
if (!IsDirect) {
CalleeReg = getRegForValue(Call->getCalledOperand());
More information about the cfe-commits
mailing list