Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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/include/clang/Basic/BuiltinsWebAssembly.def
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,12 @@ TARGET_BUILTIN(__builtin_wasm_ref_is_null_extern, "ii", "nct", "reference-types"
// return type.
TARGET_BUILTIN(__builtin_wasm_ref_null_func, "i", "nct", "reference-types")

// Check if the runtime type of a function pointer matches its static type. Used
// to avoid "function signature mismatch" traps. Takes a function pointer, uses
// table.get to look up the pointer in __indirect_function_table and then
// ref.test to test the type.
TARGET_BUILTIN(__builtin_wasm_test_function_pointer_signature, "i.", "nct", "reference-types")

// Table builtins
TARGET_BUILTIN(__builtin_wasm_table_set, "viii", "t", "reference-types")
TARGET_BUILTIN(__builtin_wasm_table_get, "iii", "t", "reference-types")
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -7575,6 +7575,8 @@ def err_typecheck_illegal_increment_decrement : Error<
"cannot %select{decrement|increment}1 value of type %0">;
def err_typecheck_expect_int : Error<
"used type %0 where integer is required">;
def err_typecheck_expect_function_pointer
: Error<"used type %0 where function pointer is required">;
def err_typecheck_expect_hlsl_resource : Error<
"used type %0 where __hlsl_resource_t is required">;
def err_typecheck_arithmetic_incomplete_or_sizeless_type : Error<
Expand Down Expand Up @@ -13202,6 +13204,10 @@ def err_wasm_builtin_arg_must_match_table_element_type : Error <
"%ordinal0 argument must match the element type of the WebAssembly table in the %ordinal1 argument">;
def err_wasm_builtin_arg_must_be_integer_type : Error <
"%ordinal0 argument must be an integer">;
def err_wasm_builtin_test_fp_sig_cannot_include_reference_type
: Error<"not supported for "
"function pointers with a reference type %select{return "
"value|parameter}0">;

// OpenACC diagnostics.
def warn_acc_routine_unimplemented
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Sema/SemaWasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class SemaWasm : public SemaBase {
bool BuiltinWasmTableGrow(CallExpr *TheCall);
bool BuiltinWasmTableFill(CallExpr *TheCall);
bool BuiltinWasmTableCopy(CallExpr *TheCall);
bool BuiltinWasmTestFunctionPointerSignature(CallExpr *TheCall);

WebAssemblyImportNameAttr *
mergeImportNameAttr(Decl *D, const WebAssemblyImportNameAttr &AL);
Expand Down
61 changes: 61 additions & 0 deletions clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@

#include "CGBuiltin.h"
#include "clang/Basic/TargetBuiltins.h"
#include "llvm/ADT/APInt.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/IntrinsicsWebAssembly.h"
#include "llvm/Support/ErrorHandling.h"

using namespace clang;
using namespace CodeGen;
Expand Down Expand Up @@ -218,6 +221,64 @@ Value *CodeGenFunction::EmitWebAssemblyBuiltinExpr(unsigned BuiltinID,
Function *Callee = CGM.getIntrinsic(Intrinsic::wasm_ref_null_func);
return Builder.CreateCall(Callee);
}
case WebAssembly::BI__builtin_wasm_test_function_pointer_signature: {
Value *FuncRef = EmitScalarExpr(E->getArg(0));

// Get the function type from the argument's static type
QualType ArgType = E->getArg(0)->getType();
const PointerType *PtrTy = ArgType->getAs<PointerType>();
assert(PtrTy && "Sema should have ensured this is a function pointer");

const FunctionType *FuncTy = PtrTy->getPointeeType()->getAs<FunctionType>();
assert(FuncTy && "Sema should have ensured this is a function pointer");

// In the llvm IR, we won't have access any more to the type of the function
// pointer so we need to insert this type information somehow. The
// @llvm.wasm.ref.test.func takes varargs arguments whose values are unused
// to indicate the type of the function to test for. See the test here:
// llvm/test/CodeGen/WebAssembly/ref-test-func.ll
//
// The format is: first we include the return types (since this is a C
// function pointer, there will be 0 or one of these) then a token type to
// indicate the boundary between return types and param types, then the
// param types.

llvm::FunctionType *LLVMFuncTy =
cast<llvm::FunctionType>(ConvertType(QualType(FuncTy, 0)));

uint NParams = LLVMFuncTy->getNumParams();
std::vector<Value *> Args;
Args.reserve(NParams + 2);
// 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());
// The token type indicates the boundary between return types and param
// types.
Args.push_back(PoisonValue::get(llvm::Type::getTokenTy(getLLVMContext())));
for (uint i = 0; i < NParams; i++) {
addType(LLVMFuncTy->getParamType(i));
}
Function *Callee = CGM.getIntrinsic(Intrinsic::wasm_ref_test_func);
return Builder.CreateCall(Callee, Args);
}
case WebAssembly::BI__builtin_wasm_swizzle_i8x16: {
Value *Src = EmitScalarExpr(E->getArg(0));
Value *Indices = EmitScalarExpr(E->getArg(1));
Expand Down
49 changes: 49 additions & 0 deletions clang/lib/Sema/SemaWasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,53 @@ bool SemaWasm::BuiltinWasmTableCopy(CallExpr *TheCall) {
return false;
}

bool SemaWasm::BuiltinWasmTestFunctionPointerSignature(CallExpr *TheCall) {
if (SemaRef.checkArgCount(TheCall, 1))
return true;

Expr *FuncPtrArg = TheCall->getArg(0);
QualType ArgType = FuncPtrArg->getType();

// Check that the argument is a function pointer
const PointerType *PtrTy = ArgType->getAs<PointerType>();
if (!PtrTy) {
return Diag(FuncPtrArg->getBeginLoc(),
diag::err_typecheck_expect_function_pointer)
<< ArgType << FuncPtrArg->getSourceRange();
}

const FunctionProtoType *FuncTy =
PtrTy->getPointeeType()->getAs<FunctionProtoType>();
if (!FuncTy) {
return Diag(FuncPtrArg->getBeginLoc(),
diag::err_typecheck_expect_function_pointer)
<< 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);

return false;
}

bool SemaWasm::CheckWebAssemblyBuiltinFunctionCall(const TargetInfo &TI,
unsigned BuiltinID,
CallExpr *TheCall) {
Expand All @@ -249,6 +296,8 @@ bool SemaWasm::CheckWebAssemblyBuiltinFunctionCall(const TargetInfo &TI,
return BuiltinWasmTableFill(TheCall);
case WebAssembly::BI__builtin_wasm_table_copy:
return BuiltinWasmTableCopy(TheCall);
case WebAssembly::BI__builtin_wasm_test_function_pointer_signature:
return BuiltinWasmTestFunctionPointerSignature(TheCall);
}

return false;
Expand Down
21 changes: 21 additions & 0 deletions clang/test/CodeGen/builtins-wasm.c
Original file line number Diff line number Diff line change
Expand Up @@ -751,3 +751,24 @@ void *tp (void) {
return __builtin_thread_pointer ();
// 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);

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));
}

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)
use(__builtin_wasm_test_function_pointer_signature(func));
}

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)
use(__builtin_wasm_test_function_pointer_signature(func));
}
24 changes: 24 additions & 0 deletions clang/test/Sema/builtins-wasm.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,27 @@ void test_table_copy(int dst_idx, int src_idx, int nelem) {
__builtin_wasm_table_copy(table, table, dst_idx, src_idx, table); // expected-error {{5th argument must be an integer}}
__builtin_wasm_table_copy(table, table, dst_idx, src_idx, 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
(void)__builtin_wasm_test_function_pointer_signature(); // expected-error {{too few arguments to function call, expected 1, have 0}}
(void)__builtin_wasm_test_function_pointer_signature((F1)0, (F2)0); // expected-error {{too many arguments to function call, expected 1, have 2}}

// // 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);
res = __builtin_wasm_test_function_pointer_signature((F2)0);

// Test return type
_Static_assert(EXPR_HAS_TYPE(__builtin_wasm_test_function_pointer_signature((F1)0), int), "");
}
Loading