[WebAssembly] Represent reference types as TargetExtType - #203165
Conversation
|
@llvm/pr-subscribers-llvm-transforms @llvm/pr-subscribers-llvm-globalisel Author: Hood Chatham (hoodmane) ChangesOriginally #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 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 cc @QuantumSegfault Patch is 98.62 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/203165.diff 57 Files Affected:
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..cb167d2b8f449 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,41 @@ 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: recover the underlying function pointer.
+ 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 +2883,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..70c1d4f6a6ba9 100644
--- a/clang/lib/CodeGen/CodeGenTypes.cpp
+++ b/clang/lib/CodeGen/CodeGenTypes.cpp
@@ -645,6 +645,12 @@ llvm::Type *CodeGenTypes::ConvertType(QualType T) {
case Type::Pointer: {
const PointerType *PTy = cast<PointerType>(Ty);
QualType ETy = PTy->getPointeeType();
+ // A WebAssembly funcref is represented as the opaque reference type
+ // target("wasm.funcref").
+ 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..7923196f7478e
--- /dev/null
+++ b/clang/test/CodeGen/WebAssembly/wasm-funcref-to-ptr-error.c
@@ -0,0 +1,22 @@
+// 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
+
+// A WebAssembly funcref is an opaque reference type. Converting one to a plain
+// function pointer is only meaningful when the result is immediately called
+// (which lowers to a table.set + call_indirect through the funcref call table).
+// If the resulting pointer escapes (is stored, returned, or passed along), the
+// conversion is not representable and is diagnosed rather than crashing 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 [[...
[truncated]
|
|
@llvm/pr-subscribers-clang-codegen Author: Hood Chatham (hoodmane) ChangesOriginally #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 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 cc @QuantumSegfault Patch is 98.62 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/203165.diff 57 Files Affected:
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..cb167d2b8f449 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,41 @@ 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: recover the underlying function pointer.
+ 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 +2883,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..70c1d4f6a6ba9 100644
--- a/clang/lib/CodeGen/CodeGenTypes.cpp
+++ b/clang/lib/CodeGen/CodeGenTypes.cpp
@@ -645,6 +645,12 @@ llvm::Type *CodeGenTypes::ConvertType(QualType T) {
case Type::Pointer: {
const PointerType *PTy = cast<PointerType>(Ty);
QualType ETy = PTy->getPointeeType();
+ // A WebAssembly funcref is represented as the opaque reference type
+ // target("wasm.funcref").
+ 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..7923196f7478e
--- /dev/null
+++ b/clang/test/CodeGen/WebAssembly/wasm-funcref-to-ptr-error.c
@@ -0,0 +1,22 @@
+// 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
+
+// A WebAssembly funcref is an opaque reference type. Converting one to a plain
+// function pointer is only meaningful when the result is immediately called
+// (which lowers to a table.set + call_indirect through the funcref call table).
+// If the resulting pointer escapes (is stored, returned, or passed along), the
+// conversion is not representable and is diagnosed rather than crashing 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 [[...
[truncated]
|
|
@llvm/pr-subscribers-llvm-selectiondag Author: Hood Chatham (hoodmane) ChangesOriginally #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 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 cc @QuantumSegfault Patch is 98.62 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/203165.diff 57 Files Affected:
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..cb167d2b8f449 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,41 @@ 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: recover the underlying function pointer.
+ 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 +2883,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..70c1d4f6a6ba9 100644
--- a/clang/lib/CodeGen/CodeGenTypes.cpp
+++ b/clang/lib/CodeGen/CodeGenTypes.cpp
@@ -645,6 +645,12 @@ llvm::Type *CodeGenTypes::ConvertType(QualType T) {
case Type::Pointer: {
const PointerType *PTy = cast<PointerType>(Ty);
QualType ETy = PTy->getPointeeType();
+ // A WebAssembly funcref is represented as the opaque reference type
+ // target("wasm.funcref").
+ 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..7923196f7478e
--- /dev/null
+++ b/clang/test/CodeGen/WebAssembly/wasm-funcref-to-ptr-error.c
@@ -0,0 +1,22 @@
+// 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
+
+// A WebAssembly funcref is an opaque reference type. Converting one to a plain
+// function pointer is only meaningful when the result is immediately called
+// (which lowers to a table.set + call_indirect through the funcref call table).
+// If the resulting pointer escapes (is stored, returned, or passed along), the
+// conversion is not representable and is diagnosed rather than crashing 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 [[...
[truncated]
|
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
Originally llvm#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@gmail.com> Co-authored-by: Paulo Matos <pmatos@igalia.com>
ae3f67d to
475bcb6
Compare
|
Incidentally, I noticed while writing this that we never emit |
🐧 Linux x64 Test Results
✅ The build succeeded and all tests passed. |
🪟 Windows x64 Test Results
✅ The build succeeded and all tests passed. |
Way ahead of you. But maybe you can help push things forward. It works, but in order to properly test it, I wanted to check against status quo. But status quo doesn't work at all. In order to fix existing thing, we need to move forward with #180900. Perhaps you have an opinion? |
QuantumSegfault
left a comment
There was a problem hiding this comment.
Looks pretty good overall.
It's unfortunate we have to do that weird cast hack to call now...but probably the best that can be done at the moment
e82c582 to
e01a780
Compare
This reverts commit fdcf148.
|
I claim this resolves #69894 so I should probably add a test for it. |
Fix test from #203165 when no wasm target is configured
|
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/46/builds/37101 Here is the relevant piece of the build log for the reference |
|
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/65/builds/36320 Here is the relevant piece of the build log for the reference |
|
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/190/builds/44709 Here is the relevant piece of the build log for the reference |
|
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/27/builds/743 Here is the relevant piece of the build log for the reference |
|
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/154/builds/33089 Here is the relevant piece of the build log for the reference |
|
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/227/builds/2802 Here is the relevant piece of the build log for the reference |
|
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/229/builds/2486 Here is the relevant piece of the build log for the reference |
|
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/23/builds/20673 Here is the relevant piece of the build log for the reference |
|
Looks like my new test isn't properly skipped when the wasm target isn't built. |
Already taken care of: #205722 |
|
Thanks @dschuff and @QuantumSegfault! Also, we should probably close these: |
Done. |
|
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/193/builds/16978 Here is the relevant piece of the build log for the reference |
|
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/88/builds/23921 Here is the relevant piece of the build log for the reference |
|
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/198/builds/13892 Here is the relevant piece of the build log for the reference |
|
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/161/builds/12043 Here is the relevant piece of the build log for the reference |
|
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/13/builds/13746 Here is the relevant piece of the build log for the reference |
Originally llvm#71540 by Paolo Matos, I picked it up and finished it. Resolves llvm#69894. 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. cc @QuantumSegfault --------- Co-authored-by: Paulo Matos <pmatos@igalia.com>
) Fix test from llvm#203165 when no wasm target is configured
…etExtType (#203165)"
```
/Users/alatsis/Desktop/rebranch/swift-2/lib/AST/Builtins.cpp:2659:11: error: enumeration values 'WasmExternref' and 'WasmFuncref' not handled in switch [-Werror,-Wswitch]
switch (D.Kind) {
^
```
The LLVM commit models WebAssembly externref/funcref as
target("wasm.externref") / target("wasm.funcref") TargetExtTypes and
adds the corresponding WasmExternref and WasmFuncref enumerators to
IITDescriptor::Kind. These are opaque WebAssembly reference types with
no Swift representation, so they join the leading group of kinds that
"cannot be expressed in swift yet" and return an empty Type().
See llvm/llvm-project@765f0d1 (llvm/llvm-project#203165).
Assisted-by: Claude Code
…etExtType (#203165)"
```
/Users/alatsis/Desktop/rebranch/swift-2/lib/AST/Builtins.cpp:2659:11: error: enumeration values 'WasmExternref' and 'WasmFuncref' not handled in switch [-Werror,-Wswitch]
switch (D.Kind) {
^
```
The LLVM commit models WebAssembly externref/funcref as
target("wasm.externref") / target("wasm.funcref") TargetExtTypes and
adds the corresponding WasmExternref and WasmFuncref enumerators to
IITDescriptor::Kind. These are opaque WebAssembly reference types with
no Swift representation, so they join the leading group of kinds that
"cannot be expressed in swift yet" and return an empty Type().
See llvm/llvm-project@765f0d1 (llvm/llvm-project#203165).
Assisted-by: Claude Code
…etExtType (#203165)"
```
/Users/alatsis/Desktop/rebranch/swift-2/lib/AST/Builtins.cpp:2659:11: error: enumeration values 'WasmExternref' and 'WasmFuncref' not handled in switch [-Werror,-Wswitch]
switch (D.Kind) {
^
```
The LLVM commit models WebAssembly externref/funcref as
target("wasm.externref") / target("wasm.funcref") TargetExtTypes and
adds the corresponding WasmExternref and WasmFuncref enumerators to
IITDescriptor::Kind. These are opaque WebAssembly reference types with
no Swift representation, so they join the leading group of kinds that
"cannot be expressed in swift yet" and return an empty Type().
See llvm/llvm-project@765f0d1 (llvm/llvm-project#203165).
Assisted-by: Claude Code
…tType (#203165)" Add the new `IITDescriptor::Kind` cases to the "cannot be expressed in Swift yet" bucket. See llvm/llvm-project@765f0d1 (llvm/llvm-project#203165).
…etExtType (#203165)"
```
/Users/alatsis/Desktop/rebranch/swift-2/lib/AST/Builtins.cpp:2659:11: error: enumeration values 'WasmExternref' and 'WasmFuncref' not handled in switch [-Werror,-Wswitch]
switch (D.Kind) {
^
```
The LLVM commit models WebAssembly externref/funcref as
target("wasm.externref") / target("wasm.funcref") TargetExtTypes and
adds the corresponding WasmExternref and WasmFuncref enumerators to
IITDescriptor::Kind. These are opaque WebAssembly reference types with
no Swift representation, so they join the leading group of kinds that
"cannot be expressed in swift yet" and return an empty Type().
See llvm/llvm-project@765f0d1 (llvm/llvm-project#203165).
Assisted-by: Claude Code
Originally #71540 by Paolo Matos, I picked it up and finished it. Resolves #69894.
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_funcrefandwasm.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_ptris 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_PTRin 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.cc @QuantumSegfault