https://github.com/HendrikHuebner updated https://github.com/llvm/llvm-project/pull/218967
From be662fa39fb2553ec3889098db0ff28563d02b99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hendrik=20H=C3=BCbner?= <[email protected]> Date: Wed, 26 Aug 2026 19:44:16 +0200 Subject: [PATCH] [CodeGen][ObjC] Implement @finally for WebAssembly EH --- clang/lib/CodeGen/CGException.cpp | 39 +++- clang/lib/CodeGen/CGObjCRuntime.cpp | 13 +- .../CodeGenObjC/gnustep2-wasm32-finally.m | 204 ++++++++++++++++++ clang/test/CodeGenObjCXX/wasm32-eh-objcxx.mm | 37 ++++ 4 files changed, 279 insertions(+), 14 deletions(-) create mode 100644 clang/test/CodeGenObjC/gnustep2-wasm32-finally.m diff --git a/clang/lib/CodeGen/CGException.cpp b/clang/lib/CodeGen/CGException.cpp index bc09fe767de45..fe6b050ac9100 100644 --- a/clang/lib/CodeGen/CGException.cpp +++ b/clang/lib/CodeGen/CGException.cpp @@ -1403,6 +1403,7 @@ namespace { // If the end of the finally is reachable, check whether this was // for EH. If so, rethrow. + const bool IsWasm = EHPersonality::get(CGF).isWasmPersonality(); if (CGF.HaveInsertPoint()) { llvm::BasicBlock *RethrowBB = CGF.createBasicBlock("finally.rethrow"); llvm::BasicBlock *ContBB = CGF.createBasicBlock("finally.cont"); @@ -1412,15 +1413,15 @@ namespace { CGF.Builder.CreateCondBr(ShouldRethrow, RethrowBB, ContBB); CGF.EmitBlock(RethrowBB); - if (SavedExnVar) { - CGF.EmitRuntimeCallOrInvoke(RethrowFn, CGF.Builder.CreateAlignedLoad( - CGF.Int8PtrTy, SavedExnVar, - CGF.getPointerAlign())); - + if (IsWasm || !SavedExnVar) { + CGF.EmitNoreturnRuntimeCallOrInvoke(RethrowFn, {}); } else { - CGF.EmitRuntimeCallOrInvoke(RethrowFn); + CGF.EmitRuntimeCallOrInvoke( + RethrowFn, + CGF.Builder.CreateAlignedLoad(CGF.Int8PtrTy, SavedExnVar, + CGF.getPointerAlign())); + CGF.Builder.CreateUnreachable(); } - CGF.Builder.CreateUnreachable(); CGF.EmitBlock(ContBB); @@ -1506,15 +1507,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 (EHPersonality::get(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. @@ -1535,6 +1555,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 099622f690678..e539e905a5d00 100644 --- a/clang/lib/CodeGen/CGObjCRuntime.cpp +++ b/clang/lib/CodeGen/CGObjCRuntime.cpp @@ -150,22 +150,18 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF, if (S.getNumCatchStmts()) 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 (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); @@ -224,6 +220,7 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF, } // We save the old funclet pad here before we traverse each catch handler. + llvm::Instruction *SavedFuncletPad = CGF.CurrentFuncletPad; SaveAndRestore RestoreCurrentFuncletPad(CGF.CurrentFuncletPad); llvm::BasicBlock *WasmCatchStartBlock = nullptr; llvm::CatchPadInst *CPI = nullptr; @@ -312,8 +309,10 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF, CGF.Builder.restoreIP(SavedIP); // Pop out of the finally. - if (!useFunclets && S.getFinallyStmt()) + if (!IsMSVC && S.getFinallyStmt()) { + CGF.CurrentFuncletPad = SavedFuncletPad; FinallyInfo.exit(CGF); + } if (Cont.isValid()) CGF.EmitBlock(Cont.getBlock()); diff --git a/clang/test/CodeGenObjC/gnustep2-wasm32-finally.m b/clang/test/CodeGenObjC/gnustep2-wasm32-finally.m new file mode 100644 index 0000000000000..c92945e90ba1f --- /dev/null +++ b/clang/test/CodeGenObjC/gnustep2-wasm32-finally.m @@ -0,0 +1,204 @@ +// REQUIRES: webassembly-registered-target +// RUN: %clang_cc1 -target-feature +exception-handling -triple wasm32-unknown-emscripten -fobjc-runtime=gnustep-2.2 -fexceptions -fobjc-exceptions -exception-model=wasm -mllvm -wasm-enable-eh -emit-llvm -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: invoke void @mayThrowObjC() +// CHECK: catchswitch within none +// CHECK: catchpad within +// CHECK: cleanup: +// CHECK: br i1 {{.*}}, label %finally.rethrow, label %finally.cont +// CHECK: finally.rethrow: +// CHECK: invoke void @__cxa_rethrow() +// CHECK: finally.catchall: +// CHECK: catchret from + +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: catchswitch within none +// CHECK: catchpad within +// CHECK: cleanup: +// CHECK: br i1 {{.*}}, label %finally.rethrow, label %finally.cont +// CHECK: finally.rethrow: +// CHECK: invoke void @__cxa_rethrow() +// CHECK: catchswitch within none +// CHECK: catchpad within +// CHECK: finally.catchall: +// CHECK: catchret from + +int finallyNoCatch(Object *object) { + int value = 0; + @try { + mayThrowObjC(); + value = 1; + } @finally { + value += object != (Object *)0; + } + return value; +} + +// CHECK-LABEL: define{{.*}} @finallyNoCatch +// CHECK: invoke void @mayThrowObjC() +// CHECK: catchswitch within none +// CHECK: catchpad within +// CHECK: cleanup: +// CHECK: br i1 {{.*}}, label %finally.rethrow, label %finally.cont +// CHECK: finally.rethrow: +// CHECK: invoke void @__cxa_rethrow() +// CHECK: finally.catchall: +// CHECK: catchret from + +int throwInTryFinally(Object *object) { + int value = 0; + @try { + @throw (id)0; + } @catch (...) { + value = 1; + } @finally { + value += object != (Object *)0; + } + return value; +} + +// CHECK-LABEL: define{{.*}} @throwInTryFinally +// CHECK: invoke void @objc_exception_throw +// CHECK: catchswitch within none +// CHECK: catchpad within +// CHECK: catchswitch within none +// CHECK: catchpad within +// CHECK: finally.catchall: +// CHECK: catchret from +// CHECK: cleanup: +// CHECK: br i1 {{.*}}, label %finally.rethrow, label %finally.cont + +int throwInCatchFinally(Object *object) { + @try { + mayThrowObjC(); + } @catch (...) { + @throw; + } @finally { + (void)object; + } +} + +// CHECK-LABEL: define{{.*}} @throwInCatchFinally +// CHECK: invoke void @mayThrowObjC() +// CHECK: catchswitch within none +// CHECK: catchpad within +// CHECK: catch: +// CHECK: invoke void @__cxa_rethrow() +// CHECK: catchswitch within none +// CHECK: catchpad within +// CHECK: finally.catchall: + +int throwInFinally(Object *object) { + @try { + mayThrowObjC(); + } @finally { + @throw object; + } +} + +// CHECK-LABEL: define{{.*}} @throwInFinally +// CHECK: invoke void @mayThrowObjC() +// CHECK: catchswitch within none +// CHECK: catchpad within +// CHECK: cleanup: +// CHECK: invoke void @objc_exception_throw(ptr +// CHECK-NEXT: to label %{{.*}} unwind label %ehcleanup + +int throwInFinallyNoException(Object *object) { + @try { + } @finally { + @throw object; + } +} + +// CHECK-LABEL: define{{.*}} @throwInFinallyNoException +// CHECK: invoke void @objc_exception_throw(ptr +// CHECK-NEXT: to label %{{.*}} unwind label %ehcleanup +// CHECK-NOT: catchswitch within none + +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: invoke void @mayThrowObjC() +// CHECK: catchswitch within none +// CHECK: catchswitch within none +// CHECK: finally.catchall: +// CHECK: finally.catchall{{.*}}: + +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: store i32 1, ptr %value +// CHECK: store i32 3, ptr %cleanup.dest.slot +// CHECK: br i1 {{.*}}, label %finally.rethrow, label %finally.cont +// CHECK: ret i32 + +int gotoOutCatchFinally(Object *object) { + int value = 0; + @try { + mayThrowObjC(); + } @catch (...) { + value = 1; + goto done; + } @finally { + value += object != (Object *)0; + } +done: + return value; +} + +// CHECK-LABEL: define{{.*}} @gotoOutCatchFinally +// CHECK: catchswitch within none +// CHECK: catchret from +// CHECK: br label %cleanup diff --git a/clang/test/CodeGenObjCXX/wasm32-eh-objcxx.mm b/clang/test/CodeGenObjCXX/wasm32-eh-objcxx.mm index 3af09f7055b98..e11a27afb547d 100644 --- a/clang/test/CodeGenObjCXX/wasm32-eh-objcxx.mm +++ b/clang/test/CodeGenObjCXX/wasm32-eh-objcxx.mm @@ -33,6 +33,43 @@ 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: [[TRY_CLEANUP:%.*]] = cleanuppad within +// CHECK: invoke{{.*}} @_ZN18ThrowingDestructorD1Ev{{.*}}[ "funclet"(token [[TRY_CLEANUP]]) ] +// CHECK: cleanupret from [[TRY_CLEANUP]] +// CHECK: catchswitch within none +// CHECK: catchret from + +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: [[CATCHPAD:%.*]] = catchpad within +// CHECK: invoke{{.*}} @_ZN18ThrowingDestructorD1Ev{{.*}}[ "funclet"(token [[CATCHPAD]]) ] +// CHECK: catchret from [[CATCHPAD]] +// CHECK: catchswitch within none +// CHECK: catchpad within +// CHECK: finally.catchall: + int combinedCxxObjcEH() { @try { try { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
