Skip to content

Commit 2e15efd

Browse files
aheejinmemfrob
authored and
memfrob
committed
[WebAssembly] Fix incorrect grouping and sorting of exceptions
This CL is not big but contains changes that span multiple analyses and passes. This description is very long because it tries to explain basics on what each pass/analysis does and why we need this change on top of that. Please feel free to skip parts that are not necessary for your understanding. --- `WasmEHFuncInfo` contains the mapping of <EH pad, the EH pad's next unwind destination>. The value (unwind dest) here is where an exception should end up when it is not caught by the key (EH pad). We record this info in WasmEHPrepare to fix catch mismatches, because the CFG itself does not have this info. A CFG only contains BBs and predecessor-successor relationship between them, but in `WasmEHFuncInfo` the unwind destination BB is not necessarily a successor or the key EH pad BB. Their relationship can be intuitively explained by this C++ code snippet: ``` try { try { foo(); } catch (int) { // EH pad ... } } catch (...) { // unwind destination } ``` So when `foo()` throws, it goes to `catch (int)` first. But if it is not caught by it, it ends up in the next unwind destination `catch (...)`. This unwind destination is what you see in `catchswitch`'s `unwind label %bb` part. --- `WebAssemblyExceptionInfo` groups exceptions so that they can be sorted continuously together in CFGSort, as we do for loops. What this analysis does is very simple: it creates a single `WebAssemblyException` per EH pad, and all BBs that are dominated by that EH pad are included in this exception. We also identify subexception relationship in this way: if EHPad A domiantes EHPad B, EHPad B's exception is a subexception of EHPad A's exception. This simple rule turns out to be incorrect in some cases. In `WasmEHFuncInfo`, if EHPad A's unwind destination is EHPad B, it means semantically EHPad B should not be included in EHPad A's exception, because it does not make sense to rethrow/delegate to an inner scope. This is what happened in CFGStackify as a result of this: ``` try try catch ... <- %dest_bb is among here! end delegate %dest_bb ``` So this patch adds a phase in `WebAssemblyExceptionInfo::recalculate` to make sure excptions' unwind destinations are not subexceptions of their unwind sources in `WasmEHFuncInfo`. But this alone does not prevent `dest_bb` in the example above from being sorted within the inner `catch`'s exception, even if its exception is not a subexception of that `catch`'s exception anymore, because of how CFGSort works, which will be explained below. --- CFGSort places BBs within the same `SortRegion` (loop or exception) continuously together so they can be demarcated with `loop`-`end_loop` or `catch`-`end_try` in CFGStackify. `SortRegion` is a wrapper for one of `MachineLoop` or `WebAssemblyException`. `SortRegionInfo` already does some complicated things because there discrepancies between those two data structures. `WebAssemblyException` is what we control, and it is defined as an EH pad as its header and BBs dominated by the header as its BBs (with a newly added exception of unwind destinations explained in the previous paragraph). But `MachineLoop` is an LLVM data structure and uses the standard loop detection algorithm. So by the algorithm, BBs that are 1. dominated by the loop header and 2. have a path back to its header. Because of the second condition, many BBs that are dominated by the loop header are not included in the loop. So BBs that contain `return` or branches to outside of the loop are not technically included in `MachineLoop`, but they can be sorted together with the loop with no problem. Maybe to relax the condition, in CFGSort, when we are in a `SortRegion` we allow sorting of not only BBs that belong to the current innermost region but also BBs that are by the current region header. (This was written this way from the first version written by Dan, when only loops existed.) But now, we have cases in exceptions when EHPad B is the unwind destination for EHPad A, even if EHPad B is dominated by EHPad A it should not be included in EHPad A's exception, and should not be sorted within EHPad A. One way to make things work, at least correctly, is change `dominates` condition to `contains` condition for `SortRegion` when sorting BBs, but this will change compilation results for existing non-EH code and I can't be sure it will not degrade performance or code size. I think it will degrade performance because it will force many BBs dominated by a loop, which don't have the path back to the header, to be placed after the loop and it will likely to create more branches and blocks. So this does a little hacky check when adding BBs to `Preferred` list: (`Preferred` list is a ready list. CFGSort maintains ready list in two priority queues: `Preferred` and `Ready`. I'm not very sure why, but it was written that way from the beginning. BBs are first added to `Preferred` list and then some of them are pushed to `Ready` list, so here we only need to guard condition for `Preferred` list.) When adding a BB to `Preferred` list, we check if that BB is an unwind destination of another BB. To do this, this adds the reverse mapping, `UnwindDestToSrc`, and getter methods to `WasmEHFuncInfo`. And if the BB is an unwind destination, it checks if the current stack of regions (`Entries`) contains its source BB by traversing the stack backwards. If we find its unwind source in there, we add the BB to its `Deferred` list, to make sure that unwind destination BB is added to `Preferred` list only after that region with the unwind source BB is sorted and popped from the stack. --- This does not contain a new test that crashes because of this bug, but this fix changes the result for one of existing test case. This test case didn't crash because it fortunately didn't contain `delegate` to the incorrectly placed unwind destination BB. Fixes emscripten-core/emscripten#13514. Reviewed By: dschuff, tlively Differential Revision: https://reviews.llvm.org/D97247
1 parent 9352f31 commit 2e15efd

File tree

7 files changed

+126
-30
lines changed

7 files changed

+126
-30
lines changed

llvm/include/llvm/CodeGen/WasmEHFuncInfo.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,27 +32,42 @@ struct WasmEHFuncInfo {
3232
// When there is an entry <A, B>, if an exception is not caught by A, it
3333
// should next unwind to the EH pad B.
3434
DenseMap<BBOrMBB, BBOrMBB> SrcToUnwindDest;
35+
DenseMap<BBOrMBB, BBOrMBB> UnwindDestToSrc; // reverse map
3536

3637
// Helper functions
3738
const BasicBlock *getUnwindDest(const BasicBlock *BB) const {
3839
return SrcToUnwindDest.lookup(BB).get<const BasicBlock *>();
3940
}
41+
const BasicBlock *getUnwindSrc(const BasicBlock *BB) const {
42+
return UnwindDestToSrc.lookup(BB).get<const BasicBlock *>();
43+
}
4044
void setUnwindDest(const BasicBlock *BB, const BasicBlock *Dest) {
4145
SrcToUnwindDest[BB] = Dest;
46+
UnwindDestToSrc[Dest] = BB;
4247
}
4348
bool hasUnwindDest(const BasicBlock *BB) const {
4449
return SrcToUnwindDest.count(BB);
4550
}
51+
bool hasUnwindSrc(const BasicBlock *BB) const {
52+
return UnwindDestToSrc.count(BB);
53+
}
4654

4755
MachineBasicBlock *getUnwindDest(MachineBasicBlock *MBB) const {
4856
return SrcToUnwindDest.lookup(MBB).get<MachineBasicBlock *>();
4957
}
58+
MachineBasicBlock *getUnwindSrc(MachineBasicBlock *MBB) const {
59+
return UnwindDestToSrc.lookup(MBB).get<MachineBasicBlock *>();
60+
}
5061
void setUnwindDest(MachineBasicBlock *MBB, MachineBasicBlock *Dest) {
5162
SrcToUnwindDest[MBB] = Dest;
63+
UnwindDestToSrc[Dest] = MBB;
5264
}
5365
bool hasUnwindDest(MachineBasicBlock *MBB) const {
5466
return SrcToUnwindDest.count(MBB);
5567
}
68+
bool hasUnwindSrc(MachineBasicBlock *MBB) const {
69+
return UnwindDestToSrc.count(MBB);
70+
}
5671
};
5772

5873
// Analyze the IR in the given function to build WasmEHFuncInfo.

llvm/lib/CodeGen/SelectionDAG/FunctionLoweringInfo.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,13 @@ void FunctionLoweringInfo::set(const Function &fn, MachineFunction &mf,
341341
NewMap[MBBMap[Src]] = MBBMap[Dst];
342342
}
343343
EHInfo.SrcToUnwindDest = std::move(NewMap);
344+
NewMap.clear();
345+
for (auto &KV : EHInfo.UnwindDestToSrc) {
346+
const auto *Src = KV.first.get<const BasicBlock *>();
347+
const auto *Dst = KV.second.get<const BasicBlock *>();
348+
NewMap[MBBMap[Src]] = MBBMap[Dst];
349+
}
350+
EHInfo.UnwindDestToSrc = std::move(NewMap);
344351
}
345352
}
346353

llvm/lib/Target/WebAssembly/WebAssemblyCFGSort.cpp

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "llvm/CodeGen/MachineLoopInfo.h"
3030
#include "llvm/CodeGen/MachineRegisterInfo.h"
3131
#include "llvm/CodeGen/Passes.h"
32+
#include "llvm/CodeGen/WasmEHFuncInfo.h"
3233
#include "llvm/Support/Debug.h"
3334
#include "llvm/Support/raw_ostream.h"
3435
using namespace llvm;
@@ -218,6 +219,7 @@ static void sortBlocks(MachineFunction &MF, const MachineLoopInfo &MLI,
218219
CompareBlockNumbersBackwards>
219220
Ready;
220221

222+
const auto *EHInfo = MF.getWasmEHFuncInfo();
221223
SortRegionInfo SRI(MLI, WEI);
222224
SmallVector<Entry, 4> Entries;
223225
for (MachineBasicBlock *MBB = &MF.front();;) {
@@ -245,8 +247,33 @@ static void sortBlocks(MachineFunction &MF, const MachineLoopInfo &MLI,
245247
if (SuccL->getHeader() == Succ && SuccL->contains(MBB))
246248
continue;
247249
// Decrement the predecessor count. If it's now zero, it's ready.
248-
if (--NumPredsLeft[Succ->getNumber()] == 0)
250+
if (--NumPredsLeft[Succ->getNumber()] == 0) {
251+
// When we are in a SortRegion, we allow sorting of not only BBs that
252+
// belong to the current (innermost) region but also BBs that are
253+
// dominated by the current region header. But we should not do this for
254+
// exceptions because there can be cases in which, for example:
255+
// EHPad A's unwind destination (where the exception lands when it is
256+
// not caught by EHPad A) is EHPad B, so EHPad B does not belong to the
257+
// exception dominated by EHPad A. But EHPad B is dominated by EHPad A,
258+
// so EHPad B can be sorted within EHPad A's exception. This is
259+
// incorrect because we may end up delegating/rethrowing to an inner
260+
// scope in CFGStackify. So here we make sure those unwind destinations
261+
// are deferred until their unwind source's exception is sorted.
262+
if (EHInfo && EHInfo->hasUnwindSrc(Succ)) {
263+
auto *UnwindSrc = EHInfo->getUnwindSrc(Succ);
264+
bool IsDeferred = false;
265+
for (Entry &E : reverse(Entries)) {
266+
if (E.TheRegion->getHeader() == UnwindSrc) {
267+
E.Deferred.push_back(Succ);
268+
IsDeferred = true;
269+
break;
270+
}
271+
}
272+
if (IsDeferred)
273+
continue;
274+
}
249275
Preferred.push(Succ);
276+
}
250277
}
251278
// Determine the block to follow MBB. First try to find a preferred block,
252279
// to preserve the original block order when possible.

llvm/lib/Target/WebAssembly/WebAssemblyExceptionInfo.cpp

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "llvm/ADT/PostOrderIterator.h"
1818
#include "llvm/CodeGen/MachineDominanceFrontier.h"
1919
#include "llvm/CodeGen/MachineDominators.h"
20+
#include "llvm/CodeGen/WasmEHFuncInfo.h"
2021
#include "llvm/InitializePasses.h"
2122

2223
using namespace llvm;
@@ -39,12 +40,13 @@ bool WebAssemblyExceptionInfo::runOnMachineFunction(MachineFunction &MF) {
3940
releaseMemory();
4041
auto &MDT = getAnalysis<MachineDominatorTree>();
4142
auto &MDF = getAnalysis<MachineDominanceFrontier>();
42-
recalculate(MDT, MDF);
43+
recalculate(MF, MDT, MDF);
4344
return false;
4445
}
4546

4647
void WebAssemblyExceptionInfo::recalculate(
47-
MachineDominatorTree &MDT, const MachineDominanceFrontier &MDF) {
48+
MachineFunction &MF, MachineDominatorTree &MDT,
49+
const MachineDominanceFrontier &MDF) {
4850
// Postorder traversal of the dominator tree.
4951
SmallVector<std::unique_ptr<WebAssemblyException>, 8> Exceptions;
5052
for (auto DomNode : post_order(&MDT)) {
@@ -56,6 +58,49 @@ void WebAssemblyExceptionInfo::recalculate(
5658
Exceptions.push_back(std::move(WE));
5759
}
5860

61+
// WasmEHFuncInfo contains a map of <catchpad, its next unwind destination>,
62+
// which means, if an exception is not caught by the catchpad, it should end
63+
// up in the next unwind destination stored in this data structure. (It is
64+
// written as catchswitch's 'unwind' destination in ll files.) The below is an
65+
// intuitive example of their relationship in C++ code:
66+
// try {
67+
// try {
68+
// } catch (int) { // catchpad
69+
// ... // this catch (int) { ... } is grouped as an exception
70+
// }
71+
// } catch (...) { // next unwind destination
72+
// }
73+
// (The example is try-catches for illustration purpose, but the unwind
74+
// destination can be also a cleanuppad generated by destructor calls.) So the
75+
// unwind destination is in the outside of the catchpad's exception.
76+
//
77+
// We group exceptions in this analysis simply by including all BBs dominated
78+
// by an EH pad. But in case the EH pad's unwind destination does not have any
79+
// children outside of the exception, that unwind destination ends up also
80+
// being dominated by the EH pad and included in the exception, which is not
81+
// semantically correct, because it unwinds/rethrows into an inner scope.
82+
//
83+
// Here we extract those unwind destinations from their (incorrect) parent
84+
// exception. Note that the unwind destinations may not be an immediate
85+
// children of the parent exception, so we have to traverse the parent chain.
86+
const auto *EHInfo = MF.getWasmEHFuncInfo();
87+
for (auto &MBB : MF) {
88+
if (!MBB.isEHPad())
89+
continue;
90+
auto *EHPad = &MBB;
91+
if (!EHInfo->hasUnwindDest(EHPad))
92+
continue;
93+
auto *UnwindDest = EHInfo->getUnwindDest(EHPad);
94+
auto *WE = getExceptionFor(EHPad);
95+
auto *UnwindDestWE = getExceptionFor(UnwindDest);
96+
if (WE->contains(UnwindDestWE)) {
97+
if (WE->getParentException())
98+
UnwindDestWE->setParentException(WE->getParentException());
99+
else
100+
UnwindDestWE->setParentException(nullptr);
101+
}
102+
}
103+
59104
// Add BBs to exceptions
60105
for (auto DomNode : post_order(&MDT)) {
61106
MachineBasicBlock *MBB = DomNode->getBlock();

llvm/lib/Target/WebAssembly/WebAssemblyExceptionInfo.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ class WebAssemblyExceptionInfo final : public MachineFunctionPass {
137137

138138
bool runOnMachineFunction(MachineFunction &) override;
139139
void releaseMemory() override;
140-
void recalculate(MachineDominatorTree &MDT,
140+
void recalculate(MachineFunction &MF, MachineDominatorTree &MDT,
141141
const MachineDominanceFrontier &MDF);
142142
void getAnalysisUsage(AnalysisUsage &AU) const override;
143143

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

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -97,40 +97,42 @@ try.cont: ; preds = %catch, %catch2, %en
9797

9898
; CHECK-LABEL: test1
9999
; CHECK: try
100-
; CHECK: call foo
100+
; CHECK: call foo
101101
; CHECK: catch
102102
; CHECK: block
103103
; CHECK: block
104-
; CHECK: br_if 0, {{.*}} # 0: down to label[[L0:[0-9]+]]
105-
; CHECK: call $drop=, __cxa_begin_catch
104+
; CHECK: br_if 0, {{.*}} # 0: down to label[[L0:[0-9+]]]
105+
; CHECK: call $drop=, __cxa_begin_catch, $0
106106
; CHECK: try
107-
; CHECK: call foo
108-
; CHECK: br 2 # 2: down to label[[L1:[0-9]+]]
109-
; CHECK: catch
110107
; CHECK: try
108+
; CHECK: call foo
109+
; CHECK: br 3 # 3: down to label[[L1:[0-9+]]]
110+
; CHECK: catch
111111
; CHECK: block
112-
; CHECK: br_if 0, {{.*}} # 0: down to label[[L2:[0-9]+]]
113-
; CHECK: call $drop=, __cxa_begin_catch
114-
; CHECK: try
115-
; CHECK: call foo
116-
; CHECK: br 2 # 2: down to label[[L3:[0-9]+]]
117-
; CHECK: catch
118-
; CHECK: call __cxa_end_catch
119-
; CHECK: rethrow 0 # down to catch[[C0:[0-9]+]]
120-
; CHECK: end_try
121-
; CHECK: end_block # label[[L2]]:
122-
; CHECK: rethrow 1 # down to catch[[C0]]
123-
; CHECK: catch_all # catch[[C0]]:
124-
; CHECK: call __cxa_end_catch
125-
; CHECK: rethrow 0 # to caller
126-
; CHECK: end_try # label[[L3]]:
127-
; CHECK: call __cxa_end_catch
128-
; CHECK: br 2 # 2: down to label[[L1]]
112+
; CHECK: block
113+
; CHECK: br_if 0, {{.*}} # 0: down to label[[L2:[0-9+]]]
114+
; CHECK: call $drop=, __cxa_begin_catch
115+
; CHECK: try
116+
; CHECK: call foo
117+
; CHECK: br 2 # 2: down to label[[L3:[0-9+]]]
118+
; CHECK: catch_all
119+
; CHECK: call __cxa_end_catch
120+
; CHECK: rethrow 0 # down to catch[[L4:[0-9+]]]
121+
; CHECK: end_try
122+
; CHECK: end_block # label[[L2]]:
123+
; CHECK: rethrow 1 # down to catch[[L4]]
124+
; CHECK: end_block # label[[L3]]:
125+
; CHECK: call __cxa_end_catch
126+
; CHECK: br 3 # 3: down to label[[L1]]
127+
; CHECK: end_try
128+
; CHECK: catch_all # catch[[L4]]:
129+
; CHECK: call __cxa_end_catch
130+
; CHECK: rethrow 0 # to caller
129131
; CHECK: end_try
130132
; CHECK: end_block # label[[L0]]:
131133
; CHECK: rethrow 1 # to caller
132134
; CHECK: end_block # label[[L1]]:
133-
; CHECK: call __cxa_end_catch
135+
; CHECK: call __cxa_end_catch
134136
; CHECK: end_try
135137
define void @test1() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
136138
entry:

llvm/unittests/Target/WebAssembly/WebAssemblyExceptionInfoTest.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ body: |
169169
MachineDominanceFrontier MDF;
170170
MDT.runOnMachineFunction(*MF);
171171
MDF.getBase().analyze(MDT.getBase());
172-
WEI.recalculate(MDT, MDF);
172+
WEI.recalculate(*MF, MDT, MDF);
173173

174174
// Exception info structure:
175175
// |- bb2 (ehpad), bb3, bb4, bb5, bb6, bb8, bb9, bb10
@@ -344,7 +344,7 @@ body: |
344344
MachineDominanceFrontier MDF;
345345
MDT.runOnMachineFunction(*MF);
346346
MDF.getBase().analyze(MDT.getBase());
347-
WEI.recalculate(MDT, MDF);
347+
WEI.recalculate(*MF, MDT, MDF);
348348

349349
// Exception info structure:
350350
// |- bb1 (ehpad), bb2, bb3, bb4, bb5, bb6, bb7, bb8, bb10, bb11, bb12

0 commit comments

Comments
 (0)