Skip to content

Commit 834debf

Browse files
committed
[WebAssembly] Fix block marker placing after fixUnwindMismatches
Summary: This fixes a few things that are connected. It is very hard to provide an independent test case for each of those fixes, because they are interconnected and sometimes one masks another. The provided test case triggers some of those bugs below but not all. --- 1. Background: `placeBlockMarker` takes a BB, and if the BB is a destination of some branch, it places `end_block` marker there, and computes the nearest common dominator of all predecessors (what we call 'header') and places a `block` marker there. When we first place markers, we traverse BBs from top to bottom. For example, when there are 5 BBs A, B, C, D, and E and B, D, and E are branch destinations, if mark the BB given to `placeBlockMarker` with `*` and draw a rectangle representing the border of `block` and `end_block` markers, the process is going to look like ``` ------- ----- |-----| --- |---| ||---|| |A| ||A|| |||A||| --- --> |---| --> ||---|| *B | B | || B || C | C | || C || D ----- |-----| E *D | D | E ------- *E ``` which means when we first place markers, we go from inner to outer scopes. So when we place a `block` marker, if the header already contains other `block` or `try` marker, it has to belong to an inner scope, so the existing `block`/`try` markers should go _after_ the new marker. This was the assumption we had. But after placing all markers we run `fixUnwindMismatches` function. There we do some control flow transformation and create some branches, and we call `placeBlockMarker` again to place `block`/`end_block` markers for those newly created branches. We can't assume that we are traversing branch destination BBs from top to bottom now because we are basically inserting some new markers in the middle of existing markers. Fix: In `placeBlockMarker`, we don't have the assumption that the BB given is in the order of top to bottom, and when placing `block` markers, calculates whether existing `block` or `try` markers are inner or outer scopes with respect to the current scope. --- 2. Background: In `fixUnwindMismatches`, when there is a call whose correct unwind destination mismatches the current destination after initially placing `try` markers, we wrap that with a new nested `try`/`catch`/`end` and jump to the correct handler within the new `catch`. The correct handler code is split as a separate BB from its original EH pad so it can be branched to. Here's an example: - Before ``` mbb: call @foo <- Unwind destination mismatch! wrong-ehpad: catch ... cont: end_try ... correct-ehpad: catch [handler code] ``` - After ``` mbb: try (new) call @foo nested-ehpad: (new) catch (new) local.set n / drop (new) br %handleri (new) nested-end: (new) end_try (new) wrong-ehpad: catch ... cont: end_try ... correct-ehpad: catch local.set n / drop (new) handler: (new) end_try [handler code] ``` Note that after this transformation, it is possible there are no calls to actually unwind to `correct-ehpad` here. `call @foo` now branches to `handler`, and there can be no other calls to unwind to `correct-ehpad`. In this case `correct-ehpad` does not have any predecessors anymore. This can cause a bug in `placeBlockMarker`, because we may need to place `end_block` marker in `handler`, and `placeBlockMarker` computes the nearest common dominator of all predecessors. If one of `handler`'s predecessor (here `correct-ehpad`) does not have any predecessors, i.e., no way of reaching it, we cannot correctly compute the common dominator of predecessors of `handler`, and end up placing no `block`/`end` markers. This bug actually sometimes masks the bug 1. Fix: When we have an EH pad that does not have any predecessors after this transformation, deletes all its successors, so that its successors don't have any dangling predecessors. --- 3. Background: Actually the `handler` BB in the example shown in bug 2 doesn't need `end_block` marker, despite it being a new branch destination, because it already has `end_try` marker which can serve the same purpose. I just put that example there for an illustration purpose. There is a case we actually need to place `end_block` marker: when the branch dest is the appendix BB. The appendix BB is created when there is a call that is supposed to unwind to the caller ends up unwinding to a wrong EH pad. In this case we also wrap the call with a nested `try`/`catch`/`end`, create an 'appendix' BB at the very end of the function, and branch to that BB, where we rethrow the exception to the caller. Fix: When we don't actually need to place block markers, we don't. --- 4. In case we fall through to the continuation BB after the catch block, after extracting handler code in `fixUnwindMismatches` (refer to bug 2 for an example), we now have to add a branch to it to bypass the handler. - Before ``` try ... (falls through to 'cont') catch handler body end <-- cont ``` - After ``` try ... br %cont (new) catch end handler body <-- cont ``` The problem is, we haven't been placing a new `end_block` marker in the `cont` BB in this case. We should, and this fixes it. But it is hard to provide a test case that triggers this bug, because the current compilation pipeline from .ll to .s does not generate this kind of code; we always have a `br` after `invoke`. But code without `br` is still valid, and we can have that kind of code if we have some pipeline changes or optimizations later. Even mir test cases cannot trigger this part for now, because we don't encode auxiliary EH-related data structures (such as `WasmEHFuncInfo`) in mir now. Those functionalities can be added later, but I don't think we should block this fix on that. Reviewers: dschuff Subscribers: sbc100, jgravelle-google, hiraditya, sunfish, llvm-commits Tags: #llvm Differential Revision: https://reviews.llvm.org/D79324
1 parent d5eb7ff commit 834debf

File tree

2 files changed

+78
-10
lines changed

2 files changed

+78
-10
lines changed

llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -277,11 +277,19 @@ void WebAssemblyCFGStackify::placeBlockMarker(MachineBasicBlock &MBB) {
277277
#endif
278278
}
279279

280-
// All previously inserted BLOCK/TRY markers should be after the BLOCK
281-
// because they are all nested blocks.
280+
// If there is a previously placed BLOCK/TRY marker and its corresponding
281+
// END marker is before the current BLOCK's END marker, that should be
282+
// placed after this BLOCK. Otherwise it should be placed before this BLOCK
283+
// marker.
282284
if (MI.getOpcode() == WebAssembly::BLOCK ||
283-
MI.getOpcode() == WebAssembly::TRY)
284-
AfterSet.insert(&MI);
285+
MI.getOpcode() == WebAssembly::TRY) {
286+
if (BeginToEnd[&MI]->getParent()->getNumber() <= MBB.getNumber())
287+
AfterSet.insert(&MI);
288+
#ifndef NDEBUG
289+
else
290+
BeforeSet.insert(&MI);
291+
#endif
292+
}
285293

286294
#ifndef NDEBUG
287295
// All END_(BLOCK|LOOP|TRY) markers should be before the BLOCK.
@@ -866,6 +874,10 @@ bool WebAssemblyCFGStackify::fixUnwindMismatches(MachineFunction &MF) {
866874
// In new CFG, <destination to branch to, register containing exnref>
867875
DenseMap<MachineBasicBlock *, unsigned> BrDestToExnReg;
868876

877+
// Destinations for branches that will be newly added, for which a new
878+
// BLOCK/END_BLOCK markers are necessary.
879+
SmallVector<MachineBasicBlock *, 8> BrDests;
880+
869881
// Gather possibly throwing calls (i.e., previously invokes) whose current
870882
// unwind destination is not the same as the original CFG.
871883
for (auto &MBB : reverse(MF)) {
@@ -1075,6 +1087,7 @@ bool WebAssemblyCFGStackify::fixUnwindMismatches(MachineFunction &MF) {
10751087
? DebugLoc()
10761088
: EHPadLayoutPred->rbegin()->getDebugLoc();
10771089
BuildMI(EHPadLayoutPred, DL, TII.get(WebAssembly::BR)).addMBB(Cont);
1090+
BrDests.push_back(Cont);
10781091
}
10791092
}
10801093

@@ -1178,8 +1191,16 @@ bool WebAssemblyCFGStackify::fixUnwindMismatches(MachineFunction &MF) {
11781191

11791192
// Fix predecessor-successor relationship.
11801193
NestedCont->transferSuccessors(MBB);
1181-
if (EHPad)
1194+
if (EHPad) {
11821195
NestedCont->removeSuccessor(EHPad);
1196+
// If EHPad does not have any predecessors left after removing
1197+
// NextedCont predecessor, remove its successor too, because this EHPad
1198+
// is not reachable from the entry BB anyway. We can't remove EHPad BB
1199+
// itself because it can contain 'catch' or 'end', which are necessary
1200+
// for keeping try-catch-end structure.
1201+
if (EHPad->pred_empty())
1202+
EHPad->removeSuccessor(BrDest);
1203+
}
11831204
MBB->addSuccessor(NestedEHPad);
11841205
MBB->addSuccessor(NestedCont);
11851206
NestedEHPad->addSuccessor(BrDest);
@@ -1211,10 +1232,14 @@ bool WebAssemblyCFGStackify::fixUnwindMismatches(MachineFunction &MF) {
12111232
// Recompute the dominator tree.
12121233
getAnalysis<MachineDominatorTree>().runOnMachineFunction(MF);
12131234

1214-
// Place block markers for newly added branches.
1215-
SmallVector <MachineBasicBlock *, 8> BrDests;
1216-
for (auto &P : BrDestToTryRanges)
1217-
BrDests.push_back(P.first);
1235+
// Place block markers for newly added branches, if necessary.
1236+
1237+
// If we've created an appendix BB and a branch to it, place a block/end_block
1238+
// marker for that. For some new branches, those branch destination BBs start
1239+
// with a hoisted end_try marker, so we don't need a new marker there.
1240+
if (AppendixBB)
1241+
BrDests.push_back(AppendixBB);
1242+
12181243
llvm::sort(BrDests,
12191244
[&](const MachineBasicBlock *A, const MachineBasicBlock *B) {
12201245
auto ANum = A->getNumber();

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

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -850,8 +850,51 @@ terminate7: ; preds = %ehcleanup
850850
unreachable
851851
}
852852

853+
; We don't need to call placeBlockMarker after fixUnwindMismatches unless the
854+
; destination is the appendix BB at the very end. This should not crash.
855+
define void @test16(i32* %p, i32 %a, i32 %b) personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
856+
entry:
857+
br label %loop
858+
859+
loop:
860+
invoke void @foo()
861+
to label %bb0 unwind label %catch.dispatch0
862+
863+
bb0:
864+
%cmp = icmp ne i32 %a, %b
865+
br i1 %cmp, label %bb1, label %last
866+
867+
bb1: ; preds = %bb0
868+
invoke void @bar()
869+
to label %try.cont unwind label %catch.dispatch1
870+
871+
catch.dispatch0: ; preds = %loop
872+
%0 = catchswitch within none [label %catch.start0] unwind to caller
873+
874+
catch.start0: ; preds = %catch.dispatch0
875+
%1 = catchpad within %0 [i8* null]
876+
%2 = call i8* @llvm.wasm.get.exception(token %1)
877+
%3 = call i32 @llvm.wasm.get.ehselector(token %1)
878+
catchret from %1 to label %try.cont
879+
880+
catch.dispatch1: ; preds = %bb1
881+
%4 = catchswitch within none [label %catch.start1] unwind to caller
882+
883+
catch.start1: ; preds = %catch.dispatch1
884+
%5 = catchpad within %4 [i8* null]
885+
%6 = call i8* @llvm.wasm.get.exception(token %5)
886+
%7 = call i32 @llvm.wasm.get.ehselector(token %5)
887+
catchret from %5 to label %try.cont
888+
889+
try.cont: ; preds = %catch.start, %loop
890+
br label %loop
891+
892+
last:
893+
ret void
894+
}
895+
853896
; Check if the unwind destination mismatch stats are correct
854-
; NOSORT-STAT: 15 wasm-cfg-stackify - Number of EH pad unwind mismatches found
897+
; NOSORT-STAT: 16 wasm-cfg-stackify - Number of EH pad unwind mismatches found
855898

856899
declare void @foo()
857900
declare void @bar()

0 commit comments

Comments
 (0)