Skip to content

[clang][WebAssembly] Support reference types in test_function_pointer_signature #150921

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
29 changes: 10 additions & 19 deletions clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,35 +246,26 @@ Value *CodeGenFunction::EmitWebAssemblyBuiltinExpr(unsigned BuiltinID,
llvm::FunctionType *LLVMFuncTy =
cast<llvm::FunctionType>(ConvertType(QualType(FuncTy, 0)));

bool VarArg = LLVMFuncTy->isVarArg();
unsigned NParams = LLVMFuncTy->getNumParams();
std::vector<Value *> Args;
Args.reserve(NParams + 3);
Args.reserve(NParams + 3 + VarArg);
Copy link
Member

Choose a reason for hiding this comment

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

I see you have a test for calling the intrinsic below, but: Vararg functions basically have a pointer to the vararg buffer as their last argument, so I'm wondering whether we need to take any more special cases to distinguish between that vs a function that just has a pointer in the last position. Those would be compatible in wasm, but not in C. But I guess the builtin/intrinsic is specifically for testing wasm compatibility vs C compatibility, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah it's just testing for wasm compatibility. I don't think it's possible to test for C compatibility since the runtime doesn't know about that.

// The only real argument is the FuncRef
Args.push_back(FuncRef);

// Add the type information
auto addType = [this, &Args](llvm::Type *T) {
if (T->isVoidTy()) {
// Do nothing
} else if (T->isFloatingPointTy()) {
Args.push_back(ConstantFP::get(T, 0));
} else if (T->isIntegerTy()) {
Args.push_back(ConstantInt::get(T, 0));
} else if (T->isPointerTy()) {
Args.push_back(ConstantPointerNull::get(llvm::PointerType::get(
getLLVMContext(), T->getPointerAddressSpace())));
} else {
// TODO: Handle reference types. For now, we reject them in Sema.
llvm_unreachable("Unhandled type");
}
};

addType(LLVMFuncTy->getReturnType());
llvm::Type *RetType = LLVMFuncTy->getReturnType();
if (!RetType->isVoidTy()) {
Args.push_back(PoisonValue::get(RetType));
}
// The token type indicates the boundary between return types and param
// types.
Args.push_back(PoisonValue::get(llvm::Type::getTokenTy(getLLVMContext())));
for (unsigned i = 0; i < NParams; i++) {
addType(LLVMFuncTy->getParamType(i));
Args.push_back(PoisonValue::get(LLVMFuncTy->getParamType(i)));
}
if (VarArg) {
Args.push_back(PoisonValue::get(Builder.getPtrTy()));
}
Function *Callee = CGM.getIntrinsic(Intrinsic::wasm_ref_test_func);
return Builder.CreateCall(Callee, Args);
Expand Down
18 changes: 0 additions & 18 deletions clang/lib/Sema/SemaWasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,24 +250,6 @@ bool SemaWasm::BuiltinWasmTestFunctionPointerSignature(CallExpr *TheCall) {
<< ArgType << FuncPtrArg->getSourceRange();
}

// Check that the function pointer doesn't use reference types
if (FuncTy->getReturnType().isWebAssemblyReferenceType()) {
return Diag(
FuncPtrArg->getBeginLoc(),
diag::err_wasm_builtin_test_fp_sig_cannot_include_reference_type)
<< 0 << FuncTy->getReturnType() << FuncPtrArg->getSourceRange();
}
auto NParams = FuncTy->getNumParams();
for (unsigned I = 0; I < NParams; I++) {
if (FuncTy->getParamType(I).isWebAssemblyReferenceType()) {
return Diag(
FuncPtrArg->getBeginLoc(),
diag::
err_wasm_builtin_test_fp_sig_cannot_include_reference_type)
<< 1 << FuncPtrArg->getSourceRange();
}
}

// Set return type to int (the result of the test)
TheCall->setType(getASTContext().IntTy);

Expand Down
53 changes: 48 additions & 5 deletions clang/test/CodeGen/builtins-wasm.c
Original file line number Diff line number Diff line change
Expand Up @@ -752,23 +752,66 @@ void *tp (void) {
// WEBASSEMBLY: call {{.*}} @llvm.thread.pointer.p0()
}

typedef void (*Fvoid)(void);
typedef float (*Ffloats)(float, double, int);
typedef void (*Fpointers)(Fvoid, Ffloats, void*, int*, int***, char[5]);

void use(int);

typedef void (*Fvoid)(void);
void test_function_pointer_signature_void(Fvoid func) {
// WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison)
use(__builtin_wasm_test_function_pointer_signature(func));
}

typedef float (*Ffloats)(float, double, int);
void test_function_pointer_signature_floats(Ffloats func) {
// WEBASSEMBLY: tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, float 0.000000e+00, token poison, float 0.000000e+00, double 0.000000e+00, i32 0)
// WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, float poison, token poison, float poison, double poison, i32 poison)
use(__builtin_wasm_test_function_pointer_signature(func));
}

typedef void (*Fpointers)(Fvoid, Ffloats, void*, int*, int***, char[5]);
void test_function_pointer_signature_pointers(Fpointers func) {
// WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, ptr null, ptr null, ptr null, ptr null, ptr null, ptr null)
// WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, ptr poison, ptr poison, ptr poison, ptr poison, ptr poison, ptr poison)
use(__builtin_wasm_test_function_pointer_signature(func));
}

typedef void (*FVarArgs)(int, ...);
void test_function_pointer_signature_varargs(FVarArgs func) {
// WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, i32 poison, ptr poison)
use(__builtin_wasm_test_function_pointer_signature(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)
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)
use(__builtin_wasm_test_function_pointer_signature(func));
}

// Some tests that we get struct ABIs correct. There is no special code in
// __builtin_wasm_test_function_pointer_signature for this, it gets handled by
// the normal type lowering code.
// Single element structs are unboxed, multi element structs are passed on
// stack.
typedef struct {double x;} (*Fstructs1)(struct {double x;}, struct {float x;}, struct {double x; float y;});
void test_function_pointer_structs1(Fstructs1 func) {
// WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, double poison, token poison, double poison, float poison, ptr poison)
use(__builtin_wasm_test_function_pointer_signature(func));
}

// Two element return struct ==> return ptr on stack
typedef struct {double x; double y;} (*Fstructs2)(void);
void test_function_pointer_structs2(Fstructs2 func) {
// WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, ptr poison)
use(__builtin_wasm_test_function_pointer_signature(func));
}

// Return union ==> return ptr on stack, one element union => unboxed
typedef union {double x; float y;} (*FUnions)(union {double x; float y;}, union {double x;});
void test_function_pointer_unions(FUnions func) {
// WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, ptr poison, ptr poison, double poison)
use(__builtin_wasm_test_function_pointer_signature(func));
}
4 changes: 0 additions & 4 deletions clang/test/Sema/builtins-wasm.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@ void test_table_copy(int dst_idx, int src_idx, int nelem) {

typedef void (*F1)(void);
typedef int (*F2)(int);
typedef int (*F3)(__externref_t);
typedef __externref_t (*F4)(int);

void test_function_pointer_signature() {
// Test argument count validation
Expand All @@ -68,8 +66,6 @@ void test_function_pointer_signature() {
// // Test argument type validation - should require function pointer
(void)__builtin_wasm_test_function_pointer_signature((void*)0); // expected-error {{used type 'void *' where function pointer is required}}
(void)__builtin_wasm_test_function_pointer_signature((int)0); // expected-error {{used type 'int' where function pointer is required}}
(void)__builtin_wasm_test_function_pointer_signature((F3)0); // expected-error {{not supported for function pointers with a reference type parameter}}
(void)__builtin_wasm_test_function_pointer_signature((F4)0); // expected-error {{not supported for function pointers with a reference type return value}}

// // Test valid usage
int res = __builtin_wasm_test_function_pointer_signature((F1)0);
Expand Down
9 changes: 9 additions & 0 deletions llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,15 @@ static APInt encodeFunctionSignature(SelectionDAG *DAG, SDLoc &DL,
if (VT == MVT::f64) {
return wasm::ValType::F64;
}
if (VT == MVT::externref) {
return wasm::ValType::EXTERNREF;
}
if (VT == MVT::funcref) {
return wasm::ValType::FUNCREF;
}
if (VT == MVT::exnref) {
return wasm::ValType::EXNREF;
}
LLVM_DEBUG(errs() << "Unhandled type for llvm.wasm.ref.test.func: " << VT
<< "\n");
llvm_unreachable("Unhandled type for llvm.wasm.ref.test.func");
Expand Down
30 changes: 23 additions & 7 deletions llvm/test/CodeGen/WebAssembly/ref-test-func.ll
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ define void @test_fpsig_return_i32(ptr noundef %func) local_unnamed_addr #0 {
; CHECK-NEXT: call use
; CHECK-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i32 0)
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i32 poison)
tail call void @use(i32 noundef %res) #3
ret void
}
Expand All @@ -48,7 +48,7 @@ define void @test_fpsig_return_i64(ptr noundef %func) local_unnamed_addr #0 {
; CHECK-NEXT: call use
; CHECK-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i64 0)
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i64 poison)
tail call void @use(i32 noundef %res) #3
ret void
}
Expand All @@ -65,7 +65,7 @@ define void @test_fpsig_return_f32(ptr noundef %func) local_unnamed_addr #0 {
; CHECK-NEXT: call use
; CHECK-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, float 0.)
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, float poison)
tail call void @use(i32 noundef %res) #3
ret void
}
Expand All @@ -82,7 +82,7 @@ define void @test_fpsig_return_f64(ptr noundef %func) local_unnamed_addr #0 {
; CHECK-NEXT: call use
; CHECK-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, double 0.)
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, double poison)
tail call void @use(i32 noundef %res) #3
ret void
}
Expand All @@ -100,7 +100,7 @@ define void @test_fpsig_param_i32(ptr noundef %func) local_unnamed_addr #0 {
; CHECK-NEXT: call use
; CHECK-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, double 0.)
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, double poison)
tail call void @use(i32 noundef %res) #3
ret void
}
Expand All @@ -118,7 +118,7 @@ define void @test_fpsig_multiple_params_and_returns(ptr noundef %func) local_unn
; CHECK-NEXT: call use
; CHECK-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i32 0, i64 0, float 0., double 0., token poison, i64 0, float 0., i64 0)
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i32 poison, i64 poison, float poison, double poison, token poison, i64 poison, float poison, i64 poison)
tail call void @use(i32 noundef %res) #3
ret void
}
Expand All @@ -137,10 +137,26 @@ define void @test_fpsig_ptrs(ptr noundef %func) local_unnamed_addr #0 {
; CHECK-NEXT: call use
; CHECK-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, ptr null, token poison, ptr null, ptr null)
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, ptr poison, token poison, ptr poison, ptr poison)
tail call void @use(i32 noundef %res) #3
ret void
}

define void @test_reference_types(ptr noundef %func) local_unnamed_addr #0 {
; CHECK-LABEL: test_reference_types:
; CHK32: .functype test_reference_types (i32) -> ()
; CHK64: .functype test_reference_types (i64) -> ()
; CHECK-NEXT: # %bb.0: # %entry
; CHECK-NEXT: local.get 0
; CHK64-NEXT: i32.wrap_i64
; CHECK-NEXT: table.get __indirect_function_table
; CHECK-NEXT: ref.test (funcref, externref) -> (externref)
; CHECK-NEXT: call use
; CHECK-NEXT: # fallthrough-return
entry:
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, ptr addrspace(10) poison, token poison, ptr addrspace(20) poison, ptr addrspace(10) poison)
tail call void @use(i32 noundef %res) #3
ret void
}

declare void @use(i32 noundef) local_unnamed_addr #1