[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