https://github.com/hoodmane updated https://github.com/llvm/llvm-project/pull/147076
>From ba4e21486455fcee36e5521050562cd9be35e9b4 Mon Sep 17 00:00:00 2001 From: Hood Chatham <roberthoodchat...@gmail.com> Date: Sat, 10 May 2025 22:01:09 -0400 Subject: [PATCH 1/6] [WebAssembly] Add ref.test_func handling to AsmParser --- .../AsmParser/WebAssemblyAsmParser.cpp | 2 ++ .../lib/Target/WebAssembly/WebAssemblyInstrRef.td | 8 ++++++++ llvm/test/MC/WebAssembly/reference-types.s | 15 +++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp index 7ee6a3d8304be..3fad1710c30c4 100644 --- a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp +++ b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp @@ -668,6 +668,8 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser { if (parseFunctionTableOperand(&FunctionTable)) return true; ExpectFuncType = true; + } else if (Name == "ref.test_func") { + ExpectFuncType = true; } // Returns true if the next tokens are a catch clause diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td b/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td index 2654a09387fd4..8786d78c8e5af 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td +++ b/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td @@ -36,6 +36,14 @@ multiclass REF_I<WebAssemblyRegClass rc, ValueType vt, string ht> { Requires<[HasReferenceTypes]>; } +defm REF_TEST_FUNCREF : + I<(outs I32: $res), + (ins TypeIndex:$type, FUNCREF: $ref), + (outs), + (ins TypeIndex:$type), + [], + "ref.test_func\t$type, $ref", "ref.test_func $type", 0xfb14>; + defm "" : REF_I<FUNCREF, funcref, "func">; defm "" : REF_I<EXTERNREF, externref, "extern">; defm "" : REF_I<EXNREF, exnref, "exn">; diff --git a/llvm/test/MC/WebAssembly/reference-types.s b/llvm/test/MC/WebAssembly/reference-types.s index cfadede8295ef..8f3bca79bb68f 100644 --- a/llvm/test/MC/WebAssembly/reference-types.s +++ b/llvm/test/MC/WebAssembly/reference-types.s @@ -27,6 +27,21 @@ ref_null_test: drop end_function +# CHECK-LABEL: ref_test_test: +# CHECK: ref.null_func # encoding: [0xd0,0x70] +# CHECK: ref.test () -> () # encoding: [0xfb,0x14,0x80'A',0x80'A',0x80'A',0x80'A',A] +# CHECK: # fixup A - offset: 2, value: .Ltypeindex0@TYPEINDEX, kind: fixup_uleb128_i32 +# CHECK: ref.null_func # encoding: [0xd0,0x70] +# CHECK: ref.test () -> (i32) # encoding: [0xfb,0x14,0x80'A',0x80'A',0x80'A',0x80'A',A] +# CHECK: # fixup A - offset: 2, value: .Ltypeindex1@TYPEINDEX, kind: fixup_uleb128_i32 +ref_test_test: + .functype ref_test_test () -> (i32, i32) + ref.null_func + ref.test_func () -> () + ref.null_func + ref.test_func () -> (i32) + end_function + # CHECK-LABEL: ref_sig_test_funcref: # CHECK-NEXT: .functype ref_sig_test_funcref (funcref) -> (funcref) ref_sig_test_funcref: >From 6a4693a238723f78c9126071b0b0be2ae5481af9 Mon Sep 17 00:00:00 2001 From: Hood Chatham <roberthoodchat...@gmail.com> Date: Mon, 12 May 2025 20:19:55 -0400 Subject: [PATCH 2/6] Rename to ref.test --- .../lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp | 2 +- llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td | 2 +- llvm/test/MC/WebAssembly/reference-types.s | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp index 3fad1710c30c4..c1b3936c1dcec 100644 --- a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp +++ b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp @@ -668,7 +668,7 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser { if (parseFunctionTableOperand(&FunctionTable)) return true; ExpectFuncType = true; - } else if (Name == "ref.test_func") { + } else if (Name == "ref.test") { ExpectFuncType = true; } diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td b/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td index 8786d78c8e5af..40b87a084c687 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td +++ b/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td @@ -42,7 +42,7 @@ defm REF_TEST_FUNCREF : (outs), (ins TypeIndex:$type), [], - "ref.test_func\t$type, $ref", "ref.test_func $type", 0xfb14>; + "ref.test\t$type, $ref", "ref.test $type", 0xfb14>; defm "" : REF_I<FUNCREF, funcref, "func">; defm "" : REF_I<EXTERNREF, externref, "extern">; diff --git a/llvm/test/MC/WebAssembly/reference-types.s b/llvm/test/MC/WebAssembly/reference-types.s index 8f3bca79bb68f..08aafb23969eb 100644 --- a/llvm/test/MC/WebAssembly/reference-types.s +++ b/llvm/test/MC/WebAssembly/reference-types.s @@ -37,9 +37,9 @@ ref_null_test: ref_test_test: .functype ref_test_test () -> (i32, i32) ref.null_func - ref.test_func () -> () + ref.test () -> () ref.null_func - ref.test_func () -> (i32) + ref.test () -> (i32) end_function # CHECK-LABEL: ref_sig_test_funcref: >From 44ac5de19d2220a8777f7979b65f99626e5e8981 Mon Sep 17 00:00:00 2001 From: Hood Chatham <roberthoodchat...@gmail.com> Date: Wed, 2 Jul 2025 20:53:56 +0200 Subject: [PATCH 3/6] Add __builtin_wasm_test_function_pointer_signature This uses ref.test to check whether the function pointer's runtime type matches its static type. If so, then calling it won't trap with "indirect call signature mismatch". This would be very useful here: https://github.com/python/cpython/blob/main/Python/emscripten_trampoline.c and would allow us to fix function pointer mismatches on the WASI target and the Emscripten target in a uniform way. --- .../clang/Basic/BuiltinsWebAssembly.def | 6 + .../clang/Basic/DiagnosticSemaKinds.td | 6 + clang/include/clang/Sema/SemaWasm.h | 1 + .../CodeGen/TargetBuiltins/WebAssembly.cpp | 58 +++++++++ clang/lib/Sema/SemaWasm.cpp | 49 ++++++++ llvm/include/llvm/IR/IntrinsicsWebAssembly.td | 4 + .../WebAssembly/WebAssemblyISelLowering.cpp | 114 ++++++++++++++++++ .../Target/WebAssembly/WebAssemblyInstrRef.td | 5 + .../WebAssembly/WebAssemblyMCInstLower.cpp | 75 ++++++++++++ 9 files changed, 318 insertions(+) diff --git a/clang/include/clang/Basic/BuiltinsWebAssembly.def b/clang/include/clang/Basic/BuiltinsWebAssembly.def index ab480369b3820..85c6ddc125169 100644 --- a/clang/include/clang/Basic/BuiltinsWebAssembly.def +++ b/clang/include/clang/Basic/BuiltinsWebAssembly.def @@ -198,6 +198,12 @@ TARGET_BUILTIN(__builtin_wasm_ref_null_extern, "i", "nct", "reference-types") // return type. TARGET_BUILTIN(__builtin_wasm_ref_null_func, "i", "nct", "reference-types") +// Check if the static 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") diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index e5a7cdc14a737..2ec556ee64fe5 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -7460,6 +7460,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< @@ -12995,6 +12997,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<"__builtin_wasm_test_function_pointer_signature not supported for " + "function pointers with reference types in their " + "%select{return|parameter}0 type">; // OpenACC diagnostics. def warn_acc_routine_unimplemented diff --git a/clang/include/clang/Sema/SemaWasm.h b/clang/include/clang/Sema/SemaWasm.h index 8841fdff23035..f97b72ff58579 100644 --- a/clang/include/clang/Sema/SemaWasm.h +++ b/clang/include/clang/Sema/SemaWasm.h @@ -36,6 +36,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); diff --git a/clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp b/clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp index 698f43215a1be..18ac488d8628c 100644 --- a/clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp +++ b/clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp @@ -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; @@ -213,6 +216,61 @@ 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 anymore to the type of the function + // pointer so we need to insert this type information somehow. We gave the + // @llvm.wasm.ref.test.func varargs and here we add an extra 0 argument of + // the type corresponding to the type of each argument of the function + // signature. When we lower from the IR we'll use the types of these + // arguments to determine the signature we want to test for. + + // Make a type index constant with 0. This gets replaced by the actual type + // in WebAssemblyMCInstLower.cpp. + llvm::FunctionType *LLVMFuncTy = + cast<llvm::FunctionType>(ConvertType(QualType(FuncTy, 0))); + + uint NParams = LLVMFuncTy->getNumParams(); + std::vector<Value *> Args; + Args.reserve(NParams + 1); + // 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()) { + // Use TokenTy as dummy for void b/c the verifier rejects a + // void arg with 'Instruction operands must be first-class values!' + // TokenTy isn't a first class value either but apparently the verifier + // doesn't mind it. + Args.push_back( + UndefValue::get(llvm::Type::getTokenTy(getLLVMContext()))); + } else if (T->isFloatingPointTy()) { + Args.push_back(ConstantFP::get(T, 0)); + } else if (T->isIntegerTy()) { + Args.push_back(ConstantInt::get(T, 0)); + } else { + // TODO: Handle reference types here. For now, we reject them in Sema. + llvm_unreachable("Unhandled type"); + } + }; + + addType(LLVMFuncTy->getReturnType()); + 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)); diff --git a/clang/lib/Sema/SemaWasm.cpp b/clang/lib/Sema/SemaWasm.cpp index c0fa05bc17609..ca881550fad13 100644 --- a/clang/lib/Sema/SemaWasm.cpp +++ b/clang/lib/Sema/SemaWasm.cpp @@ -216,6 +216,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) { @@ -236,6 +283,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; diff --git a/llvm/include/llvm/IR/IntrinsicsWebAssembly.td b/llvm/include/llvm/IR/IntrinsicsWebAssembly.td index f592ff287a0e3..fb61d8a11e5c0 100644 --- a/llvm/include/llvm/IR/IntrinsicsWebAssembly.td +++ b/llvm/include/llvm/IR/IntrinsicsWebAssembly.td @@ -43,6 +43,10 @@ def int_wasm_ref_is_null_exn : DefaultAttrsIntrinsic<[llvm_i32_ty], [llvm_exnref_ty], [IntrNoMem], "llvm.wasm.ref.is_null.exn">; +def int_wasm_ref_test_func + : DefaultAttrsIntrinsic<[llvm_i32_ty], [llvm_ptr_ty, llvm_vararg_ty], + [IntrNoMem], "llvm.wasm.ref.test.func">; + //===----------------------------------------------------------------------===// // Table intrinsics //===----------------------------------------------------------------------===// diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp index aac68b32da13a..1024bb69e6a49 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp @@ -18,6 +18,7 @@ #include "WebAssemblySubtarget.h" #include "WebAssemblyTargetMachine.h" #include "WebAssemblyUtilities.h" +#include "llvm/BinaryFormat/Wasm.h" #include "llvm/CodeGen/CallingConvLower.h" #include "llvm/CodeGen/MachineFrameInfo.h" #include "llvm/CodeGen/MachineInstrBuilder.h" @@ -505,6 +506,51 @@ MVT WebAssemblyTargetLowering::getScalarShiftAmountTy(const DataLayout & /*DL*/, return Result; } +static MachineBasicBlock *LowerRefTestFuncRef(MachineInstr &MI, DebugLoc DL, + MachineBasicBlock *BB, + const TargetInstrInfo &TII) { + // Lower a REF_TEST_FUNCREF_PSEUDO instruction into a REF_TEST_FUNCREF + // instruction by combining the signature info Imm operands that + // SelectionDag/InstrEmitter.cpp makes into one CImm operand. Put this into + // the type index placeholder for REF_TEST_FUNCREF + Register ResultReg = MI.getOperand(0).getReg(); + Register FuncRefReg = MI.getOperand(1).getReg(); + + auto NParams = MI.getNumOperands() - 3; + auto Sig = APInt(NParams * 64, 0); + + { + uint64_t V = MI.getOperand(2).getImm(); + Sig |= int64_t(V); + } + + for (unsigned I = 3; I < MI.getNumOperands(); I++) { + const MachineOperand &MO = MI.getOperand(I); + if (!MO.isImm()) { + // I'm not really sure what these are or where they come from but it seems + // to be okay to ignore them + continue; + } + uint16_t V = MO.getImm(); + Sig <<= 64; + Sig |= int64_t(V); + } + + ConstantInt *TypeInfo = + ConstantInt::get(BB->getParent()->getFunction().getContext(), Sig); + + // Put the type info first in the placeholder for the type index, then the + // actual funcref arg + BuildMI(*BB, MI, DL, TII.get(WebAssembly::REF_TEST_FUNCREF), ResultReg) + .addCImm(TypeInfo) + .addReg(FuncRefReg); + + // Remove the original instruction + MI.eraseFromParent(); + + return BB; +} + // Lower an fp-to-int conversion operator from the LLVM opcode, which has an // undefined result on invalid/overflow, to the WebAssembly opcode, which // traps on invalid/overflow. @@ -866,6 +912,8 @@ MachineBasicBlock *WebAssemblyTargetLowering::EmitInstrWithCustomInserter( switch (MI.getOpcode()) { default: llvm_unreachable("Unexpected instr type to insert"); + case WebAssembly::REF_TEST_FUNCREF_PSEUDO: + return LowerRefTestFuncRef(MI, DL, BB, TII); case WebAssembly::FP_TO_SINT_I32_F32: return LowerFPToInt(MI, DL, BB, TII, false, false, false, WebAssembly::I32_TRUNC_S_F32); @@ -2260,6 +2308,72 @@ SDValue WebAssemblyTargetLowering::LowerIntrinsic(SDValue Op, DAG.getTargetExternalSymbol(TlsBase, PtrVT)), 0); } + case Intrinsic::wasm_ref_test_func: { + // First emit the TABLE_GET instruction to convert function pointer ==> + // funcref + MachineFunction &MF = DAG.getMachineFunction(); + auto PtrVT = getPointerTy(MF.getDataLayout()); + MCSymbol *Table = + WebAssembly::getOrCreateFunctionTableSymbol(MF.getContext(), Subtarget); + SDValue TableSym = DAG.getMCSymbol(Table, PtrVT); + SDValue FuncRef = + SDValue(DAG.getMachineNode(WebAssembly::TABLE_GET_FUNCREF, DL, + MVT::funcref, TableSym, Op.getOperand(1)), + 0); + + SmallVector<SDValue, 4> Ops; + Ops.push_back(FuncRef); + + // We want to encode the type information into an APInt which we'll put + // in a CImm. However, in SelectionDag/InstrEmitter.cpp there is no code + // path that emits a CImm. So we need a custom inserter to put it in. + + // We'll put each type argument in a separate TargetConstant which gets + // lowered to a MachineInstruction Imm. We combine these into a CImm in our + // custom inserter because it creates a problem downstream to have all these + // extra immediates. + { + SDValue Operand = Op.getOperand(2); + MVT VT = Operand.getValueType().getSimpleVT(); + WebAssembly::BlockType V; + if (VT == MVT::Untyped) { + V = WebAssembly::BlockType::Void; + } else if (VT == MVT::i32) { + V = WebAssembly::BlockType::I32; + } else if (VT == MVT::i64) { + V = WebAssembly::BlockType::I64; + } else if (VT == MVT::f32) { + V = WebAssembly::BlockType::F32; + } else if (VT == MVT::f64) { + V = WebAssembly::BlockType::F64; + } else { + llvm_unreachable("Unhandled type!"); + } + Ops.push_back(DAG.getTargetConstant((int64_t)V, DL, MVT::i64)); + } + + for (unsigned i = 3; i < Op.getNumOperands(); ++i) { + SDValue Operand = Op.getOperand(i); + MVT VT = Operand.getValueType().getSimpleVT(); + wasm::ValType V; + if (VT == MVT::i32) { + V = wasm::ValType::I32; + } else if (VT == MVT::i64) { + V = wasm::ValType::I64; + } else if (VT == MVT::f32) { + V = wasm::ValType::F32; + } else if (VT == MVT::f64) { + V = wasm::ValType::F64; + } else { + llvm_unreachable("Unhandled type!"); + } + Ops.push_back(DAG.getTargetConstant((int64_t)V, DL, MVT::i64)); + } + + return SDValue(DAG.getMachineNode(WebAssembly::REF_TEST_FUNCREF_PSEUDO, DL, + MVT::i32, Ops), + 0); + } } } diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td b/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td index 40b87a084c687..0c61f5770e748 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td +++ b/llvm/lib/Target/WebAssembly/WebAssemblyInstrRef.td @@ -36,6 +36,11 @@ multiclass REF_I<WebAssemblyRegClass rc, ValueType vt, string ht> { Requires<[HasReferenceTypes]>; } +let usesCustomInserter = 1, isPseudo = 1 in defm REF_TEST_FUNCREF_PSEUDO + : I<(outs I32:$res), (ins TypeIndex:$type, FUNCREF:$ref, variable_ops), + (outs), (ins TypeIndex:$type), [], "ref.test.pseudo\t$type, $ref", + "ref.test.pseudo $type", -1>; + defm REF_TEST_FUNCREF : I<(outs I32: $res), (ins TypeIndex:$type, FUNCREF: $ref), diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp index 8c8629203bca7..770942d09e429 100644 --- a/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp @@ -15,13 +15,17 @@ #include "WebAssemblyMCInstLower.h" #include "MCTargetDesc/WebAssemblyMCExpr.h" #include "MCTargetDesc/WebAssemblyMCTargetDesc.h" +#include "MCTargetDesc/WebAssemblyMCTypeUtilities.h" #include "TargetInfo/WebAssemblyTargetInfo.h" #include "Utils/WebAssemblyTypeUtilities.h" #include "WebAssemblyAsmPrinter.h" #include "WebAssemblyMachineFunctionInfo.h" #include "WebAssemblyUtilities.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/BinaryFormat/Wasm.h" #include "llvm/CodeGen/AsmPrinter.h" #include "llvm/CodeGen/MachineFunction.h" +#include "llvm/CodeGen/MachineOperand.h" #include "llvm/IR/Constants.h" #include "llvm/MC/MCAsmInfo.h" #include "llvm/MC/MCContext.h" @@ -198,11 +202,81 @@ void WebAssemblyMCInstLower::lower(const MachineInstr *MI, MCOp = MCOperand::createReg(WAReg); break; } + case llvm::MachineOperand::MO_CImmediate: { + // Lower type index placeholder for ref.test + // Currently this is the only way that CImmediates show up so panic if we + // get confused. + unsigned DescIndex = I - NumVariadicDefs; + if (DescIndex >= Desc.NumOperands) { + llvm_unreachable("unexpected CImmediate operand"); + } + const MCOperandInfo &Info = Desc.operands()[DescIndex]; + if (Info.OperandType != WebAssembly::OPERAND_TYPEINDEX) { + llvm_unreachable("unexpected CImmediate operand"); + } + auto CImm = MO.getCImm()->getValue(); + auto NumWords = CImm.getNumWords(); + // Extract the type data we packed into the CImm in LowerRefTestFuncRef. + // We need to load the words from most significant to least significant + // order because of the way we bitshifted them in from the right. + // The return type needs special handling because it could be void. + auto ReturnType = static_cast<WebAssembly::BlockType>( + CImm.extractBitsAsZExtValue(64, (NumWords - 1) * 64)); + assert(ReturnType != WebAssembly::BlockType::Invalid); + SmallVector<wasm::ValType, 2> Returns; + switch (ReturnType) { + case WebAssembly::BlockType::Invalid: + llvm_unreachable("Invalid return type"); + case WebAssembly::BlockType::I32: + Returns = {wasm::ValType::I32}; + break; + case WebAssembly::BlockType::I64: + Returns = {wasm::ValType::I64}; + break; + case WebAssembly::BlockType::F32: + Returns = {wasm::ValType::F32}; + break; + case WebAssembly::BlockType::F64: + Returns = {wasm::ValType::F64}; + break; + case WebAssembly::BlockType::Void: + Returns = {}; + break; + case WebAssembly::BlockType::Exnref: + Returns = {wasm::ValType::EXNREF}; + break; + case WebAssembly::BlockType::Externref: + Returns = {wasm::ValType::EXTERNREF}; + break; + case WebAssembly::BlockType::Funcref: + Returns = {wasm::ValType::FUNCREF}; + break; + case WebAssembly::BlockType::V128: + Returns = {wasm::ValType::V128}; + break; + case WebAssembly::BlockType::Multivalue: { + llvm_unreachable("Invalid return type"); + } + } + SmallVector<wasm::ValType, 4> Params; + + for (int I = NumWords - 2; I >= 0; I--) { + auto Val = CImm.extractBitsAsZExtValue(64, 64 * I); + auto ParamType = static_cast<wasm::ValType>(Val); + Params.push_back(ParamType); + } + MCOp = lowerTypeIndexOperand(std::move(Returns), std::move(Params)); + break; + } case MachineOperand::MO_Immediate: { unsigned DescIndex = I - NumVariadicDefs; if (DescIndex < Desc.NumOperands) { const MCOperandInfo &Info = Desc.operands()[DescIndex]; + // Replace type index placeholder with actual type index. The type index + // placeholders are immedates or CImmediates and have an operand type of + // OPERAND_TYPEINDEX or OPERAND_SIGNATURE. if (Info.OperandType == WebAssembly::OPERAND_TYPEINDEX) { + // Lower type index placeholder for a CALL_INDIRECT instruction SmallVector<wasm::ValType, 4> Returns; SmallVector<wasm::ValType, 4> Params; @@ -230,6 +304,7 @@ void WebAssemblyMCInstLower::lower(const MachineInstr *MI, break; } if (Info.OperandType == WebAssembly::OPERAND_SIGNATURE) { + // Lower type index placeholder for blocks auto BT = static_cast<WebAssembly::BlockType>(MO.getImm()); assert(BT != WebAssembly::BlockType::Invalid); if (BT == WebAssembly::BlockType::Multivalue) { >From a763b1e581835378756e9c69754ae438d6bbc35a Mon Sep 17 00:00:00 2001 From: Hood Chatham <roberthoodchat...@gmail.com> Date: Fri, 4 Jul 2025 17:37:38 +0200 Subject: [PATCH 4/6] Update sema test --- .../clang/Basic/DiagnosticSemaKinds.td | 6 ++--- clang/test/Sema/builtins-wasm.c | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 2ec556ee64fe5..8e9af3d5dbfac 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -12998,9 +12998,9 @@ def err_wasm_builtin_arg_must_match_table_element_type : Error < 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<"__builtin_wasm_test_function_pointer_signature not supported for " - "function pointers with reference types in their " - "%select{return|parameter}0 type">; + : Error<"not supported for " + "function pointers with a reference type %select{return " + "value|parameter}0">; // OpenACC diagnostics. def warn_acc_routine_unimplemented diff --git a/clang/test/Sema/builtins-wasm.c b/clang/test/Sema/builtins-wasm.c index beb430616233a..de9f493f34833 100644 --- a/clang/test/Sema/builtins-wasm.c +++ b/clang/test/Sema/builtins-wasm.c @@ -50,3 +50,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), ""); +} >From 7cdc5d34b4d79cc635de24d0be14477109800a65 Mon Sep 17 00:00:00 2001 From: Hood Chatham <roberthoodchat...@gmail.com> Date: Fri, 4 Jul 2025 17:47:57 +0200 Subject: [PATCH 5/6] Update builtins-wasm.c CodeGen test --- clang/test/CodeGen/builtins-wasm.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/clang/test/CodeGen/builtins-wasm.c b/clang/test/CodeGen/builtins-wasm.c index 263cfd3ab4c69..d8eb7ba2fc7be 100644 --- a/clang/test/CodeGen/builtins-wasm.c +++ b/clang/test/CodeGen/builtins-wasm.c @@ -745,3 +745,27 @@ void *tp (void) { return __builtin_thread_pointer (); // WEBASSEMBLY: call {{.*}} @llvm.thread.pointer() } + + +typedef void (*funcref_t)(); +typedef int (*funcref_int_t)(int); +typedef float (*F1)(float, double, int); +typedef int (*F2)(float, double, int); +typedef int (*F3)(int, int, int); +typedef void (*F4)(int, int, int); +typedef void (*F5)(void); + +void use(int); + +void test_function_pointer_signature_void(F1 func) { + // WEBASSEMBLY: %0 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, float 0.000000e+00, float 0.000000e+00, double 0.000000e+00, i32 0) + use(__builtin_wasm_test_function_pointer_signature(func)); + // WEBASSEMBLY: %1 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i32 0, float 0.000000e+00, double 0.000000e+00, i32 0) + use(__builtin_wasm_test_function_pointer_signature((F2)func)); + // WEBASSEMBLY: %2 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, i32 0, i32 0, i32 0, i32 0) + use(__builtin_wasm_test_function_pointer_signature((F3)func)); + // WEBASSEMBLY: %3 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token undef, i32 0, i32 0, i32 0) + use(__builtin_wasm_test_function_pointer_signature((F4)func)); + // WEBASSEMBLY: %4 = tail call i32 (ptr, ...) @llvm.wasm.ref.test.func(ptr %func, token undef) + use(__builtin_wasm_test_function_pointer_signature((F5)func)); +} >From fe3b6553c73345d5911de8f2cc0c05dbd5222381 Mon Sep 17 00:00:00 2001 From: Hood Chatham <roberthoodchat...@gmail.com> Date: Fri, 4 Jul 2025 18:02:01 +0200 Subject: [PATCH 6/6] Use Poison instead of Undef --- clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp b/clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp index 18ac488d8628c..56231a3a357c9 100644 --- a/clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp +++ b/clang/lib/CodeGen/TargetBuiltins/WebAssembly.cpp @@ -253,7 +253,7 @@ Value *CodeGenFunction::EmitWebAssemblyBuiltinExpr(unsigned BuiltinID, // TokenTy isn't a first class value either but apparently the verifier // doesn't mind it. Args.push_back( - UndefValue::get(llvm::Type::getTokenTy(getLLVMContext()))); + PoisonValue::get(llvm::Type::getTokenTy(getLLVMContext()))); } else if (T->isFloatingPointTy()) { Args.push_back(ConstantFP::get(T, 0)); } else if (T->isIntegerTy()) { _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits