https://github.com/HendrikHuebner updated 
https://github.com/llvm/llvm-project/pull/218967

From 5b174f79b2f045f0056613b6d31caf66850cf05b 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             |  42 ++++-
 clang/lib/CodeGen/CGObjCRuntime.cpp           |  10 +-
 .../CodeGenObjC/gnustep2-wasm32-finally.m     | 177 ++++++++++++++++++
 3 files changed, 217 insertions(+), 12 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..eb093c65bcf5f 100644
--- a/clang/lib/CodeGen/CGException.cpp
+++ b/clang/lib/CodeGen/CGException.cpp
@@ -1412,15 +1412,19 @@ namespace {
         CGF.Builder.CreateCondBr(ShouldRethrow, RethrowBB, ContBB);
 
         CGF.EmitBlock(RethrowBB);
-        if (SavedExnVar) {
-          CGF.EmitRuntimeCallOrInvoke(RethrowFn, CGF.Builder.CreateAlignedLoad(
-                                                     CGF.Int8PtrTy, 
SavedExnVar,
-                                                     CGF.getPointerAlign()));
-
+        if (EHPersonality::get(CGF).isWasmPersonality()) {
+          CGF.EmitNoreturnRuntimeCallOrInvoke(RethrowFn, {});
         } else {
-          CGF.EmitRuntimeCallOrInvoke(RethrowFn);
+          if (SavedExnVar) {
+            CGF.EmitRuntimeCallOrInvoke(
+                RethrowFn,
+                CGF.Builder.CreateAlignedLoad(CGF.Int8PtrTy, SavedExnVar,
+                                              CGF.getPointerAlign()));
+          } else {
+            CGF.EmitRuntimeCallOrInvoke(RethrowFn);
+          }
+          CGF.Builder.CreateUnreachable();
         }
-        CGF.Builder.CreateUnreachable();
 
         CGF.EmitBlock(ContBB);
 
@@ -1506,15 +1510,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 +1558,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..8c67c7815439a 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.cpp
+++ b/clang/lib/CodeGen/CGObjCRuntime.cpp
@@ -156,15 +156,12 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
 
   CodeGenFunction::FinallyInfo FinallyInfo;
   if (const ObjCAtFinallyStmt *Finally = S.getFinallyStmt()) {
-    if (!useFunclets) {
+    if (!useFunclets || IsWasm) {
       // 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)
@@ -224,6 +221,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 +310,10 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
   CGF.Builder.restoreIP(SavedIP);
 
   // Pop out of the finally.
-  if (!useFunclets && S.getFinallyStmt())
+  if ((!useFunclets || IsWasm) && 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..24b886162c983
--- /dev/null
+++ b/clang/test/CodeGenObjC/gnustep2-wasm32-finally.m
@@ -0,0 +1,177 @@
+// 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();
+
+int finallySimple(Object *object) {
+  int value = 0;
+  @try {
+    mayThrowObjC();
+    value = 1;
+  } @catch (...) {
+    value = 2;
+  } @finally {
+    value += object != (Object *)0;
+  }
+  return value;
+}
+
+int finallyNoCatch(Object *object) {
+  int value = 0;
+  @try {
+    mayThrowObjC();
+    value = 1;
+  } @finally {
+    value += object != (Object *)0;
+  }
+  return value;
+}
+
+int throwInTryFinally(Object *object) {
+  int value = 0;
+  @try {
+    @throw (id)0;
+  } @catch (...) {
+    value = 1;
+  } @finally {
+    value += object != (Object *)0;
+  }
+  return value;
+}
+
+int throwInCatchFinally(Object *object) {
+  @try {
+    mayThrowObjC();
+  } @catch (...) {
+    @throw;
+  } @finally {
+    (void)object;
+  }
+}
+
+int throwInFinally(Object *object) {
+  @try {
+    mayThrowObjC();
+  } @catch (...) {
+  } @finally {
+    @throw object;
+  }
+}
+
+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;
+}
+
+int gotoOutFinally(Object *object) {
+  int value = 0;
+  @try {
+    value = 1;
+    goto done;
+  } @finally {
+    value += object != (Object *)0;
+  }
+done:
+  return value;
+}
+
+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{{.*}} @finallySimple
+// CHECK:       invoke void @mayThrowObjC()
+// CHECK-NEXT:  to label %{{.*}} unwind label %{{.*}}
+// CHECK:       catch.dispatch:
+// CHECK:       catchswitch within none
+// CHECK:       catch.start:
+// CHECK:       [[TRY_PAD:%.*]] = catchpad within
+// CHECK:       invoke.cont:
+// CHECK:       br label %cleanup
+// CHECK:       cleanup:
+// CHECK:       [[FINALLY_SHOULD_THROW:%.*]] = load i1, ptr 
{{.*}}finally.for-eh
+// CHECK:       br i1 [[FINALLY_SHOULD_THROW]], label %finally.rethrow, label 
%finally.cont
+// CHECK:       finally.rethrow:
+// CHECK-NEXT:  invoke void @__cxa_rethrow()
+// CHECK-NEXT:  to label %unreachable unwind label %ehcleanup
+// CHECK:       catch:
+// CHECK:       call ptr @__cxa_begin_catch(ptr {{.*}}){{.*}}[ "funclet"(token 
[[TRY_PAD]]) ]
+// CHECK:       invoke void @__cxa_end_catch() [ "funclet"(token [[TRY_PAD]]) ]
+// CHECK:       catchswitch within none
+// CHECK:       catch.start{{.*}}:
+// CHECK:       [[FINALLY_PAD:%.*]] = catchpad within
+// CHECK:       catchret from [[TRY_PAD]]
+// CHECK:       finally.catchall:
+// CHECK:       call ptr @__cxa_begin_catch(ptr {{.*}}){{.*}}[ "funclet"(token 
[[FINALLY_PAD]]) ]
+// CHECK:       catchret from [[FINALLY_PAD]]
+// CHECK:       ehcleanup:
+// CHECK-NEXT:  [[CLEANUP_PAD:%.*]] = cleanuppad within none []
+// CHECK:       invoke void @__cxa_end_catch() [ "funclet"(token 
[[CLEANUP_PAD]]) ]
+
+// CHECK-LABEL: define{{.*}} @finallyNoCatch
+// CHECK:       invoke void @mayThrowObjC()
+// CHECK:       catchswitch within none
+// CHECK:       catchpad within
+// CHECK:       finally.catchall:
+// CHECK:       call ptr @__cxa_begin_catch
+// CHECK:       catchret from
+// CHECK:       br label %cleanup
+
+// CHECK-LABEL: define{{.*}} @throwInTryFinally
+// CHECK:       invoke void @objc_exception_throw
+// CHECK:       catchpad within
+// CHECK:       finally.catchall:
+// CHECK:       call ptr @__cxa_begin_catch
+// CHECK:       invoke void @__cxa_rethrow()
+
+// CHECK-LABEL: define{{.*}} @throwInCatchFinally
+// CHECK:       catchswitch within none
+// CHECK:       invoke void @__cxa_rethrow()
+// CHECK:       finally.catchall:
+// CHECK:       catchret from
+
+// CHECK-LABEL: define{{.*}} @throwInFinally
+// CHECK:       invoke void @objc_exception_throw
+// CHECK:       catchswitch within none
+// CHECK:       finally.catchall:
+
+// CHECK-LABEL: define{{.*}} @nestedTryCatchFinally
+// CHECK:       catchswitch within none
+// CHECK:       catchswitch within none
+// CHECK:       finally.catchall{{.*}}:
+// CHECK:       finally.catchall{{.*}}:
+
+// 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
+
+// CHECK-LABEL: define{{.*}} @gotoOutCatchFinally
+// CHECK:       catchswitch within none
+// CHECK:       catchret from
+// CHECK:       br label %cleanup

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

Reply via email to