Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,12 @@ WebAssembly Support

- Fixed a crash when ``__funcref`` is applied to a non-function pointer type.
(#GH118233)
- WebAssembly reference types (``__externref_t`` and ``__funcref`` function
pointers) now lower to the opaque IR types ``target("wasm.externref")`` and
``target("wasm.funcref")`` instead of ``ptr addrspace(10)`` /
``ptr addrspace(20)``.
- Fixed a compiler crash at ``-O2`` when reference-type values were passed
through control flow that the SLP vectorizer tried to vectorize.

AVR Support
^^^^^^^^^^^
Expand Down
21 changes: 19 additions & 2 deletions clang/lib/CodeGen/CGExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
#include "llvm/IR/Constants.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/IntrinsicsWebAssembly.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/MDBuilder.h"
#include "llvm/IR/MatrixBuilder.h"
Expand Down Expand Up @@ -6579,6 +6580,21 @@ static GlobalDecl getGlobalDeclForDirectCall(const FunctionDecl *FD) {
CGCallee CodeGenFunction::EmitCallee(const Expr *E) {
E = E->IgnoreParens();

// 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.
auto ConvertFuncrefToPtr = [&](llvm::Value *CalleePtr) -> llvm::Value * {
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);
return Builder.CreateCall(ToPtr, {CalleePtr});
}
return CalleePtr;
};

// Look through function-to-pointer decay.
if (auto ICE = dyn_cast<ImplicitCastExpr>(E)) {
if (ICE->getCastKind() == CK_FunctionToPointerDecay ||
Expand All @@ -6603,7 +6619,8 @@ CGCallee CodeGenFunction::EmitCallee(const Expr *E) {
GD = GlobalDecl(VD);
}
CGCalleeInfo CalleeInfo(FunctionType->getAs<FunctionProtoType>(), GD);
CGCallee Callee(CalleeInfo, Result.first, Result.second);
CGCallee Callee(CalleeInfo, ConvertFuncrefToPtr(Result.first),
Result.second);
return Callee;
}
}
Expand Down Expand Up @@ -6647,7 +6664,7 @@ CGCallee CodeGenFunction::EmitCallee(const Expr *E) {

CGCalleeInfo calleeInfo(functionType->getAs<FunctionProtoType>(), GD);
CGPointerAuthInfo pointerAuth = CGM.getFunctionPointerAuthInfo(functionType);
CGCallee callee(calleeInfo, calleePtr, pointerAuth);
CGCallee callee(calleeInfo, ConvertFuncrefToPtr(calleePtr), pointerAuth);
return callee;
}

Expand Down
11 changes: 8 additions & 3 deletions clang/lib/CodeGen/CGExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2881,9 +2881,14 @@ llvm::Constant *ConstantEmitter::emitNullForMemory(CodeGenModule &CGM,
}

llvm::Constant *CodeGenModule::EmitNullConstant(QualType T) {
if (T->getAs<PointerType>())
return getNullPointer(
cast<llvm::PointerType>(getTypes().ConvertTypeForMem(T)), T);
if (T->getAs<PointerType>()) {
llvm::Type *LT = getTypes().ConvertTypeForMem(T);
if (auto *PT = dyn_cast<llvm::PointerType>(LT))
return getNullPointer(PT, T);
// Some pointer types do not lower to an LLVM pointer (e.g. a WebAssembly
// funcref, which is an opaque reference type). Use the type's zero value.
return llvm::Constant::getNullValue(LT);
}

if (getTypes().isZeroInitializable(T))
return llvm::Constant::getNullValue(getTypes().ConvertTypeForMem(T));
Expand Down
43 changes: 40 additions & 3 deletions clang/lib/CodeGen/CGExprScalar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should this comment be updated, since this works when directly called? or is this case just for a standalone funcref_to_ptr?

We could implement a standalone funcref_to_ptr by appending an entry to the indirect function table and then returning a table.get of that entry. That smells like the kind of thing that might best be a libcall, since runtimes like Emscripten are at least aware of the indirect function table (and it doesn't have to be in this PR). Also I'm not sure what the utility would be but it seems worth doing for completeness.

@hoodmane hoodmane Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

is this case just for a standalone funcref_to_ptr?

Yes.

We could implement a standalone funcref_to_ptr by appending an entry to the indirect function table

For now let's not do that. I think for it to work correctly we need a stack of funcrefs, and it's just not worth it.

// 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),
Comment thread
QuantumSegfault marked this conversation as resolved.
// 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()) {
Expand All @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/CodeGen/CodeGenTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
28 changes: 14 additions & 14 deletions clang/test/CodeGen/WebAssembly/builtins-table-externref.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -60,19 +60,19 @@ 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) {
return __builtin_wasm_table_grow(table, ref, 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) {
Expand Down
16 changes: 8 additions & 8 deletions clang/test/CodeGen/WebAssembly/builtins-table-funcref.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -37,19 +37,19 @@ 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) {
return __builtin_wasm_table_grow(table, ref, 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) {
Expand Down
4 changes: 2 additions & 2 deletions clang/test/CodeGen/WebAssembly/builtins-test-fp-sig.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down
8 changes: 4 additions & 4 deletions clang/test/CodeGen/WebAssembly/wasm-externref.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
21 changes: 21 additions & 0 deletions clang/test/CodeGen/WebAssembly/wasm-funcref-to-ptr-error.c
Original file line number Diff line number Diff line change
@@ -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;
}
Loading