[clang] 2c0a22d - [CodeGen][Objective-C] Add WebAssembly `@finally` support (#218967)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Oct 18 02:44:45 PDT 2026
Author: Hendrik Hübner
Date: 2026-10-18T11:44:40+02:00
New Revision: 2c0a22d6b4331131fe58dd77456654189876a7eb
URL: https://github.com/llvm/llvm-project/commit/2c0a22d6b4331131fe58dd77456654189876a7eb
DIFF: https://github.com/llvm/llvm-project/commit/2c0a22d6b4331131fe58dd77456654189876a7eb.diff
LOG: [CodeGen][Objective-C] Add WebAssembly `@finally` support (#218967)
This PR adds `@finally` support for WebAssembly codegen.
We reuse the existing `CodeGenFunction::FinallyInfo` infrastructure,
which essentially creates a catch-all handler around the try/catch. This
needs a slight adaptation for fit WebAssembly's exception model, where
the catch handlers are merged into a single catchpad.
Additionally, we now emit the rethrow function using the
`EmitNoreturnRuntimeCallOrInvoke` which avoids creating an unnecessary
continuation block here.
Co-authored-by: Hendrik Huebner <hhuebner at Hendriks-MacBook-Pro.local>
Added:
clang/test/CodeGenObjC/gnustep2-wasm32-finally.m
Modified:
clang/lib/CodeGen/CGException.cpp
clang/lib/CodeGen/CGObjCRuntime.cpp
clang/test/CodeGenObjCXX/wasm32-eh-objcxx.mm
Removed:
################################################################################
diff --git a/clang/lib/CodeGen/CGException.cpp b/clang/lib/CodeGen/CGException.cpp
index 0a1edfa69b2f4f..781a8079488a2b 100644
--- a/clang/lib/CodeGen/CGException.cpp
+++ b/clang/lib/CodeGen/CGException.cpp
@@ -1266,14 +1266,13 @@ namespace {
CGF.EmitBlock(RethrowBB);
if (SavedExnVar) {
- CGF.EmitRuntimeCallOrInvoke(RethrowFn, CGF.Builder.CreateAlignedLoad(
- CGF.Int8PtrTy, SavedExnVar,
- CGF.getPointerAlign()));
-
+ CGF.EmitNoreturnRuntimeCallOrInvoke(
+ RethrowFn,
+ {CGF.Builder.CreateAlignedLoad(CGF.Int8PtrTy, SavedExnVar,
+ CGF.getPointerAlign())});
} else {
- CGF.EmitRuntimeCallOrInvoke(RethrowFn);
+ CGF.EmitNoreturnRuntimeCallOrInvoke(RethrowFn, {});
}
- CGF.Builder.CreateUnreachable();
CGF.EmitBlock(ContBB);
@@ -1359,15 +1358,34 @@ void CodeGenFunction::FinallyInfo::exit(CodeGenFunction &CGF) {
EHCatchScope &catchScope = cast<EHCatchScope>(*CGF.EHStack.begin());
llvm::BasicBlock *catchBB = catchScope.getHandler(0).Block;
+ llvm::BasicBlock *DispatchBlock = nullptr;
+ if (catchScope.hasEHBranches())
+ DispatchBlock = catchScope.getCachedEHDispatchBlock();
CGF.popCatchScope();
+ llvm::CatchPadInst *CPI = nullptr;
+
// If there are any references to the catch-all block, emit it.
if (catchBB->use_empty()) {
delete catchBB;
} else {
+ SaveAndRestore RestoreCurrentFuncletPad(CGF.CurrentFuncletPad);
+ if (getEHPersonality(CGF).isWasmPersonality() && DispatchBlock) {
+ auto *CatchSwitch =
+ cast<llvm::CatchSwitchInst>(DispatchBlock->getFirstNonPHIIt());
+ llvm::BasicBlock *CatchStartBlock = CatchSwitch->hasUnwindDest()
+ ? CatchSwitch->getSuccessor(1)
+ : CatchSwitch->getSuccessor(0);
+ CPI = cast<llvm::CatchPadInst>(CatchStartBlock->getFirstNonPHIIt());
+ CGF.CurrentFuncletPad = CPI;
+ }
+
CGBuilderTy::InsertPoint savedIP = CGF.Builder.saveAndClearIP();
CGF.EmitBlock(catchBB);
+ if (CPI)
+ CGF.EHStack.pushCleanup<CatchRetScope>(NormalCleanup, CPI);
+
llvm::Value *exn = nullptr;
// If there's a begin-catch function, call it.
@@ -1388,6 +1406,11 @@ void CodeGenFunction::FinallyInfo::exit(CodeGenFunction &CGF) {
// Thread a jump through the finally cleanup.
CGF.EmitBranchThroughCleanup(RethrowDest);
+ // The catchret must be emitted while the catchpad is active. The branch
+ // through the finally cleanup is then resolved after leaving the catchpad.
+ if (CPI)
+ CGF.PopCleanupBlock();
+
CGF.Builder.restoreIP(savedIP);
}
diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp b/clang/lib/CodeGen/CGObjCRuntime.cpp
index 3a009a1b248d28..a5e826bd046fa5 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.cpp
+++ b/clang/lib/CodeGen/CGObjCRuntime.cpp
@@ -156,16 +156,13 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
CodeGenFunction::FinallyInfo FinallyInfo;
if (const ObjCAtFinallyStmt *Finally = S.getFinallyStmt()) {
- if (!useFunclets) {
+ if (!IsMSVC) {
// 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) {
+ } else {
CodeGenFunction HelperCGF(CGM, /*suppressNewContext=*/true);
if (!CGF.CurSEHParent)
CGF.CurSEHParent = cast<NamedDecl>(CGF.CurFuncDecl);
@@ -179,7 +176,7 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
}
}
- if (useFunclets)
+ if (useFunclets && IsMSVC)
if (const ObjCAtFinallyStmt *Finally = S.getFinallyStmt()) {
CodeGenFunction HelperCGF(CGM, /*suppressNewContext=*/true);
if (!CGF.CurSEHParent)
@@ -244,96 +241,98 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
}
// 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. 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);
+ {
+ 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;
+ }
- CodeGenFunction::LexicalScope Cleanups(CGF, Handler.Body->getSourceRange());
- SaveAndRestore RevertAfterScope(CGF.CurrentFuncletPad);
- if (IsMSVC) {
- llvm::BasicBlock::iterator CPICandidate =
- Handler.Block->getFirstNonPHIIt();
- if (CPICandidate != Handler.Block->end()) {
- if ((CPI = dyn_cast_or_null<llvm::CatchPadInst>(CPICandidate))) {
- CGF.CurrentFuncletPad = CPI;
- CPI->setOperand(2, CGF.getExceptionSlot().emitRawPointer(CGF));
+ // Remember where we were.
+ CGBuilderTy::InsertPoint SavedIP = CGF.Builder.saveAndClearIP();
+
+ // 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 (IsMSVC) {
+ llvm::BasicBlock::iterator CPICandidate =
+ Handler.Block->getFirstNonPHIIt();
+ if (CPICandidate != Handler.Block->end()) {
+ if ((CPI = dyn_cast_or_null<llvm::CatchPadInst>(CPICandidate))) {
+ CGF.CurrentFuncletPad = CPI;
+ CPI->setOperand(2, CGF.getExceptionSlot().emitRawPointer(CGF));
+ }
}
}
- }
- if (CPI) {
- // A catchpad requires a matching catchret instruction. We emit this in
- // form of a cleanup.
- 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();
+ llvm::Value *RawExn = CGF.getExceptionFromSlot();
- // Enter the catch.
- llvm::Value *Exn = RawExn;
- if (beginCatchFn)
- Exn = CGF.EmitNounwindRuntimeCall(beginCatchFn, RawExn, "exn.adjusted");
+ // Enter the catch.
+ llvm::Value *Exn = RawExn;
+ if (beginCatchFn)
+ Exn = CGF.EmitNounwindRuntimeCall(beginCatchFn, RawExn, "exn.adjusted");
- if (endCatchFn) {
- // Add a cleanup to leave the catch.
- bool EndCatchMightThrow = (Handler.Variable == nullptr);
+ if (endCatchFn) {
+ // Add a cleanup to leave the catch.
+ bool EndCatchMightThrow = (Handler.Variable == nullptr);
- CGF.EHStack.pushCleanup<CallObjCEndCatch>(NormalAndEHCleanup,
- EndCatchMightThrow,
- endCatchFn);
- }
+ CGF.EHStack.pushCleanup<CallObjCEndCatch>(
+ NormalAndEHCleanup, EndCatchMightThrow, endCatchFn);
+ }
- // Bind the catch parameter if it exists.
- if (const VarDecl *CatchParam = Handler.Variable) {
- llvm::Type *CatchType = CGF.ConvertType(CatchParam->getType());
- llvm::Value *CastExn = CGF.Builder.CreateBitCast(Exn, CatchType);
+ // Bind the catch parameter if it exists.
+ if (const VarDecl *CatchParam = Handler.Variable) {
+ llvm::Type *CatchType = CGF.ConvertType(CatchParam->getType());
+ llvm::Value *CastExn = CGF.Builder.CreateBitCast(Exn, CatchType);
- CGF.EmitAutoVarDecl(*CatchParam);
- EmitInitOfCatchParam(CGF, CastExn, CatchParam);
- }
+ CGF.EmitAutoVarDecl(*CatchParam);
+ 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();
+ // 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();
- // Leave any cleanups associated with the catch.
- Cleanups.ForceCleanup();
+ // Leave any cleanups associated with the catch.
+ Cleanups.ForceCleanup();
- CGF.EmitBranchThroughCleanup(Cont);
- }
+ CGF.EmitBranchThroughCleanup(Cont);
+ }
- if (IsWasm && !HasCatchAll && WasmCatchStartBlock) {
- CGF.WasmEmitFallthroughRethrow(WasmCatchStartBlock);
- }
+ if (IsWasm && !HasCatchAll && WasmCatchStartBlock) {
+ CGF.WasmEmitFallthroughRethrow(WasmCatchStartBlock);
+ }
- // Go back to the try-statement fallthrough.
- CGF.Builder.restoreIP(SavedIP);
+ // Go back to the try-statement fallthrough.
+ CGF.Builder.restoreIP(SavedIP);
+ }
// Pop out of the finally.
if (S.getFinallyStmt()) {
- if (useFunclets) {
+ if (IsMSVC) {
CGF.PopCleanupBlock();
} else {
FinallyInfo.exit(CGF);
diff --git a/clang/test/CodeGenObjC/gnustep2-wasm32-finally.m b/clang/test/CodeGenObjC/gnustep2-wasm32-finally.m
new file mode 100644
index 00000000000000..d7dbc758a3e0e2
--- /dev/null
+++ b/clang/test/CodeGenObjC/gnustep2-wasm32-finally.m
@@ -0,0 +1,294 @@
+// REQUIRES: webassembly-registered-target
+// RUN: %clang -fno-discard-value-names -target wasm32-unknown-emscripten -fobjc-runtime=gnustep-2.2 -fexceptions -fobjc-exceptions -fwasm-exceptions -emit-llvm -S -o - %s | FileCheck %s
+
+__attribute__((objc_root_class)) @interface Object @end
+extern void mayThrowObjC();
+
+void emptyFinally(void) {
+ @try {
+ mayThrowObjC();
+ } @finally {
+ }
+}
+
+// CHECK-LABEL: define{{.*}} @emptyFinally
+// CHECK: catch.dispatch:
+// CHECK-NEXT: [[EMPTY_SWITCH:%.*]] = catchswitch within none [label %catch.start] unwind to caller
+// CHECK: catch.start:
+// CHECK-NEXT: [[EMPTY_PAD:%.*]] = catchpad within [[EMPTY_SWITCH]] [ptr null]
+// CHECK: br label %finally.catchall
+// CHECK: {{^}}cleanup:
+// CHECK: %finally.shouldthrow = load i1, ptr %finally.for-eh
+// CHECK-NEXT: br i1 %finally.shouldthrow, label %finally.rethrow, label %finally.cont
+// CHECK: finally.rethrow:
+// CHECK-NEXT: invoke void @__cxa_rethrow()
+// CHECK: finally.cont:
+// CHECK: finally.catchall:
+// CHECK-NEXT: %exn = load ptr, ptr %exn.slot
+// CHECK-NEXT: %{{.*}} = call ptr @__cxa_begin_catch(ptr %exn)
+// CHECK-NEXT: store i1 true, ptr %finally.for-eh
+// CHECK-NEXT: store i32 2, ptr %cleanup.dest.slot
+// CHECK-NEXT: catchret from [[EMPTY_PAD]] to label %{{.*}}
+// CHECK: ehcleanup:
+// CHECK-NEXT: [[EMPTY_CLEANUP:%.*]] = cleanuppad within none []
+// CHECK-NEXT: %finally.endcatch = load i1, ptr %finally.for-eh
+// CHECK-NEXT: br i1 %finally.endcatch, label %{{.*}}, label %finally.cleanup.cont
+// CHECK: finally.cleanup.cont:
+// CHECK-NEXT: cleanupret from [[EMPTY_CLEANUP]] unwind to caller
+
+int finallySimple(Object *object) {
+ int value = 0;
+ @try {
+ mayThrowObjC();
+ value = 1;
+ } @catch (...) {
+ value = 2;
+ } @finally {
+ value += object != (Object *)0;
+ }
+ return value;
+}
+
+// CHECK-LABEL: define{{.*}} @finallySimple
+// CHECK: invoke void @mayThrowObjC()
+// CHECK-NEXT: to label %invoke.cont unwind label %catch.dispatch
+// CHECK: catch.dispatch:
+// CHECK-NEXT: [[SIMPLE_SWITCH:%.*]] = catchswitch within none [label %catch.start] unwind label %catch.dispatch2
+// CHECK: catch.start:
+// CHECK-NEXT: [[SIMPLE_PAD:%.*]] = catchpad within [[SIMPLE_SWITCH]] [ptr null]
+// CHECK: br label %catch
+// CHECK: invoke.cont:
+// CHECK-NEXT: store i32 1, ptr %value
+// CHECK-NEXT: store i32 0, ptr %cleanup.dest.slot
+// CHECK-NEXT: br label %cleanup
+// CHECK: cleanup:
+// CHECK: %add = add nsw i32
+// CHECK-NEXT: store i32 %add, ptr %value
+// CHECK-NEXT: %finally.shouldthrow = load i1, ptr %finally.for-eh
+// CHECK-NEXT: br i1 %finally.shouldthrow, label %finally.rethrow, label %finally.cont
+// CHECK: finally.rethrow:
+// CHECK-NEXT: invoke void @__cxa_rethrow()
+// CHECK-NEXT: to label %unreachable unwind label %ehcleanup
+// CHECK: finally.cont:
+// CHECK-NEXT: store i32 %cleanup.dest.saved, ptr %cleanup.dest.slot
+// CHECK-NEXT: %cleanup.dest = load i32, ptr %cleanup.dest.slot
+// CHECK-NEXT: switch i32 %cleanup.dest, label %unreachable [
+// CHECK-NEXT: i32 0, label %cleanup.cont
+// CHECK-NEXT: i32 2, label %eh.cont
+// CHECK-NEXT: i32 3, label %unreachable
+// CHECK-NEXT: ]
+// CHECK: cleanup.cont:
+// CHECK-NEXT: br label %eh.cont
+// CHECK: eh.cont:
+// CHECK-NEXT: %{{.*}} = load i32, ptr %value
+// CHECK-NEXT: ret i32 %{{.*}}
+// CHECK: catch:
+// CHECK-NEXT: %exn = load ptr, ptr %exn.slot
+// CHECK-NEXT: %exn.adjusted = call ptr @__cxa_begin_catch(ptr %exn)
+// CHECK-NEXT: store i32 2, ptr %value
+// CHECK-NEXT: invoke void @__cxa_end_catch()
+// CHECK-NEXT: to label %invoke.cont1 unwind label %catch.dispatch2
+// CHECK: catch.dispatch2:
+// CHECK-NEXT: [[SIMPLE_FINALLY_SWITCH:%.*]] = catchswitch within none [label %catch.start3] unwind to caller
+// CHECK: catch.start3:
+// CHECK-NEXT: [[SIMPLE_FINALLY_PAD:%.*]] = catchpad within [[SIMPLE_FINALLY_SWITCH]] [ptr null]
+// CHECK: br label %finally.catchall
+// CHECK: invoke.cont1:
+// CHECK-NEXT: catchret from [[SIMPLE_PAD]] to label %catchret.dest
+// CHECK: catchret.dest:
+// CHECK-NEXT: store i32 2, ptr %cleanup.dest.slot
+// CHECK-NEXT: br label %cleanup
+// CHECK: finally.catchall:
+// CHECK-NEXT: %exn4 = load ptr, ptr %exn.slot
+// CHECK-NEXT: %{{.*}} = call ptr @__cxa_begin_catch(ptr %exn4)
+// CHECK-NEXT: store i1 true, ptr %finally.for-eh
+// CHECK-NEXT: store i32 3, ptr %cleanup.dest.slot
+// CHECK-NEXT: catchret from [[SIMPLE_FINALLY_PAD]] to label %catchret.dest5
+// CHECK: catchret.dest5:
+// CHECK-NEXT: br label %cleanup
+// CHECK: ehcleanup:
+// CHECK-NEXT: [[SIMPLE_CLEANUP:%.*]] = cleanuppad within none []
+// CHECK-NEXT: %finally.endcatch = load i1, ptr %finally.for-eh
+// CHECK-NEXT: br i1 %finally.endcatch, label %finally.endcatch6, label %finally.cleanup.cont
+// CHECK: finally.endcatch6:
+// CHECK-NEXT: invoke void @__cxa_end_catch()
+// CHECK-NEXT: to label %invoke.cont7 unwind label %terminate
+// CHECK: invoke.cont7:
+// CHECK-NEXT: br label %finally.cleanup.cont
+// CHECK: finally.cleanup.cont:
+// CHECK-NEXT: cleanupret from [[SIMPLE_CLEANUP]] unwind to caller
+
+int finallyNoCatch(Object *object) {
+ int value = 0;
+ @try {
+ mayThrowObjC();
+ value = 1;
+ } @finally {
+ value += object != (Object *)0;
+ }
+ return value;
+}
+
+// CHECK-LABEL: define{{.*}} @finallyNoCatch
+// CHECK: catch.dispatch:
+// CHECK-NEXT: [[NO_CATCH_SWITCH:%.*]] = catchswitch within none [label %catch.start] unwind to caller
+// CHECK: catch.start:
+// CHECK-NEXT: [[NO_CATCH_PAD:%.*]] = catchpad within [[NO_CATCH_SWITCH]] [ptr null]
+// CHECK: br label %finally.catchall
+// CHECK: {{^}}cleanup:
+// CHECK: %finally.shouldthrow = load i1, ptr %finally.for-eh
+// CHECK-NEXT: br i1 %finally.shouldthrow, label %finally.rethrow, label %finally.cont
+// CHECK: finally.rethrow:
+// CHECK-NEXT: invoke void @__cxa_rethrow()
+// CHECK: finally.cont:
+// CHECK: finally.catchall:
+// CHECK-NEXT: %exn = load ptr, ptr %exn.slot
+// CHECK-NEXT: %{{.*}} = call ptr @__cxa_begin_catch(ptr %exn)
+// CHECK-NEXT: store i1 true, ptr %finally.for-eh
+// CHECK-NEXT: store i32 2, ptr %cleanup.dest.slot
+// CHECK-NEXT: catchret from [[NO_CATCH_PAD]] to label %{{.*}}
+// CHECK: ehcleanup:
+// CHECK-NEXT: [[NO_CATCH_CLEANUP:%.*]] = cleanuppad within none []
+// CHECK-NEXT: %finally.endcatch = load i1, ptr %finally.for-eh
+// CHECK-NEXT: br i1 %finally.endcatch, label %{{.*}}, label %finally.cleanup.cont
+// CHECK: finally.cleanup.cont:
+// CHECK-NEXT: cleanupret from [[NO_CATCH_CLEANUP]] unwind to caller
+
+int throwInCatchFinally(Object *object) {
+ @try {
+ mayThrowObjC();
+ } @catch (...) {
+ @throw;
+ } @finally {
+ (void)object;
+ }
+}
+
+// CHECK-LABEL: define{{.*}} @throwInCatchFinally
+// CHECK: {{^}}cleanup:
+// CHECK: %finally.shouldthrow = load i1, ptr %finally.for-eh
+// CHECK-NEXT: br i1 %finally.shouldthrow, label %finally.rethrow, label %finally.cont
+// CHECK: finally.rethrow:
+// CHECK-NEXT: invoke void @__cxa_rethrow()
+// CHECK: finally.cont:
+// CHECK: catch:
+// CHECK: invoke void @__cxa_rethrow()
+// CHECK-NEXT: to label %unreachable unwind label %ehcleanup
+// CHECK: catch.dispatch{{[0-9]+}}:
+// CHECK-NEXT: [[CATCH_FINALLY_SWITCH:%.*]] = catchswitch within none [label %catch.start{{[0-9]+}}] unwind to caller
+// CHECK: catch.start{{[0-9]+}}:
+// CHECK: [[CATCH_FINALLY_PAD:%.*]] = catchpad within [[CATCH_FINALLY_SWITCH]] [ptr null]
+// CHECK: br label %finally.catchall
+// CHECK: finally.catchall:
+// CHECK: %{{.*}} = call ptr @__cxa_begin_catch(ptr %{{.*}})
+// CHECK-NEXT: store i1 true, ptr %finally.for-eh
+// CHECK-NEXT: store i32 3, ptr %cleanup.dest.slot
+// CHECK-NEXT: catchret from [[CATCH_FINALLY_PAD]] to label %{{.*}}
+// CHECK: ehcleanup{{[0-9]+}}:
+// CHECK-NEXT: [[CATCH_FINALLY_CLEANUP:%.*]] = cleanuppad within none []
+// CHECK: finally.cleanup.cont:
+// CHECK-NEXT: cleanupret from [[CATCH_FINALLY_CLEANUP]] unwind to caller
+
+int throwInFinally(Object *object) {
+ @try {
+ mayThrowObjC();
+ } @finally {
+ @throw object;
+ }
+}
+
+// CHECK-LABEL: define{{.*}} @throwInFinally
+// CHECK: cleanup:
+// CHECK: invoke void @objc_exception_throw(ptr %{{.*}})
+// CHECK-NEXT: to label %invoke.cont1 unwind label %ehcleanup
+// CHECK: invoke.cont1:
+// CHECK-NEXT: unreachable
+// CHECK: finally.catchall:
+// CHECK: catchret from %{{.*}} to label %{{.*}}
+// CHECK: ehcleanup:
+// CHECK: [[THROW_CLEANUP:%.*]] = cleanuppad within none []
+// CHECK: finally.cleanup.cont:
+// CHECK-NEXT: cleanupret from [[THROW_CLEANUP]] unwind to caller
+// CHECK-NOT: finally.rethrow:
+
+int throwInFinallyNoException(Object *object) {
+ @try {
+ } @finally {
+ @throw object;
+ }
+}
+
+// CHECK-LABEL: define{{.*}} @throwInFinallyNoException
+// CHECK: entry:
+// CHECK: invoke void @objc_exception_throw(ptr %{{.*}})
+// CHECK-NEXT: to label %invoke.cont unwind label %ehcleanup
+// CHECK: invoke.cont:
+// CHECK-NEXT: unreachable
+// CHECK: ehcleanup:
+// CHECK-NEXT: [[NO_EXCEPTION_CLEANUP:%.*]] = cleanuppad within none []
+// CHECK-NEXT: %finally.endcatch = load i1, ptr %finally.for-eh
+// CHECK-NEXT: br i1 %finally.endcatch, label %{{.*}}, label %finally.cleanup.cont
+// CHECK: finally.cleanup.cont:
+// CHECK-NEXT: cleanupret from [[NO_EXCEPTION_CLEANUP]] unwind to caller
+// CHECK-NOT: catchswitch within none
+// CHECK-NOT: finally.rethrow:
+
+int nestedTryCatchFinally(Object *object) {
+ int value = 0;
+ @try {
+ @try {
+ mayThrowObjC();
+ } @catch (...) {
+ value = 1;
+ } @finally {
+ value += 2;
+ }
+ } @catch (...) {
+ value = 3;
+ } @finally {
+ value += object != (Object *)0;
+ }
+ return value;
+}
+
+// CHECK-LABEL: define{{.*}} @nestedTryCatchFinally
+// CHECK: {{^}}cleanup:
+// CHECK: %finally.shouldthrow = load i1, ptr %finally.for-eh1
+// CHECK-NEXT: br i1 %finally.shouldthrow, label %finally.rethrow, label %finally.cont
+// CHECK: finally.rethrow:
+// CHECK-NEXT: invoke void @__cxa_rethrow()
+// CHECK: {{^}}cleanup{{[0-9]+}}:
+// CHECK: %finally.shouldthrow{{[0-9]+}} = load i1, ptr %finally.for-eh
+// CHECK-NEXT: br i1 %finally.shouldthrow{{[0-9]+}}, label %finally.rethrow{{[0-9]+}}, label %finally.cont{{[0-9]+}}
+// CHECK: finally.rethrow{{[0-9]+}}:
+// CHECK-NEXT: invoke void @__cxa_rethrow()
+// CHECK: finally.catchall:
+// CHECK: catchret from %{{.*}} to label %{{.*}}
+// CHECK: finally.catchall{{[0-9]+}}:
+// CHECK: catchret from %{{.*}} to label %{{.*}}
+
+int gotoOutFinally(Object *object) {
+ int value = 0;
+ @try {
+ value = 1;
+ goto done;
+ } @finally {
+ value += object != (Object *)0;
+ }
+done:
+ return value;
+}
+
+// CHECK-LABEL: define{{.*}} @gotoOutFinally
+// CHECK: entry:
+// CHECK: store i32 1, ptr %value
+// CHECK-NEXT: store i32 3, ptr %cleanup.dest.slot
+// CHECK: %finally.shouldthrow = load i1, ptr %finally.for-eh
+// CHECK-NEXT: br i1 %finally.shouldthrow, label %finally.rethrow, label %finally.cont
+// CHECK: finally.rethrow:
+// CHECK-NEXT: invoke void @__cxa_rethrow()
+// CHECK: finally.cont:
+// CHECK: i32 3, label %done
+// CHECK: ehcleanup:
+// CHECK: finally.cleanup.cont:
+// CHECK-NEXT: cleanupret from %{{.*}} unwind to caller
diff --git a/clang/test/CodeGenObjCXX/wasm32-eh-objcxx.mm b/clang/test/CodeGenObjCXX/wasm32-eh-objcxx.mm
index cfc87f4fe5b547..97859b88c0cf6f 100644
--- a/clang/test/CodeGenObjCXX/wasm32-eh-objcxx.mm
+++ b/clang/test/CodeGenObjCXX/wasm32-eh-objcxx.mm
@@ -33,6 +33,161 @@ int cxxDestructorsAroundCatch() {
extern void mayThrowObjC();
+int cleanupInTryFinally() {
+ @try {
+ ThrowingDestructor object;
+ mayThrowObjC();
+ } @finally {
+ }
+ return 0;
+}
+
+// CHECK-LABEL: define{{.*}} @_Z{{[0-9]+}}cleanupInTryFinallyv
+// CHECK: invoke void @_Z{{[0-9]+}}mayThrowObjCv()
+// CHECK-NEXT: to label %invoke.cont unwind label %ehcleanup
+// CHECK: invoke.cont:
+// CHECK-NEXT: %{{.*}} = invoke noundef ptr @_ZN18ThrowingDestructorD1Ev{{.*}}%object
+// CHECK-NEXT: to label %invoke.cont1 unwind label %catch.dispatch
+// CHECK: invoke.cont1:
+// CHECK-NEXT: store i32 0, ptr %cleanup.dest.slot
+// CHECK-NEXT: br label %cleanup
+// CHECK: cleanup:
+// CHECK-NEXT: %cleanup.dest.saved = load i32, ptr %cleanup.dest.slot
+// CHECK-NEXT: %finally.shouldthrow = load i1, ptr %finally.for-eh
+// CHECK-NEXT: br i1 %finally.shouldthrow, label %finally.rethrow, label %finally.cont
+// CHECK: finally.rethrow:
+// CHECK-NEXT: invoke void @__cxa_rethrow()
+// CHECK-NEXT: to label %unreachable unwind label %ehcleanup4
+// CHECK: finally.cont:
+// CHECK-NEXT: store i32 %cleanup.dest.saved, ptr %cleanup.dest.slot
+// CHECK-NEXT: %cleanup.dest = load i32, ptr %cleanup.dest.slot
+// CHECK-NEXT: switch i32 %cleanup.dest, label %unreachable [
+// CHECK-NEXT: i32 0, label %cleanup.cont
+// CHECK-NEXT: i32 2, label %unreachable
+// CHECK-NEXT: ]
+// CHECK: cleanup.cont:
+// CHECK-NEXT: ret i32 0
+// CHECK: ehcleanup:
+// CHECK-NEXT: [[TRY_CLEANUP:%.*]] = cleanuppad within none []
+// CHECK-NEXT: %{{.*}} = invoke noundef ptr @_ZN18ThrowingDestructorD1Ev{{.*}}[ "funclet"(token [[TRY_CLEANUP]]) ]
+// CHECK-NEXT: to label %invoke.cont2 unwind label %terminate
+// CHECK: invoke.cont2:
+// CHECK-NEXT: cleanupret from [[TRY_CLEANUP]] unwind label %catch.dispatch
+// CHECK: catch.dispatch:
+// CHECK-NEXT: [[TRY_SWITCH:%.*]] = catchswitch within none [label %catch.start] unwind to caller
+// CHECK: catch.start:
+// CHECK-NEXT: [[TRY_PAD:%.*]] = catchpad within [[TRY_SWITCH]] [ptr null]
+// CHECK: br label %finally.catchall
+// CHECK: finally.catchall:
+// CHECK-NEXT: %exn = load ptr, ptr %exn.slot
+// CHECK-NEXT: %{{.*}} = call ptr @__cxa_begin_catch(ptr %exn)
+// CHECK-NEXT: store i1 true, ptr %finally.for-eh
+// CHECK-NEXT: store i32 2, ptr %cleanup.dest.slot
+// CHECK-NEXT: catchret from [[TRY_PAD]] to label %catchret.dest
+// CHECK: catchret.dest:
+// CHECK-NEXT: br label %cleanup
+// CHECK: ehcleanup4:
+// CHECK-NEXT: [[TRY_FINALLY_CLEANUP:%.*]] = cleanuppad within none []
+// CHECK-NEXT: %finally.endcatch = load i1, ptr %finally.for-eh
+// CHECK-NEXT: br i1 %finally.endcatch, label %finally.endcatch5, label %finally.cleanup.cont
+// CHECK: finally.endcatch5:
+// CHECK-NEXT: invoke void @__cxa_end_catch()
+// CHECK-NEXT: to label %invoke.cont6 unwind label %terminate7
+// CHECK: invoke.cont6:
+// CHECK-NEXT: br label %finally.cleanup.cont
+// CHECK: finally.cleanup.cont:
+// CHECK-NEXT: cleanupret from [[TRY_FINALLY_CLEANUP]] unwind to caller
+
+int cleanupInCatchFinally() {
+ @try {
+ mayThrowObjC();
+ } @catch (...) {
+ ThrowingDestructor object;
+ return 1;
+ } @finally {
+ }
+ return 0;
+}
+
+// CHECK-LABEL: define{{.*}} @_Z{{[0-9]+}}cleanupInCatchFinallyv
+// CHECK: invoke void @_Z{{[0-9]+}}mayThrowObjCv()
+// CHECK-NEXT: to label %invoke.cont unwind label %catch.dispatch
+// CHECK: catch.dispatch:
+// CHECK-NEXT: [[CATCH_SWITCH:%.*]] = catchswitch within none [label %catch.start] unwind label %catch.dispatch4
+// CHECK: catch.start:
+// CHECK-NEXT: [[CATCH_PAD:%.*]] = catchpad within [[CATCH_SWITCH]] [ptr null]
+// CHECK: br label %catch
+// CHECK: invoke.cont:
+// CHECK-NEXT: store i32 0, ptr %cleanup.dest.slot
+// CHECK-NEXT: br label %cleanup
+// CHECK: cleanup:
+// CHECK-NEXT: %cleanup.dest.saved = load i32, ptr %cleanup.dest.slot
+// CHECK-NEXT: %finally.shouldthrow = load i1, ptr %finally.for-eh
+// CHECK-NEXT: br i1 %finally.shouldthrow, label %finally.rethrow, label %finally.cont
+// CHECK: finally.rethrow:
+// CHECK-NEXT: invoke void @__cxa_rethrow()
+// CHECK-NEXT: to label %unreachable unwind label %ehcleanup8
+// CHECK: finally.cont:
+// CHECK-NEXT: store i32 %cleanup.dest.saved, ptr %cleanup.dest.slot
+// CHECK-NEXT: %cleanup.dest = load i32, ptr %cleanup.dest.slot
+// CHECK-NEXT: switch i32 %cleanup.dest, label %unreachable [
+// CHECK-NEXT: i32 0, label %cleanup.cont
+// CHECK-NEXT: i32 1, label %return
+// CHECK-NEXT: i32 3, label %unreachable
+// CHECK-NEXT: ]
+// CHECK: cleanup.cont:
+// CHECK-NEXT: br label %eh.cont
+// CHECK: eh.cont:
+// CHECK-NEXT: store i32 0, ptr %retval
+// CHECK-NEXT: br label %return
+// CHECK: catch:
+// CHECK-NEXT: %exn = load ptr, ptr %exn.slot
+// CHECK-NEXT: %exn.adjusted = call ptr @__cxa_begin_catch(ptr %exn)
+// CHECK-NEXT: store i32 1, ptr %retval
+// CHECK-NEXT: store i32 1, ptr %cleanup.dest.slot
+// CHECK-NEXT: %{{.*}} = invoke noundef ptr @_ZN18ThrowingDestructorD1Ev{{.*}}[ "funclet"(token [[CATCH_PAD]]) ]
+// CHECK-NEXT: to label %invoke.cont1 unwind label %ehcleanup
+// CHECK: invoke.cont1:
+// CHECK-NEXT: invoke void @__cxa_end_catch()
+// CHECK-NEXT: to label %invoke.cont2 unwind label %catch.dispatch4
+// CHECK: invoke.cont2:
+// CHECK-NEXT: catchret from [[CATCH_PAD]] to label %catchret.dest
+// CHECK: ehcleanup:
+// CHECK-NEXT: [[CATCH_CLEANUP:%.*]] = cleanuppad within [[CATCH_PAD]] []
+// CHECK-NEXT: invoke void @__cxa_end_catch()
+// CHECK-NEXT: to label %invoke.cont3 unwind label %terminate
+// CHECK: invoke.cont3:
+// CHECK-NEXT: cleanupret from [[CATCH_CLEANUP]] unwind label %catch.dispatch4
+// CHECK: catch.dispatch4:
+// CHECK-NEXT: [[FINALLY_SWITCH:%.*]] = catchswitch within none [label %catch.start5] unwind to caller
+// CHECK: catch.start5:
+// CHECK-NEXT: [[FINALLY_PAD:%.*]] = catchpad within [[FINALLY_SWITCH]] [ptr null]
+// CHECK: br label %finally.catchall
+// CHECK: catchret.dest:
+// CHECK-NEXT: br label %cleanup
+// CHECK: finally.catchall:
+// CHECK-NEXT: %exn6 = load ptr, ptr %exn.slot
+// CHECK-NEXT: %{{.*}} = call ptr @__cxa_begin_catch(ptr %exn6)
+// CHECK-NEXT: store i1 true, ptr %finally.for-eh
+// CHECK-NEXT: store i32 3, ptr %cleanup.dest.slot
+// CHECK-NEXT: catchret from [[FINALLY_PAD]] to label %catchret.dest7
+// CHECK: catchret.dest7:
+// CHECK-NEXT: br label %cleanup
+// CHECK: ehcleanup8:
+// CHECK-NEXT: [[CATCH_FINALLY_CLEANUP:%.*]] = cleanuppad within none []
+// CHECK-NEXT: %finally.endcatch = load i1, ptr %finally.for-eh
+// CHECK-NEXT: br i1 %finally.endcatch, label %finally.endcatch9, label %finally.cleanup.cont
+// CHECK: finally.endcatch9:
+// CHECK-NEXT: invoke void @__cxa_end_catch()
+// CHECK-NEXT: to label %invoke.cont10 unwind label %terminate11
+// CHECK: invoke.cont10:
+// CHECK-NEXT: br label %finally.cleanup.cont
+// CHECK: finally.cleanup.cont:
+// CHECK-NEXT: cleanupret from [[CATCH_FINALLY_CLEANUP]] unwind to caller
+// CHECK: return:
+// CHECK-NEXT: %{{.*}} = load i32, ptr %retval
+// CHECK-NEXT: ret i32 %{{.*}}
+
int combinedCxxObjcEH() {
@try {
try {
More information about the cfe-commits
mailing list