[clang] [CodeGen] Implement Objective-C WebAssembly exception handling support (PR #215562)
Hendrik Hübner via cfe-commits
cfe-commits at lists.llvm.org
Tue Aug 11 06:39:26 PDT 2026
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?= <hhuebner at MacBookPro.lan>
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 <service at hugomelder.com>
---
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?= <hhuebner at hhuebnerMacBookPro.local>
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
+ at end
+
+ at interface ExceptionA : Object
+ at end
+
+ at interface ExceptionB : Object
+ at 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
+ at 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?= <hhuebner at hhuebnerMacBookPro.local>
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]
More information about the cfe-commits
mailing list