Skip to content

Commit 0a11c10

Browse files
hoodmaneYonahGoldberg
authored andcommitted
[WebAssembly] Fix: fixCallUnwindMismatches after fixCatchUnwindMismatches (llvm#187484)
`fixCallUnwindMismatches()` adds an extra try block around call sites with incorrect unwind targets. `fixCatchUnwindMismatches()` handles catch blocks that have incorrect next unwind destinations. Previously we ran `fixCallUnwindMismatches()` first and then ran `fixCatchUnwindMismatches()`. The problem is that `fixCatchUnwindMismatches()` wraps entire try blocks which can change the unwind destination of the calls inside. If the calls had an incorrect unwind target to begin with, they will be wrapped already and so the outer wrapping won't alter their unwind target. However, if they start out with a correct unwind target, they won't get wrapped and then that can be messed up by `fixCatchUnwindMismatches()`. The fix is to run `fixCatchUnwindMismatches()` first. `fixCallUnwindMismatches()` never messes up the result of `fixCatchUnwindMismatches()` so this is the correct order. Resolves llvm#187302
1 parent 7cd7215 commit 0a11c10

5 files changed

Lines changed: 146 additions & 36 deletions

File tree

llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1836,6 +1836,8 @@ bool WebAssemblyCFGStackify::fixCallUnwindMismatches(MachineFunction &MF) {
18361836
for (auto &MI : reverse(MBB)) {
18371837
if (WebAssembly::isTry(MI.getOpcode()))
18381838
EHPadStack.pop_back();
1839+
else if (MI.getOpcode() == WebAssembly::DELEGATE)
1840+
EHPadStack.push_back(MI.getOperand(0).getMBB());
18391841
else if (WebAssembly::isCatch(MI.getOpcode()))
18401842
EHPadStack.push_back(MI.getParent());
18411843

@@ -1923,8 +1925,10 @@ bool WebAssemblyCFGStackify::fixCallUnwindMismatches(MachineFunction &MF) {
19231925
RecordCallerMismatchRange(EHPadStack.back());
19241926

19251927
// If EHPadStack is empty, that means it correctly unwinds to the caller
1926-
// if it throws, so we're good. If MI does not throw, we're good too.
1927-
else if (EHPadStack.empty() || !MayThrow) {
1928+
// if it throws, so we're good. A delegate targeting FakeCallerBB also
1929+
// correctly unwinds to the caller. If MI does not throw, we're good too.
1930+
else if (EHPadStack.empty() || EHPadStack.back() == FakeCallerBB ||
1931+
!MayThrow) {
19281932
}
19291933

19301934
// We found an instruction that unwinds to the caller but currently has an
@@ -1940,6 +1944,8 @@ bool WebAssemblyCFGStackify::fixCallUnwindMismatches(MachineFunction &MF) {
19401944
// Update EHPadStack.
19411945
if (WebAssembly::isTry(MI.getOpcode()))
19421946
EHPadStack.pop_back();
1947+
else if (MI.getOpcode() == WebAssembly::DELEGATE)
1948+
EHPadStack.push_back(MI.getOperand(0).getMBB());
19431949
else if (WebAssembly::isCatch(MI.getOpcode()))
19441950
EHPadStack.push_back(MI.getParent());
19451951
}
@@ -2169,7 +2175,8 @@ bool WebAssemblyCFGStackify::fixCatchUnwindMismatches(MachineFunction &MF) {
21692175

21702176
// The EHPad's next unwind destination is the caller, but we incorrectly
21712177
// unwind to another EH pad.
2172-
else if (!EHPadStack.empty() && !EHInfo->hasUnwindDest(EHPad)) {
2178+
else if (!EHPadStack.empty() && EHPadStack.back() != FakeCallerBB &&
2179+
!EHInfo->hasUnwindDest(EHPad)) {
21732180
EHPadToUnwindDest[EHPad] = getFakeCallerBlock(MF);
21742181
LLVM_DEBUG(dbgs()
21752182
<< "- Catch unwind mismatch:\nEHPad = " << EHPad->getName()
@@ -2474,8 +2481,11 @@ void WebAssemblyCFGStackify::placeMarkers(MachineFunction &MF) {
24742481
// Add an 'unreachable' after 'end_try_table's.
24752482
addUnreachableAfterTryTables(MF, TII);
24762483
// Fix mismatches in unwind destinations induced by linearizing the code.
2477-
fixCallUnwindMismatches(MF);
2484+
// Run fixCatchUnwindMismatches() first so that fixCallUnwindMismatches()
2485+
// will see and correct any new call/rethrow unwind mismatches introduced by
2486+
// fixCatchUnwindMismatches().
24782487
fixCatchUnwindMismatches(MF);
2488+
fixCallUnwindMismatches(MF);
24792489
// addUnreachableAfterTryTables and fixUnwindMismatches create new BBs, so
24802490
// we need to recalculate ScopeTops.
24812491
recalculateScopeTops(MF);

llvm/test/CodeGen/WebAssembly/cfg-stackify-eh-legacy.ll

Lines changed: 100 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -659,11 +659,9 @@ try.cont: ; preds = %catch.start0
659659
; --- try-delegate ends (call unwind mismatch)
660660
; NOSORT: catch
661661
; NOSORT: call {{.*}} __cxa_begin_catch
662-
; --- try-delegate starts (call unwind mismatch)
663-
; NOSORT: try
664-
; NOSORT: call __cxa_end_catch
665-
; NOSORT: delegate 3 # label/catch{{[0-9]+}}: to caller
666-
; --- try-delegate ends (call unwind mismatch)
662+
; __cxa_end_catch doesn't need its own try-delegate because the enclosing
663+
; catch-mismatch delegate already routes to caller.
664+
; NOSORT: call __cxa_end_catch
667665
; NOSORT: end_try
668666
; NOSORT: delegate 1 # label/catch{{[0-9]+}}: to caller
669667
; --- try-delegate ends (catch unwind mismatch)
@@ -1735,9 +1733,104 @@ unreachable: ; preds = %rethrow, %entry
17351733
unreachable
17361734
}
17371735

1736+
; Regression test for issue #187302: fixCallUnwindMismatches() was run first and
1737+
; found that the rethrow had the correct unwind target so did not wrap it in a
1738+
; try/delegate. Then fixCatchUnwindMismatches() added a try/delegate for
1739+
;
1740+
; invoke void @do_catch unwind label %terminate_catchswitch
1741+
;
1742+
; because otherwise it was going to unwind to outer_catchswitch rather than
1743+
; terminate_catchswitch. But this made the rethrow incorrectly transfer control
1744+
; to terminate_catchswitch rather than outer_catchswitch.
1745+
; Fixed by running fixCatchUnwindMismatches() before fixCallUnwindMismatches().
1746+
;
1747+
; This pattern occurs in Rust's catch_unwind inside a destructor.
1748+
1749+
; CHECK-LABEL: catch_unwind_in_cleanup:
1750+
; CHECK: catch_all
1751+
; CHECK: try
1752+
; CHECK: try
1753+
; CHECK: call resume_unwind
1754+
; CHECK: catch {{.*}} __cpp_exception
1755+
; CHECK: call do_catch
1756+
; end_cleanup and rethrow are inside the inner catch's scope, but each gets
1757+
; its own try-delegate to route exceptions to the correct destination rather
1758+
; than going through the catch-mismatch delegate to the terminate handler.
1759+
; CHECK: try
1760+
; CHECK: call end_cleanup
1761+
; CHECK: delegate 7
1762+
; (caller)
1763+
; CHECK: try
1764+
; CHECK: rethrow 7
1765+
; (Rethrow exception caught by outermost catch_all)
1766+
; CHECK: delegate 2
1767+
; (outer_catchswitch)
1768+
; CHECK: end_try
1769+
; CHECK: delegate 3
1770+
; (terminate)
1771+
; CHECK: catch {{.*}} __cpp_exception
1772+
; CHECK: call do_catch
1773+
; CHECK: return
1774+
define void @catch_unwind_in_cleanup() personality ptr @__gxx_wasm_personality_v0 {
1775+
start:
1776+
invoke void @resume_unwind(i32 1)
1777+
to label %unreachable unwind label %outer_cleanuppad
1778+
1779+
outer_cleanuppad:
1780+
%outer_pad = cleanuppad within none []
1781+
call void @start_cleanup() [ "funclet"(token %outer_pad) ]
1782+
invoke void @resume_unwind(i32 2) [ "funclet"(token %outer_pad) ]
1783+
to label %unreachable unwind label %inner_catchswitch
1784+
1785+
inner_catchswitch:
1786+
%inner_cs = catchswitch within %outer_pad [label %inner_catchpad] unwind label %terminate_catchswitch
1787+
1788+
inner_catchpad:
1789+
%inner_cp = catchpad within %inner_cs [ptr null]
1790+
%exn = call ptr @llvm.wasm.get.exception(token %inner_cp)
1791+
%sel = call i32 @llvm.wasm.get.ehselector(token %inner_cp)
1792+
invoke void @do_catch(ptr %exn, i32 2) [ "funclet"(token %inner_cp) ]
1793+
to label %inner_catchret unwind label %terminate_catchswitch
1794+
1795+
terminate_catchswitch:
1796+
%term_cs = catchswitch within %outer_pad [label %terminate_catchpad] unwind label %outer_catchswitch
1797+
1798+
terminate_catchpad:
1799+
%term_pad = catchpad within %term_cs [ptr null]
1800+
call void @panic_in_cleanup() [ "funclet"(token %term_pad) ]
1801+
unreachable
1802+
1803+
inner_catchret:
1804+
catchret from %inner_cp to label %outer_cleanupret
1805+
1806+
outer_cleanupret:
1807+
call void @end_cleanup() [ "funclet"(token %outer_pad) ]
1808+
cleanupret from %outer_pad unwind label %outer_catchswitch
1809+
1810+
outer_catchswitch:
1811+
%outer_cs = catchswitch within none [label %outer_catchpad] unwind to caller
1812+
outer_catchpad:
1813+
%outer_cp = catchpad within %outer_cs [ptr null]
1814+
%exn2 = call ptr @llvm.wasm.get.exception(token %outer_cp)
1815+
%sel2 = call i32 @llvm.wasm.get.ehselector(token %outer_cp)
1816+
call void @do_catch(ptr %exn2, i32 1) [ "funclet"(token %outer_cp) ]
1817+
catchret from %outer_cp to label %done
1818+
1819+
done:
1820+
ret void
1821+
unreachable:
1822+
unreachable
1823+
}
1824+
1825+
declare void @resume_unwind(i32) #1
1826+
declare void @do_catch(ptr, i32) #0
1827+
declare void @start_cleanup()
1828+
declare void @end_cleanup()
1829+
declare void @panic_in_cleanup() #2
1830+
17381831
; Check if the unwind destination mismatch stats are correct
1739-
; NOSORT: 24 wasm-cfg-stackify - Number of call unwind mismatches found
1740-
; NOSORT: 5 wasm-cfg-stackify - Number of catch unwind mismatches found
1832+
; NOSORT: 25 wasm-cfg-stackify - Number of call unwind mismatches found
1833+
; NOSORT: 7 wasm-cfg-stackify - Number of catch unwind mismatches found
17411834

17421835
declare void @foo()
17431836
declare void @bar()

llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1507,7 +1507,7 @@ unreachable: ; preds = %rethrow, %entry
15071507
}
15081508

15091509
; Check if the unwind destination mismatch stats are correct
1510-
; NOSORT: 23 wasm-cfg-stackify - Number of call unwind mismatches found
1510+
; NOSORT: 24 wasm-cfg-stackify - Number of call unwind mismatches found
15111511
; NOSORT: 4 wasm-cfg-stackify - Number of catch unwind mismatches found
15121512

15131513
declare void @foo()

llvm/test/CodeGen/WebAssembly/exception-legacy.ll

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -429,16 +429,14 @@ unreachable: ; preds = %rethrow
429429
; CHECK: try
430430
; CHECK: try
431431
; CHECK: call __cxa_throw
432-
; CHECK: catch
432+
; CHECK: catch
433433
; CHECK: call __cxa_end_catch
434434
; CHECK: try
435435
; CHECK: try
436436
; Note that this rethrow targets the top-level catch_all
437437
; CHECK: rethrow 4
438438
; CHECK: catch
439-
; CHECK: try
440-
; CHECK: call __cxa_end_catch
441-
; CHECK: delegate 5
439+
; CHECK: call __cxa_end_catch
442440
; CHECK: return
443441
; CHECK: end_try
444442
; CHECK: delegate 3

llvm/test/CodeGen/WebAssembly/exception.ll

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -484,34 +484,43 @@ unreachable: ; preds = %rethrow
484484
; try_table (catch_all_ref 0)'s caught exception is stored in local 2
485485
; CHECK: local.set 2
486486
; CHECK: block
487+
; catch_all 0 dispatches to %terminate.i (the 'call _ZSt9terminatev' instruction).
487488
; CHECK: try_table (catch_all 0)
488-
; CHECK: block
489-
; CHECK: block i32
490-
; CHECK: try_table (catch __cpp_exception 0)
491-
; CHECK: call __cxa_throw
489+
; CHECK: block exnref
490+
; CHECK: block
491+
; CHECK: block i32
492+
; CHECK: try_table (catch __cpp_exception 0)
493+
; CHECK: call __cxa_throw
494+
; CHECK: end_try_table
495+
; CHECK: end_block
496+
;
497+
; invoke __cxa_end_catch... unwind label %terminate.i
498+
; should unwind to %terminate.i. catch_all_ref 1 points to the
499+
; try_table (catch_all 0) which in turn dispatches to %terminate.i
500+
; CHECK: try_table (catch_all_ref 1)
501+
; CHECK: call __cxa_end_catch
492502
; CHECK: end_try_table
493-
; CHECK: end_block
494-
; CHECK: call __cxa_end_catch
495-
; CHECK: block i32
496-
; CHECK: try_table (catch_all_ref 5)
497-
; CHECK: try_table (catch __cpp_exception 1)
503+
; CHECK: block i32
504+
; CHECK: try_table (catch_all_ref 6)
505+
; CHECK: try_table (catch __cpp_exception 1)
498506
; Note that the throw_ref below targets the top-level catch_all_ref (local 2)
499-
; CHECK: local.get 2
500-
; CHECK: throw_ref
507+
; CHECK: local.get 2
508+
; CHECK: throw_ref
509+
; CHECK: end_try_table
501510
; CHECK: end_try_table
511+
; CHECK: end_block
512+
; CHECK: try_table (catch_all_ref 5)
513+
; CHECK: call __cxa_end_catch
502514
; CHECK: end_try_table
515+
; CHECK: return
503516
; CHECK: end_block
504-
; CHECK: try_table (catch_all_ref 4)
505-
; CHECK: call __cxa_end_catch
506-
; CHECK: end_try_table
507-
; CHECK: return
508-
; CHECK: end_block
509-
; CHECK: end_try_table
517+
; CHECK: throw_ref
518+
; CHECK: end_try_table
519+
; CHECK: end_block
520+
; CHECK: call _ZSt9terminatev
510521
; CHECK: end_block
511-
; CHECK: call _ZSt9terminatev
512522
; CHECK: end_block
513-
; CHECK: end_block
514-
; CHECK: throw_ref
523+
; CHECK: throw_ref
515524
define void @inlined_cleanupret() personality ptr @__gxx_wasm_personality_v0 {
516525
entry:
517526
%exception = tail call ptr @__cxa_allocate_exception(i32 4)

0 commit comments

Comments
 (0)