Skip to content

Commit b9c3284

Browse files
authored
[clang][WebAssembly] Support reftypes & varargs in test_function_pointer_signature (#150921)
I fixed support for varargs functions (previously it didn't crash but the codegen was incorrect). I added tests for structs and unions which already work. With the multivalue abi they crash in the backend, so I added a sema check that rejects structs and unions for that abi. It will also crash in the backend if passed an int128 or float128 type.
1 parent 11e1d46 commit b9c3284

File tree

9 files changed

+151
-70
lines changed

9 files changed

+151
-70
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13234,9 +13234,9 @@ def err_wasm_builtin_arg_must_match_table_element_type : Error <
1323413234
"%ordinal0 argument must match the element type of the WebAssembly table in the %ordinal1 argument">;
1323513235
def err_wasm_builtin_arg_must_be_integer_type : Error <
1323613236
"%ordinal0 argument must be an integer">;
13237-
def err_wasm_builtin_test_fp_sig_cannot_include_reference_type
13238-
: Error<"not supported for "
13239-
"function pointers with a reference type %select{return "
13237+
def err_wasm_builtin_test_fp_sig_cannot_include_struct_or_union
13238+
: Error<"not supported with the multivalue ABI for "
13239+
"function pointers with a struct/union as %select{return "
1324013240
"value|parameter}0">;
1324113241

1324213242
// OpenACC diagnostics.

clang/include/clang/Sema/SemaWasm.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ class SemaWasm : public SemaBase {
3737
bool BuiltinWasmTableGrow(CallExpr *TheCall);
3838
bool BuiltinWasmTableFill(CallExpr *TheCall);
3939
bool BuiltinWasmTableCopy(CallExpr *TheCall);
40-
bool BuiltinWasmTestFunctionPointerSignature(CallExpr *TheCall);
40+
bool BuiltinWasmTestFunctionPointerSignature(const TargetInfo &TI,
41+
CallExpr *TheCall);
4142

4243
WebAssemblyImportNameAttr *
4344
mergeImportNameAttr(Decl *D, const WebAssemblyImportNameAttr &AL);

clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -246,35 +246,26 @@ Value *CodeGenFunction::EmitWebAssemblyBuiltinExpr(unsigned BuiltinID,
246246
llvm::FunctionType *LLVMFuncTy =
247247
cast<llvm::FunctionType>(ConvertType(QualType(FuncTy, 0)));
248248

249+
bool VarArg = LLVMFuncTy->isVarArg();
249250
unsigned NParams = LLVMFuncTy->getNumParams();
250251
std::vector<Value *> Args;
251-
Args.reserve(NParams + 3);
252+
Args.reserve(NParams + 3 + VarArg);
252253
// The only real argument is the FuncRef
253254
Args.push_back(FuncRef);
254255

255256
// Add the type information
256-
auto addType = [this, &Args](llvm::Type *T) {
257-
if (T->isVoidTy()) {
258-
// Do nothing
259-
} else if (T->isFloatingPointTy()) {
260-
Args.push_back(ConstantFP::get(T, 0));
261-
} else if (T->isIntegerTy()) {
262-
Args.push_back(ConstantInt::get(T, 0));
263-
} else if (T->isPointerTy()) {
264-
Args.push_back(ConstantPointerNull::get(llvm::PointerType::get(
265-
getLLVMContext(), T->getPointerAddressSpace())));
266-
} else {
267-
// TODO: Handle reference types. For now, we reject them in Sema.
268-
llvm_unreachable("Unhandled type");
269-
}
270-
};
271-
272-
addType(LLVMFuncTy->getReturnType());
257+
llvm::Type *RetType = LLVMFuncTy->getReturnType();
258+
if (!RetType->isVoidTy()) {
259+
Args.push_back(PoisonValue::get(RetType));
260+
}
273261
// The token type indicates the boundary between return types and param
274262
// types.
275263
Args.push_back(PoisonValue::get(llvm::Type::getTokenTy(getLLVMContext())));
276264
for (unsigned i = 0; i < NParams; i++) {
277-
addType(LLVMFuncTy->getParamType(i));
265+
Args.push_back(PoisonValue::get(LLVMFuncTy->getParamType(i)));
266+
}
267+
if (VarArg) {
268+
Args.push_back(PoisonValue::get(Builder.getPtrTy()));
278269
}
279270
Function *Callee = CGM.getIntrinsic(Intrinsic::wasm_ref_test_func);
280271
return Builder.CreateCall(Callee, Args);

clang/lib/Sema/SemaWasm.cpp

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "clang/Basic/AddressSpaces.h"
1818
#include "clang/Basic/DiagnosticSema.h"
1919
#include "clang/Basic/TargetBuiltins.h"
20+
#include "clang/Basic/TargetInfo.h"
2021
#include "clang/Sema/Attr.h"
2122
#include "clang/Sema/Sema.h"
2223

@@ -227,7 +228,8 @@ bool SemaWasm::BuiltinWasmTableCopy(CallExpr *TheCall) {
227228
return false;
228229
}
229230

230-
bool SemaWasm::BuiltinWasmTestFunctionPointerSignature(CallExpr *TheCall) {
231+
bool SemaWasm::BuiltinWasmTestFunctionPointerSignature(const TargetInfo &TI,
232+
CallExpr *TheCall) {
231233
if (SemaRef.checkArgCount(TheCall, 1))
232234
return true;
233235

@@ -250,27 +252,31 @@ bool SemaWasm::BuiltinWasmTestFunctionPointerSignature(CallExpr *TheCall) {
250252
<< ArgType << FuncPtrArg->getSourceRange();
251253
}
252254

253-
// Check that the function pointer doesn't use reference types
254-
if (FuncTy->getReturnType().isWebAssemblyReferenceType()) {
255-
return Diag(
256-
FuncPtrArg->getBeginLoc(),
257-
diag::err_wasm_builtin_test_fp_sig_cannot_include_reference_type)
258-
<< 0 << FuncTy->getReturnType() << FuncPtrArg->getSourceRange();
259-
}
260-
auto NParams = FuncTy->getNumParams();
261-
for (unsigned I = 0; I < NParams; I++) {
262-
if (FuncTy->getParamType(I).isWebAssemblyReferenceType()) {
255+
if (TI.getABI() == "experimental-mv") {
256+
auto isStructOrUnion = [](QualType T) {
257+
return T->isUnionType() || T->isStructureType();
258+
};
259+
if (isStructOrUnion(FuncTy->getReturnType())) {
263260
return Diag(
264261
FuncPtrArg->getBeginLoc(),
265262
diag::
266-
err_wasm_builtin_test_fp_sig_cannot_include_reference_type)
267-
<< 1 << FuncPtrArg->getSourceRange();
263+
err_wasm_builtin_test_fp_sig_cannot_include_struct_or_union)
264+
<< 0 << FuncTy->getReturnType() << FuncPtrArg->getSourceRange();
265+
}
266+
auto NParams = FuncTy->getNumParams();
267+
for (unsigned I = 0; I < NParams; I++) {
268+
if (isStructOrUnion(FuncTy->getParamType(I))) {
269+
return Diag(
270+
FuncPtrArg->getBeginLoc(),
271+
diag::
272+
err_wasm_builtin_test_fp_sig_cannot_include_struct_or_union)
273+
<< 1 << FuncPtrArg->getSourceRange();
274+
}
268275
}
269276
}
270277

271278
// Set return type to int (the result of the test)
272279
TheCall->setType(getASTContext().IntTy);
273-
274280
return false;
275281
}
276282

@@ -297,7 +303,7 @@ bool SemaWasm::CheckWebAssemblyBuiltinFunctionCall(const TargetInfo &TI,
297303
case WebAssembly::BI__builtin_wasm_table_copy:
298304
return BuiltinWasmTableCopy(TheCall);
299305
case WebAssembly::BI__builtin_wasm_test_function_pointer_signature:
300-
return BuiltinWasmTestFunctionPointerSignature(TheCall);
306+
return BuiltinWasmTestFunctionPointerSignature(TI, TheCall);
301307
}
302308

303309
return false;
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// RUN: %clang_cc1 -triple wasm32-unknown-unknown -target-feature +gc -O3 -emit-llvm -DSINGLE_VALUE -o - %s | FileCheck %s -check-prefixes WEBASSEMBLY,WEBASSEMBLY-SV
2+
// RUN: %clang_cc1 -triple wasm64-unknown-unknown -target-feature +gc -O3 -emit-llvm -DSINGLE_VALUE -o - %s | FileCheck %s -check-prefixes WEBASSEMBLY,WEBASSEMBLY-SV
3+
// RUN: %clang_cc1 -triple wasm64-unknown-unknown -target-feature +gc -target-abi experimental-mv -O3 -emit-llvm -o - %s 2>&1 | FileCheck %s -check-prefixes WEBASSEMBLY
4+
// RUN: not %clang_cc1 -triple wasm64-unknown-unknown -O3 -emit-llvm -o - %s 2>&1 | FileCheck %s -check-prefixes MISSING-GC
5+
6+
void use(int);
7+
8+
typedef void (*Fvoid)(void);
9+
void test_function_pointer_signature_void(Fvoid func) {
10+
// MISSING-GC: error: '__builtin_wasm_test_function_pointer_signature' needs target feature gc
11+
// WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison)
12+
use(__builtin_wasm_test_function_pointer_signature(func));
13+
}
14+
15+
typedef float (*Ffloats)(float, double, int);
16+
void test_function_pointer_signature_floats(Ffloats func) {
17+
// WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, float poison, token poison, float poison, double poison, i32 poison)
18+
use(__builtin_wasm_test_function_pointer_signature(func));
19+
}
20+
21+
typedef void (*Fpointers)(Fvoid, Ffloats, void*, int*, int***, char[5]);
22+
void test_function_pointer_signature_pointers(Fpointers func) {
23+
// 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)
24+
use(__builtin_wasm_test_function_pointer_signature(func));
25+
}
26+
27+
typedef void (*FVarArgs)(int, ...);
28+
void test_function_pointer_signature_varargs(FVarArgs func) {
29+
// WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, i32 poison, ptr poison)
30+
use(__builtin_wasm_test_function_pointer_signature(func));
31+
}
32+
33+
typedef __externref_t (*FExternRef)(__externref_t, __externref_t);
34+
void test_function_pointer_externref(FExternRef func) {
35+
// 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)
36+
use(__builtin_wasm_test_function_pointer_signature(func));
37+
}
38+
39+
typedef __funcref Fpointers (*FFuncRef)(__funcref Fvoid, __funcref Ffloats);
40+
void test_function_pointer_funcref(FFuncRef func) {
41+
// 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)
42+
use(__builtin_wasm_test_function_pointer_signature(func));
43+
}
44+
45+
#ifdef SINGLE_VALUE
46+
// Some tests that we get struct ABIs correct. There is no special code in
47+
// __builtin_wasm_test_function_pointer_signature for this, it gets handled by
48+
// the normal type lowering code.
49+
// Single element structs are unboxed, multi element structs are passed on
50+
// stack.
51+
typedef struct {double x;} (*Fstructs1)(struct {double x;}, struct {float x;}, struct {double x; float y;});
52+
void test_function_pointer_structs1(Fstructs1 func) {
53+
// WEBASSEMBLY-SV: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, double poison, token poison, double poison, float poison, ptr poison)
54+
use(__builtin_wasm_test_function_pointer_signature(func));
55+
}
56+
57+
// Two element return struct ==> return ptr on stack
58+
typedef struct {double x; double y;} (*Fstructs2)(void);
59+
void test_function_pointer_structs2(Fstructs2 func) {
60+
// WEBASSEMBLY-SV: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, ptr poison)
61+
use(__builtin_wasm_test_function_pointer_signature(func));
62+
}
63+
64+
// Return union ==> return ptr on stack, one element union => unboxed
65+
typedef union {double x; float y;} (*FUnions)(union {double x; float y;}, union {double x;});
66+
void test_function_pointer_unions(FUnions func) {
67+
// WEBASSEMBLY-SV: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, ptr poison, ptr poison, double poison)
68+
use(__builtin_wasm_test_function_pointer_signature(func));
69+
}
70+
#endif

clang/test/CodeGen/builtins-wasm.c

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -751,24 +751,3 @@ void *tp (void) {
751751
return __builtin_thread_pointer ();
752752
// WEBASSEMBLY: call {{.*}} @llvm.thread.pointer.p0()
753753
}
754-
755-
typedef void (*Fvoid)(void);
756-
typedef float (*Ffloats)(float, double, int);
757-
typedef void (*Fpointers)(Fvoid, Ffloats, void*, int*, int***, char[5]);
758-
759-
void use(int);
760-
761-
void test_function_pointer_signature_void(Fvoid func) {
762-
// WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison)
763-
use(__builtin_wasm_test_function_pointer_signature(func));
764-
}
765-
766-
void test_function_pointer_signature_floats(Ffloats func) {
767-
// 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)
768-
use(__builtin_wasm_test_function_pointer_signature(func));
769-
}
770-
771-
void test_function_pointer_signature_pointers(Fpointers func) {
772-
// 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)
773-
use(__builtin_wasm_test_function_pointer_signature(func));
774-
}

clang/test/Sema/builtins-wasm.c

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// RUN: %clang_cc1 -fsyntax-only -verify -triple wasm32 -target-feature +reference-types %s
2+
// RUN: %clang_cc1 -fsyntax-only -verify -triple wasm32 -target-abi experimental-mv -DMULTIVALUE -target-feature +reference-types %s
23

34
#define EXPR_HAS_TYPE(expr, type) _Generic((expr), type : 1, default : 0)
45

@@ -57,8 +58,8 @@ void test_table_copy(int dst_idx, int src_idx, int nelem) {
5758

5859
typedef void (*F1)(void);
5960
typedef int (*F2)(int);
60-
typedef int (*F3)(__externref_t);
61-
typedef __externref_t (*F4)(int);
61+
typedef void (*F3)(struct {int x; double y;});
62+
typedef struct {int x; double y;} (*F4)(void);
6263

6364
void test_function_pointer_signature() {
6465
// Test argument count validation
@@ -68,13 +69,21 @@ void test_function_pointer_signature() {
6869
// // Test argument type validation - should require function pointer
6970
(void)__builtin_wasm_test_function_pointer_signature((void*)0); // expected-error {{used type 'void *' where function pointer is required}}
7071
(void)__builtin_wasm_test_function_pointer_signature((int)0); // expected-error {{used type 'int' where function pointer is required}}
71-
(void)__builtin_wasm_test_function_pointer_signature((F3)0); // expected-error {{not supported for function pointers with a reference type parameter}}
72-
(void)__builtin_wasm_test_function_pointer_signature((F4)0); // expected-error {{not supported for function pointers with a reference type return value}}
7372

7473
// // Test valid usage
7574
int res = __builtin_wasm_test_function_pointer_signature((F1)0);
7675
res = __builtin_wasm_test_function_pointer_signature((F2)0);
7776

7877
// Test return type
7978
_Static_assert(EXPR_HAS_TYPE(__builtin_wasm_test_function_pointer_signature((F1)0), int), "");
79+
80+
#ifdef MULTIVALUE
81+
// Test that struct arguments and returns are rejected with multivalue abi
82+
(void)__builtin_wasm_test_function_pointer_signature((F3)0); // expected-error {{not supported with the multivalue ABI for function pointers with a struct/union as parameter}}
83+
(void)__builtin_wasm_test_function_pointer_signature((F4)0); // expected-error {{not supported with the multivalue ABI for function pointers with a struct/union as return value}}
84+
#else
85+
// with default abi they are fine
86+
(void)__builtin_wasm_test_function_pointer_signature((F3)0);
87+
(void)__builtin_wasm_test_function_pointer_signature((F4)0);
88+
#endif
8089
}

llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,15 @@ static APInt encodeFunctionSignature(SelectionDAG *DAG, SDLoc &DL,
136136
if (VT == MVT::f64) {
137137
return wasm::ValType::F64;
138138
}
139+
if (VT == MVT::externref) {
140+
return wasm::ValType::EXTERNREF;
141+
}
142+
if (VT == MVT::funcref) {
143+
return wasm::ValType::FUNCREF;
144+
}
145+
if (VT == MVT::exnref) {
146+
return wasm::ValType::EXNREF;
147+
}
139148
LLVM_DEBUG(errs() << "Unhandled type for llvm.wasm.ref.test.func: " << VT
140149
<< "\n");
141150
llvm_unreachable("Unhandled type for llvm.wasm.ref.test.func");

llvm/test/CodeGen/WebAssembly/ref-test-func.ll

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ define void @test_fpsig_return_i32(ptr noundef %func) local_unnamed_addr #0 {
3131
; CHECK-NEXT: call use
3232
; CHECK-NEXT: # fallthrough-return
3333
entry:
34-
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i32 0)
34+
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i32 poison)
3535
tail call void @use(i32 noundef %res) #3
3636
ret void
3737
}
@@ -48,7 +48,7 @@ define void @test_fpsig_return_i64(ptr noundef %func) local_unnamed_addr #0 {
4848
; CHECK-NEXT: call use
4949
; CHECK-NEXT: # fallthrough-return
5050
entry:
51-
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i64 0)
51+
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i64 poison)
5252
tail call void @use(i32 noundef %res) #3
5353
ret void
5454
}
@@ -65,7 +65,7 @@ define void @test_fpsig_return_f32(ptr noundef %func) local_unnamed_addr #0 {
6565
; CHECK-NEXT: call use
6666
; CHECK-NEXT: # fallthrough-return
6767
entry:
68-
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, float 0.)
68+
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, float poison)
6969
tail call void @use(i32 noundef %res) #3
7070
ret void
7171
}
@@ -82,7 +82,7 @@ define void @test_fpsig_return_f64(ptr noundef %func) local_unnamed_addr #0 {
8282
; CHECK-NEXT: call use
8383
; CHECK-NEXT: # fallthrough-return
8484
entry:
85-
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, double 0.)
85+
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, double poison)
8686
tail call void @use(i32 noundef %res) #3
8787
ret void
8888
}
@@ -100,7 +100,7 @@ define void @test_fpsig_param_i32(ptr noundef %func) local_unnamed_addr #0 {
100100
; CHECK-NEXT: call use
101101
; CHECK-NEXT: # fallthrough-return
102102
entry:
103-
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, double 0.)
103+
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, double poison)
104104
tail call void @use(i32 noundef %res) #3
105105
ret void
106106
}
@@ -118,7 +118,7 @@ define void @test_fpsig_multiple_params_and_returns(ptr noundef %func) local_unn
118118
; CHECK-NEXT: call use
119119
; CHECK-NEXT: # fallthrough-return
120120
entry:
121-
%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)
121+
%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)
122122
tail call void @use(i32 noundef %res) #3
123123
ret void
124124
}
@@ -137,10 +137,26 @@ define void @test_fpsig_ptrs(ptr noundef %func) local_unnamed_addr #0 {
137137
; CHECK-NEXT: call use
138138
; CHECK-NEXT: # fallthrough-return
139139
entry:
140-
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, ptr null, token poison, ptr null, ptr null)
140+
%res = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, ptr poison, token poison, ptr poison, ptr poison)
141141
tail call void @use(i32 noundef %res) #3
142142
ret void
143143
}
144144

145+
define void @test_reference_types(ptr noundef %func) local_unnamed_addr #0 {
146+
; CHECK-LABEL: test_reference_types:
147+
; CHK32: .functype test_reference_types (i32) -> ()
148+
; CHK64: .functype test_reference_types (i64) -> ()
149+
; CHECK-NEXT: # %bb.0: # %entry
150+
; CHECK-NEXT: local.get 0
151+
; CHK64-NEXT: i32.wrap_i64
152+
; CHECK-NEXT: table.get __indirect_function_table
153+
; CHECK-NEXT: ref.test (funcref, externref) -> (externref)
154+
; CHECK-NEXT: call use
155+
; CHECK-NEXT: # fallthrough-return
156+
entry:
157+
%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)
158+
tail call void @use(i32 noundef %res) #3
159+
ret void
160+
}
145161

146162
declare void @use(i32 noundef) local_unnamed_addr #1

0 commit comments

Comments
 (0)