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 99ef9ae51e1dd80179299983533912ed5b43cfe1 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     | 165 +++++++++++++++++-
 .../function-pointer-runtime-cast.c           |  54 ++++++
 4 files changed, 243 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..c17ae4d520110 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,156 @@ 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
+  // Use LinkOnceODRLinkage to match the wrapper function, allowing the linker
+  // to merge globals across translation units into a single shared variable
+  llvm::GlobalVariable *FnPtrGlobal = M.getNamedGlobal(GlobalName);
+  if (!FnPtrGlobal) {
+    FnPtrGlobal = new llvm::GlobalVariable(
+        M, PtrTy, /*isConstant=*/false, llvm::GlobalValue::LinkOnceODRLinkage,
+        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..15ebbdbe6be11
--- /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 = linkonce_odr 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

Reply via email to