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

This PR adds WebAssembly exception handling support for Objective-C/C++ 
`@try`/`@catch`. `@finally` will be implemented in a subsequent patch.

This PR is based on #183753 and was co-authored by @hmelder. I addressed the 
review comments on the old PR, fixed an issue and added more tests.

The FileCheck assertions were generated with AI assistance.

From d8c68edc92522a33caf5087addb7df7fd6bb8938 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hendrik=20H=C3=BCbner?= <[email protected]>
Date: Thu, 6 Aug 2026 15:51:43 +0200
Subject: [PATCH 1/3] [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.

Co-authored-by: hmelder <[email protected]>
---
 clang/lib/CodeGen/CGException.cpp           |  52 ++++++----
 clang/lib/CodeGen/CGObjCGNU.cpp             |  12 +--
 clang/lib/CodeGen/CGObjCRuntime.cpp         | 105 ++++++++++++++++++--
 clang/lib/CodeGen/CodeGenFunction.h         |  10 +-
 clang/lib/Driver/ToolChains/Clang.cpp       |   3 +-
 clang/test/CodeGenObjC/gnustep2-wasm32-eh.m |  35 +++++++
 6 files changed, 177 insertions(+), 40 deletions(-)
 create mode 100644 clang/test/CodeGenObjC/gnustep2-wasm32-eh.m

diff --git a/clang/lib/CodeGen/CGException.cpp 
b/clang/lib/CodeGen/CGException.cpp
index b0fb3b4d85d15..83f1e3dd29e35 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.
@@ -1207,11 +1214,30 @@ 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::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->getTerminatorOrNull())
+    RethrowBlock = cast<llvm::CondBrInst>(TI)->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) {
@@ -1320,24 +1346,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->getTerminatorOrNull())
-      RethrowBlock = cast<llvm::CondBrInst>(TI)->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/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp
index 32a1afe310629..1e7f6dc6a4e8d 100644
--- a/clang/lib/CodeGen/CGObjCGNU.cpp
+++ b/clang/lib/CodeGen/CGObjCGNU.cpp
@@ -2373,12 +2373,13 @@ 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();
+  usesSEHExceptions = Triple.isWindowsMSVCEnvironment();
   usesCxxExceptions =
-      cgm.getContext().getTargetInfo().getTriple().isOSCygMing() &&
-      isRuntime(ObjCRuntime::GNUstep, 2);
+      (Triple.isOSCygMing() && isRuntime(ObjCRuntime::GNUstep, 2)) ||
+      Triple.isWasm();
 
   CodeGenTypes &Types = CGM.getTypes();
   IntTy = cast<llvm::IntegerType>(
@@ -4155,8 +4156,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",
diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp 
b/clang/lib/CodeGen/CGObjCRuntime.cpp
index a83a4ce67a9c6..cca1b37431055 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,8 @@
 #include "clang/CodeGen/CGFunctionInfo.h"
 #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;
@@ -148,12 +151,24 @@ 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)
-    if (const ObjCAtFinallyStmt *Finally = S.getFinallyStmt())
-      FinallyInfo.enter(CGF, Finally->getFinallyBody(),
-                        beginCatchFn, endCatchFn, exceptionRethrowFn);
+  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);
+    } else if (IsWasm) {
+      // dispatchBlock is finally.catchall
+      // emitWasmCatchPadBlock()
+      // CurrentFuncletPad = ...
+      llvm_unreachable("@finally not implemented for WASM");
+    }
+  }
 
   SmallVector<CatchHandler, 8> Handlers;
 
@@ -182,12 +197,13 @@ 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);
   }
 
-  if (useFunclets)
+  if (IsMSVC)
     if (const ObjCAtFinallyStmt *Finally = S.getFinallyStmt()) {
         CodeGenFunction HelperCGF(CGM, /*suppressNewContext=*/true);
         if (!CGF.CurSEHParent)
@@ -212,31 +228,92 @@ 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.
-  if (S.getNumCatchStmts())
-    CGF.popCatchScope();
+  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();
+  }
+
+  // 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;
+  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.
+  // 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;
     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) {
+      // A catchpad requires a matching catchret instruction. We emit this in
+      // form of a cleanup.
+      CGF.EHStack.pushCleanup<CatchRetScope>(NormalCleanup, CPI);
+    }
+
     llvm::Value *RawExn = CGF.getExceptionFromSlot();
 
     // Enter the catch.
@@ -262,6 +339,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();
@@ -272,6 +351,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);
 
diff --git a/clang/lib/CodeGen/CodeGenFunction.h 
b/clang/lib/CodeGen/CodeGenFunction.h
index e89d754c309c8..b907a1c86c2e7 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -1326,7 +1326,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).
-  void 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);
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp 
b/clang/lib/Driver/ToolChains/Clang.cpp
index d2e22920aa432..efbf5d28f56fe 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -8638,7 +8638,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();
diff --git a/clang/test/CodeGenObjC/gnustep2-wasm32-eh.m 
b/clang/test/CodeGenObjC/gnustep2-wasm32-eh.m
new file mode 100644
index 0000000000000..9b4482be569a0
--- /dev/null
+++ b/clang/test/CodeGenObjC/gnustep2-wasm32-eh.m
@@ -0,0 +1,35 @@
+// 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 f9b5f5997b5de29d66a5611866b5881ff81f2593 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hendrik=20H=C3=BCbner?= <[email protected]>
Date: Fri, 7 Aug 2026 14:47:23 +0200
Subject: [PATCH 2/3] Address review comments, add more tests and fix
 formatting issues.

---
 clang/lib/CodeGen/CGException.cpp            |  18 +-
 clang/lib/CodeGen/CGObjCGNU.cpp              |   7 +-
 clang/lib/CodeGen/CGObjCRuntime.cpp          |  40 ++--
 clang/test/CodeGenObjC/gnustep2-wasm32-eh.m  |  35 ----
 clang/test/CodeGenObjC/wasm32-eh-arc.m       |  29 +++
 clang/test/CodeGenObjC/wasm32-eh.m           | 202 +++++++++++++++++++
 clang/test/CodeGenObjCXX/wasm32-eh-objcxx.mm | 102 ++++++++++
 7 files changed, 364 insertions(+), 69 deletions(-)
 delete mode 100644 clang/test/CodeGenObjC/gnustep2-wasm32-eh.m
 create mode 100644 clang/test/CodeGenObjC/wasm32-eh-arc.m
 create mode 100644 clang/test/CodeGenObjC/wasm32-eh.m
 create mode 100644 clang/test/CodeGenObjCXX/wasm32-eh-objcxx.mm

diff --git a/clang/lib/CodeGen/CGException.cpp 
b/clang/lib/CodeGen/CGException.cpp
index 83f1e3dd29e35..f8391a0d041f2 100644
--- a/clang/lib/CodeGen/CGException.cpp
+++ b/clang/lib/CodeGen/CGException.cpp
@@ -150,6 +150,8 @@ static const EHPersonality &getObjCPersonality(const 
TargetInfo &Target,
   const llvm::Triple &T = Target.getTriple();
   if (T.isWindowsMSVCEnvironment())
     return EHPersonality::MSVC_CxxFrameHandler3;
+  if (T.isWasm())
+    return EHPersonality::GNU_Wasm_CPlusPlus;
 
   switch (L.ObjCRuntime.getKind()) {
   case ObjCRuntime::FragileMacOSX:
@@ -161,9 +163,7 @@ 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))
+    if (L.ObjCRuntime.getVersion() >= VersionTuple(1, 7))
       return EHPersonality::GNUstep_ObjC;
     [[fallthrough]];
   case ObjCRuntime::GCC:
@@ -205,6 +205,8 @@ static const EHPersonality &getObjCXXPersonality(const 
TargetInfo &Target,
   auto Triple = Target.getTriple();
   if (Triple.isWindowsMSVCEnvironment())
     return EHPersonality::MSVC_CxxFrameHandler3;
+  if (Triple.isWasm())
+    return EHPersonality::GNU_Wasm_CPlusPlus;
 
   switch (L.ObjCRuntime.getKind()) {
   // In the fragile ABI, just use C++ exception handling and hope
@@ -221,12 +223,9 @@ static const EHPersonality &getObjCXXPersonality(const 
TargetInfo &Target,
     return getObjCPersonality(Target, CGOpts, L);
 
   case ObjCRuntime::GNUstep:
-    if (Triple.isWasm())
-      return EHPersonality::GNU_Wasm_CPlusPlus;
-    else if (Triple.isOSCygMing())
+    if (Triple.isOSCygMing())
       return EHPersonality::GNU_CPlusPlus_SEH;
-    else
-      return EHPersonality::GNU_ObjCXX;
+    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.
@@ -1346,7 +1345,8 @@ void CodeGenFunction::ExitCXXTryStmt(const CXXTryStmt &S, 
bool IsFnTryBlock) {
       Builder.CreateBr(ContBB);
   }
 
-  if (EHPersonality::get(*this).isWasmPersonality() && !HasCatchAll) {
+  if (EHPersonality::get(*this).isWasmPersonality() && !HasCatchAll &&
+      WasmCatchStartBlock) {
     WasmEmitFallthroughRethrow(WasmCatchStartBlock);
   }
 
diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp
index 1e7f6dc6a4e8d..e4581feb5a21c 100644
--- a/clang/lib/CodeGen/CGObjCGNU.cpp
+++ b/clang/lib/CodeGen/CGObjCGNU.cpp
@@ -4156,7 +4156,7 @@ llvm::Function *CGObjCGNU::ModuleInitFunction() {
   if (!ClassAliases.empty()) {
     llvm::Type *ArgTypes[2] = {PtrTy, PtrToInt8Ty};
     llvm::FunctionType *RegisterAliasTy =
-        llvm::FunctionType::get(BoolTy, ArgTypes, false);
+        llvm::FunctionType::get(Builder.getVoidTy(), ArgTypes, false);
     llvm::Function *RegisterAlias = llvm::Function::Create(
       RegisterAliasTy,
       llvm::GlobalValue::ExternalWeakLinkage, "class_registerAlias_np",
@@ -4338,15 +4338,14 @@ void CGObjCGNU::EmitThrowStmt(CodeGenFunction &CGF,
     // that was passed into the `@catch` block, then this code path is not
     // reached and we will instead call `objc_exception_throw` with an explicit
     // argument.
-    llvm::CallBase *Throw = CGF.EmitRuntimeCallOrInvoke(ExceptionReThrowFn);
-    Throw->setDoesNotReturn();
+    CGF.EmitNoreturnRuntimeCallOrInvoke(ExceptionReThrowFn, {});
   } else {
     ExceptionAsObject = CGF.Builder.CreateBitCast(ExceptionAsObject, IdTy);
     llvm::CallBase *Throw =
         CGF.EmitRuntimeCallOrInvoke(ExceptionThrowFn, ExceptionAsObject);
     Throw->setDoesNotReturn();
+    CGF.Builder.CreateUnreachable();
   }
-  CGF.Builder.CreateUnreachable();
   if (ClearInsertionPoint)
     CGF.Builder.ClearInsertionPoint();
 }
diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp 
b/clang/lib/CodeGen/CGObjCRuntime.cpp
index cca1b37431055..263b81331084d 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.cpp
+++ b/clang/lib/CodeGen/CGObjCRuntime.cpp
@@ -162,11 +162,10 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
       // implemented in FinallyInfo. Here we enter a new EHCatchScope.
       FinallyInfo.enter(CGF, Finally->getFinallyBody(), beginCatchFn,
                         endCatchFn, exceptionRethrowFn);
-    } else if (IsWasm) {
-      // dispatchBlock is finally.catchall
-      // emitWasmCatchPadBlock()
-      // CurrentFuncletPad = ...
-      llvm_unreachable("@finally not implemented for WASM");
+    } else {
+      CGF.ErrorUnsupported(
+          Finally,
+          "@finally is not implemented for funclet based exception handling");
     }
   }
 
@@ -203,27 +202,26 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
       Catch->setHandler(I, { Handlers[I].TypeInfo, Handlers[I].Flags }, 
Handlers[I].Block);
   }
 
-  if (IsMSVC)
+  if (IsMSVC) {
     if (const ObjCAtFinallyStmt *Finally = S.getFinallyStmt()) {
-        CodeGenFunction HelperCGF(CGM, /*suppressNewContext=*/true);
-        if (!CGF.CurSEHParent)
-            CGF.CurSEHParent = cast<NamedDecl>(CGF.CurFuncDecl);
-        // Outline the finally block.
-        const Stmt *FinallyBlock = Finally->getFinallyBody();
-        HelperCGF.startOutlinedSEHHelper(CGF, /*isFilter*/false, FinallyBlock);
-
-        // Emit the original filter expression, convert to i32, and return.
-        HelperCGF.EmitStmt(FinallyBlock);
+      CodeGenFunction HelperCGF(CGM, /*suppressNewContext=*/true);
+      if (!CGF.CurSEHParent)
+        CGF.CurSEHParent = cast<NamedDecl>(CGF.CurFuncDecl);
+      // Outline the finally block.
+      const Stmt *FinallyBlock = Finally->getFinallyBody();
+      HelperCGF.startOutlinedSEHHelper(CGF, /*isFilter*/ false, FinallyBlock);
 
-        HelperCGF.FinishFunction(FinallyBlock->getEndLoc());
+      // Emit the original filter expression, convert to i32, and return.
+      HelperCGF.EmitStmt(FinallyBlock);
 
-        llvm::Function *FinallyFunc = HelperCGF.CurFn;
+      HelperCGF.FinishFunction(FinallyBlock->getEndLoc());
 
+      llvm::Function *FinallyFunc = HelperCGF.CurFn;
 
-        // Push a cleanup for __finally blocks.
-        CGF.pushSEHCleanup(NormalAndEHCleanup, FinallyFunc);
+      // Push a cleanup for __finally blocks.
+      CGF.pushSEHCleanup(NormalAndEHCleanup, FinallyFunc);
     }
-
+  }
 
   // Emit the try body.
   CGF.EmitStmt(S.getTryBody());
@@ -351,7 +349,7 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
     CGF.EmitBranchThroughCleanup(Cont);
   }
 
-  if (IsWasm && !HasCatchAll) {
+  if (IsWasm && !HasCatchAll && WasmCatchStartBlock) {
     CGF.WasmEmitFallthroughRethrow(WasmCatchStartBlock);
   }
 
diff --git a/clang/test/CodeGenObjC/gnustep2-wasm32-eh.m 
b/clang/test/CodeGenObjC/gnustep2-wasm32-eh.m
deleted file mode 100644
index 9b4482be569a0..0000000000000
--- a/clang/test/CodeGenObjC/gnustep2-wasm32-eh.m
+++ /dev/null
@@ -1,35 +0,0 @@
-// 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;
-}
diff --git a/clang/test/CodeGenObjC/wasm32-eh-arc.m 
b/clang/test/CodeGenObjC/wasm32-eh-arc.m
new file mode 100644
index 0000000000000..1100e3bfbf28d
--- /dev/null
+++ b/clang/test/CodeGenObjC/wasm32-eh-arc.m
@@ -0,0 +1,29 @@
+// RUN: %clang_cc1 -triple wasm32-unknown-emscripten 
-fobjc-runtime=gnustep-2.2 -fobjc-arc -fexceptions -fobjc-exceptions 
-exception-model=wasm -mllvm -wasm-enable-eh -emit-llvm -o - %s | FileCheck 
--enable-var-scope %s
+__attribute__((objc_root_class)) @interface Object @end
+extern void mayThrowObjC();
+
+int arcRethrow(Object *value) {
+  @try {
+    mayThrowObjC();
+  } @catch (id caught) {
+    @throw;
+  }
+  return 0;
+}
+
+// CHECK-LABEL: define{{.*}} @arcRethrow
+// CHECK: invoke void @mayThrowObjC()
+// CHECK-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[CATCH_DISPATCH:.*]]
+// CHECK: [[CATCH_DISPATCH]]:
+// CHECK-NEXT: [[CATCHSWITCH:%.*]] = catchswitch within none [label 
%[[CATCH_START:.*]]] unwind to caller
+// CHECK: [[CATCH_START]]:
+// CHECK-NEXT: [[CATCHPAD:%.*]] = catchpad within [[CATCHSWITCH]] [ptr 
@__objc_id_type_info]
+// CHECK: br i1 %{{.*}}, label %[[CATCH:.*]], label %[[RETHROW:.*]]
+// CHECK: [[RETHROW]]:
+// CHECK-NEXT: call void @llvm.wasm.rethrow()
+// CHECK-NEXT: unreachable
+// CHECK: [[INVOKE_CONT]]:
+// CHECK: br label %{{.*}}
+// CHECK: [[CATCH]]:
+// CHECK: invoke void @__cxa_rethrow(){{.*}}[ "funclet"(token [[CATCHPAD]]) ]
+// CHECK-NEXT: to label %unreachable unwind label
diff --git a/clang/test/CodeGenObjC/wasm32-eh.m 
b/clang/test/CodeGenObjC/wasm32-eh.m
new file mode 100644
index 0000000000000..e35d81de51bf5
--- /dev/null
+++ b/clang/test/CodeGenObjC/wasm32-eh.m
@@ -0,0 +1,202 @@
+// 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 --enable-var-scope %s
+
+__attribute__((objc_root_class)) @interface Object
+@end
+
+@interface ExceptionA : Object
+@end
+
+@interface ExceptionB : Object
+@end
+
+void mayThrow(void) {
+  @throw (id)1;
+}
+
+int basicCatchAll(void) {
+  @try {
+    mayThrow();
+  } @catch (...) {
+    return 1;
+  }
+  return 0;
+}
+
+// CHECK-LABEL: define{{.*}} @basicCatchAll
+// CHECK: invoke void @mayThrow()
+// CHECK-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[CATCH_DISPATCH:.*]]
+// CHECK: [[CATCH_DISPATCH]]:
+// CHECK-NEXT: [[CATCHSWITCH:%.*]] = catchswitch within none [label 
%[[CATCH_START:.*]]] unwind to caller
+// CHECK: [[CATCH_START]]:
+// CHECK-NEXT: [[CATCHPAD:%.*]] = catchpad within [[CATCHSWITCH]] [ptr null]
+// CHECK: br label %[[CATCH_ALL:.*]]
+// CHECK: [[INVOKE_CONT]]:
+// CHECK: br label %[[EH_CONT:.*]]
+// CHECK: [[CATCH_ALL]]:
+// CHECK: call ptr @__cxa_begin_catch
+// CHECK: call void @__cxa_end_catch()
+// CHECK: catchret from [[CATCHPAD]] to label %[[CATCHRET_DEST:.*]]
+// CHECK: [[CATCHRET_DEST]]:
+// CHECK-NEXT: br label %return
+
+int twoTypedHandlers(void) {
+  @try {
+    mayThrow();
+  } @catch (ExceptionA *exception) {
+    return 1;
+  } @catch (ExceptionB *exception) {
+    return 2;
+  }
+  return 0;
+}
+
+// CHECK-LABEL: define{{.*}} @twoTypedHandlers
+// CHECK: invoke void @mayThrow()
+// CHECK-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[CATCH_DISPATCH:.*]]
+// CHECK: [[CATCH_DISPATCH]]:
+// CHECK-NEXT: [[CATCHSWITCH:%.*]] = catchswitch within none [label 
%[[CATCH_START:.*]]] unwind to caller
+// CHECK: [[CATCH_START]]:
+// CHECK-NEXT: [[CATCHPAD:%.*]] = catchpad within [[CATCHSWITCH]] [ptr 
@__objc_eh_typeinfo_ExceptionA, ptr @__objc_eh_typeinfo_ExceptionB]
+// CHECK: br i1 %{{.*}}, label %[[CATCH:.*]], label %[[CATCH_FALLTHROUGH:.*]]
+// CHECK: [[CATCH_FALLTHROUGH]]:
+// CHECK: br i1 %{{.*}}, label %[[CATCH2:.*]], label %[[RETHROW:.*]]
+// CHECK: [[RETHROW]]:
+// CHECK-NEXT: call void @llvm.wasm.rethrow()
+// CHECK-NEXT: unreachable
+// CHECK: [[INVOKE_CONT]]:
+// CHECK: br label %[[EH_CONT:.*]]
+// CHECK: [[EH_CONT]]:
+// CHECK: br label %return
+// CHECK: [[CATCH]]:
+// CHECK: catchret from [[CATCHPAD]] to label %[[CATCHRET_DEST:.*]]
+// CHECK: [[CATCHRET_DEST]]:
+// CHECK-NEXT: br label %return
+// CHECK: [[CATCH2]]:
+// CHECK: catchret from [[CATCHPAD]] to label %[[CATCHRET_DEST2:.*]]
+// CHECK: [[CATCHRET_DEST2]]:
+// CHECK-NEXT: br label %return
+
+int typedHandlerAndCatchAll(void) {
+  @try {
+    mayThrow();
+  } @catch (ExceptionA *exception) {
+    return 1;
+  } @catch (...) {
+    return 2;
+  }
+  return 0;
+}
+
+// CHECK-LABEL: define{{.*}} @typedHandlerAndCatchAll
+// CHECK: invoke void @mayThrow()
+// CHECK-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[CATCH_DISPATCH:.*]]
+// CHECK: [[CATCH_DISPATCH]]:
+// CHECK-NEXT: [[CATCHSWITCH:%.*]] = catchswitch within none [label 
%[[CATCH_START:.*]]] unwind to caller
+// CHECK: [[CATCH_START]]:
+// CHECK-NEXT: [[CATCHPAD:%.*]] = catchpad within [[CATCHSWITCH]] [ptr 
@__objc_eh_typeinfo_ExceptionA, ptr null]
+// CHECK: br i1 %{{.*}}, label %[[CATCH:.*]], label %[[CATCH_ALL:.*]]
+// CHECK: [[INVOKE_CONT]]:
+// CHECK: br label %[[EH_CONT:.*]]
+// CHECK: [[EH_CONT]]:
+// CHECK: br label %return
+// CHECK: [[CATCH]]:
+// CHECK: catchret from [[CATCHPAD]] to label %[[CATCHRET_DEST:.*]]
+// CHECK: [[CATCHRET_DEST]]:
+// CHECK-NEXT: br label %return
+// CHECK: [[CATCH_ALL]]:
+// CHECK: catchret from [[CATCHPAD]] to label %[[CATCHRET_DEST_ALL:.*]]
+// CHECK: [[CATCHRET_DEST_ALL]]:
+// CHECK-NEXT: br label %return
+
+int nestedTryCatch(void) {
+  @try {
+    @try {
+      mayThrow();
+    } @catch (ExceptionA *exception) {
+      return 1;
+    }
+  } @catch (...) {
+    return 2;
+  }
+  return 0;
+}
+
+// CHECK-LABEL: define{{.*}} @nestedTryCatch
+// CHECK: invoke void @mayThrow()
+// CHECK-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[CATCH_DISPATCH:.*]]
+// CHECK: [[CATCH_DISPATCH]]:
+// CHECK-NEXT: [[CATCHSWITCH:%.*]] = catchswitch within none [label 
%[[CATCH_START:.*]]] unwind label %[[CATCH_DISPATCH1:.*]]
+// CHECK: [[CATCH_START]]:
+// CHECK-NEXT: [[CATCHPAD:%.*]] = catchpad within [[CATCHSWITCH]] [ptr 
@__objc_eh_typeinfo_ExceptionA]
+// CHECK: br i1 %{{.*}}, label %[[CATCH:.*]], label %[[RETHROW:.*]]
+// CHECK: [[RETHROW]]:
+// CHECK: invoke void @llvm.wasm.rethrow(){{.*}}[ "funclet"(token 
[[CATCHPAD]]) ]
+// CHECK-NEXT: to label %[[UNREACHABLE:.*]] unwind label %[[CATCH_DISPATCH1]]
+// CHECK: [[CATCH_DISPATCH1]]:
+// CHECK-NEXT: [[CATCHSWITCH1:%.*]] = catchswitch within none [label 
%[[CATCH_START2:.*]]] unwind to caller
+// CHECK: [[CATCH_START2]]:
+// CHECK-NEXT: [[CATCHPAD1:%.*]] = catchpad within [[CATCHSWITCH1]] [ptr null]
+// CHECK: [[INVOKE_CONT]]:
+// CHECK: br label %[[EH_CONT:.*]]
+// CHECK: [[EH_CONT]]:
+// CHECK: br label %[[EH_CONT2:.*]]
+// CHECK: [[EH_CONT2]]:
+// CHECK: br label %return
+// CHECK: [[CATCH]]:
+// CHECK: catchret from [[CATCHPAD]] to label %[[CATCHRET_DEST:.*]]
+// CHECK: [[CATCHRET_DEST]]:
+// CHECK-NEXT: br label %return
+// CHECK: [[CATCH_ALL:.*]]:
+// CHECK: catchret from [[CATCHPAD1]] to label %[[CATCHRET_DEST_ALL:.*]]
+// CHECK: [[CATCHRET_DEST_ALL]]:
+// CHECK-NEXT: br label %return
+
+int emptyCatch(void) {
+  @try {
+    mayThrow();
+  } @catch (ExceptionA *exception) {
+  }
+  return 0;
+}
+
+// CHECK-LABEL: define{{.*}} @emptyCatch
+// CHECK: invoke void @mayThrow()
+// CHECK-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[CATCH_DISPATCH:.*]]
+// CHECK: [[CATCH_DISPATCH]]:
+// CHECK-NEXT: [[CATCHSWITCH:%.*]] = catchswitch within none [label 
%[[CATCH_START:.*]]] unwind to caller
+// CHECK: [[CATCH_START]]:
+// CHECK-NEXT: [[CATCHPAD:%.*]] = catchpad within [[CATCHSWITCH]] [ptr 
@__objc_eh_typeinfo_ExceptionA]
+// CHECK: br i1 %{{.*}}, label %[[CATCH:.*]], label %[[RETHROW:.*]]
+// CHECK: [[RETHROW]]:
+// CHECK-NEXT: call void @llvm.wasm.rethrow()
+// CHECK-NEXT: unreachable
+// CHECK: [[INVOKE_CONT]]:
+// CHECK: br label %[[EH_CONT:.*]]
+// CHECK: [[EH_CONT]]:
+// CHECK: [[CATCH]]:
+// CHECK: catchret from [[CATCHPAD]] to label %[[CATCHRET_DEST:.*]]
+// CHECK: [[CATCHRET_DEST]]:
+// CHECK-NEXT: br label %[[EH_CONT]]
+
+int explicitRethrow(void) {
+  @try {
+    mayThrow();
+  } @catch (...) {
+    @throw;
+  }
+  return 0;
+}
+
+// CHECK-LABEL: define{{.*}} @explicitRethrow
+// CHECK: invoke void @mayThrow()
+// CHECK-NEXT: to label %{{.*}} unwind label %[[CATCH_DISPATCH:.*]]
+// CHECK: [[CATCH_DISPATCH]]:
+// CHECK-NEXT: [[CATCHSWITCH:%.*]] = catchswitch within none [label 
%[[CATCH_START:.*]]] unwind to caller
+// CHECK: [[CATCH_START]]:
+// CHECK-NEXT: [[CATCHPAD:%.*]] = catchpad within [[CATCHSWITCH]] [ptr null]
+// CHECK: br label %[[CATCH_ALL:.*]]
+// CHECK: [[CATCH_ALL]]:
+// CHECK: invoke void @__cxa_rethrow(){{.*}}[ "funclet"(token [[CATCHPAD]]) ]
+// CHECK-NEXT: to label %[[UNREACHABLE:.*]] unwind label %{{.*}}
+// CHECK: [[UNREACHABLE]]:
+// CHECK-NEXT: unreachable
diff --git a/clang/test/CodeGenObjCXX/wasm32-eh-objcxx.mm 
b/clang/test/CodeGenObjCXX/wasm32-eh-objcxx.mm
new file mode 100644
index 0000000000000..2c4ef5dfaf68d
--- /dev/null
+++ b/clang/test/CodeGenObjCXX/wasm32-eh-objcxx.mm
@@ -0,0 +1,102 @@
+// RUN: %clang_cc1 -target-feature +exception-handling -triple 
wasm32-unknown-emscripten -fobjc-runtime=gnustep-2.2 -fexceptions 
-fobjc-exceptions -fcxx-exceptions -exception-model=wasm -mllvm -wasm-enable-eh 
-emit-llvm -o - %s | FileCheck --enable-var-scope %s
+
+struct ThrowingDestructor {
+  ~ThrowingDestructor() noexcept(false);
+};
+
+extern void mayThrowCXX();
+
+int cxxDestructorsAroundCatch() {
+  try {
+    ThrowingDestructor guard;
+    mayThrowCXX();
+  } catch (...) {
+    ThrowingDestructor caught;
+    return 1;
+  }
+  return 0;
+}
+
+// CHECK-LABEL: define{{.*}} @_Z25cxxDestructorsAroundCatchv
+// CHECK: invoke void @_Z{{[0-9]+}}mayThrowCXXv()
+// CHECK-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[EHCLEANUP:.*]]
+// CHECK: [[INVOKE_CONT]]:
+// CHECK: invoke{{.*}} @_ZN18ThrowingDestructorD1Ev
+// CHECK-NEXT: to label %{{.*}} unwind label %[[CATCH_DISPATCH:.*]]
+// CHECK: [[EHCLEANUP]]:
+// CHECK: [[CLEANUPPAD:%.*]] = cleanuppad within none []
+// CHECK: invoke{{.*}} @_ZN18ThrowingDestructorD1Ev{{.*}}[ "funclet"(token 
[[CLEANUPPAD]]) ]
+// CHECK: cleanupret from [[CLEANUPPAD]] unwind label %[[CATCH_DISPATCH]]
+// CHECK: [[CATCH_DISPATCH]]:
+// CHECK-NEXT: [[CATCHSWITCH:%.*]] = catchswitch within none [label 
%[[CATCH_START:.*]]] unwind to caller
+// CHECK: [[CATCH_START]]:
+// CHECK-NEXT: [[CATCHPAD:%.*]] = catchpad within [[CATCHSWITCH]] [ptr null]
+// CHECK: br label %[[CATCH_ALL:.*]]
+// CHECK: [[CATCH_ALL]]:
+// CHECK: invoke{{.*}} @_ZN18ThrowingDestructorD1Ev{{.*}}[ "funclet"(token 
[[CATCHPAD]]) ]
+// CHECK: catchret from [[CATCHPAD]] to label %{{.*}}
+// CHECK: [[CLEANUPPAD1:%.*]] = cleanuppad within [[CATCHPAD]] []
+// CHECK: cleanupret from [[CLEANUPPAD1]] unwind to caller
+
+__attribute__((objc_root_class)) @interface Object
+@end
+
+extern void mayThrowObjC();
+
+int combinedCxxObjcEH() {
+  @try {
+    try {
+      mayThrowCXX();
+    } catch (Object *exception) {
+      @try {
+        mayThrowObjC();
+      } @catch (Object *nestedException) {
+        return 1;
+      }
+      return 2;
+    } catch (int value) {
+      return value;
+    }
+  } @catch (...) {
+    return 3;
+  }
+  return 0;
+}
+
+// CHECK-LABEL: define{{.*}} @_Z{{[0-9]+}}combinedCxxObjcEHv
+// CHECK: invoke void @_Z{{[0-9]+}}mayThrowCXXv()
+// CHECK-NEXT: to label %{{.*}} unwind label %[[CATCH_DISPATCH:.*]]
+// CHECK: [[CATCH_DISPATCH]]:
+// CHECK-NEXT: [[CATCHSWITCH:%.*]] = catchswitch within none [label 
%[[CATCH_START:.*]]] unwind label %[[CATCH_DISPATCH1:.*]]
+// CHECK: [[CATCH_START]]:
+// CHECK-NEXT: [[CATCHPAD:%.*]] = catchpad within [[CATCHSWITCH]] [ptr 
@__objc_eh_typeinfo_Object, ptr @_ZTIi]
+// CHECK: br i1 %{{.*}}, label %[[CATCH2:.*]], label %[[CATCH_FALLTHROUGH:.*]]
+// CHECK: [[CATCH2]]:
+// CHECK: invoke void @_Z{{[0-9]+}}mayThrowObjCv()
+// CHECK-NEXT: to label %{{.*}} unwind label %[[CATCH_DISPATCH5:.*]]
+// CHECK: [[CATCH_DISPATCH5]]:
+// CHECK-NEXT: [[CATCHSWITCH1:%.*]] = catchswitch within [[CATCHPAD]] [label 
%[[CATCH_START6:.*]]] unwind label %[[EHCLEANUP:.*]]
+// CHECK: [[CATCH_START6]]:
+// CHECK-NEXT: [[CATCHPAD1:%.*]] = catchpad within [[CATCHSWITCH1]] [ptr 
@__objc_eh_typeinfo_Object]
+// CHECK: br i1 %{{.*}}, label %[[CATCH9:.*]], label %[[RETHROW8:.*]]
+// CHECK: [[RETHROW8]]:
+// CHECK: invoke void @llvm.wasm.rethrow(){{.*}}[ "funclet"(token 
[[CATCHPAD1]]) ]
+// CHECK: [[CATCH_FALLTHROUGH]]:
+// CHECK: br i1 %{{.*}}, label %[[CATCH:.*]], label %[[RETHROW:.*]]
+// CHECK: [[CATCH]]:
+// CHECK: catchret from [[CATCHPAD]] to label %{{.*}}
+// CHECK: [[RETHROW]]:
+// CHECK: invoke void @llvm.wasm.rethrow(){{.*}}[ "funclet"(token 
[[CATCHPAD]]) ]
+// CHECK-NEXT: to label %{{.*}} unwind label %[[CATCH_DISPATCH1]]
+// CHECK: [[CATCH_DISPATCH1]]:
+// CHECK-NEXT: [[CATCHSWITCH2:%.*]] = catchswitch within none [label 
%[[CATCH_START15:.*]]] unwind to caller
+// CHECK: [[CATCH_START15]]:
+// CHECK-NEXT: [[CATCHPAD2:%.*]] = catchpad within [[CATCHSWITCH2]] [ptr null]
+// CHECK: br label %[[CATCH_ALL:.*]]
+// CHECK: [[CATCH9]]:
+// CHECK: catchret from [[CATCHPAD1]] to label %{{.*}}
+// CHECK: [[EHCLEANUP]]:
+// CHECK: [[CLEANUPPAD:%.*]] = cleanuppad within [[CATCHPAD]] []
+// CHECK: cleanupret from [[CLEANUPPAD]] unwind label %[[CATCH_DISPATCH1]]
+// CHECK: [[CATCH_ALL]]:
+// CHECK: catchret from [[CATCHPAD2]] to label %{{.*}}

From 1654a644170e582df07311e9650e1275c47409bb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hendrik=20H=C3=BCbner?= <[email protected]>
Date: Tue, 11 Aug 2026 15:29:20 +0200
Subject: [PATCH 3/3] Simplify test assertions

---
 clang/test/CodeGenObjCXX/wasm32-eh-objcxx.mm | 58 ++++----------------
 1 file changed, 11 insertions(+), 47 deletions(-)

diff --git a/clang/test/CodeGenObjCXX/wasm32-eh-objcxx.mm 
b/clang/test/CodeGenObjCXX/wasm32-eh-objcxx.mm
index 2c4ef5dfaf68d..4b989e6122fdd 100644
--- a/clang/test/CodeGenObjCXX/wasm32-eh-objcxx.mm
+++ b/clang/test/CodeGenObjCXX/wasm32-eh-objcxx.mm
@@ -19,24 +19,13 @@ int cxxDestructorsAroundCatch() {
 
 // CHECK-LABEL: define{{.*}} @_Z25cxxDestructorsAroundCatchv
 // CHECK: invoke void @_Z{{[0-9]+}}mayThrowCXXv()
-// CHECK-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[EHCLEANUP:.*]]
-// CHECK: [[INVOKE_CONT]]:
-// CHECK: invoke{{.*}} @_ZN18ThrowingDestructorD1Ev
-// CHECK-NEXT: to label %{{.*}} unwind label %[[CATCH_DISPATCH:.*]]
-// CHECK: [[EHCLEANUP]]:
 // CHECK: [[CLEANUPPAD:%.*]] = cleanuppad within none []
 // CHECK: invoke{{.*}} @_ZN18ThrowingDestructorD1Ev{{.*}}[ "funclet"(token 
[[CLEANUPPAD]]) ]
-// CHECK: cleanupret from [[CLEANUPPAD]] unwind label %[[CATCH_DISPATCH]]
-// CHECK: [[CATCH_DISPATCH]]:
-// CHECK-NEXT: [[CATCHSWITCH:%.*]] = catchswitch within none [label 
%[[CATCH_START:.*]]] unwind to caller
-// CHECK: [[CATCH_START]]:
-// CHECK-NEXT: [[CATCHPAD:%.*]] = catchpad within [[CATCHSWITCH]] [ptr null]
-// CHECK: br label %[[CATCH_ALL:.*]]
-// CHECK: [[CATCH_ALL]]:
+// CHECK: cleanupret from [[CLEANUPPAD]] unwind label %{{.*}}
+// CHECK: [[CATCHSWITCH:%.*]] = catchswitch within none [label %{{.*}}] unwind 
to caller
+// CHECK: [[CATCHPAD:%.*]] = catchpad within [[CATCHSWITCH]] [ptr null]
 // CHECK: invoke{{.*}} @_ZN18ThrowingDestructorD1Ev{{.*}}[ "funclet"(token 
[[CATCHPAD]]) ]
 // CHECK: catchret from [[CATCHPAD]] to label %{{.*}}
-// CHECK: [[CLEANUPPAD1:%.*]] = cleanuppad within [[CATCHPAD]] []
-// CHECK: cleanupret from [[CLEANUPPAD1]] unwind to caller
 
 __attribute__((objc_root_class)) @interface Object
 @end
@@ -65,38 +54,13 @@ int combinedCxxObjcEH() {
 
 // CHECK-LABEL: define{{.*}} @_Z{{[0-9]+}}combinedCxxObjcEHv
 // CHECK: invoke void @_Z{{[0-9]+}}mayThrowCXXv()
-// CHECK-NEXT: to label %{{.*}} unwind label %[[CATCH_DISPATCH:.*]]
-// CHECK: [[CATCH_DISPATCH]]:
-// CHECK-NEXT: [[CATCHSWITCH:%.*]] = catchswitch within none [label 
%[[CATCH_START:.*]]] unwind label %[[CATCH_DISPATCH1:.*]]
-// CHECK: [[CATCH_START]]:
-// CHECK-NEXT: [[CATCHPAD:%.*]] = catchpad within [[CATCHSWITCH]] [ptr 
@__objc_eh_typeinfo_Object, ptr @_ZTIi]
-// CHECK: br i1 %{{.*}}, label %[[CATCH2:.*]], label %[[CATCH_FALLTHROUGH:.*]]
-// CHECK: [[CATCH2]]:
-// CHECK: invoke void @_Z{{[0-9]+}}mayThrowObjCv()
-// CHECK-NEXT: to label %{{.*}} unwind label %[[CATCH_DISPATCH5:.*]]
-// CHECK: [[CATCH_DISPATCH5]]:
-// CHECK-NEXT: [[CATCHSWITCH1:%.*]] = catchswitch within [[CATCHPAD]] [label 
%[[CATCH_START6:.*]]] unwind label %[[EHCLEANUP:.*]]
-// CHECK: [[CATCH_START6]]:
-// CHECK-NEXT: [[CATCHPAD1:%.*]] = catchpad within [[CATCHSWITCH1]] [ptr 
@__objc_eh_typeinfo_Object]
-// CHECK: br i1 %{{.*}}, label %[[CATCH9:.*]], label %[[RETHROW8:.*]]
-// CHECK: [[RETHROW8]]:
-// CHECK: invoke void @llvm.wasm.rethrow(){{.*}}[ "funclet"(token 
[[CATCHPAD1]]) ]
-// CHECK: [[CATCH_FALLTHROUGH]]:
-// CHECK: br i1 %{{.*}}, label %[[CATCH:.*]], label %[[RETHROW:.*]]
-// CHECK: [[CATCH]]:
+// CHECK: [[CATCHSWITCH:%.*]] = catchswitch within none [label %{{.*}}] unwind 
label %{{.*}}
+// CHECK: [[CATCHPAD:%.*]] = catchpad within [[CATCHSWITCH]] [ptr 
@__objc_eh_typeinfo_Object, ptr @_ZTIi]
+// CHECK: invoke void @_Z{{[0-9]+}}mayThrowObjCv() [ "funclet"(token 
[[CATCHPAD]]) ]
+// CHECK: [[NESTED_SWITCH:%.*]] = catchswitch within [[CATCHPAD]] [label 
%{{.*}}] unwind label %{{.*}}
+// CHECK: [[NESTED_PAD:%.*]] = catchpad within [[NESTED_SWITCH]] [ptr 
@__objc_eh_typeinfo_Object]
+// CHECK: invoke void @llvm.wasm.rethrow(){{.*}}[ "funclet"(token 
[[NESTED_PAD]]) ]
 // CHECK: catchret from [[CATCHPAD]] to label %{{.*}}
-// CHECK: [[RETHROW]]:
 // CHECK: invoke void @llvm.wasm.rethrow(){{.*}}[ "funclet"(token 
[[CATCHPAD]]) ]
-// CHECK-NEXT: to label %{{.*}} unwind label %[[CATCH_DISPATCH1]]
-// CHECK: [[CATCH_DISPATCH1]]:
-// CHECK-NEXT: [[CATCHSWITCH2:%.*]] = catchswitch within none [label 
%[[CATCH_START15:.*]]] unwind to caller
-// CHECK: [[CATCH_START15]]:
-// CHECK-NEXT: [[CATCHPAD2:%.*]] = catchpad within [[CATCHSWITCH2]] [ptr null]
-// CHECK: br label %[[CATCH_ALL:.*]]
-// CHECK: [[CATCH9]]:
-// CHECK: catchret from [[CATCHPAD1]] to label %{{.*}}
-// CHECK: [[EHCLEANUP]]:
-// CHECK: [[CLEANUPPAD:%.*]] = cleanuppad within [[CATCHPAD]] []
-// CHECK: cleanupret from [[CLEANUPPAD]] unwind label %[[CATCH_DISPATCH1]]
-// CHECK: [[CATCH_ALL]]:
-// CHECK: catchret from [[CATCHPAD2]] to label %{{.*}}
+// CHECK: [[OUTER_SWITCH:%.*]] = catchswitch within none [label %{{.*}}] 
unwind to caller
+// CHECK: [[OUTER_PAD:%.*]] = catchpad within [[OUTER_SWITCH]] [ptr null]

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

Reply via email to