https://github.com/hmelder updated 
https://github.com/llvm/llvm-project/pull/183753

>From 38fb99534154f37f43a69318407550db2dbe45f5 Mon Sep 17 00:00:00 2001
From: hmelder <[email protected]>
Date: Fri, 21 Nov 2025 10:42:47 +0000
Subject: [PATCH 01/13] [clang] Whitelist wasm when targeting GNUstep 2.x

---
 clang/lib/Driver/ToolChains/Clang.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Driver/ToolChains/Clang.cpp 
b/clang/lib/Driver/ToolChains/Clang.cpp
index ab671d032644b..6f583af41999e 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -8091,7 +8091,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();

>From 6e135cdab416fb5c03ab3f78a61a575a1a6644ed Mon Sep 17 00:00:00 2001
From: hmelder <[email protected]>
Date: Fri, 21 Nov 2025 10:50:35 +0000
Subject: [PATCH 02/13] [CodeGen][ObjC] Mangle public symbols for wasm

Emscripten requires that exported symbols. See
https://github.com/emscripten-core/emscripten/pull/23563.
---
 clang/lib/CodeGen/CGObjCGNU.cpp | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp
index 32fd6b760ed11..698fe16df7b85 100644
--- a/clang/lib/CodeGen/CGObjCGNU.cpp
+++ b/clang/lib/CodeGen/CGObjCGNU.cpp
@@ -179,8 +179,16 @@ class CGObjCGNU : public CGObjCRuntime {
       (R.getVersion() >= VersionTuple(major, minor));
   }
 
-  std::string ManglePublicSymbol(StringRef Name) {
-    return (StringRef(CGM.getTriple().isOSBinFormatCOFF() ? "$_" : "._") + 
Name).str();
+  const std::string ManglePublicSymbol(StringRef Name) {
+    StringRef prefix = "._";
+
+    // Exported symbols in Emscripten must be a valid Javascript identifier.
+    auto triple = CGM.getTriple();
+    if (triple.isOSBinFormatCOFF() || triple.isOSBinFormatWasm()) {
+      prefix = "$_";
+    }
+
+    return (prefix + Name).str();
   }
 
   std::string SymbolForProtocol(Twine Name) {

>From 90ce962fd0b450eef7670fc2ad07165cff2a555a Mon Sep 17 00:00:00 2001
From: hmelder <[email protected]>
Date: Fri, 21 Nov 2025 13:04:33 +0000
Subject: [PATCH 03/13] [CodeGen][ObjC] Fix class_registerAlias_np signature

---
 clang/lib/CodeGen/CGObjCGNU.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp
index 698fe16df7b85..080b263e5fd4d 100644
--- a/clang/lib/CodeGen/CGObjCGNU.cpp
+++ b/clang/lib/CodeGen/CGObjCGNU.cpp
@@ -4119,8 +4119,7 @@ llvm::Function *CGObjCGNU::ModuleInitFunction() {
   if (!ClassAliases.empty()) {
     llvm::Type *ArgTypes[2] = {PtrTy, PtrToInt8Ty};
     llvm::FunctionType *RegisterAliasTy =
-      llvm::FunctionType::get(Builder.getVoidTy(),
-                              ArgTypes, false);
+        llvm::FunctionType::get(BoolTy, ArgTypes, false);
     llvm::Function *RegisterAlias = llvm::Function::Create(
       RegisterAliasTy,
       llvm::GlobalValue::ExternalWeakLinkage, "class_registerAlias_np",

>From 2a9692a67daf099e97d10daa3c5e7c691717e81f Mon Sep 17 00:00:00 2001
From: hmelder <[email protected]>
Date: Fri, 28 Nov 2025 16:28:35 +0000
Subject: [PATCH 04/13] [CodeGen][ObjC] Add WASM symbol mangling test

---
 .../CodeGenObjC/gnustep2-wasm32-symbols.m     | 23 +++++++++++++++++++
 1 file changed, 23 insertions(+)
 create mode 100644 clang/test/CodeGenObjC/gnustep2-wasm32-symbols.m

diff --git a/clang/test/CodeGenObjC/gnustep2-wasm32-symbols.m 
b/clang/test/CodeGenObjC/gnustep2-wasm32-symbols.m
new file mode 100644
index 0000000000000..7da73b8f1903e
--- /dev/null
+++ b/clang/test/CodeGenObjC/gnustep2-wasm32-symbols.m
@@ -0,0 +1,23 @@
+// RUN: %clang_cc1 -triple wasm32-unknown-emscripten -emit-llvm 
-fobjc-runtime=gnustep-2.2 -o - %s | FileCheck %s
+
+@class NSString;
+
+@protocol AProtocol
+- (void) meth;
+@end
+
+@interface AClass <AProtocol>
+@end
+
+@implementation AClass
+- (void) meth {}
+@end
+
+// Make sure that all public symbols are mangled correctly. All exported 
symbols
+// must be valid Javascript identifiers in Emscripten.
+// CHECK: $"$_OBJC_PROTOCOL_AProtocol" = comdat any
+// CHECK: @"$_OBJC_METACLASS_AClass"
+// CHECK: @"$_OBJC_PROTOCOL_AProtocol"
+// CHECK: @"$_OBJC_CLASS_AClass"
+// CHECK: @"$_OBJC_REF_CLASS_AClass"
+// CHECK: @"$_OBJC_INIT_CLASS_AClass"

>From 912f59824d1b4ec4b96fbe85c62d8782e1f3d516 Mon Sep 17 00:00:00 2001
From: hmelder <[email protected]>
Date: Wed, 14 Jan 2026 10:23:18 +0000
Subject: [PATCH 05/13] [CodeGen][ObjC] Use C++-based EH for WASM targets

The Wasm EH implementation in Clang pretty much hard-codes
__gxx_wasm_personality_v0 by calling the veneer function
_Unwind_CallPersonality instead of calling the personality function
directly.

While it is possible to remove _Unwind_CallPersonality and instead
generate its body in CG, this will add a couple of instructions in each
catch block. Doable, but we can also do the following:

Since we already have C++-based EH for MinGW in CGObjCGNU, reusing it
for Wasm saves us from implementing our own personality function
and objc_begin_catch/objc_end_catch functions.
---
 clang/lib/CodeGen/CGException.cpp | 13 ++++++++++---
 clang/lib/CodeGen/CGObjCGNU.cpp   |  9 ++++-----
 2 files changed, 14 insertions(+), 8 deletions(-)

diff --git a/clang/lib/CodeGen/CGException.cpp 
b/clang/lib/CodeGen/CGException.cpp
index e9d20672ce185..749dde5a92dcd 100644
--- a/clang/lib/CodeGen/CGException.cpp
+++ b/clang/lib/CodeGen/CGException.cpp
@@ -161,6 +161,8 @@ static const EHPersonality &getObjCPersonality(const 
TargetInfo &Target,
   case ObjCRuntime::GNUstep:
     if (T.isOSCygMing())
       return EHPersonality::GNU_CPlusPlus_SEH;
+    else if (T.isWasm())
+      return EHPersonality::GNU_Wasm_CPlusPlus;
     else if (L.ObjCRuntime.getVersion() >= VersionTuple(1, 7))
       return EHPersonality::GNUstep_ObjC;
     [[fallthrough]];
@@ -200,7 +202,8 @@ static const EHPersonality &getCXXPersonality(const 
TargetInfo &Target,
 static const EHPersonality &getObjCXXPersonality(const TargetInfo &Target,
                                                  const CodeGenOptions &CGOpts,
                                                  const LangOptions &L) {
-  if (Target.getTriple().isWindowsMSVCEnvironment())
+  auto Triple = Target.getTriple();
+  if (Triple.isWindowsMSVCEnvironment())
     return EHPersonality::MSVC_CxxFrameHandler3;
 
   switch (L.ObjCRuntime.getKind()) {
@@ -218,8 +221,12 @@ static const EHPersonality &getObjCXXPersonality(const 
TargetInfo &Target,
     return getObjCPersonality(Target, CGOpts, L);
 
   case ObjCRuntime::GNUstep:
-    return Target.getTriple().isOSCygMing() ? EHPersonality::GNU_CPlusPlus_SEH
-                                            : EHPersonality::GNU_ObjCXX;
+    if (Triple.isWasm())
+      return EHPersonality::GNU_Wasm_CPlusPlus;
+    else if (Triple.isOSCygMing())
+      return EHPersonality::GNU_CPlusPlus_SEH;
+    else
+      return EHPersonality::GNU_ObjCXX;
 
   // The GCC runtime's personality function inherently doesn't support
   // mixed EH.  Use the ObjC personality just to avoid returning null.
diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp
index 080b263e5fd4d..f9835703a68a9 100644
--- a/clang/lib/CodeGen/CGObjCGNU.cpp
+++ b/clang/lib/CodeGen/CGObjCGNU.cpp
@@ -2368,12 +2368,11 @@ CGObjCGNU::CGObjCGNU(CodeGenModule &cgm, unsigned 
runtimeABIVersion,
     MetaClassPtrAlias(nullptr), RuntimeVersion(runtimeABIVersion),
     ProtocolVersion(protocolClassVersion), ClassABIVersion(classABI) {
 
+  auto Triple = cgm.getContext().getTargetInfo().getTriple();
+
   msgSendMDKind = VMContext.getMDKindID("GNUObjCMessageSend");
-  usesSEHExceptions =
-      cgm.getContext().getTargetInfo().getTriple().isWindowsMSVCEnvironment();
-  usesCxxExceptions =
-      cgm.getContext().getTargetInfo().getTriple().isOSCygMing() &&
-      isRuntime(ObjCRuntime::GNUstep, 2);
+  usesSEHExceptions = Triple.isWindowsMSVCEnvironment();
+  usesCxxExceptions = (Triple.isOSCygMing() && isRuntime(ObjCRuntime::GNUstep, 
2)) || Triple.isWasm();
 
   CodeGenTypes &Types = CGM.getTypes();
   IntTy = cast<llvm::IntegerType>(

>From 21ce13271e3b275c30c27bbfa2892a407d49f021 Mon Sep 17 00:00:00 2001
From: hmelder <[email protected]>
Date: Mon, 26 Jan 2026 08:02:32 +0000
Subject: [PATCH 06/13] [Clang][CodeGen] Return dispatch block when popping
 catch scope

---
 clang/lib/CodeGen/CGException.cpp   | 4 +++-
 clang/lib/CodeGen/CodeGenFunction.h | 2 +-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/clang/lib/CodeGen/CGException.cpp 
b/clang/lib/CodeGen/CGException.cpp
index 749dde5a92dcd..0dbeed2b43d38 100644
--- a/clang/lib/CodeGen/CGException.cpp
+++ b/clang/lib/CodeGen/CGException.cpp
@@ -1210,11 +1210,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/CodeGenFunction.h 
b/clang/lib/CodeGen/CodeGenFunction.h
index 1073de1d25ec7..b44c8acda8488 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -1304,7 +1304,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);

>From 44ded3da5093ccc83a00e53b9845b186eff913f5 Mon Sep 17 00:00:00 2001
From: hmelder <[email protected]>
Date: Mon, 26 Jan 2026 10:48:09 +0000
Subject: [PATCH 07/13] [Clang] Create Wasm EH helper functions for ObjC EH

---
 clang/lib/CodeGen/CGException.cpp   | 45 +++++++++++++++--------------
 clang/lib/CodeGen/CodeGenFunction.h | 10 ++++++-
 2 files changed, 32 insertions(+), 23 deletions(-)

diff --git a/clang/lib/CodeGen/CGException.cpp 
b/clang/lib/CodeGen/CGException.cpp
index 0dbeed2b43d38..dbf09bc102ce9 100644
--- a/clang/lib/CodeGen/CGException.cpp
+++ b/clang/lib/CodeGen/CGException.cpp
@@ -1210,15 +1210,35 @@ static void emitCatchDispatchBlock(CodeGenFunction &CGF,
   }
 }
 
-LLVM::BasicBlock *CodeGenFunction::popCatchScope() {
+llvm::BasicBlock *CodeGenFunction::popCatchScope() {
   EHCatchScope &catchScope = cast<EHCatchScope>(*EHStack.begin());
-  LLVM::BasicBlock *dispatchBlock = catchScope.getCachedEHDispatchBlock();
+  llvm::BasicBlock *dispatchBlock = catchScope.getCachedEHDispatchBlock();
   if (catchScope.hasEHBranches())
     emitCatchDispatchBlock(*this, catchScope);
   EHStack.popCatch();
   return dispatchBlock;
 }
 
+
+void CodeGenFunction::WasmEmitFallthroughRethrow(llvm::BasicBlock 
*WasmCatchStartBlock) {
+  assert(WasmCatchStartBlock);
+  // Navigate for the "rethrow" block. For CXX exceptions this was 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());
+  Builder.SetInsertPoint(RethrowBlock);
+  llvm::Function *RethrowInCatchFn =
+      CGM.getIntrinsic(llvm::Intrinsic::wasm_rethrow);
+  EmitNoreturnRuntimeCallOrInvoke(RethrowInCatchFn, {});
+}
+
 void CodeGenFunction::ExitCXXTryStmt(const CXXTryStmt &S, bool IsFnTryBlock) {
   unsigned NumHandlers = S.getNumHandlers();
   EHCatchScope &CatchScope = cast<EHCatchScope>(*EHStack.begin());
@@ -1325,27 +1345,8 @@ void CodeGenFunction::ExitCXXTryStmt(const CXXTryStmt 
&S, bool IsFnTryBlock) {
       Builder.CreateBr(ContBB);
   }
 
-  // Because in wasm we merge all catch clauses into one big catchpad, in case
-  // none of the types in catch handlers matches after we test against each of
-  // them, we should unwind to the next EH enclosing scope. We generate a call
-  // to rethrow function here to do that.
   if (EHPersonality::get(*this).isWasmPersonality() && !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());
-    Builder.SetInsertPoint(RethrowBlock);
-    llvm::Function *RethrowInCatchFn =
-        CGM.getIntrinsic(llvm::Intrinsic::wasm_rethrow);
-    EmitNoreturnRuntimeCallOrInvoke(RethrowInCatchFn, {});
+    WasmEmitFallthroughRethrow(WasmCatchStartBlock);
   }
 
   EmitBlock(ContBB);
diff --git a/clang/lib/CodeGen/CodeGenFunction.h 
b/clang/lib/CodeGen/CodeGenFunction.h
index b44c8acda8488..53e581cffc6ad 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -1304,7 +1304,15 @@ 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).
-  LLVM::BasicBlock *popCatchScope();
+  llvm::BasicBlock *popCatchScope();
+
+  // This function should be called after emitting all catch clauses and none
+  // of them were 'catch-all' clauses. 
+  // Because in wasm we merge all catch clauses into one big catchpad, in case
+  // none of the types in catch handlers matches after we test against each of
+  // them, we should unwind to the next EH enclosing scope. We generate a call
+  // to rethrow function here to do that.
+  void WasmEmitFallthroughRethrow(llvm::BasicBlock *WasmCatchStartBlock);
 
   llvm::BasicBlock *getEHResumeBlock(bool isCleanup);
   llvm::BasicBlock *getEHDispatchBlock(EHScopeStack::stable_iterator scope);

>From 925137e31004624c45cee178cc1104614bd75d3c Mon Sep 17 00:00:00 2001
From: hmelder <[email protected]>
Date: Mon, 26 Jan 2026 10:49:09 +0000
Subject: [PATCH 08/13] [ObjC] Support Wasm EH

---
 clang/lib/CodeGen/CGObjCRuntime.cpp | 47 +++++++++++++++++++++++++----
 1 file changed, 41 insertions(+), 6 deletions(-)

diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp 
b/clang/lib/CodeGen/CGObjCRuntime.cpp
index 3d47dc9560c65..e33ad45fa061d 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.cpp
+++ b/clang/lib/CodeGen/CGObjCRuntime.cpp
@@ -13,6 +13,7 @@
 
//===----------------------------------------------------------------------===//
 
 #include "CGObjCRuntime.h"
+#include "Address.h"
 #include "CGCXXABI.h"
 #include "CGCleanup.h"
 #include "CGRecordLayout.h"
@@ -23,6 +24,7 @@
 #include "clang/CodeGen/CGFunctionInfo.h"
 #include "clang/CodeGen/CodeGenABITypes.h"
 #include "llvm/IR/Instruction.h"
+#include "llvm/IR/Instructions.h"
 #include "llvm/Support/SaveAndRestore.h"
 
 using namespace clang;
@@ -148,6 +150,8 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
     Cont = CGF.getJumpDestInCurrentScope("eh.cont");
 
   bool useFunclets = EHPersonality::get(CGF).usesFuncletPads();
+  bool IsWasm = EHPersonality::get(CGF).isWasmPersonality();
+  bool IsMSVC = EHPersonality::get(CGF).isMSVCPersonality();
 
   CodeGenFunction::FinallyInfo FinallyInfo;
   if (!useFunclets)
@@ -187,7 +191,7 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
       Catch->setHandler(I, { Handlers[I].TypeInfo, Handlers[I].Flags }, 
Handlers[I].Block);
   }
 
-  if (useFunclets)
+  if (IsMSVC)
     if (const ObjCAtFinallyStmt *Finally = S.getFinallyStmt()) {
         CodeGenFunction HelperCGF(CGM, /*suppressNewContext=*/true);
         if (!CGF.CurSEHParent)
@@ -213,30 +217,57 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
   CGF.EmitStmt(S.getTryBody());
 
   // Leave the try.
-  if (S.getNumCatchStmts())
-    CGF.popCatchScope();
+  llvm::BasicBlock *DispatchBlock = nullptr;
+  if (S.getNumCatchStmts()) {
+     DispatchBlock = CGF.popCatchScope();
+  }
+
+  // TODO(hugo): Better documentation
+  // 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 *CPI = nullptr;
+  if (!!DispatchBlock && IsWasm) {
+    auto *CatchSwitch =
+        cast<llvm::CatchSwitchInst>(DispatchBlock->getFirstNonPHIIt());
+    WasmCatchStartBlock = CatchSwitch->hasUnwindDest()
+                              ? CatchSwitch->getSuccessor(1)
+                              : CatchSwitch->getSuccessor(0);
+    CPI =
+        cast<llvm::CatchPadInst>(WasmCatchStartBlock->getFirstNonPHIIt());
+    CGF.CurrentFuncletPad = CPI;
+  }
 
   // Remember where we were.
   CGBuilderTy::InsertPoint SavedIP = CGF.Builder.saveAndClearIP();
 
   // Emit the handlers.
+  // TODO(hugo): Document
+  bool HasCatchAll = false;
   for (CatchHandler &Handler : Handlers) {
+    HasCatchAll |= Handler.TypeInfo == nullptr;
     CGF.EmitBlock(Handler.Block);
 
     CodeGenFunction::LexicalScope Cleanups(CGF, 
Handler.Body->getSourceRange());
     SaveAndRestore RevertAfterScope(CGF.CurrentFuncletPad);
-    if (useFunclets) {
+    if (IsMSVC) {
       llvm::BasicBlock::iterator CPICandidate =
           Handler.Block->getFirstNonPHIIt();
       if (CPICandidate != Handler.Block->end()) {
-        if (auto *CPI = dyn_cast_or_null<llvm::CatchPadInst>(CPICandidate)) {
+        CPI = dyn_cast_or_null<llvm::CatchPadInst>(CPICandidate);
+        if (!!CPI) {
           CGF.CurrentFuncletPad = CPI;
           CPI->setOperand(2, CGF.getExceptionSlot().emitRawPointer(CGF));
-          CGF.EHStack.pushCleanup<CatchRetScope>(NormalCleanup, CPI);
         }
       }
     }
 
+    if (!!CPI) {
+      CGF.EHStack.pushCleanup<CatchRetScope>(NormalCleanup, CPI);
+    }
+
     llvm::Value *RawExn = CGF.getExceptionFromSlot();
 
     // Enter the catch.
@@ -272,6 +303,10 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
     CGF.EmitBranchThroughCleanup(Cont);
   }
 
+  if (IsWasm && !HasCatchAll) {
+    CGF.WasmEmitFallthroughRethrow(WasmCatchStartBlock);
+  }
+
   // Go back to the try-statement fallthrough.
   CGF.Builder.restoreIP(SavedIP);
 

>From afee02ea7d9ef2172a0409313e94c71eb32fb79c Mon Sep 17 00:00:00 2001
From: hmelder <[email protected]>
Date: Mon, 26 Jan 2026 10:49:51 +0000
Subject: [PATCH 09/13] [ObjC][GNU] Use Cxx Exceptions on Wasm

---
 clang/lib/CodeGen/CGObjCGNU.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp
index f9835703a68a9..6a3ceb7d10e3e 100644
--- a/clang/lib/CodeGen/CGObjCGNU.cpp
+++ b/clang/lib/CodeGen/CGObjCGNU.cpp
@@ -2371,8 +2371,10 @@ CGObjCGNU::CGObjCGNU(CodeGenModule &cgm, unsigned 
runtimeABIVersion,
   auto Triple = cgm.getContext().getTargetInfo().getTriple();
 
   msgSendMDKind = VMContext.getMDKindID("GNUObjCMessageSend");
+  auto Triple = cgm.getContext().getTargetInfo().getTriple();
   usesSEHExceptions = Triple.isWindowsMSVCEnvironment();
-  usesCxxExceptions = (Triple.isOSCygMing() && isRuntime(ObjCRuntime::GNUstep, 
2)) || Triple.isWasm();
+  usesCxxExceptions = (Triple.isOSCygMing() &&
+      isRuntime(ObjCRuntime::GNUstep, 2)) || Triple.isWasm();
 
   CodeGenTypes &Types = CGM.getTypes();
   IntTy = cast<llvm::IntegerType>(

>From 8b556cc9e6e14b4edc2371c5ad0771b3cff9ba04 Mon Sep 17 00:00:00 2001
From: hmelder <[email protected]>
Date: Fri, 27 Feb 2026 11:35:51 +0000
Subject: [PATCH 10/13] [Clang][ObjC] Document EH codegen

---
 clang/lib/CodeGen/CGObjCRuntime.cpp | 53 +++++++++++++++++++++++++----
 1 file changed, 47 insertions(+), 6 deletions(-)

diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp 
b/clang/lib/CodeGen/CGObjCRuntime.cpp
index e33ad45fa061d..a8d9d0750acf0 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.cpp
+++ b/clang/lib/CodeGen/CGObjCRuntime.cpp
@@ -156,6 +156,9 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
   CodeGenFunction::FinallyInfo FinallyInfo;
   if (!useFunclets)
     if (const ObjCAtFinallyStmt *Finally = S.getFinallyStmt())
+      // The finally statement is executed as a cleanup for the normal and
+      // exceptional control flow out of a try-catch block. This is all
+      // implemented in FinallyInfo. Here we enter a new EHCatchScope.
       FinallyInfo.enter(CGF, Finally->getFinallyBody(),
                         beginCatchFn, endCatchFn, exceptionRethrowFn);
 
@@ -186,6 +189,7 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
       Handler.TypeInfo = GetEHType(CatchDecl->getType());
     }
 
+    // Create a new catch scope
     EHCatchScope *Catch = CGF.EHStack.pushCatch(Handlers.size());
     for (unsigned I = 0, E = Handlers.size(); I != E; ++I)
       Catch->setHandler(I, { Handlers[I].TypeInfo, Handlers[I].Flags }, 
Handlers[I].Block);
@@ -216,16 +220,48 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
   // Emit the try body.
   CGF.EmitStmt(S.getTryBody());
 
+  // lpad or catch.dispatch (the dispatch block) has now been emitted
+  //
+  // Here an example:
+  // void may_throw();
+  // @try {
+  //    may_throw();
+  // } @catch(id a) {
+  // } @catch(id b) {
+  // [...]
+  // 
+  // With funclet-based exception handling, the dispatch block is created in 
+  // getEHDispatchBlock() <- getInvokeDestImpl() <- EmitCall().
+  // The following IR is emitted in this case:
+  // On aarch64-linux-gnu (landing-pad based)
+  //   %call = invoke i32 @may_throw()
+  //      to label %invoke.cont unwind label %lpad, !dbg !19
+  // On aarch64-pc-windows-msvc (funclet based)
+  // %call = invoke i32 @may_throw()
+  //      to label %invoke.cont unwind label %catch.dispatch, !dbg !17
+
   // Leave the try.
   llvm::BasicBlock *DispatchBlock = nullptr;
   if (S.getNumCatchStmts()) {
+     // The dispatch block that was created during the emission of the try 
block
+     // was cached. We retrieve it when popping the current catch scope. 
      DispatchBlock = CGF.popCatchScope();
   }
 
-  // TODO(hugo): Better documentation
-  // 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.
+  // On Windows and WASM, the new exception handling instructions are used.
+  // 
+  // Continuing with the previous example, on Windows, we emit one catchpad for
+  // every catch handler. This is not the case for WASM where all catch 
handlers
+  // merged into one big catchpad:
+  //
+  // catch.dispatch:
+  // %0 = catchswitch within none [label %catch.start] unwind to caller
+  // catch.start:
+  //   %1 = catchpad within %0 [ptr @__objc_id_type_info, ptr null]
+  //   [...]
+  //   br i1 %matches, label %catch, label %catch2
+  //
+  // We save the old funclet pad here before we traverse each catch handler.
   SaveAndRestore RestoreCurrentFuncletPad(CGF.CurrentFuncletPad);
   llvm::BasicBlock *WasmCatchStartBlock = nullptr;
   llvm::CatchPadInst *CPI = nullptr;
@@ -243,8 +279,9 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
   // Remember where we were.
   CGBuilderTy::InsertPoint SavedIP = CGF.Builder.saveAndClearIP();
 
-  // Emit the handlers.
-  // TODO(hugo): Document
+  // Emit the handlers. If there is no catch-all handler, we need to emit a
+  // fallthrough block in WASM. We therefore need to know if we have a
+  // catch-all handler in this catch scope.
   bool HasCatchAll = false;
   for (CatchHandler &Handler : Handlers) {
     HasCatchAll |= Handler.TypeInfo == nullptr;
@@ -265,6 +302,8 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
     }
 
     if (!!CPI) {
+      // A catchpad requires a matching catchret instruction. We emit this in
+      // form of a cleanup.
       CGF.EHStack.pushCleanup<CatchRetScope>(NormalCleanup, CPI);
     }
 
@@ -293,6 +332,8 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
       EmitInitOfCatchParam(CGF, CastExn, CatchParam);
     }
 
+    // The body of the handler might have more try-catch blocks, so we need to
+    // save the current exception before emitting the body.
     CGF.ObjCEHValueStack.push_back(Exn);
     CGF.EmitStmt(Handler.Body);
     CGF.ObjCEHValueStack.pop_back();

>From 58928368c444993a654ff3ab93c093f3d247290e Mon Sep 17 00:00:00 2001
From: hmelder <[email protected]>
Date: Fri, 27 Feb 2026 12:00:49 +0000
Subject: [PATCH 11/13] [CodeGen][ObjC] Disable @finally for WASM

---
 clang/lib/CodeGen/CGObjCRuntime.cpp | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp 
b/clang/lib/CodeGen/CGObjCRuntime.cpp
index a8d9d0750acf0..76d7a80651604 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.cpp
+++ b/clang/lib/CodeGen/CGObjCRuntime.cpp
@@ -25,6 +25,7 @@
 #include "clang/CodeGen/CodeGenABITypes.h"
 #include "llvm/IR/Instruction.h"
 #include "llvm/IR/Instructions.h"
+#include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/SaveAndRestore.h"
 
 using namespace clang;
@@ -154,13 +155,17 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
   bool IsMSVC = EHPersonality::get(CGF).isMSVCPersonality();
 
   CodeGenFunction::FinallyInfo FinallyInfo;
-  if (!useFunclets)
-    if (const ObjCAtFinallyStmt *Finally = S.getFinallyStmt())
+  if (const ObjCAtFinallyStmt *Finally = S.getFinallyStmt()) {
+    if (!useFunclets) {
       // The finally statement is executed as a cleanup for the normal and
       // exceptional control flow out of a try-catch block. This is all
       // implemented in FinallyInfo. Here we enter a new EHCatchScope.
-      FinallyInfo.enter(CGF, Finally->getFinallyBody(),
-                        beginCatchFn, endCatchFn, exceptionRethrowFn);
+      FinallyInfo.enter(CGF, Finally->getFinallyBody(), beginCatchFn,
+                        endCatchFn, exceptionRethrowFn);
+    } else if (IsWasm) {
+      llvm_unreachable("@finally not implemented for WASM");
+    }
+  }
 
   SmallVector<CatchHandler, 8> Handlers;
 

>From f152b462b7d84823cf1b6a0a2164d1418ead6fa1 Mon Sep 17 00:00:00 2001
From: hmelder <[email protected]>
Date: Fri, 27 Feb 2026 13:57:28 +0000
Subject: [PATCH 12/13] [CodeGen][ObjC] Add WASM EH test case

---
 clang/test/CodeGenObjC/gnustep2-wasm32-eh.m | 37 +++++++++++++++++++++
 1 file changed, 37 insertions(+)
 create mode 100644 clang/test/CodeGenObjC/gnustep2-wasm32-eh.m

diff --git a/clang/test/CodeGenObjC/gnustep2-wasm32-eh.m 
b/clang/test/CodeGenObjC/gnustep2-wasm32-eh.m
new file mode 100644
index 0000000000000..08aadfe507003
--- /dev/null
+++ b/clang/test/CodeGenObjC/gnustep2-wasm32-eh.m
@@ -0,0 +1,37 @@
+// RUN: %clang_cc1 -triple wasm32-unknown-emscripten -fobjc-exceptions 
-fexceptions -exception-model=wasm -mllvm -wasm-enable-eh -emit-llvm 
-fobjc-runtime=gnustep-2.2 -o - %s | FileCheck %s
+
+void may_throw(void) {
+        @throw (id) 1;
+}
+
+int main(void) {
+        int retval = 0;
+        @try {
+                may_throw();
+                // CHECK: invoke void @may_throw()
+                // CHECK-NEXT: to label %[[INVOKE_CONT:.*]] unwind label 
%[[CATCH_DISPATCH:.*]]
+        }
+        // Check that the dispatch block has been emitted correctly.
+        // CHECK: [[CATCH_DISPATCH]]:
+        // CHECK-NEXT: %[[CATCHSWITCH:.*]] = catchswitch within none [label 
%[[CATCH_START:.*]] unwind to caller
+
+
+        // The native WASM EH uses the new exception handling IR instructions
+        // (catchswitch, catchpad, etc.) that are also used when targeting 
Windows MSVC.
+        // For SEH, we emit a catchpad instruction for each catch statement. 
On WASM, we
+        // merge all catch statements into one big catch block.
+
+        // CHECK: catchpad within %[[CATCHSWITCH]] [ptr @__objc_id_type_info, 
ptr null]
+
+        // We use the cxa functions instead of objc_{begin,end}_catch.
+        // CHECK: call ptr @__cxa_begin_catch
+        @catch(id a) {
+            retval = 1;
+        }
+        @catch(...) {
+            retval = 2;
+        }
+        return retval;
+}
+
+

>From b3840f4f9b393cb1d591aea5d7eeba1079421744 Mon Sep 17 00:00:00 2001
From: hmelder <[email protected]>
Date: Fri, 27 Feb 2026 15:41:15 +0000
Subject: [PATCH 13/13] [CodeGen][ObjC] Formatting

---
 clang/lib/CodeGen/CGException.cpp   |  4 ++--
 clang/lib/CodeGen/CGObjCGNU.cpp     |  6 +++---
 clang/lib/CodeGen/CGObjCRuntime.cpp | 18 ++++++++++--------
 clang/lib/CodeGen/CodeGenFunction.h |  2 +-
 4 files changed, 16 insertions(+), 14 deletions(-)

diff --git a/clang/lib/CodeGen/CGException.cpp 
b/clang/lib/CodeGen/CGException.cpp
index dbf09bc102ce9..5df9d57ca9b45 100644
--- a/clang/lib/CodeGen/CGException.cpp
+++ b/clang/lib/CodeGen/CGException.cpp
@@ -1219,8 +1219,8 @@ llvm::BasicBlock *CodeGenFunction::popCatchScope() {
   return dispatchBlock;
 }
 
-
-void CodeGenFunction::WasmEmitFallthroughRethrow(llvm::BasicBlock 
*WasmCatchStartBlock) {
+void CodeGenFunction::WasmEmitFallthroughRethrow(
+    llvm::BasicBlock *WasmCatchStartBlock) {
   assert(WasmCatchStartBlock);
   // Navigate for the "rethrow" block. For CXX exceptions this was created in
   // emitWasmCatchPadBlock(). Wasm uses landingpad-style conditional branches
diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp
index 6a3ceb7d10e3e..20023070d273f 100644
--- a/clang/lib/CodeGen/CGObjCGNU.cpp
+++ b/clang/lib/CodeGen/CGObjCGNU.cpp
@@ -2371,10 +2371,10 @@ CGObjCGNU::CGObjCGNU(CodeGenModule &cgm, unsigned 
runtimeABIVersion,
   auto Triple = cgm.getContext().getTargetInfo().getTriple();
 
   msgSendMDKind = VMContext.getMDKindID("GNUObjCMessageSend");
-  auto Triple = cgm.getContext().getTargetInfo().getTriple();
   usesSEHExceptions = Triple.isWindowsMSVCEnvironment();
-  usesCxxExceptions = (Triple.isOSCygMing() &&
-      isRuntime(ObjCRuntime::GNUstep, 2)) || Triple.isWasm();
+  usesCxxExceptions =
+      (Triple.isOSCygMing() && isRuntime(ObjCRuntime::GNUstep, 2)) ||
+      Triple.isWasm();
 
   CodeGenTypes &Types = CGM.getTypes();
   IntTy = cast<llvm::IntegerType>(
diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp 
b/clang/lib/CodeGen/CGObjCRuntime.cpp
index 76d7a80651604..218d861673a0b 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.cpp
+++ b/clang/lib/CodeGen/CGObjCRuntime.cpp
@@ -163,6 +163,9 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
       FinallyInfo.enter(CGF, Finally->getFinallyBody(), beginCatchFn,
                         endCatchFn, exceptionRethrowFn);
     } else if (IsWasm) {
+      // dispatchBlock is finally.catchall
+      // emitWasmCatchPadBlock()
+      // CurrentFuncletPad = ...
       llvm_unreachable("@finally not implemented for WASM");
     }
   }
@@ -234,8 +237,8 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
   // } @catch(id a) {
   // } @catch(id b) {
   // [...]
-  // 
-  // With funclet-based exception handling, the dispatch block is created in 
+  //
+  // With funclet-based exception handling, the dispatch block is created in
   // getEHDispatchBlock() <- getInvokeDestImpl() <- EmitCall().
   // The following IR is emitted in this case:
   // On aarch64-linux-gnu (landing-pad based)
@@ -248,13 +251,13 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
   // Leave the try.
   llvm::BasicBlock *DispatchBlock = nullptr;
   if (S.getNumCatchStmts()) {
-     // The dispatch block that was created during the emission of the try 
block
-     // was cached. We retrieve it when popping the current catch scope. 
-     DispatchBlock = CGF.popCatchScope();
+    // The dispatch block that was created during the emission of the try block
+    // was cached. We retrieve it when popping the current catch scope.
+    DispatchBlock = CGF.popCatchScope();
   }
 
   // On Windows and WASM, the new exception handling instructions are used.
-  // 
+  //
   // Continuing with the previous example, on Windows, we emit one catchpad for
   // every catch handler. This is not the case for WASM where all catch 
handlers
   // merged into one big catchpad:
@@ -276,8 +279,7 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
     WasmCatchStartBlock = CatchSwitch->hasUnwindDest()
                               ? CatchSwitch->getSuccessor(1)
                               : CatchSwitch->getSuccessor(0);
-    CPI =
-        cast<llvm::CatchPadInst>(WasmCatchStartBlock->getFirstNonPHIIt());
+    CPI = cast<llvm::CatchPadInst>(WasmCatchStartBlock->getFirstNonPHIIt());
     CGF.CurrentFuncletPad = CPI;
   }
 
diff --git a/clang/lib/CodeGen/CodeGenFunction.h 
b/clang/lib/CodeGen/CodeGenFunction.h
index 53e581cffc6ad..c18e559f0333b 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -1307,7 +1307,7 @@ class CodeGenFunction : public CodeGenTypeCache {
   llvm::BasicBlock *popCatchScope();
 
   // This function should be called after emitting all catch clauses and none
-  // of them were 'catch-all' clauses. 
+  // of them were 'catch-all' clauses.
   // Because in wasm we merge all catch clauses into one big catchpad, in case
   // none of the types in catch handlers matches after we test against each of
   // them, we should unwind to the next EH enclosing scope. We generate a call

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

Reply via email to