https://github.com/turran updated https://github.com/llvm/llvm-project/pull/153168
>From fc8c1a0e57efe07c9d4a9f46ad733fbba481cad6 Mon Sep 17 00:00:00 2001 From: Jorge Zapata <[email protected]> Date: Mon, 23 Jun 2025 12:53:57 +0200 Subject: [PATCH 1/7] [wasm] Support different signature function pointers --- clang/lib/CodeGen/CGCall.cpp | 20 ++ clang/lib/CodeGen/CGExprConstant.cpp | 13 ++ clang/lib/CodeGen/TargetInfo.h | 11 ++ clang/lib/CodeGen/Targets/WebAssembly.cpp | 222 ++++++++++++++++++++++ 4 files changed, 266 insertions(+) diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp index b7b79e7051181..2b86004944118 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -5067,6 +5067,26 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E, return; } + // For WebAssembly target we need to create thunk functions + // to properly handle function pointers args with a different signature. + // Due to opaque pointers, this can not be handled in LLVM + // (WebAssemblyFixFunctionBitcast) anymore + if (CGM.getTriple().isWasm() && type->isFunctionPointerType()) { + if (const DeclRefExpr *DRE = + CGM.getTargetCodeGenInfo().getWasmFunctionDeclRefExpr( + E, CGM.getContext())) { + llvm::Value *V = EmitLValue(DRE).getPointer(*this); + llvm::Function *Thunk = + CGM.getTargetCodeGenInfo().getOrCreateWasmFunctionPointerThunk( + CGM, V, DRE->getDecl()->getType(), type); + if (Thunk) { + RValue R = RValue::get(Thunk); + args.add(R, type); + return; + } + } + } + args.add(EmitAnyExprToTemp(E), type); } diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp index 24712d3325b2e..57d93887ce2e2 100644 --- a/clang/lib/CodeGen/CGExprConstant.cpp +++ b/clang/lib/CodeGen/CGExprConstant.cpp @@ -2285,6 +2285,19 @@ ConstantLValueEmitter::tryEmitBase(const APValue::LValueBase &base) { if (const auto *FD = dyn_cast<FunctionDecl>(D)) { llvm::Constant *C = CGM.getRawFunctionPointer(FD); + // ForWebAssembly target we need to create thunk functions + // to properly handle function pointers args with a different signature + // Due to opaque pointers, this can not be handled in LLVM + // (WebAssemblyFixFunctionBitcast) anymore + if (CGM.getTriple().isWasm() && DestType->isFunctionPointerType()) { + llvm::Function *Thunk = + CGM.getTargetCodeGenInfo().getOrCreateWasmFunctionPointerThunk( + CGM, C, D->getType(), DestType); + if (Thunk) { + C = Thunk; + } + } + if (FD->getType()->isCFIUncheckedCalleeFunctionType()) C = llvm::NoCFIValue::get(cast<llvm::GlobalValue>(C)); return PtrAuthSign(C); diff --git a/clang/lib/CodeGen/TargetInfo.h b/clang/lib/CodeGen/TargetInfo.h index 98ee894fe557f..c708f798627e5 100644 --- a/clang/lib/CodeGen/TargetInfo.h +++ b/clang/lib/CodeGen/TargetInfo.h @@ -403,6 +403,17 @@ class TargetCodeGenInfo { /// Return the WebAssembly funcref reference type. virtual llvm::Type *getWasmFuncrefReferenceType() const { return nullptr; } + virtual const DeclRefExpr *getWasmFunctionDeclRefExpr(const Expr *E, + ASTContext &Ctx) const { + return nullptr; + } + + virtual llvm::Function *getOrCreateWasmFunctionPointerThunk( + CodeGenModule &CGM, llvm::Value *OriginalFnPtr, QualType SrcType, + QualType DstType) const { + return nullptr; + } + /// Emit the device-side copy of the builtin surface type. virtual bool emitCUDADeviceBuiltinSurfaceDeviceCopy(CodeGenFunction &CGF, LValue Dst, diff --git a/clang/lib/CodeGen/Targets/WebAssembly.cpp b/clang/lib/CodeGen/Targets/WebAssembly.cpp index ebe996a4edd8d..b0a4f35c80c3d 100644 --- a/clang/lib/CodeGen/Targets/WebAssembly.cpp +++ b/clang/lib/CodeGen/Targets/WebAssembly.cpp @@ -9,9 +9,14 @@ #include "ABIInfoImpl.h" #include "TargetInfo.h" +#include "clang/AST/ParentMapContext.h" +#include <sstream> + using namespace clang; using namespace clang::CodeGen; +#define DEBUG_TYPE "clang-target-wasm" + //===----------------------------------------------------------------------===// // WebAssembly ABI Implementation // @@ -93,6 +98,112 @@ class WebAssemblyTargetCodeGenInfo final : public TargetCodeGenInfo { virtual llvm::Type *getWasmFuncrefReferenceType() const override { return llvm::Type::getWasm_FuncrefTy(getABIInfo().getVMContext()); } + + virtual const DeclRefExpr * + getWasmFunctionDeclRefExpr(const Expr *E, ASTContext &Ctx) const override { + // Go down in the tree until finding the DeclRefExpr + const DeclRefExpr *DRE = findDeclRefExpr(E); + if (!DRE) + return nullptr; + + // Final case. The argument is a declared function + if (isa<FunctionDecl>(DRE->getDecl())) { + return DRE; + } + + // Complex case. The argument is a variable, we need to check + // every assignment of the variable and see if we are bitcasting + // or not. + if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) { + DRE = findDeclRefExprForVarUp(E, VD, Ctx); + if (DRE) + return DRE; + + // If no assignment exists on every parent scope, check for the + // initialization + if (!DRE && VD->hasInit()) { + return getWasmFunctionDeclRefExpr(VD->getInit(), Ctx); + } + } + + return nullptr; + } + + virtual llvm::Function *getOrCreateWasmFunctionPointerThunk( + CodeGenModule &CGM, llvm::Value *OriginalFnPtr, QualType SrcType, + QualType DstType) const override { + + // Get the signatures + const FunctionProtoType *SrcProtoType = SrcType->getAs<FunctionProtoType>(); + const FunctionProtoType *DstProtoType = DstType->getAs<PointerType>() + ->getPointeeType() + ->getAs<FunctionProtoType>(); + + // This should only work for different number of arguments + if (DstProtoType->getNumParams() <= SrcProtoType->getNumParams()) + return nullptr; + + // Get the llvm function types + llvm::FunctionType *DstFunctionType = llvm::cast<llvm::FunctionType>( + CGM.getTypes().ConvertType(QualType(DstProtoType, 0))); + llvm::FunctionType *SrcFunctionType = llvm::cast<llvm::FunctionType>( + CGM.getTypes().ConvertType(QualType(SrcProtoType, 0))); + + // Construct the Thunk function with the Target (destination) signature + std::string ThunkName = getThunkName(OriginalFnPtr->getName().str(), + DstProtoType, CGM.getContext()); + llvm::Module &M = CGM.getModule(); + llvm::Function *Thunk = llvm::Function::Create( + DstFunctionType, llvm::Function::InternalLinkage, ThunkName, M); + + // Build the thunk body + llvm::IRBuilder<> Builder( + llvm::BasicBlock::Create(M.getContext(), "entry", Thunk)); + + // Gather the arguments for calling the original function + std::vector<llvm::Value *> CallArgs; + unsigned CallN = SrcProtoType->getNumParams(); + + auto ArgIt = Thunk->arg_begin(); + for (unsigned i = 0; i < CallN && ArgIt != Thunk->arg_end(); ++i, ++ArgIt) { + llvm::Value *A = &*ArgIt; + CallArgs.push_back(A); + } + + // Create the call to the original function pointer + llvm::CallInst *Call = + Builder.CreateCall(SrcFunctionType, OriginalFnPtr, CallArgs); + + // Handle return type + llvm::Type *ThunkRetTy = DstFunctionType->getReturnType(); + + if (ThunkRetTy->isVoidTy()) { + Builder.CreateRetVoid(); + } else { + llvm::Value *Ret = Call; + if (Ret->getType() != ThunkRetTy) + Ret = Builder.CreateBitCast(Ret, ThunkRetTy); + Builder.CreateRet(Ret); + } + LLVM_DEBUG(llvm::dbgs() << "getOrCreateWasmFunctionPointerThunk:" + << " from " << OriginalFnPtr->getName().str() + << " to " << ThunkName << "\n"); + return Thunk; + } + +private: + // Build the thunk name: "%s_{type1}_{type2}_..." + std::string getThunkName(std::string OrigName, + const FunctionProtoType *DstProto, + const ASTContext &Ctx) const; + std::string sanitizeTypeString(const std::string &typeStr) const; + std::string getTypeName(const QualType &qt, const ASTContext &Ctx) const; + const DeclRefExpr *findDeclRefExpr(const Expr *E) const; + const DeclRefExpr *findDeclRefExprForVarDown(const Stmt *Parent, + const VarDecl *V, + ASTContext &Ctx) const; + const DeclRefExpr *findDeclRefExprForVarUp(const Expr *E, const VarDecl *V, + ASTContext &Ctx) const; }; /// Classify argument of given type \p Ty. @@ -171,3 +282,114 @@ CodeGen::createWebAssemblyTargetCodeGenInfo(CodeGenModule &CGM, WebAssemblyABIKind K) { return std::make_unique<WebAssemblyTargetCodeGenInfo>(CGM.getTypes(), K); } + +// Helper to sanitize type name string for use in function name +std::string WebAssemblyTargetCodeGenInfo::sanitizeTypeString( + const std::string &typeStr) const { + std::string s; + for (char c : typeStr) { + if (isalnum(c)) + s += c; + else if (c == ' ') + s += '_'; + else + s += '_'; + } + return s; +} + +// Helper to generate the type string from QualType +std::string +WebAssemblyTargetCodeGenInfo::getTypeName(const QualType &qt, + const ASTContext &Ctx) const { + PrintingPolicy Policy(Ctx.getLangOpts()); + Policy.SuppressTagKeyword = true; + Policy.SuppressScope = true; + Policy.AnonymousTagLocations = false; + std::string typeStr = qt.getAsString(Policy); + return sanitizeTypeString(typeStr); +} + +std::string +WebAssemblyTargetCodeGenInfo::getThunkName(std::string OrigName, + const FunctionProtoType *DstProto, + const ASTContext &Ctx) const { + std::ostringstream oss; + oss << "__" << OrigName; + for (unsigned i = 0; i < DstProto->getNumParams(); ++i) { + oss << "_" << getTypeName(DstProto->getParamType(i), Ctx); + } + return oss.str(); +} + +/// Recursively find the first DeclRefExpr in an Expr subtree. +/// Returns nullptr if not found. +const DeclRefExpr * +WebAssemblyTargetCodeGenInfo::findDeclRefExpr(const Expr *E) const { + if (!E) + return nullptr; + + // In case it is a function call, abort + if (isa<CallExpr>(E)) + return nullptr; + + // If this node is a DeclRefExpr, return it. + if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) + return DRE; + + // Otherwise, recurse into children. + for (const Stmt *Child : E->children()) { + if (const auto *ChildExpr = dyn_cast_or_null<Expr>(Child)) { + if (const DeclRefExpr *Found = findDeclRefExpr(ChildExpr)) + return Found; + } + } + return nullptr; +} + +const DeclRefExpr *WebAssemblyTargetCodeGenInfo::findDeclRefExprForVarDown( + const Stmt *Parent, const VarDecl *V, ASTContext &Ctx) const { + if (!Parent) + return nullptr; + + // Find down every assignment of V + // FIXME we need to stop before the expression where V is used + const BinaryOperator *A = nullptr; + for (const Stmt *Child : Parent->children()) { + if (const auto *BO = dyn_cast_or_null<BinaryOperator>(Child)) { + if (!BO->isAssignmentOp()) + continue; + auto *LHS = llvm::dyn_cast<DeclRefExpr>(BO->getLHS()); + if (LHS && LHS->getDecl() == V) { + A = BO; + } + } + } + + // We have an assignment of the Var, recurse in it + if (A) { + return getWasmFunctionDeclRefExpr(A->getRHS(), Ctx); + } + + return nullptr; +} + +const DeclRefExpr *WebAssemblyTargetCodeGenInfo::findDeclRefExprForVarUp( + const Expr *E, const VarDecl *V, ASTContext &Ctx) const { + const clang::Stmt *cur = E; + while (cur) { + auto parents = Ctx.getParentMapContext().getParents(*cur); + if (parents.empty()) + break; + const clang::Stmt *parentStmt = parents[0].get<clang::Stmt>(); + if (!parentStmt) + break; + if (const auto *CS = dyn_cast<clang::CompoundStmt>(parentStmt)) { + const DeclRefExpr *DRE = findDeclRefExprForVarDown(CS, V, Ctx); + if (DRE) + return DRE; + } + cur = parentStmt; + } + return nullptr; +} \ No newline at end of file >From 7d746cf68884ca7f7f90dc8fd107755890c483ad Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen <[email protected]> Date: Mon, 11 Aug 2025 14:47:55 +0200 Subject: [PATCH 2/7] Prefer use of Wasm signatures when building the thunk name --- clang/lib/CodeGen/Targets/WebAssembly.cpp | 66 ++++++++++++----------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/clang/lib/CodeGen/Targets/WebAssembly.cpp b/clang/lib/CodeGen/Targets/WebAssembly.cpp index b0a4f35c80c3d..983f550d48349 100644 --- a/clang/lib/CodeGen/Targets/WebAssembly.cpp +++ b/clang/lib/CodeGen/Targets/WebAssembly.cpp @@ -10,7 +10,6 @@ #include "TargetInfo.h" #include "clang/AST/ParentMapContext.h" -#include <sstream> using namespace clang; using namespace clang::CodeGen; @@ -192,10 +191,11 @@ class WebAssemblyTargetCodeGenInfo final : public TargetCodeGenInfo { } private: - // Build the thunk name: "%s_{type1}_{type2}_..." + // Build the thunk name: "%s_{OrigName}_{WasmSig}" std::string getThunkName(std::string OrigName, const FunctionProtoType *DstProto, const ASTContext &Ctx) const; + char getTypeSig(const QualType &Ty, const ASTContext &Ctx) const; std::string sanitizeTypeString(const std::string &typeStr) const; std::string getTypeName(const QualType &qt, const ASTContext &Ctx) const; const DeclRefExpr *findDeclRefExpr(const Expr *E) const; @@ -283,43 +283,49 @@ CodeGen::createWebAssemblyTargetCodeGenInfo(CodeGenModule &CGM, return std::make_unique<WebAssemblyTargetCodeGenInfo>(CGM.getTypes(), K); } -// Helper to sanitize type name string for use in function name -std::string WebAssemblyTargetCodeGenInfo::sanitizeTypeString( - const std::string &typeStr) const { - std::string s; - for (char c : typeStr) { - if (isalnum(c)) - s += c; - else if (c == ' ') - s += '_'; - else - s += '_'; +// Helper to get the type signature character for a given QualType +// Returns a character that represents the given QualType in a wasm signature. +// See getInvokeSig() in WebAssemblyAsmPrinter for related logic. +char WebAssemblyTargetCodeGenInfo::getTypeSig(const QualType &Ty, + const ASTContext &Ctx) const { + if (Ty->isAnyPointerType()) { + return Ctx.getTypeSize(Ctx.VoidPtrTy) == 32 ? 'i' : 'j'; + } + if (Ty->isIntegerType()) { + return Ctx.getTypeSize(Ty) <= 32 ? 'i' : 'j'; + } + if (Ty->isFloatingType()) { + return Ctx.getTypeSize(Ty) <= 32 ? 'f' : 'd'; + } + if (Ty->isVectorType()) { + return 'V'; + } + if (Ty->isWebAssemblyTableType()) { + return 'F'; + } + if (Ty->isWebAssemblyExternrefType()) { + return 'X'; } - return s; -} -// Helper to generate the type string from QualType -std::string -WebAssemblyTargetCodeGenInfo::getTypeName(const QualType &qt, - const ASTContext &Ctx) const { - PrintingPolicy Policy(Ctx.getLangOpts()); - Policy.SuppressTagKeyword = true; - Policy.SuppressScope = true; - Policy.AnonymousTagLocations = false; - std::string typeStr = qt.getAsString(Policy); - return sanitizeTypeString(typeStr); + llvm_unreachable("Unhandled QualType"); } std::string WebAssemblyTargetCodeGenInfo::getThunkName(std::string OrigName, const FunctionProtoType *DstProto, const ASTContext &Ctx) const { - std::ostringstream oss; - oss << "__" << OrigName; + + std::string ThunkName = "__" + OrigName + "_"; + QualType RetTy = DstProto->getReturnType(); + if (RetTy->isVoidType()) { + ThunkName += 'v'; + } else { + ThunkName += getTypeSig(RetTy, Ctx); + } for (unsigned i = 0; i < DstProto->getNumParams(); ++i) { - oss << "_" << getTypeName(DstProto->getParamType(i), Ctx); + ThunkName += getTypeSig(DstProto->getParamType(i), Ctx); } - return oss.str(); + return ThunkName; } /// Recursively find the first DeclRefExpr in an Expr subtree. @@ -392,4 +398,4 @@ const DeclRefExpr *WebAssemblyTargetCodeGenInfo::findDeclRefExprForVarUp( cur = parentStmt; } return nullptr; -} \ No newline at end of file +} >From 6bfecf12bb569662d2e09d393f629b936fd9ebd5 Mon Sep 17 00:00:00 2001 From: Jorge Zapata <[email protected]> Date: Mon, 11 Aug 2025 16:31:04 +0200 Subject: [PATCH 3/7] [wasm] Add a thunk cache --- clang/lib/CodeGen/Targets/WebAssembly.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/clang/lib/CodeGen/Targets/WebAssembly.cpp b/clang/lib/CodeGen/Targets/WebAssembly.cpp index 983f550d48349..1bbc9ba4311fc 100644 --- a/clang/lib/CodeGen/Targets/WebAssembly.cpp +++ b/clang/lib/CodeGen/Targets/WebAssembly.cpp @@ -8,6 +8,7 @@ #include "ABIInfoImpl.h" #include "TargetInfo.h" +#include "llvm/ADT/StringMap.h" #include "clang/AST/ParentMapContext.h" @@ -56,6 +57,7 @@ class WebAssemblyTargetCodeGenInfo final : public TargetCodeGenInfo { : TargetCodeGenInfo(std::make_unique<WebAssemblyABIInfo>(CGT, K)) { SwiftInfo = std::make_unique<SwiftABIInfo>(CGT, /*SwiftErrorInRegister=*/false); + ThunkCache = llvm::StringMap<llvm::Function *>(); } void setTargetAttributes(const Decl *D, llvm::GlobalValue *GV, @@ -151,6 +153,16 @@ class WebAssemblyTargetCodeGenInfo final : public TargetCodeGenInfo { // Construct the Thunk function with the Target (destination) signature std::string ThunkName = getThunkName(OriginalFnPtr->getName().str(), DstProtoType, CGM.getContext()); + // Check if we already have a thunk for this function + if (auto It = ThunkCache.find(ThunkName); It != ThunkCache.end()) { + LLVM_DEBUG(llvm::dbgs() << "getOrCreateWasmFunctionPointerThunk: " + << "found existing thunk for " + << OriginalFnPtr->getName().str() << " as " + << ThunkName << "\n"); + return It->second; + } + + // Create the thunk function llvm::Module &M = CGM.getModule(); llvm::Function *Thunk = llvm::Function::Create( DstFunctionType, llvm::Function::InternalLinkage, ThunkName, M); @@ -187,10 +199,14 @@ class WebAssemblyTargetCodeGenInfo final : public TargetCodeGenInfo { LLVM_DEBUG(llvm::dbgs() << "getOrCreateWasmFunctionPointerThunk:" << " from " << OriginalFnPtr->getName().str() << " to " << ThunkName << "\n"); + // Cache the thunk + ThunkCache[ThunkName] = Thunk; return Thunk; } private: + // The thunk cache + mutable llvm::StringMap<llvm::Function *> ThunkCache; // Build the thunk name: "%s_{OrigName}_{WasmSig}" std::string getThunkName(std::string OrigName, const FunctionProtoType *DstProto, >From 90770b32fa0782f0ce57b770766f78423cb1ab2d Mon Sep 17 00:00:00 2001 From: Jorge Zapata <[email protected]> Date: Tue, 12 Aug 2025 12:02:29 +0200 Subject: [PATCH 4/7] [wasm] Add function pointer thunk tests --- .../CodeGenWebAssembly/function-pointer-arg.c | 25 ++++++++++++++++ .../function-pointer-field.c | 30 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 clang/test/CodeGenWebAssembly/function-pointer-arg.c create mode 100644 clang/test/CodeGenWebAssembly/function-pointer-field.c diff --git a/clang/test/CodeGenWebAssembly/function-pointer-arg.c b/clang/test/CodeGenWebAssembly/function-pointer-arg.c new file mode 100644 index 0000000000000..ff7b4186bbf7b --- /dev/null +++ b/clang/test/CodeGenWebAssembly/function-pointer-arg.c @@ -0,0 +1,25 @@ +// REQUIRES: webassembly-registered-target +// RUN: %clang_cc1 -triple wasm32-unknown-unknown -emit-llvm -O0 -o - %s | FileCheck %s + +// Test of function pointer bitcast in a function argument with different argument number in wasm32 + +#define FUNCTION_POINTER(f) ((FunctionPointer)(f)) +typedef int (*FunctionPointer)(int a, int b); + +int fp_as_arg(FunctionPointer fp, int a, int b) { + return fp(a, b); +} + +int fp_less(int a) { + return a; +} + +// CHECK-LABEL: @test +// CHECK: call i32 @fp_as_arg(ptr noundef @__fp_less_iii, i32 noundef 10, i32 noundef 20) +void test() { + fp_as_arg(FUNCTION_POINTER(fp_less), 10, 20); +} + +// CHECK: define internal i32 @__fp_less_iii(i32 %0, i32 %1) +// CHECK: %2 = call i32 @fp_less(i32 %0) +// CHECK: ret i32 %2 \ No newline at end of file diff --git a/clang/test/CodeGenWebAssembly/function-pointer-field.c b/clang/test/CodeGenWebAssembly/function-pointer-field.c new file mode 100644 index 0000000000000..103a265ebf5fb --- /dev/null +++ b/clang/test/CodeGenWebAssembly/function-pointer-field.c @@ -0,0 +1,30 @@ +// REQUIRES: webassembly-registered-target +// RUN: %clang_cc1 -triple wasm32-unknown-unknown -emit-llvm -O0 -o - %s | FileCheck %s + +// Test of function pointer bitcast in a struct field with different argument number in wasm32 + +#define FUNCTION_POINTER(f) ((FunctionPointer)(f)) +typedef int (*FunctionPointer)(int a, int b); + +// CHECK: @__const.test.sfp = private unnamed_addr constant %struct._StructWithFunctionPointer { ptr @__fp_less_iii }, align 4 + +typedef struct _StructWithFunctionPointer { + FunctionPointer fp; +} StructWithFunctionPointer; + +int fp_less(int a) { + return a; +} + +// CHECK-LABEL: @test +void test() { + StructWithFunctionPointer sfp = { + FUNCTION_POINTER(fp_less) + }; + + int a1 = sfp.fp(10, 20); +} + +// CHECK: define internal i32 @__fp_less_iii(i32 %0, i32 %1) +// CHECK: %2 = call i32 @fp_less(i32 %0) +// CHECK: ret i32 %2 >From 2686f61152163f03adf67e7139f09eaa1f51e4d8 Mon Sep 17 00:00:00 2001 From: Jorge Zapata <[email protected]> Date: Wed, 3 Sep 2025 17:14:02 +0200 Subject: [PATCH 5/7] [wasm] Add -fwasm-fix-function-bitcasts to enable the thunk generation --- clang/include/clang/Basic/LangOptions.def | 2 ++ clang/include/clang/Options/Options.td | 6 ++++++ clang/lib/CodeGen/CGCall.cpp | 3 ++- clang/lib/CodeGen/CGExprConstant.cpp | 4 +++- clang/lib/Driver/ToolChains/WebAssembly.cpp | 4 ++++ clang/test/CodeGenWebAssembly/function-pointer-arg.c | 2 +- clang/test/CodeGenWebAssembly/function-pointer-field.c | 2 +- 7 files changed, 19 insertions(+), 4 deletions(-) diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index 6bba142aaf428..ae6fe51fd9794 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -530,6 +530,8 @@ LANGOPT(EnableLifetimeSafetyTUAnalysis, 1, 0, NotCompatible, "Lifetime safety at LANGOPT(PreserveVec3Type, 1, 0, NotCompatible, "Preserve 3-component vector type") LANGOPT(Reflection , 1, 0, NotCompatible, "C++26 Reflection") +LANGOPT(WasmFixFunctionBitcasts, 1, 0, Compatible, "Enable auto-generation of thunks for mismatched function pointer casts in WebAssembly") + #undef LANGOPT #undef ENUM_LANGOPT #undef VALUE_LANGOPT diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 7e612aad92cda..c811a58d98f05 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -10083,6 +10083,7 @@ def fexperimental_emit_sgep "(experimental).">, MarshallingInfoFlag<LangOpts<"EmitStructuredGEP">>; +// WebAssembly-only Options def no_wasm_opt : Flag<["--"], "no-wasm-opt">, Group<m_Group>, HelpText<"Disable the wasm-opt optimizer">, @@ -10091,3 +10092,8 @@ def wasm_opt : Flag<["--"], "wasm-opt">, Group<m_Group>, HelpText<"Enable the wasm-opt optimizer (default)">, MarshallingInfoNegativeFlag<LangOpts<"NoWasmOpt">>; +def fwasm_fix_function_bitcasts : Flag<["-"], "fwasm-fix-function-bitcasts">, + Group<f_Group>, + HelpText<"Enable auto-generation of thunks for mismatched function pointer casts in WebAssembly">, + Visibility<[ClangOption, CC1Option]>, + MarshallingInfoFlag<LangOpts<"WasmFixFunctionBitcasts">>; \ No newline at end of file diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp index 2b86004944118..c7d616c724de7 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -5071,7 +5071,8 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E, // to properly handle function pointers args with a different signature. // Due to opaque pointers, this can not be handled in LLVM // (WebAssemblyFixFunctionBitcast) anymore - if (CGM.getTriple().isWasm() && type->isFunctionPointerType()) { + if (CGM.getTriple().isWasm() && CGM.getLangOpts().WasmFixFunctionBitcasts && + type->isFunctionPointerType()) { if (const DeclRefExpr *DRE = CGM.getTargetCodeGenInfo().getWasmFunctionDeclRefExpr( E, CGM.getContext())) { diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp index 57d93887ce2e2..a7025dc65e45a 100644 --- a/clang/lib/CodeGen/CGExprConstant.cpp +++ b/clang/lib/CodeGen/CGExprConstant.cpp @@ -2289,7 +2289,9 @@ ConstantLValueEmitter::tryEmitBase(const APValue::LValueBase &base) { // to properly handle function pointers args with a different signature // Due to opaque pointers, this can not be handled in LLVM // (WebAssemblyFixFunctionBitcast) anymore - if (CGM.getTriple().isWasm() && DestType->isFunctionPointerType()) { + if (CGM.getTriple().isWasm() && + CGM.getLangOpts().WasmFixFunctionBitcasts && + DestType->isFunctionPointerType()) { llvm::Function *Thunk = CGM.getTargetCodeGenInfo().getOrCreateWasmFunctionPointerThunk( CGM, C, D->getType(), DestType); diff --git a/clang/lib/Driver/ToolChains/WebAssembly.cpp b/clang/lib/Driver/ToolChains/WebAssembly.cpp index e532ef0743cc2..1bd893f311bc2 100644 --- a/clang/lib/Driver/ToolChains/WebAssembly.cpp +++ b/clang/lib/Driver/ToolChains/WebAssembly.cpp @@ -432,6 +432,10 @@ void WebAssembly::addClangTargetOptions(const ArgList &DriverArgs, CC1Args.push_back("-wasm-enable-eh"); } + if (DriverArgs.getLastArg(options::OPT_fwasm_fix_function_bitcasts)) { + CC1Args.push_back("-fwasm-fix-function-bitcasts"); + } + for (const Arg *A : DriverArgs.filtered(options::OPT_mllvm)) { StringRef Opt = A->getValue(0); if (Opt.starts_with("-emscripten-cxx-exceptions-allowed")) { diff --git a/clang/test/CodeGenWebAssembly/function-pointer-arg.c b/clang/test/CodeGenWebAssembly/function-pointer-arg.c index ff7b4186bbf7b..4ca85f11fe15f 100644 --- a/clang/test/CodeGenWebAssembly/function-pointer-arg.c +++ b/clang/test/CodeGenWebAssembly/function-pointer-arg.c @@ -1,5 +1,5 @@ // REQUIRES: webassembly-registered-target -// RUN: %clang_cc1 -triple wasm32-unknown-unknown -emit-llvm -O0 -o - %s | FileCheck %s +// RUN: %clang_cc1 -triple wasm32-unknown-unknown -emit-llvm -O0 -fwasm-fix-function-bitcasts -o - %s | FileCheck %s // Test of function pointer bitcast in a function argument with different argument number in wasm32 diff --git a/clang/test/CodeGenWebAssembly/function-pointer-field.c b/clang/test/CodeGenWebAssembly/function-pointer-field.c index 103a265ebf5fb..8c8424dc922e8 100644 --- a/clang/test/CodeGenWebAssembly/function-pointer-field.c +++ b/clang/test/CodeGenWebAssembly/function-pointer-field.c @@ -1,5 +1,5 @@ // REQUIRES: webassembly-registered-target -// RUN: %clang_cc1 -triple wasm32-unknown-unknown -emit-llvm -O0 -o - %s | FileCheck %s +// RUN: %clang_cc1 -triple wasm32-unknown-unknown -emit-llvm -O0 -fwasm-fix-function-bitcasts -o - %s | FileCheck %s // Test of function pointer bitcast in a struct field with different argument number in wasm32 >From 2e5a015b97914b3cce271aa4215c22187284643e Mon Sep 17 00:00:00 2001 From: Jorge Zapata <[email protected]> Date: Tue, 7 Apr 2026 01:52:05 +0200 Subject: [PATCH 6/7] [wasm] Add a more general approach at bitcast --- clang/lib/CodeGen/CGCall.cpp | 21 ---------- clang/lib/CodeGen/CGExprScalar.cpp | 21 ++++++++++ clang/lib/CodeGen/Targets/WebAssembly.cpp | 38 ++++++++++--------- .../function-pointer-local.c | 28 ++++++++++++++ .../function-pointer-void-assign.c | 24 ++++++++++++ 5 files changed, 93 insertions(+), 39 deletions(-) create mode 100644 clang/test/CodeGenWebAssembly/function-pointer-local.c create mode 100644 clang/test/CodeGenWebAssembly/function-pointer-void-assign.c diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp index c7d616c724de7..b7b79e7051181 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -5067,27 +5067,6 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E, return; } - // For WebAssembly target we need to create thunk functions - // to properly handle function pointers args with a different signature. - // Due to opaque pointers, this can not be handled in LLVM - // (WebAssemblyFixFunctionBitcast) anymore - if (CGM.getTriple().isWasm() && CGM.getLangOpts().WasmFixFunctionBitcasts && - type->isFunctionPointerType()) { - if (const DeclRefExpr *DRE = - CGM.getTargetCodeGenInfo().getWasmFunctionDeclRefExpr( - E, CGM.getContext())) { - llvm::Value *V = EmitLValue(DRE).getPointer(*this); - llvm::Function *Thunk = - CGM.getTargetCodeGenInfo().getOrCreateWasmFunctionPointerThunk( - CGM, V, DRE->getDecl()->getType(), type); - if (Thunk) { - RValue R = RValue::get(Thunk); - args.add(R, type); - return; - } - } - } - args.add(EmitAnyExprToTemp(E), type); } diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp index a8dcf22992983..7369060e60354 100644 --- a/clang/lib/CodeGen/CGExprScalar.cpp +++ b/clang/lib/CodeGen/CGExprScalar.cpp @@ -2670,6 +2670,27 @@ Value *ScalarExprEmitter::VisitCastExpr(CastExpr *CE) { case CK_BlockPointerToObjCPointerCast: case CK_AnyPointerToBlockPointerCast: case CK_BitCast: { + // For WebAssembly, intercept function pointer bitcasts to a type with a + // different number of arguments and generate a thunk instead. This is + // necessary because WebAssembly enforces strict call-site/callee signature + // matching at runtime. The fix is gated on -fwasm-fix-function-bitcasts + // and only triggers when the source expression can be statically traced + // back to a concrete function declaration. + if (CGF.CGM.getTriple().isWasm() && + CGF.CGM.getLangOpts().WasmFixFunctionBitcasts && + DestTy->isFunctionPointerType()) { + if (const DeclRefExpr *DRE = + CGF.CGM.getTargetCodeGenInfo().getWasmFunctionDeclRefExpr( + E, CGF.CGM.getContext())) { + llvm::Value *V = EmitLValue(DRE).getPointer(CGF); + llvm::Function *Thunk = + CGF.CGM.getTargetCodeGenInfo().getOrCreateWasmFunctionPointerThunk( + CGF.CGM, V, DRE->getDecl()->getType(), DestTy); + if (Thunk) + return Thunk; + } + } + Value *Src = Visit(E); llvm::Type *SrcTy = Src->getType(); llvm::Type *DstTy = ConvertType(DestTy); diff --git a/clang/lib/CodeGen/Targets/WebAssembly.cpp b/clang/lib/CodeGen/Targets/WebAssembly.cpp index 1bbc9ba4311fc..3fc2693a11e15 100644 --- a/clang/lib/CodeGen/Targets/WebAssembly.cpp +++ b/clang/lib/CodeGen/Targets/WebAssembly.cpp @@ -374,18 +374,21 @@ const DeclRefExpr *WebAssemblyTargetCodeGenInfo::findDeclRefExprForVarDown( if (!Parent) return nullptr; - // Find down every assignment of V - // FIXME we need to stop before the expression where V is used + // Find down every assignment of V. + // Standalone expression statements appear as Expr nodes directly under the + // CompoundStmt, so cast each child to Expr (if possible) and check for + // a BinaryOperator assignment. const BinaryOperator *A = nullptr; for (const Stmt *Child : Parent->children()) { - if (const auto *BO = dyn_cast_or_null<BinaryOperator>(Child)) { - if (!BO->isAssignmentOp()) - continue; - auto *LHS = llvm::dyn_cast<DeclRefExpr>(BO->getLHS()); - if (LHS && LHS->getDecl() == V) { - A = BO; - } - } + const auto *BO = dyn_cast_or_null<BinaryOperator>(Child); + if (!BO) + if (const auto *E = dyn_cast_or_null<Expr>(Child)) + BO = dyn_cast<BinaryOperator>(E->IgnoreParenCasts()); + if (!BO || !BO->isAssignmentOp()) + continue; + auto *LHS = llvm::dyn_cast<DeclRefExpr>(BO->getLHS()); + if (LHS && LHS->getDecl() == V) + A = BO; } // We have an assignment of the Var, recurse in it @@ -398,20 +401,19 @@ const DeclRefExpr *WebAssemblyTargetCodeGenInfo::findDeclRefExprForVarDown( const DeclRefExpr *WebAssemblyTargetCodeGenInfo::findDeclRefExprForVarUp( const Expr *E, const VarDecl *V, ASTContext &Ctx) const { - const clang::Stmt *cur = E; - while (cur) { - auto parents = Ctx.getParentMapContext().getParents(*cur); + // Use a DynTypedNode to walk parents, since a Stmt may be parented by a Decl + // (e.g. a VarDecl initializer) and get<Stmt>() would return nullptr there. + auto cur = clang::DynTypedNode::create(*E); + while (true) { + auto parents = Ctx.getParentMapContext().getParents(cur); if (parents.empty()) break; - const clang::Stmt *parentStmt = parents[0].get<clang::Stmt>(); - if (!parentStmt) - break; - if (const auto *CS = dyn_cast<clang::CompoundStmt>(parentStmt)) { + cur = parents[0]; + if (const auto *CS = cur.get<clang::CompoundStmt>()) { const DeclRefExpr *DRE = findDeclRefExprForVarDown(CS, V, Ctx); if (DRE) return DRE; } - cur = parentStmt; } return nullptr; } diff --git a/clang/test/CodeGenWebAssembly/function-pointer-local.c b/clang/test/CodeGenWebAssembly/function-pointer-local.c new file mode 100644 index 0000000000000..6320832abd19c --- /dev/null +++ b/clang/test/CodeGenWebAssembly/function-pointer-local.c @@ -0,0 +1,28 @@ +// REQUIRES: webassembly-registered-target +// RUN: %clang_cc1 -triple wasm32-unknown-unknown -emit-llvm -O0 -fwasm-fix-function-bitcasts -o - %s | FileCheck %s + +// Test of function pointer bitcast stored in a local variable with different +// argument number in wasm32. The cast happens via a CK_BitCast in the scalar +// expression path (local variable assignment), which is intercepted in +// CGExprScalar.cpp to generate a thunk — the same mechanism used for +// function-argument and struct-field cases. + +#define FUNCTION_POINTER(f) ((FunctionPointer)(f)) +typedef int (*FunctionPointer)(int a, int b); + +int fp_less(int a) { + return a; +} + +// CHECK-LABEL: @test +// CHECK: store ptr @__fp_less_iii, ptr %fp +// CHECK: %[[FP:.*]] = load ptr, ptr %fp +// CHECK: call i32 %[[FP]](i32 noundef 10, i32 noundef 20) +void test() { + FunctionPointer fp = FUNCTION_POINTER(fp_less); + fp(10, 20); +} + +// CHECK: define internal i32 @__fp_less_iii(i32 %0, i32 %1) +// CHECK: %2 = call i32 @fp_less(i32 %0) +// CHECK: ret i32 %2 diff --git a/clang/test/CodeGenWebAssembly/function-pointer-void-assign.c b/clang/test/CodeGenWebAssembly/function-pointer-void-assign.c new file mode 100644 index 0000000000000..640f84a33d127 --- /dev/null +++ b/clang/test/CodeGenWebAssembly/function-pointer-void-assign.c @@ -0,0 +1,24 @@ +// RUN: %clang_cc1 -triple wasm32-unknown-unknown -emit-llvm -O0 \ +// RUN: -fwasm-fix-function-bitcasts -o - %s | FileCheck %s + +// Test that a function pointer stored via a bare assignment into a void* +// variable is correctly wrapped in a thunk when later cast to a different +// function pointer type with more parameters. + +typedef int (*FP)(int, int); + +static int fp_one(int a) { return a < 0; } + +void test_void_ptr_bare_assign() { + // Bare assignment (no initializer) into void* — not via VD->hasInit() + void *p; + p = (void *)fp_one; + FP fp = (FP)p; + fp(10, 20); +} + +// CHECK-LABEL: define void @test_void_ptr_bare_assign +// CHECK: store ptr @__fp_one_iii, ptr %fp +// CHECK: define internal i32 @__fp_one_iii(i32 %0, i32 %1) +// CHECK-NEXT: entry: +// CHECK-NEXT: %2 = call i32 @fp_one(i32 %0) >From 079df8ea9458ebcc8ae82de38cf4411bda9c936f Mon Sep 17 00:00:00 2001 From: Jorge Zapata <[email protected]> Date: Thu, 11 Jun 2026 12:20:23 +0200 Subject: [PATCH 7/7] [wasm] Add runtime wrapper for function pointer casts This complements the existing compile-time thunk generation for statically traceable function references. --- clang/lib/CodeGen/CGExprScalar.cpp | 19 +- clang/lib/CodeGen/TargetInfo.h | 9 + clang/lib/CodeGen/Targets/WebAssembly.cpp | 163 +++++++++++++++++- .../function-pointer-runtime-cast.c | 54 ++++++ 4 files changed, 241 insertions(+), 4 deletions(-) create mode 100644 clang/test/CodeGenWebAssembly/function-pointer-runtime-cast.c diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp index 7369060e60354..544559a0ae732 100644 --- a/clang/lib/CodeGen/CGExprScalar.cpp +++ b/clang/lib/CodeGen/CGExprScalar.cpp @@ -2674,11 +2674,13 @@ Value *ScalarExprEmitter::VisitCastExpr(CastExpr *CE) { // different number of arguments and generate a thunk instead. This is // necessary because WebAssembly enforces strict call-site/callee signature // matching at runtime. The fix is gated on -fwasm-fix-function-bitcasts - // and only triggers when the source expression can be statically traced - // back to a concrete function declaration. + // and handles both compile-time (static) and runtime function pointer casts. if (CGF.CGM.getTriple().isWasm() && CGF.CGM.getLangOpts().WasmFixFunctionBitcasts && - DestTy->isFunctionPointerType()) { + DestTy->isFunctionPointerType() && + CE->getSubExpr()->getType()->isFunctionPointerType()) { + + // Try compile-time thunk first (for statically traceable function refs) if (const DeclRefExpr *DRE = CGF.CGM.getTargetCodeGenInfo().getWasmFunctionDeclRefExpr( E, CGF.CGM.getContext())) { @@ -2689,6 +2691,17 @@ Value *ScalarExprEmitter::VisitCastExpr(CastExpr *CE) { if (Thunk) return Thunk; } + + // If not statically traceable, use runtime binding for non-constant values + Value *Src = Visit(E); + if (!isa<llvm::Constant>(Src)) { + llvm::Value *RuntimeThunk = + CGF.CGM.getTargetCodeGenInfo().emitWasmRuntimeFunctionPointerBinding( + CGF, Src, CE->getSubExpr()->getType(), DestTy); + if (RuntimeThunk) + return RuntimeThunk; + } + // Fall through to normal bitcast if runtime binding returns nullptr } Value *Src = Visit(E); diff --git a/clang/lib/CodeGen/TargetInfo.h b/clang/lib/CodeGen/TargetInfo.h index c708f798627e5..91b748f6e9f27 100644 --- a/clang/lib/CodeGen/TargetInfo.h +++ b/clang/lib/CodeGen/TargetInfo.h @@ -414,6 +414,15 @@ class TargetCodeGenInfo { return nullptr; } + /// Emit runtime binding for WebAssembly function pointer casts. + /// This handles runtime function pointer values (not compile-time constants) + /// that need signature adaptation. + virtual llvm::Value *emitWasmRuntimeFunctionPointerBinding( + CodeGenFunction &CGF, llvm::Value *FnPtr, QualType SrcType, + QualType DstType) const { + return nullptr; + } + /// Emit the device-side copy of the builtin surface type. virtual bool emitCUDADeviceBuiltinSurfaceDeviceCopy(CodeGenFunction &CGF, LValue Dst, diff --git a/clang/lib/CodeGen/Targets/WebAssembly.cpp b/clang/lib/CodeGen/Targets/WebAssembly.cpp index 3fc2693a11e15..3f8fafd8d0eb5 100644 --- a/clang/lib/CodeGen/Targets/WebAssembly.cpp +++ b/clang/lib/CodeGen/Targets/WebAssembly.cpp @@ -204,13 +204,26 @@ class WebAssemblyTargetCodeGenInfo final : public TargetCodeGenInfo { return Thunk; } + llvm::Value *emitWasmRuntimeFunctionPointerBinding( + CodeGenFunction &CGF, llvm::Value *FnPtr, QualType SrcType, + QualType DstType) const override; + private: - // The thunk cache + // The thunk cache for compile-time thunks mutable llvm::StringMap<llvm::Function *> ThunkCache; + + // Runtime thunk cache: maps (SrcSig, DstSig) -> wrapper function + // The wrapper takes a function pointer and returns a thunk for it + mutable llvm::DenseMap<std::pair<const FunctionProtoType*, const FunctionProtoType*>, + llvm::Function*> RuntimeWrapperCache; + // Build the thunk name: "%s_{OrigName}_{WasmSig}" std::string getThunkName(std::string OrigName, const FunctionProtoType *DstProto, const ASTContext &Ctx) const; + std::string getRuntimeWrapperName(const FunctionProtoType *SrcProto, + const FunctionProtoType *DstProto, + const ASTContext &Ctx) const; char getTypeSig(const QualType &Ty, const ASTContext &Ctx) const; std::string sanitizeTypeString(const std::string &typeStr) const; std::string getTypeName(const QualType &qt, const ASTContext &Ctx) const; @@ -293,6 +306,154 @@ RValue WebAssemblyABIInfo::EmitVAArg(CodeGenFunction &CGF, Address VAListAddr, /*AllowHigherAlign=*/true, Slot); } +// Generate wrapper name for runtime function pointer binding +std::string WebAssemblyTargetCodeGenInfo::getRuntimeWrapperName( + const FunctionProtoType *SrcProto, const FunctionProtoType *DstProto, + const ASTContext &Ctx) const { + std::string Name = "__wasm_runtime_wrapper_"; + + // Encode source signature + QualType SrcRetTy = SrcProto->getReturnType(); + if (SrcRetTy->isVoidType()) { + Name += 'v'; + } else { + Name += getTypeSig(SrcRetTy, Ctx); + } + for (QualType ParamType : SrcProto->param_types()) { + Name += getTypeSig(ParamType, Ctx); + } + + Name += "_to_"; + + // Encode destination signature + QualType DstRetTy = DstProto->getReturnType(); + if (DstRetTy->isVoidType()) { + Name += 'v'; + } else { + Name += getTypeSig(DstRetTy, Ctx); + } + for (QualType ParamType : DstProto->param_types()) { + Name += getTypeSig(ParamType, Ctx); + } + + return Name; +} + +// Emit runtime binding for function pointer cast +// This handles cases like g_list_free_full where a runtime parameter +// needs to be cast from fewer params to more params +llvm::Value *WebAssemblyTargetCodeGenInfo::emitWasmRuntimeFunctionPointerBinding( + CodeGenFunction &CGF, llvm::Value *FnPtr, QualType SrcType, + QualType DstType) const { + + const FunctionProtoType *SrcProto = + SrcType->getPointeeType()->getAs<FunctionProtoType>(); + const FunctionProtoType *DstProto = + DstType->getPointeeType()->getAs<FunctionProtoType>(); + + if (!SrcProto || !DstProto) + return nullptr; + + // Only handle the case where we're adding params (fewer -> more) + unsigned SrcParams = SrcProto->getNumParams(); + unsigned DstParams = DstProto->getNumParams(); + + if (SrcParams >= DstParams) + return nullptr; + + // Return types must match exactly + QualType SrcRetTy = SrcProto->getReturnType(); + QualType DstRetTy = DstProto->getReturnType(); + if (!CGF.getContext().hasSameType(SrcRetTy, DstRetTy)) + return nullptr; + + LLVM_DEBUG(llvm::dbgs() << "emitWasmRuntimeFunctionPointerBinding: " + << "src params=" << SrcParams + << " dst params=" << DstParams << "\n"); + + auto Key = std::make_pair(SrcProto, DstProto); + auto It = RuntimeWrapperCache.find(Key); + + llvm::Module &M = CGF.CGM.getModule(); + llvm::LLVMContext &Context = M.getContext(); + llvm::Type *PtrTy = llvm::PointerType::getUnqual(Context); + + std::string WrapperName = getRuntimeWrapperName(SrcProto, DstProto, CGF.CGM.getContext()); + std::string GlobalName = WrapperName + "_fptr"; + + // Get or create the global variable for storing the function pointer + llvm::GlobalVariable *FnPtrGlobal = M.getNamedGlobal(GlobalName); + if (!FnPtrGlobal) { + FnPtrGlobal = new llvm::GlobalVariable( + M, PtrTy, /*isConstant=*/false, llvm::GlobalValue::InternalLinkage, + llvm::Constant::getNullValue(PtrTy), GlobalName); + // Make it thread-local to support WebAssembly threads + FnPtrGlobal->setThreadLocalMode(llvm::GlobalValue::GeneralDynamicTLSModel); + } + + llvm::Function *Wrapper; + if (It != RuntimeWrapperCache.end()) { + Wrapper = It->second; + } else { + // Create a new wrapper function that takes a function pointer + // and returns a thunk with the destination signature + + llvm::FunctionType *SrcFnType = llvm::cast<llvm::FunctionType>( + CGF.CGM.getTypes().ConvertType(QualType(SrcProto, 0))); + llvm::FunctionType *DstFnType = llvm::cast<llvm::FunctionType>( + CGF.CGM.getTypes().ConvertType(QualType(DstProto, 0))); + + // Wrapper signature: takes src function pointer, has dst signature + // Use LinkOnceODRLinkage to: + // 1. Prevent dead argument elimination (optimizer can't see all callers) + // 2. Allow linker to merge duplicates across modules (no symbol collisions) + // 3. Preserve exact signature required by WebAssembly type checking + Wrapper = llvm::Function::Create( + DstFnType, llvm::GlobalValue::LinkOnceODRLinkage, WrapperName, M); + + // Mark as noinline to prevent inlining that would expose unused parameters + Wrapper->addFnAttr(llvm::Attribute::NoInline); + Wrapper->addFnAttr(llvm::Attribute::NoUnwind); + + // Build wrapper body + llvm::BasicBlock *EntryBB = llvm::BasicBlock::Create(Context, "entry", Wrapper); + llvm::IRBuilder<> Builder(EntryBB); + + // Load the stored function pointer + llvm::Value *StoredFnPtr = Builder.CreateLoad(PtrTy, FnPtrGlobal); + + // Prepare arguments for the call (only pass what the source function expects) + llvm::SmallVector<llvm::Value *, 8> CallArgs; + auto ArgIt = Wrapper->arg_begin(); + for (unsigned i = 0; i < SrcParams && i < DstParams; ++i) { + CallArgs.push_back(&*ArgIt); + ++ArgIt; + } + + // Call the source function + llvm::CallInst *Call = Builder.CreateCall(SrcFnType, StoredFnPtr, CallArgs); + + // Return the result + if (DstFnType->getReturnType()->isVoidTy()) { + Builder.CreateRetVoid(); + } else { + Builder.CreateRet(Call); + } + + RuntimeWrapperCache[Key] = Wrapper; + } + + // Store the function pointer in the global variable + CharUnits Alignment = CGF.CGM.getPointerAlign(); + Address GlobalAddr(FnPtrGlobal, PtrTy, Alignment); + + // Store the function pointer to be used by the wrapper + CGF.Builder.CreateStore(FnPtr, GlobalAddr); + + // Return the wrapper function + return Wrapper; +} + std::unique_ptr<TargetCodeGenInfo> CodeGen::createWebAssemblyTargetCodeGenInfo(CodeGenModule &CGM, WebAssemblyABIKind K) { diff --git a/clang/test/CodeGenWebAssembly/function-pointer-runtime-cast.c b/clang/test/CodeGenWebAssembly/function-pointer-runtime-cast.c new file mode 100644 index 0000000000000..724fe69daa1e7 --- /dev/null +++ b/clang/test/CodeGenWebAssembly/function-pointer-runtime-cast.c @@ -0,0 +1,54 @@ +// REQUIRES: webassembly-registered-target +// RUN: %clang_cc1 -triple wasm32-unknown-unknown -emit-llvm -O0 -fwasm-fix-function-bitcasts -o - %s | FileCheck %s + +// Test runtime function pointer cast with different argument counts +// This simulates cases like g_list_free_full where a function pointer parameter +// is cast from fewer params to more params + +typedef void (*OneArgFunc)(void *); +typedef void (*TwoArgFunc)(void *, void *); + +// CHECK: @__wasm_runtime_wrapper_vi_to_vii_fptr = internal thread_local global ptr null + +// A function with one argument +void my_one_arg_func(void *ptr) { + // Do something +} + +// Test case 1: Direct call of casted runtime function pointer +// CHECK-LABEL: @runtime_cast_caller +void runtime_cast_caller(OneArgFunc fp, void *data) { + // Cast the runtime parameter from 1-arg to 2-arg signature and call directly + // CHECK: store ptr %{{.*}}, ptr @__wasm_runtime_wrapper_vi_to_vii_fptr + // CHECK: call void @__wasm_runtime_wrapper_vi_to_vii(ptr + ((TwoArgFunc)fp)(data, (void*)0); +} + +// The runtime wrapper should be generated once and shared by both cases +// CHECK-LABEL: define linkonce_odr void @__wasm_runtime_wrapper_vi_to_vii(ptr %0, ptr %1) +// CHECK: %{{.*}} = load ptr, ptr @__wasm_runtime_wrapper_vi_to_vii_fptr +// CHECK: call void %{{.*}}(ptr %0) +// CHECK: ret void + +// Test case 2: Pass casted runtime function pointer to another function +// This is closer to the real g_list_free_full scenario +// CHECK-LABEL: @library_function +void library_function(TwoArgFunc func, void *data) { + // CHECK: call void %{{.*}}(ptr noundef %{{.*}}, ptr noundef null) + func(data, (void*)0); +} + +// CHECK-LABEL: @indirect_caller +void indirect_caller(OneArgFunc fp, void *data) { + // Cast and pass to another function (like g_list_free_full does) + // CHECK: store ptr %{{.*}}, ptr @__wasm_runtime_wrapper_vi_to_vii_fptr + // CHECK: call void @library_function(ptr noundef @__wasm_runtime_wrapper_vi_to_vii + library_function((TwoArgFunc)fp, data); +} + +// CHECK-LABEL: @test +void test() { + // Test both scenarios + runtime_cast_caller(my_one_arg_func, (void*)0); + indirect_caller(my_one_arg_func, (void*)0); +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
