https://github.com/HendrikHuebner created 
https://github.com/llvm/llvm-project/pull/171038

Related to #169043 and part of a broader effort to support targetting 
WebAssembly for ObjectiveC.

This PR is work in progress and adds basic support for generating funclet-style 
exception handling (try/catch) for WebAssembly. For now, I've set the 
`gxx_wasm` personality function, however, we likely need to add a new one to 
handle ObjectiveC's `@finally` semantics.

From 3a4b936d2e6ab1e1faf2d5803ab7ef2f037f78d6 Mon Sep 17 00:00:00 2001
From: hhuebner <[email protected]>
Date: Sun, 7 Dec 2025 14:37:16 +0100
Subject: [PATCH] Try/Catch for ObjectiveC with WebAssembly target

---
 clang/lib/CodeGen/CGCleanup.h         |  1 +
 clang/lib/CodeGen/CGException.cpp     | 10 +++-
 clang/lib/CodeGen/CGObjCRuntime.cpp   | 66 +++++++++++++++++++++++----
 clang/lib/CodeGen/CodeGenFunction.h   |  2 +-
 clang/lib/Driver/ToolChains/Clang.cpp |  3 +-
 5 files changed, 70 insertions(+), 12 deletions(-)

diff --git a/clang/lib/CodeGen/CGCleanup.h b/clang/lib/CodeGen/CGCleanup.h
index ba78e5478ac37..5f783216f8d0a 100644
--- a/clang/lib/CodeGen/CGCleanup.h
+++ b/clang/lib/CodeGen/CGCleanup.h
@@ -675,6 +675,7 @@ struct EHPersonality {
   static const EHPersonality GNU_ObjC_SJLJ;
   static const EHPersonality GNU_ObjC_SEH;
   static const EHPersonality GNUstep_ObjC;
+  static const EHPersonality GNUstep_Wasm_ObjC;
   static const EHPersonality GNU_ObjCXX;
   static const EHPersonality NeXT_ObjC;
   static const EHPersonality GNU_CPlusPlus;
diff --git a/clang/lib/CodeGen/CGException.cpp 
b/clang/lib/CodeGen/CGException.cpp
index f86af4581c345..2bb4ab8094965 100644
--- a/clang/lib/CodeGen/CGException.cpp
+++ b/clang/lib/CodeGen/CGException.cpp
@@ -159,9 +159,11 @@ static const EHPersonality &getObjCPersonality(const 
TargetInfo &Target,
   case ObjCRuntime::WatchOS:
     return EHPersonality::NeXT_ObjC;
   case ObjCRuntime::GNUstep:
+    if (CGOpts.hasWasmExceptions())
+      return EHPersonality::GNU_Wasm_CPlusPlus;
     if (T.isOSCygMing())
       return EHPersonality::GNU_CPlusPlus_SEH;
-    else if (L.ObjCRuntime.getVersion() >= VersionTuple(1, 7))
+    if (L.ObjCRuntime.getVersion() >= VersionTuple(1, 7))
       return EHPersonality::GNUstep_ObjC;
     [[fallthrough]];
   case ObjCRuntime::GCC:
@@ -218,6 +220,8 @@ static const EHPersonality &getObjCXXPersonality(const 
TargetInfo &Target,
     return getObjCPersonality(Target, CGOpts, L);
 
   case ObjCRuntime::GNUstep:
+    if (CGOpts.hasWasmExceptions())
+      return EHPersonality::GNU_Wasm_CPlusPlus;
     return Target.getTriple().isOSCygMing() ? EHPersonality::GNU_CPlusPlus_SEH
                                             : EHPersonality::GNU_ObjCXX;
 
@@ -1203,11 +1207,13 @@ static void emitCatchDispatchBlock(CodeGenFunction &CGF,
   }
 }
 
-void CodeGenFunction::popCatchScope() {
+llvm::BasicBlock *CodeGenFunction::popCatchScope() {
   EHCatchScope &catchScope = cast<EHCatchScope>(*EHStack.begin());
+  llvm::BasicBlock *dispatchBlock = catchScope.getCachedEHDispatchBlock();
   if (catchScope.hasEHBranches())
     emitCatchDispatchBlock(*this, catchScope);
   EHStack.popCatch();
+  return dispatchBlock;
 }
 
 void CodeGenFunction::ExitCXXTryStmt(const CXXTryStmt &S, bool IsFnTryBlock) {
diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp 
b/clang/lib/CodeGen/CGObjCRuntime.cpp
index 76e0054f4c9da..aa46226956a22 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.cpp
+++ b/clang/lib/CodeGen/CGObjCRuntime.cpp
@@ -23,6 +23,8 @@
 #include "clang/CodeGen/CGFunctionInfo.h"
 #include "clang/CodeGen/CodeGenABITypes.h"
 #include "llvm/IR/Instruction.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/IntrinsicsWebAssembly.h"
 #include "llvm/Support/SaveAndRestore.h"
 
 using namespace clang;
@@ -120,6 +122,8 @@ namespace {
     llvm::Constant *TypeInfo;
     /// Flags used to differentiate cleanups and catchalls in Windows SEH
     unsigned Flags;
+
+    bool isCatchAll() const { return TypeInfo == nullptr; }
   };
 
   struct CallObjCEndCatch final : EHScopeStack::Cleanup {
@@ -148,13 +152,13 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
     Cont = CGF.getJumpDestInCurrentScope("eh.cont");
 
   bool useFunclets = EHPersonality::get(CGF).usesFuncletPads();
+  bool hasWasmExceptions = CGF.CGM.getCodeGenOpts().hasWasmExceptions();
 
   CodeGenFunction::FinallyInfo FinallyInfo;
   if (!useFunclets)
     if (const ObjCAtFinallyStmt *Finally = S.getFinallyStmt())
       FinallyInfo.enter(CGF, Finally->getFinallyBody(),
                         beginCatchFn, endCatchFn, exceptionRethrowFn);
-
   SmallVector<CatchHandler, 8> Handlers;
 
 
@@ -187,8 +191,12 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
       Catch->setHandler(I, { Handlers[I].TypeInfo, Handlers[I].Flags }, 
Handlers[I].Block);
   }
 
-  if (useFunclets)
+  if (useFunclets) {
     if (const ObjCAtFinallyStmt *Finally = S.getFinallyStmt()) {
+        if (hasWasmExceptions) {
+          CGF.ErrorUnsupported(Finally, "@finally for WASM");
+        }
+
         CodeGenFunction HelperCGF(CGM, /*suppressNewContext=*/true);
         if (!CGF.CurSEHParent)
             CGF.CurSEHParent = cast<NamedDecl>(CGF.CurFuncDecl);
@@ -207,36 +215,59 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
         // Push a cleanup for __finally blocks.
         CGF.pushSEHCleanup(NormalAndEHCleanup, FinallyFunc);
     }
+  }
 
 
   // Emit the try body.
   CGF.EmitStmt(S.getTryBody());
 
   // Leave the try.
+  llvm::BasicBlock* dispatchBlock{};
   if (S.getNumCatchStmts())
-    CGF.popCatchScope();
+    dispatchBlock = CGF.popCatchScope();
 
   // Remember where we were.
   CGBuilderTy::InsertPoint SavedIP = CGF.Builder.saveAndClearIP();
 
+  // Wasm uses Windows-style EH instructions, but merges all catch clauses into
+  // one big catchpad. So we save the old funclet pad here before we traverse
+  // each catch handler.
+  SaveAndRestore RestoreCurrentFuncletPad(CGF.CurrentFuncletPad);
+  llvm::BasicBlock *WasmCatchStartBlock = nullptr;
+  llvm::CatchPadInst* CatchPadInst{};
+  if (!!dispatchBlock && hasWasmExceptions) {
+    auto *CatchSwitch =
+        cast<llvm::CatchSwitchInst>(dispatchBlock->getFirstNonPHIIt());
+    WasmCatchStartBlock = CatchSwitch->hasUnwindDest()
+                              ? CatchSwitch->getSuccessor(1)
+                              : CatchSwitch->getSuccessor(0);
+    CatchPadInst = 
cast<llvm::CatchPadInst>(WasmCatchStartBlock->getFirstNonPHIIt());
+    CGF.CurrentFuncletPad = CatchPadInst;
+  }
+
   // Emit the handlers.
+  bool HasCatchAll = false;
   for (CatchHandler &Handler : Handlers) {
+    HasCatchAll |= Handler.isCatchAll();
     CGF.EmitBlock(Handler.Block);
 
     CodeGenFunction::LexicalScope Cleanups(CGF, 
Handler.Body->getSourceRange());
     SaveAndRestore RevertAfterScope(CGF.CurrentFuncletPad);
-    if (useFunclets) {
+
+    if (useFunclets && !hasWasmExceptions) {
       llvm::BasicBlock::iterator CPICandidate =
           Handler.Block->getFirstNonPHIIt();
       if (CPICandidate != Handler.Block->end()) {
-        if (auto *CPI = dyn_cast_or_null<llvm::CatchPadInst>(CPICandidate)) {
-          CGF.CurrentFuncletPad = CPI;
-          CPI->setOperand(2, CGF.getExceptionSlot().emitRawPointer(CGF));
-          CGF.EHStack.pushCleanup<CatchRetScope>(NormalCleanup, CPI);
+        if ((CatchPadInst = 
dyn_cast_or_null<llvm::CatchPadInst>(CPICandidate))) {
+          CGF.CurrentFuncletPad = CatchPadInst;
+          CatchPadInst->setOperand(2, 
CGF.getExceptionSlot().emitRawPointer(CGF));
         }
       }
     }
 
+    if (CatchPadInst)
+      CGF.EHStack.pushCleanup<CatchRetScope>(NormalCleanup, CatchPadInst);
+
     llvm::Value *RawExn = CGF.getExceptionFromSlot();
 
     // Enter the catch.
@@ -272,6 +303,25 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
     CGF.EmitBranchThroughCleanup(Cont);
   }
 
+  if (!!dispatchBlock && hasWasmExceptions && !HasCatchAll) {
+    assert(WasmCatchStartBlock);
+    // Navigate for the "rethrow" block we created in emitWasmCatchPadBlock().
+    // Wasm uses landingpad-style conditional branches to compare selectors, so
+    // we follow the false destination for each of the cond branches to reach
+    // the rethrow block.
+    llvm::BasicBlock *RethrowBlock = WasmCatchStartBlock;
+    while (llvm::Instruction *TI = RethrowBlock->getTerminator()) {
+      auto *BI = cast<llvm::BranchInst>(TI);
+      assert(BI->isConditional());
+      RethrowBlock = BI->getSuccessor(1);
+    }
+    assert(RethrowBlock != WasmCatchStartBlock && RethrowBlock->empty());
+    CGF.Builder.SetInsertPoint(RethrowBlock);
+    llvm::Function *RethrowInCatchFn =
+      CGM.getIntrinsic(llvm::Intrinsic::wasm_rethrow);
+    CGF.EmitNoreturnRuntimeCallOrInvoke(RethrowInCatchFn, {});
+  }
+
   // Go back to the try-statement fallthrough.
   CGF.Builder.restoreIP(SavedIP);
 
diff --git a/clang/lib/CodeGen/CodeGenFunction.h 
b/clang/lib/CodeGen/CodeGenFunction.h
index 8c4c1c8c2dc95..86c02f0ac6b2d 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -1307,7 +1307,7 @@ class CodeGenFunction : public CodeGenTypeCache {
   /// popCatchScope - Pops the catch scope at the top of the EHScope
   /// stack, emitting any required code (other than the catch handlers
   /// themselves).
-  void popCatchScope();
+  llvm::BasicBlock* popCatchScope();
 
   llvm::BasicBlock *getEHResumeBlock(bool isCleanup);
   llvm::BasicBlock *getEHDispatchBlock(EHScopeStack::stable_iterator scope);
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp 
b/clang/lib/Driver/ToolChains/Clang.cpp
index 2f0aec3ec3c37..1225789344c68 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -8004,7 +8004,8 @@ ObjCRuntime Clang::AddObjCRuntimeArgs(const ArgList &args,
     if ((runtime.getKind() == ObjCRuntime::GNUstep) &&
         (runtime.getVersion() >= VersionTuple(2, 0)))
       if (!getToolChain().getTriple().isOSBinFormatELF() &&
-          !getToolChain().getTriple().isOSBinFormatCOFF()) {
+          !getToolChain().getTriple().isOSBinFormatCOFF() &&
+          !getToolChain().getTriple().isOSBinFormatWasm()) {
         getToolChain().getDriver().Diag(
             diag::err_drv_gnustep_objc_runtime_incompatible_binary)
           << runtime.getVersion().getMajor();

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to