[clang] 6507258 - [CodeGen] Implement Objective-C WebAssembly exception handling support (#215562)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Aug 25 05:32:51 PDT 2026
Author: Hendrik Hübner
Date: 2026-08-25T12:32:46Z
New Revision: 650725891ee2d9366afe2de81776d8f2374237d7
URL: https://github.com/llvm/llvm-project/commit/650725891ee2d9366afe2de81776d8f2374237d7
DIFF: https://github.com/llvm/llvm-project/commit/650725891ee2d9366afe2de81776d8f2374237d7.diff
LOG: [CodeGen] Implement Objective-C WebAssembly exception handling support (#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.
Added:
clang/test/CodeGenObjC/wasm32-eh-arc.m
clang/test/CodeGenObjC/wasm32-eh.m
clang/test/CodeGenObjCXX/wasm32-eh-objcxx.mm
Modified:
clang/lib/CodeGen/CGException.cpp
clang/lib/CodeGen/CGObjCGNU.cpp
clang/lib/CodeGen/CGObjCRuntime.cpp
clang/lib/CodeGen/CodeGenFunction.h
clang/lib/Driver/ToolChains/Clang.cpp
Removed:
################################################################################
diff --git a/clang/lib/CodeGen/CGException.cpp b/clang/lib/CodeGen/CGException.cpp
index b0fb3b4d85d15..bc09fe767de45 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,7 +163,7 @@ static const EHPersonality &getObjCPersonality(const TargetInfo &Target,
case ObjCRuntime::GNUstep:
if (T.isOSCygMing())
return EHPersonality::GNU_CPlusPlus_SEH;
- else if (L.ObjCRuntime.getVersion() >= VersionTuple(1, 7))
+ if (L.ObjCRuntime.getVersion() >= VersionTuple(1, 7))
return EHPersonality::GNUstep_ObjC;
[[fallthrough]];
case ObjCRuntime::GCC:
@@ -200,8 +202,11 @@ 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;
+ if (Triple.isWasm())
+ return EHPersonality::GNU_Wasm_CPlusPlus;
switch (L.ObjCRuntime.getKind()) {
// In the fragile ABI, just use C++ exception handling and hope
@@ -218,8 +223,9 @@ 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.isOSCygMing())
+ return EHPersonality::GNU_CPlusPlus_SEH;
+ 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.
@@ -1214,6 +1220,23 @@ void CodeGenFunction::popCatchScope() {
EHStack.popCatch();
}
+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) {
unsigned NumHandlers = S.getNumHandlers();
EHCatchScope &CatchScope = cast<EHCatchScope>(*EHStack.begin());
@@ -1320,24 +1343,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);
@@ -1406,9 +1413,10 @@ namespace {
CGF.EmitBlock(RethrowBB);
if (SavedExnVar) {
- CGF.EmitRuntimeCallOrInvoke(RethrowFn,
- CGF.Builder.CreateAlignedLoad(CGF.Int8PtrTy, SavedExnVar,
- CGF.getPointerAlign()));
+ CGF.EmitRuntimeCallOrInvoke(RethrowFn, CGF.Builder.CreateAlignedLoad(
+ CGF.Int8PtrTy, SavedExnVar,
+ CGF.getPointerAlign()));
+
} else {
CGF.EmitRuntimeCallOrInvoke(RethrowFn);
}
diff --git a/clang/lib/CodeGen/CGObjCGNU.cpp b/clang/lib/CodeGen/CGObjCGNU.cpp
index e2fc2eca7523a..43e4c02411d15 100644
--- a/clang/lib/CodeGen/CGObjCGNU.cpp
+++ b/clang/lib/CodeGen/CGObjCGNU.cpp
@@ -2374,12 +2374,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>(
@@ -4165,8 +4166,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(Builder.getVoidTy(), ArgTypes, false);
llvm::Function *RegisterAlias = llvm::Function::Create(
RegisterAliasTy,
llvm::GlobalValue::ExternalWeakLinkage, "class_registerAlias_np",
@@ -4348,15 +4348,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 a83a4ce67a9c6..099622f690678 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,16 +151,36 @@ 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) {
+ CGF.ErrorUnsupported(Finally,
+ "@finally is not implemented for WebAssembly");
+ } else if (IsMSVC) {
+ CodeGenFunction HelperCGF(CGM, /*suppressNewContext=*/true);
+ if (!CGF.CurSEHParent)
+ CGF.CurSEHParent = cast<NamedDecl>(CGF.CurFuncDecl);
+ const Stmt *FinallyBlock = Finally->getFinallyBody();
+ HelperCGF.startOutlinedSEHHelper(CGF, /*isFilter*/ false, FinallyBlock);
+ HelperCGF.EmitStmt(FinallyBlock);
+ HelperCGF.FinishFunction(FinallyBlock->getEndLoc());
+
+ llvm::Function *FinallyFunc = HelperCGF.CurFn;
+ CGF.pushSEHCleanup(NormalAndEHCleanup, FinallyFunc);
+ }
+ }
SmallVector<CatchHandler, 8> Handlers;
-
// Enter the catch, if there is one.
if (S.getNumCatchStmts()) {
for (const ObjCAtCatchStmt *CatchStmt : S.catch_stmts()) {
@@ -182,61 +205,68 @@ 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 (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);
-
- HelperCGF.FinishFunction(FinallyBlock->getEndLoc());
-
- llvm::Function *FinallyFunc = HelperCGF.CurFn;
-
-
- // Push a cleanup for __finally blocks.
- CGF.pushSEHCleanup(NormalAndEHCleanup, FinallyFunc);
- }
-
-
// Emit the try body.
CGF.EmitStmt(S.getTryBody());
// Leave the try.
- if (S.getNumCatchStmts())
+ llvm::BasicBlock *DispatchBlock = nullptr;
+ if (S.getNumCatchStmts()) {
+ EHCatchScope &CatchScope = cast<EHCatchScope>(*CGF.EHStack.begin());
+ if (CatchScope.hasEHBranches())
+ DispatchBlock = CatchScope.getCachedEHDispatchBlock();
CGF.popCatchScope();
+ }
+
+ // 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)) {
+ if ((CPI = dyn_cast_or_null<llvm::CatchPadInst>(CPICandidate))) {
CGF.CurrentFuncletPad = CPI;
CPI->setOperand(2, CGF.getExceptionSlot().emitRawPointer(CGF));
- CGF.EHStack.pushCleanup<CatchRetScope>(NormalCleanup, CPI);
}
}
}
+ if (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 +292,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 +304,10 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
CGF.EmitBranchThroughCleanup(Cont);
}
+ if (IsWasm && !HasCatchAll && WasmCatchStartBlock) {
+ 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 dfb6f2ff65a7d..7bdc79d86ea0a 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -1329,6 +1329,14 @@ class CodeGenFunction : public CodeGenTypeCache {
/// themselves).
void 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);
llvm::BasicBlock *
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index f3aeed61a064b..b081265412752 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -8689,7 +8689,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/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..4b989e6122fdd
--- /dev/null
+++ b/clang/test/CodeGenObjCXX/wasm32-eh-objcxx.mm
@@ -0,0 +1,66 @@
+// 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: [[CLEANUPPAD:%.*]] = cleanuppad within none []
+// CHECK: invoke{{.*}} @_ZN18ThrowingDestructorD1Ev{{.*}}[ "funclet"(token [[CLEANUPPAD]]) ]
+// 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 %{{.*}}
+
+__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: [[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: invoke void @llvm.wasm.rethrow(){{.*}}[ "funclet"(token [[CATCHPAD]]) ]
+// 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