Skip to content

Commit c3dfd34

Browse files
authored
[WebAssembly] Add unreachable before catch destinations (#123915)
When `try_table`'s catch clause's destination has a return type, as in the case of catch with a concrete tag, catch_ref, and catch_all_ref. For example: ```wasm block exnref try_table (catch_all_ref 0) ... end_try_table end_block ... use exnref ... ``` This code is not valid because the block's body type is not exnref. So we add an unreachable after the 'end_try_table' to make the code valid here: ```wasm block exnref try_table (catch_all_ref 0) ... end_try_table unreachable ;; Newly added end_block ``` Because 'unreachable' is a terminator we also need to split the BB. --- We need to handle the same thing for unwind mismatch handling. In the code below, we create a "trampoline BB" that will be the destination for the nested `try_table`~`end_try_table` added to fix a unwind mismatch: ```wasm try_table (catch ... ) block exnref ... try_table (catch_all_ref N) some code end_try_table ... end_block ;; Trampoline BB throw_ref end_try_table ``` While the `block` added for the trampoline BB has the return type `exnref`, its body, which contains the nested `try_table` and other code, wouldn't have the `exnref` return type. Most times it didn't become a problem because the block's body ended with something like `br` or `return`, but that may not always be the case, especially when there is a loop. So we add an `unreachable` to make the code valid here too: ```wasm try_table (catch ... ) block exnref ... try_table (catch_all_ref N) some code end_try_table ... unreachable ;; Newly added end_block ;; Trampoline BB throw_ref end_try_table ``` In this case we just append the `unreachable` at the end of the layout predecessor BB. (This was tricky to do in the first (non-mismatch) case because there `end_try_table` and `end_block` were added in the beginning of an EH pad in `placeTryTableMarker` and moving `end_try_table` and the new `unreachable` to the previous BB caused other problems.) --- This adds many `unreaachable`s to the output, but this adds `unreachable` to only a few places to see if this is working. The FileCheck lines in `exception.ll` and `cfg-stackify-eh.ll` are already heavily redacted to only leave important control-flow instructions, so I don't think it's worth adding `unreachable`s everywhere.
1 parent 5d8390d commit c3dfd34

File tree

5 files changed

+93
-21
lines changed

5 files changed

+93
-21
lines changed

llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp

Lines changed: 88 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,6 +1297,7 @@ void WebAssemblyCFGStackify::addNestedTryDelegate(
12971297
// some code
12981298
// end_try_table
12991299
// ...
1300+
// unreachable
13001301
// end_block ;; Trampoline BB
13011302
// throw_ref
13021303
// end_try_table
@@ -1358,6 +1359,13 @@ WebAssemblyCFGStackify::getTrampolineBlock(MachineBasicBlock *UnwindDest) {
13581359
BuildMI(TrampolineBB, EndDebugLoc, TII.get(WebAssembly::THROW_REF))
13591360
.addReg(ExnReg);
13601361

1362+
// The trampoline BB's return type is exnref because it is a target of
1363+
// catch_all_ref. But the body type of the block we just created is not. We
1364+
// add an 'unreachable' right before the 'end_block' to make the code valid.
1365+
MachineBasicBlock *TrampolineLayoutPred = TrampolineBB->getPrevNode();
1366+
BuildMI(TrampolineLayoutPred, TrampolineLayoutPred->findBranchDebugLoc(),
1367+
TII.get(WebAssembly::UNREACHABLE));
1368+
13611369
registerScope(Block, EndBlock);
13621370
UnwindDestToTrampoline[UnwindDest] = TrampolineBB;
13631371
return TrampolineBB;
@@ -1465,7 +1473,7 @@ void WebAssemblyCFGStackify::addNestedTryTable(MachineInstr *RangeBegin,
14651473
// - After:
14661474
// pre_bb: (new)
14671475
// range_end
1468-
// end_try_table: (new)
1476+
// end_try_table_bb: (new)
14691477
// end_try_table
14701478
// post_bb: (previous 'ehpad')
14711479
// catch
@@ -1523,9 +1531,9 @@ void WebAssemblyCFGStackify::addNestedTryTable(MachineInstr *RangeBegin,
15231531
// end_loop
15241532
// end_try_table
15251533
//
1526-
// So if the unwind dest BB has a end_loop before an end_try_table, we split the
1527-
// BB with the end_loop as a separate BB before the end_try_table BB, so that
1528-
// after we fix the unwind mismatch, the code will be like:
1534+
// So if an end_try_table BB has an end_loop before the end_try_table, we split
1535+
// the BB with the end_loop as a separate BB before the end_try_table BB, so
1536+
// that after we fix the unwind mismatch, the code will be like:
15291537
// bb0:
15301538
// try_table
15311539
// block exnref
@@ -1538,10 +1546,10 @@ void WebAssemblyCFGStackify::addNestedTryTable(MachineInstr *RangeBegin,
15381546
// end_block
15391547
// end_try_table_bb:
15401548
// end_try_table
1541-
static void splitEndLoopBB(MachineBasicBlock *UnwindDest) {
1542-
auto &MF = *UnwindDest->getParent();
1549+
static void splitEndLoopBB(MachineBasicBlock *EndTryTableBB) {
1550+
auto &MF = *EndTryTableBB->getParent();
15431551
MachineInstr *EndTryTable = nullptr, *EndLoop = nullptr;
1544-
for (auto &MI : reverse(*UnwindDest)) {
1552+
for (auto &MI : reverse(*EndTryTableBB)) {
15451553
if (MI.getOpcode() == WebAssembly::END_TRY_TABLE) {
15461554
EndTryTable = &MI;
15471555
continue;
@@ -1555,11 +1563,11 @@ static void splitEndLoopBB(MachineBasicBlock *UnwindDest) {
15551563
return;
15561564

15571565
auto *EndLoopBB = MF.CreateMachineBasicBlock();
1558-
MF.insert(UnwindDest->getIterator(), EndLoopBB);
1566+
MF.insert(EndTryTableBB->getIterator(), EndLoopBB);
15591567
auto SplitPos = std::next(EndLoop->getIterator());
1560-
EndLoopBB->splice(EndLoopBB->end(), UnwindDest, UnwindDest->begin(),
1568+
EndLoopBB->splice(EndLoopBB->end(), EndTryTableBB, EndTryTableBB->begin(),
15611569
SplitPos);
1562-
EndLoopBB->addSuccessor(UnwindDest);
1570+
EndLoopBB->addSuccessor(EndTryTableBB);
15631571
}
15641572

15651573
bool WebAssemblyCFGStackify::fixCallUnwindMismatches(MachineFunction &MF) {
@@ -1943,8 +1951,16 @@ bool WebAssemblyCFGStackify::fixCallUnwindMismatches(MachineFunction &MF) {
19431951
// When end_loop is before end_try_table within the same BB in unwind
19441952
// destinations, we should split the end_loop into another BB.
19451953
if (!WebAssembly::WasmUseLegacyEH)
1946-
for (auto &[UnwindDest, _] : UnwindDestToTryRanges)
1947-
splitEndLoopBB(UnwindDest);
1954+
for (auto &[UnwindDest, _] : UnwindDestToTryRanges) {
1955+
auto It = EHPadToTry.find(UnwindDest);
1956+
// If UnwindDest is the fake caller block, it will not be in EHPadToTry
1957+
// map
1958+
if (It != EHPadToTry.end()) {
1959+
auto *TryTable = It->second;
1960+
auto *EndTryTable = BeginToEnd[TryTable];
1961+
splitEndLoopBB(EndTryTable->getParent());
1962+
}
1963+
}
19481964

19491965
// Now we fix the mismatches by wrapping calls with inner try-delegates.
19501966
for (auto &P : UnwindDestToTryRanges) {
@@ -2179,8 +2195,15 @@ bool WebAssemblyCFGStackify::fixCatchUnwindMismatches(MachineFunction &MF) {
21792195

21802196
// When end_loop is before end_try_table within the same BB in unwind
21812197
// destinations, we should split the end_loop into another BB.
2182-
for (auto &[_, UnwindDest] : EHPadToUnwindDest)
2183-
splitEndLoopBB(UnwindDest);
2198+
for (auto &[_, UnwindDest] : EHPadToUnwindDest) {
2199+
auto It = EHPadToTry.find(UnwindDest);
2200+
// If UnwindDest is the fake caller block, it will not be in EHPadToTry map
2201+
if (It != EHPadToTry.end()) {
2202+
auto *TryTable = It->second;
2203+
auto *EndTryTable = BeginToEnd[TryTable];
2204+
splitEndLoopBB(EndTryTable->getParent());
2205+
}
2206+
}
21842207

21852208
NumCatchUnwindMismatches += EHPadToUnwindDest.size();
21862209
SmallPtrSet<MachineBasicBlock *, 4> NewEndTryBBs;
@@ -2372,6 +2395,48 @@ static void appendEndToFunction(MachineFunction &MF,
23722395
TII.get(WebAssembly::END_FUNCTION));
23732396
}
23742397

2398+
// We added block~end_block and try_table~end_try_table markers in
2399+
// placeTryTableMarker. But When catch clause's destination has a return type,
2400+
// as in the case of catch with a concrete tag, catch_ref, and catch_all_ref.
2401+
// For example:
2402+
// block exnref
2403+
// try_table (catch_all_ref 0)
2404+
// ...
2405+
// end_try_table
2406+
// end_block
2407+
// ... use exnref ...
2408+
//
2409+
// This code is not valid because the block's body type is not exnref. So we add
2410+
// an unreachable after the 'end_try_table' to make the code valid here:
2411+
// block exnref
2412+
// try_table (catch_all_ref 0)
2413+
// ...
2414+
// end_try_table
2415+
// unreachable (new)
2416+
// end_block
2417+
//
2418+
// Because 'unreachable' is a terminator we also need to split the BB.
2419+
static void addUnreachableAfterTryTables(MachineFunction &MF,
2420+
const WebAssemblyInstrInfo &TII) {
2421+
std::vector<MachineInstr *> EndTryTables;
2422+
for (auto &MBB : MF)
2423+
for (auto &MI : MBB)
2424+
if (MI.getOpcode() == WebAssembly::END_TRY_TABLE)
2425+
EndTryTables.push_back(&MI);
2426+
2427+
for (auto *EndTryTable : EndTryTables) {
2428+
auto *MBB = EndTryTable->getParent();
2429+
auto *NewEndTryTableBB = MF.CreateMachineBasicBlock();
2430+
MF.insert(MBB->getIterator(), NewEndTryTableBB);
2431+
auto SplitPos = std::next(EndTryTable->getIterator());
2432+
NewEndTryTableBB->splice(NewEndTryTableBB->end(), MBB, MBB->begin(),
2433+
SplitPos);
2434+
NewEndTryTableBB->addSuccessor(MBB);
2435+
BuildMI(NewEndTryTableBB, EndTryTable->getDebugLoc(),
2436+
TII.get(WebAssembly::UNREACHABLE));
2437+
}
2438+
}
2439+
23752440
/// Insert BLOCK/LOOP/TRY/TRY_TABLE markers at appropriate places.
23762441
void WebAssemblyCFGStackify::placeMarkers(MachineFunction &MF) {
23772442
// We allocate one more than the number of blocks in the function to
@@ -2398,13 +2463,17 @@ void WebAssemblyCFGStackify::placeMarkers(MachineFunction &MF) {
23982463
}
23992464
}
24002465

2401-
// Fix mismatches in unwind destinations induced by linearizing the code.
24022466
if (MCAI->getExceptionHandlingType() == ExceptionHandling::Wasm &&
24032467
MF.getFunction().hasPersonalityFn()) {
2404-
bool MismatchFixed = fixCallUnwindMismatches(MF);
2405-
MismatchFixed |= fixCatchUnwindMismatches(MF);
2406-
if (MismatchFixed)
2407-
recalculateScopeTops(MF);
2468+
const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
2469+
// Add an 'unreachable' after 'end_try_table's.
2470+
addUnreachableAfterTryTables(MF, TII);
2471+
// Fix mismatches in unwind destinations induced by linearizing the code.
2472+
fixCallUnwindMismatches(MF);
2473+
fixCatchUnwindMismatches(MF);
2474+
// addUnreachableAfterTryTables and fixUnwindMismatches create new BBs, so
2475+
// we need to recalculate ScopeTops.
2476+
recalculateScopeTops(MF);
24082477
}
24092478
}
24102479

llvm/lib/Target/WebAssembly/WebAssemblyInstrControl.td

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ defm THROW_REF : I<(outs), (ins EXNREF:$exn), (outs), (ins), [],
144144
"throw_ref \t$exn", "throw_ref", 0x0a>;
145145
} // isTerminator = 1, hasCtrlDep = 1, isBarrier = 1
146146

147-
// Region within which an exception is caught: try / end_try
147+
// Region within which an exception is caught: try_table / end_try_table
148148
let Uses = [VALUE_STACK], Defs = [VALUE_STACK] in {
149149
defm TRY_TABLE : I<(outs), (ins Signature:$sig, variable_ops),
150150
(outs), (ins Signature:$sig, catch_list:$cal), [],

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,7 @@ invoke.cont: ; preds = %entry
857857
; NOSORT: loop
858858
; NOSORT: call foo
859859
; NOSORT: end_loop
860+
; NOSORT: unreachable
860861
; NOSORT: end_block # label[[L3]]:
861862
; NOSORT: throw_ref
862863
; NOSORT: end_try_table

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ body: |
7878
EH_LABEL <mcsymbol .Ltmp2>
7979
CATCHRET %bb.2, %bb.1, implicit-def dead $arguments
8080
81-
; CHECK: bb.2
81+
; This BB should remain (it will be renumbered to bb.1)
82+
; CHECK: bb.1
8283
bb.2:
8384
; predecessors: %bb.0, %bb.1
8485
RETURN implicit-def dead $arguments

llvm/test/CodeGen/WebAssembly/exception.ll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ define void @throw(ptr %p) {
3838
; CHECK: call foo
3939
; CHECK: br 2
4040
; CHECK: end_try_table
41+
; CHECK: unreachable
4142
; CHECK: end_block
4243
; CHECK: local.set 2
4344
; CHECK: local.get 0

0 commit comments

Comments
 (0)