Skip to content

Commit 6c3c8a1

Browse files
committed
[WebAssembly,clang] Add __builtin_wasm_test_function_pointer_signature clang intrinsic
Tests if the runtime type of the function pointer matches the static type. If this returns false, calling the function pointer will trap. Uses `@llvm.wasm.ref.test.func` added in llvm#147486.
1 parent a0973de commit 6c3c8a1

File tree

7 files changed

+167
-0
lines changed

7 files changed

+167
-0
lines changed

clang/include/clang/Basic/BuiltinsWebAssembly.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,12 @@ TARGET_BUILTIN(__builtin_wasm_ref_is_null_extern, "ii", "nct", "reference-types"
199199
// return type.
200200
TARGET_BUILTIN(__builtin_wasm_ref_null_func, "i", "nct", "reference-types")
201201

202+
// Check if the static type of a function pointer matches its static type. Used
203+
// to avoid "function signature mismatch" traps. Takes a function pointer, uses
204+
// table.get to look up the pointer in __indirect_function_table and then
205+
// ref.test to test the type.
206+
TARGET_BUILTIN(__builtin_wasm_test_function_pointer_signature, "i.", "nct", "reference-types")
207+
202208
// Table builtins
203209
TARGET_BUILTIN(__builtin_wasm_table_set, "viii", "t", "reference-types")
204210
TARGET_BUILTIN(__builtin_wasm_table_get, "iii", "t", "reference-types")

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7575,6 +7575,8 @@ def err_typecheck_illegal_increment_decrement : Error<
75757575
"cannot %select{decrement|increment}1 value of type %0">;
75767576
def err_typecheck_expect_int : Error<
75777577
"used type %0 where integer is required">;
7578+
def err_typecheck_expect_function_pointer
7579+
: Error<"used type %0 where function pointer is required">;
75787580
def err_typecheck_expect_hlsl_resource : Error<
75797581
"used type %0 where __hlsl_resource_t is required">;
75807582
def err_typecheck_arithmetic_incomplete_or_sizeless_type : Error<
@@ -13202,6 +13204,10 @@ def err_wasm_builtin_arg_must_match_table_element_type : Error <
1320213204
"%ordinal0 argument must match the element type of the WebAssembly table in the %ordinal1 argument">;
1320313205
def err_wasm_builtin_arg_must_be_integer_type : Error <
1320413206
"%ordinal0 argument must be an integer">;
13207+
def err_wasm_builtin_test_fp_sig_cannot_include_reference_type
13208+
: Error<"not supported for "
13209+
"function pointers with a reference type %select{return "
13210+
"value|parameter}0">;
1320513211

1320613212
// OpenACC diagnostics.
1320713213
def warn_acc_routine_unimplemented

clang/include/clang/Sema/SemaWasm.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class SemaWasm : public SemaBase {
3737
bool BuiltinWasmTableGrow(CallExpr *TheCall);
3838
bool BuiltinWasmTableFill(CallExpr *TheCall);
3939
bool BuiltinWasmTableCopy(CallExpr *TheCall);
40+
bool BuiltinWasmTestFunctionPointerSignature(CallExpr *TheCall);
4041

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

clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212

1313
#include "CGBuiltin.h"
1414
#include "clang/Basic/TargetBuiltins.h"
15+
#include "llvm/ADT/APInt.h"
16+
#include "llvm/IR/Constants.h"
1517
#include "llvm/IR/IntrinsicsWebAssembly.h"
18+
#include "llvm/Support/ErrorHandling.h"
1619

1720
using namespace clang;
1821
using namespace CodeGen;
@@ -218,6 +221,60 @@ Value *CodeGenFunction::EmitWebAssemblyBuiltinExpr(unsigned BuiltinID,
218221
Function *Callee = CGM.getIntrinsic(Intrinsic::wasm_ref_null_func);
219222
return Builder.CreateCall(Callee);
220223
}
224+
case WebAssembly::BI__builtin_wasm_test_function_pointer_signature: {
225+
Value *FuncRef = EmitScalarExpr(E->getArg(0));
226+
227+
// Get the function type from the argument's static type
228+
QualType ArgType = E->getArg(0)->getType();
229+
const PointerType *PtrTy = ArgType->getAs<PointerType>();
230+
assert(PtrTy && "Sema should have ensured this is a function pointer");
231+
232+
const FunctionType *FuncTy = PtrTy->getPointeeType()->getAs<FunctionType>();
233+
assert(FuncTy && "Sema should have ensured this is a function pointer");
234+
235+
// In the llvm IR, we won't have access anymore to the type of the function
236+
// pointer so we need to insert this type information somehow. We gave the
237+
// @llvm.wasm.ref.test.func varargs and here we add an extra 0 argument of
238+
// the type corresponding to the type of each argument of the function
239+
// signature. When we lower from the IR we'll use the types of these
240+
// arguments to determine the signature we want to test for.
241+
242+
// Make a type index constant with 0. This gets replaced by the actual type
243+
// in WebAssemblyMCInstLower.cpp.
244+
llvm::FunctionType *LLVMFuncTy =
245+
cast<llvm::FunctionType>(ConvertType(QualType(FuncTy, 0)));
246+
247+
uint NParams = LLVMFuncTy->getNumParams();
248+
std::vector<Value *> Args;
249+
Args.reserve(NParams + 2);
250+
// The only real argument is the FuncRef
251+
Args.push_back(FuncRef);
252+
253+
// Add the type information
254+
auto addType = [&Args](llvm::Type *T) {
255+
if (T->isVoidTy()) {
256+
// Do nothing
257+
} else if (T->isFloatingPointTy()) {
258+
Args.push_back(ConstantFP::get(T, 0));
259+
} else if (T->isIntegerTy()) {
260+
Args.push_back(ConstantInt::get(T, 0));
261+
} else {
262+
// TODO: Handle reference types here. For now, we reject them in Sema.
263+
llvm_unreachable("Unhandled type");
264+
}
265+
};
266+
267+
addType(LLVMFuncTy->getReturnType());
268+
// The token type indicates the boundary between return types and param
269+
// types.
270+
Args.push_back(
271+
PoisonValue::get(llvm::Type::getTokenTy(getLLVMContext())));
272+
for (uint i = 0; i < NParams; i++) {
273+
addType(LLVMFuncTy->getParamType(i));
274+
}
275+
Function *Callee = CGM.getIntrinsic(Intrinsic::wasm_ref_test_func);
276+
return Builder.CreateCall(Callee, Args);
277+
}
221278
case WebAssembly::BI__builtin_wasm_swizzle_i8x16: {
222279
Value *Src = EmitScalarExpr(E->getArg(0));
223280
Value *Indices = EmitScalarExpr(E->getArg(1));

clang/lib/Sema/SemaWasm.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,53 @@ bool SemaWasm::BuiltinWasmTableCopy(CallExpr *TheCall) {
227227
return false;
228228
}
229229

230+
bool SemaWasm::BuiltinWasmTestFunctionPointerSignature(CallExpr *TheCall) {
231+
if (SemaRef.checkArgCount(TheCall, 1))
232+
return true;
233+
234+
Expr *FuncPtrArg = TheCall->getArg(0);
235+
QualType ArgType = FuncPtrArg->getType();
236+
237+
// Check that the argument is a function pointer
238+
const PointerType *PtrTy = ArgType->getAs<PointerType>();
239+
if (!PtrTy) {
240+
return Diag(FuncPtrArg->getBeginLoc(),
241+
diag::err_typecheck_expect_function_pointer)
242+
<< ArgType << FuncPtrArg->getSourceRange();
243+
}
244+
245+
const FunctionProtoType *FuncTy =
246+
PtrTy->getPointeeType()->getAs<FunctionProtoType>();
247+
if (!FuncTy) {
248+
return Diag(FuncPtrArg->getBeginLoc(),
249+
diag::err_typecheck_expect_function_pointer)
250+
<< ArgType << FuncPtrArg->getSourceRange();
251+
}
252+
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()) {
263+
return Diag(
264+
FuncPtrArg->getBeginLoc(),
265+
diag::
266+
err_wasm_builtin_test_fp_sig_cannot_include_reference_type)
267+
<< 1 << FuncPtrArg->getSourceRange();
268+
}
269+
}
270+
271+
// Set return type to int (the result of the test)
272+
TheCall->setType(getASTContext().IntTy);
273+
274+
return false;
275+
}
276+
230277
bool SemaWasm::CheckWebAssemblyBuiltinFunctionCall(const TargetInfo &TI,
231278
unsigned BuiltinID,
232279
CallExpr *TheCall) {
@@ -249,6 +296,8 @@ bool SemaWasm::CheckWebAssemblyBuiltinFunctionCall(const TargetInfo &TI,
249296
return BuiltinWasmTableFill(TheCall);
250297
case WebAssembly::BI__builtin_wasm_table_copy:
251298
return BuiltinWasmTableCopy(TheCall);
299+
case WebAssembly::BI__builtin_wasm_test_function_pointer_signature:
300+
return BuiltinWasmTestFunctionPointerSignature(TheCall);
252301
}
253302

254303
return false;

clang/test/CodeGen/builtins-wasm.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,3 +751,27 @@ void *tp (void) {
751751
return __builtin_thread_pointer ();
752752
// WEBASSEMBLY: call {{.*}} @llvm.thread.pointer.p0()
753753
}
754+
755+
756+
typedef void (*funcref_t)();
757+
typedef int (*funcref_int_t)(int);
758+
typedef float (*F1)(float, double, int);
759+
typedef int (*F2)(float, double, int);
760+
typedef int (*F3)(int, int, int);
761+
typedef void (*F4)(int, int, int);
762+
typedef void (*F5)(void);
763+
764+
void use(int);
765+
766+
void test_function_pointer_signature_void(F1 func) {
767+
// WEBASSEMBLY: %0 = 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+
// WEBASSEMBLY: %1 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i32 0, token poison, float 0.000000e+00, double 0.000000e+00, i32 0)
770+
use(__builtin_wasm_test_function_pointer_signature((F2)func));
771+
// WEBASSEMBLY: %2 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i32 0, token poison, i32 0, i32 0, i32 0)
772+
use(__builtin_wasm_test_function_pointer_signature((F3)func));
773+
// WEBASSEMBLY: %3 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison, i32 0, i32 0, i32 0)
774+
use(__builtin_wasm_test_function_pointer_signature((F4)func));
775+
// WEBASSEMBLY: %4 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token poison)
776+
use(__builtin_wasm_test_function_pointer_signature((F5)func));
777+
}

clang/test/Sema/builtins-wasm.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,27 @@ void test_table_copy(int dst_idx, int src_idx, int nelem) {
5454
__builtin_wasm_table_copy(table, table, dst_idx, src_idx, table); // expected-error {{5th argument must be an integer}}
5555
__builtin_wasm_table_copy(table, table, dst_idx, src_idx, nelem);
5656
}
57+
58+
typedef void (*F1)(void);
59+
typedef int (*F2)(int);
60+
typedef int (*F3)(__externref_t);
61+
typedef __externref_t (*F4)(int);
62+
63+
void test_function_pointer_signature() {
64+
// Test argument count validation
65+
(void)__builtin_wasm_test_function_pointer_signature(); // expected-error {{too few arguments to function call, expected 1, have 0}}
66+
(void)__builtin_wasm_test_function_pointer_signature((F1)0, (F2)0); // expected-error {{too many arguments to function call, expected 1, have 2}}
67+
68+
// // Test argument type validation - should require function pointer
69+
(void)__builtin_wasm_test_function_pointer_signature((void*)0); // expected-error {{used type 'void *' where function pointer is required}}
70+
(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}}
73+
74+
// // Test valid usage
75+
int res = __builtin_wasm_test_function_pointer_signature((F1)0);
76+
res = __builtin_wasm_test_function_pointer_signature((F2)0);
77+
78+
// Test return type
79+
_Static_assert(EXPR_HAS_TYPE(__builtin_wasm_test_function_pointer_signature((F1)0), int), "");
80+
}

0 commit comments

Comments
 (0)