[llvm-branch-commits] [llvm] release/22.x: [WebAssembly] Fix: fixCallUnwindMismatches after fixCatchUnwindMismatches (#187484) (PR #190996)

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Wed Apr 8 08:52:44 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-backend-webassembly

Author: llvmbot

<details>
<summary>Changes</summary>

Backport f89b9a0792d011d41dcae0465e9e14facd073e9d

Requested by: @<!-- -->aheejin

---
Full diff: https://github.com/llvm/llvm-project/pull/190996.diff


5 Files Affected:

- (modified) llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp (+14-4) 
- (modified) llvm/test/CodeGen/WebAssembly/cfg-stackify-eh-legacy.ll (+100-7) 
- (modified) llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll (+1-1) 
- (modified) llvm/test/CodeGen/WebAssembly/exception-legacy.ll (+2-4) 
- (modified) llvm/test/CodeGen/WebAssembly/exception.ll (+29-20) 


``````````diff
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp
index acb889f00a48c..44d54cadf289c 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp
@@ -1836,6 +1836,8 @@ bool WebAssemblyCFGStackify::fixCallUnwindMismatches(MachineFunction &MF) {
     for (auto &MI : reverse(MBB)) {
       if (WebAssembly::isTry(MI.getOpcode()))
         EHPadStack.pop_back();
+      else if (MI.getOpcode() == WebAssembly::DELEGATE)
+        EHPadStack.push_back(MI.getOperand(0).getMBB());
       else if (WebAssembly::isCatch(MI.getOpcode()))
         EHPadStack.push_back(MI.getParent());
 
@@ -1923,8 +1925,10 @@ bool WebAssemblyCFGStackify::fixCallUnwindMismatches(MachineFunction &MF) {
         RecordCallerMismatchRange(EHPadStack.back());
 
       // If EHPadStack is empty, that means it correctly unwinds to the caller
-      // if it throws, so we're good. If MI does not throw, we're good too.
-      else if (EHPadStack.empty() || !MayThrow) {
+      // if it throws, so we're good. A delegate targeting FakeCallerBB also
+      // correctly unwinds to the caller. If MI does not throw, we're good too.
+      else if (EHPadStack.empty() || EHPadStack.back() == FakeCallerBB ||
+               !MayThrow) {
       }
 
       // We found an instruction that unwinds to the caller but currently has an
@@ -1940,6 +1944,8 @@ bool WebAssemblyCFGStackify::fixCallUnwindMismatches(MachineFunction &MF) {
       // Update EHPadStack.
       if (WebAssembly::isTry(MI.getOpcode()))
         EHPadStack.pop_back();
+      else if (MI.getOpcode() == WebAssembly::DELEGATE)
+        EHPadStack.push_back(MI.getOperand(0).getMBB());
       else if (WebAssembly::isCatch(MI.getOpcode()))
         EHPadStack.push_back(MI.getParent());
     }
@@ -2169,7 +2175,8 @@ bool WebAssemblyCFGStackify::fixCatchUnwindMismatches(MachineFunction &MF) {
 
         // The EHPad's next unwind destination is the caller, but we incorrectly
         // unwind to another EH pad.
-        else if (!EHPadStack.empty() && !EHInfo->hasUnwindDest(EHPad)) {
+        else if (!EHPadStack.empty() && EHPadStack.back() != FakeCallerBB &&
+                 !EHInfo->hasUnwindDest(EHPad)) {
           EHPadToUnwindDest[EHPad] = getFakeCallerBlock(MF);
           LLVM_DEBUG(dbgs()
                      << "- Catch unwind mismatch:\nEHPad = " << EHPad->getName()
@@ -2475,8 +2482,11 @@ void WebAssemblyCFGStackify::placeMarkers(MachineFunction &MF) {
     // Add an 'unreachable' after 'end_try_table's.
     addUnreachableAfterTryTables(MF, TII);
     // Fix mismatches in unwind destinations induced by linearizing the code.
-    fixCallUnwindMismatches(MF);
+    // Run fixCatchUnwindMismatches() first so that fixCallUnwindMismatches()
+    // will see and correct any new call/rethrow unwind mismatches introduced by
+    // fixCatchUnwindMismatches().
     fixCatchUnwindMismatches(MF);
+    fixCallUnwindMismatches(MF);
     // addUnreachableAfterTryTables and fixUnwindMismatches create new BBs, so
     // we need to recalculate ScopeTops.
     recalculateScopeTops(MF);
diff --git a/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh-legacy.ll b/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh-legacy.ll
index f0a1d9805c806..3a18a4351b76e 100644
--- a/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh-legacy.ll
+++ b/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh-legacy.ll
@@ -659,11 +659,9 @@ try.cont:                                         ; preds = %catch.start0
 ; --- try-delegate ends (call unwind mismatch)
 ; NOSORT:     catch
 ; NOSORT:       call  {{.*}} __cxa_begin_catch
-; --- try-delegate starts (call unwind mismatch)
-; NOSORT:       try
-; NOSORT:         call  __cxa_end_catch
-; NOSORT:       delegate    3            # label/catch{{[0-9]+}}: to caller
-; --- try-delegate ends (call unwind mismatch)
+; __cxa_end_catch doesn't need its own try-delegate because the enclosing
+; catch-mismatch delegate already routes to caller.
+; NOSORT:       call  __cxa_end_catch
 ; NOSORT:     end_try
 ; NOSORT:   delegate    1                # label/catch{{[0-9]+}}: to caller
 ; --- try-delegate ends (catch unwind mismatch)
@@ -1735,9 +1733,104 @@ unreachable:                                      ; preds = %rethrow, %entry
   unreachable
 }
 
+; Regression test for issue #187302: fixCallUnwindMismatches() was run first and
+; found that the rethrow had the correct unwind target so did not wrap it in a
+; try/delegate. Then fixCatchUnwindMismatches() added a try/delegate for
+;
+; invoke void @do_catch unwind label %terminate_catchswitch
+;
+; because otherwise it was going to unwind to outer_catchswitch rather than
+; terminate_catchswitch. But this made the rethrow incorrectly transfer control
+; to terminate_catchswitch rather than outer_catchswitch.
+; Fixed by running fixCatchUnwindMismatches() before fixCallUnwindMismatches().
+;
+; This pattern occurs in Rust's catch_unwind inside a destructor.
+
+; CHECK-LABEL: catch_unwind_in_cleanup:
+; CHECK:        catch_all
+; CHECK:          try
+; CHECK:            try
+; CHECK:              call  resume_unwind
+; CHECK:            catch   {{.*}} __cpp_exception
+; CHECK:              call  do_catch
+; end_cleanup and rethrow are inside the inner catch's scope, but each gets
+; its own try-delegate to route exceptions to the correct destination rather
+; than going through the catch-mismatch delegate to the terminate handler.
+; CHECK:              try
+; CHECK:                call  end_cleanup
+; CHECK:              delegate    7
+;                                (caller)
+; CHECK:              try
+; CHECK:                rethrow   7
+;                                (Rethrow exception caught by outermost catch_all)
+; CHECK:              delegate    2
+;                                (outer_catchswitch)
+; CHECK:            end_try
+; CHECK:          delegate    3
+;                            (terminate)
+; CHECK:          catch   {{.*}} __cpp_exception
+; CHECK:            call  do_catch
+; CHECK:            return
+define void @catch_unwind_in_cleanup() personality ptr @__gxx_wasm_personality_v0 {
+start:
+  invoke void @resume_unwind(i32 1)
+          to label %unreachable unwind label %outer_cleanuppad
+
+outer_cleanuppad:
+  %outer_pad = cleanuppad within none []
+  call void @start_cleanup() [ "funclet"(token %outer_pad) ]
+  invoke void @resume_unwind(i32 2) [ "funclet"(token %outer_pad) ]
+          to label %unreachable unwind label %inner_catchswitch
+
+inner_catchswitch:
+  %inner_cs = catchswitch within %outer_pad [label %inner_catchpad] unwind label %terminate_catchswitch
+
+inner_catchpad:
+  %inner_cp = catchpad within %inner_cs [ptr null]
+  %exn = call ptr @llvm.wasm.get.exception(token %inner_cp)
+  %sel = call i32 @llvm.wasm.get.ehselector(token %inner_cp)
+  invoke void @do_catch(ptr %exn, i32 2) [ "funclet"(token %inner_cp) ]
+          to label %inner_catchret unwind label %terminate_catchswitch
+
+terminate_catchswitch:
+  %term_cs = catchswitch within %outer_pad [label %terminate_catchpad] unwind label %outer_catchswitch
+
+terminate_catchpad:
+  %term_pad = catchpad within %term_cs [ptr null]
+  call void @panic_in_cleanup() [ "funclet"(token %term_pad) ]
+  unreachable
+
+inner_catchret:
+  catchret from %inner_cp to label %outer_cleanupret
+
+outer_cleanupret:
+  call void @end_cleanup() [ "funclet"(token %outer_pad) ]
+  cleanupret from %outer_pad unwind label %outer_catchswitch
+
+outer_catchswitch:
+  %outer_cs = catchswitch within none [label %outer_catchpad] unwind to caller
+outer_catchpad:
+  %outer_cp = catchpad within %outer_cs [ptr null]
+  %exn2 = call ptr @llvm.wasm.get.exception(token %outer_cp)
+  %sel2 = call i32 @llvm.wasm.get.ehselector(token %outer_cp)
+  call void @do_catch(ptr %exn2, i32 1) [ "funclet"(token %outer_cp) ]
+  catchret from %outer_cp to label %done
+
+done:
+  ret void
+unreachable:
+  unreachable
+}
+
+declare void @resume_unwind(i32) #1
+declare void @do_catch(ptr, i32) #0
+declare void @start_cleanup()
+declare void @end_cleanup()
+declare void @panic_in_cleanup() #2
+
 ; Check if the unwind destination mismatch stats are correct
-; NOSORT: 24 wasm-cfg-stackify    - Number of call unwind mismatches found
-; NOSORT:  5 wasm-cfg-stackify    - Number of catch unwind mismatches found
+; NOSORT: 25 wasm-cfg-stackify    - Number of call unwind mismatches found
+; NOSORT:  7 wasm-cfg-stackify    - Number of catch unwind mismatches found
 
 declare void @foo()
 declare void @bar()
diff --git a/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll b/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll
index 98de9a267b95a..93937fd96d887 100644
--- a/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll
+++ b/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll
@@ -1507,7 +1507,7 @@ unreachable:                                      ; preds = %rethrow, %entry
 }
 
 ; Check if the unwind destination mismatch stats are correct
-; NOSORT: 23 wasm-cfg-stackify    - Number of call unwind mismatches found
+; NOSORT: 24 wasm-cfg-stackify    - Number of call unwind mismatches found
 ; NOSORT:  4 wasm-cfg-stackify    - Number of catch unwind mismatches found
 
 declare void @foo()
diff --git a/llvm/test/CodeGen/WebAssembly/exception-legacy.ll b/llvm/test/CodeGen/WebAssembly/exception-legacy.ll
index b96a664166f42..573f69df8b859 100644
--- a/llvm/test/CodeGen/WebAssembly/exception-legacy.ll
+++ b/llvm/test/CodeGen/WebAssembly/exception-legacy.ll
@@ -429,16 +429,14 @@ unreachable:                                      ; preds = %rethrow
 ; CHECK:   try
 ; CHECK:     try
 ; CHECK:       call  __cxa_throw
-; CHECK:       catch
+; CHECK:     catch
 ; CHECK:       call  __cxa_end_catch
 ; CHECK:       try
 ; CHECK:         try
 ; Note that this rethrow targets the top-level catch_all
 ; CHECK:           rethrow   4
 ; CHECK:         catch
-; CHECK:           try
-; CHECK:             call  __cxa_end_catch
-; CHECK:           delegate    5
+; CHECK:           call  __cxa_end_catch
 ; CHECK:           return
 ; CHECK:         end_try
 ; CHECK:       delegate    3
diff --git a/llvm/test/CodeGen/WebAssembly/exception.ll b/llvm/test/CodeGen/WebAssembly/exception.ll
index 873386b3dcc6d..19c3d1b158951 100644
--- a/llvm/test/CodeGen/WebAssembly/exception.ll
+++ b/llvm/test/CodeGen/WebAssembly/exception.ll
@@ -484,34 +484,43 @@ unreachable:                                      ; preds = %rethrow
 ; try_table (catch_all_ref 0)'s caught exception is stored in local 2
 ; CHECK:     local.set  2
 ; CHECK:     block
+; catch_all 0 dispatches to %terminate.i (the 'call _ZSt9terminatev' instruction).
 ; CHECK:       try_table    (catch_all 0)
-; CHECK:         block
-; CHECK:           block     i32
-; CHECK:             try_table    (catch __cpp_exception 0)
-; CHECK:               call  __cxa_throw
+; CHECK:         block     exnref
+; CHECK:           block
+; CHECK:             block     i32
+; CHECK:               try_table    (catch __cpp_exception 0)
+; CHECK:                 call  __cxa_throw
+; CHECK:               end_try_table
+; CHECK:             end_block
+;
+; invoke __cxa_end_catch... unwind label %terminate.i
+; should unwind to %terminate.i. catch_all_ref 1 points to the
+; try_table (catch_all 0) which in turn dispatches to %terminate.i
+; CHECK:             try_table    (catch_all_ref 1)
+; CHECK:               call  __cxa_end_catch
 ; CHECK:             end_try_table
-; CHECK:           end_block
-; CHECK:           call  __cxa_end_catch
-; CHECK:           block     i32
-; CHECK:             try_table    (catch_all_ref 5)
-; CHECK:               try_table    (catch __cpp_exception 1)
+; CHECK:             block     i32
+; CHECK:               try_table    (catch_all_ref 6)
+; CHECK:                 try_table    (catch __cpp_exception 1)
 ; Note that the throw_ref below targets the top-level catch_all_ref (local 2)
-; CHECK:                 local.get  2
-; CHECK:                 throw_ref
+; CHECK:                   local.get  2
+; CHECK:                   throw_ref
+; CHECK:                 end_try_table
 ; CHECK:               end_try_table
+; CHECK:             end_block
+; CHECK:             try_table    (catch_all_ref 5)
+; CHECK:               call  __cxa_end_catch
 ; CHECK:             end_try_table
+; CHECK:             return
 ; CHECK:           end_block
-; CHECK:           try_table    (catch_all_ref 4)
-; CHECK:             call  __cxa_end_catch
-; CHECK:           end_try_table
-; CHECK:           return
-; CHECK:         end_block
-; CHECK:       end_try_table
+; CHECK:           throw_ref
+; CHECK:         end_try_table
+; CHECK:       end_block
+; CHECK:       call  _ZSt9terminatev
 ; CHECK:     end_block
-; CHECK:     call  _ZSt9terminatev
 ; CHECK:   end_block
-; CHECK: end_block
-; CHECK: throw_ref
+; CHECK:   throw_ref
 define void @inlined_cleanupret() personality ptr @__gxx_wasm_personality_v0 {
 entry:
   %exception = tail call ptr @__cxa_allocate_exception(i32 4)

``````````

</details>


https://github.com/llvm/llvm-project/pull/190996


More information about the llvm-branch-commits mailing list