Skip to content

Commit d7d17c9

Browse files
authored
Vacuum code leading up to a trap in TrapsNeverHappen mode (#5228)
This adds two rules to vacuum in TNH mode: if (..) trap() => if (..) {} { stuff, trap() } => {} That is, we assume traps never happen so an if will not branch to one, and code right before a trap can be assumed to not execute. Together, we should be removing practically all possible code in TNH mode (though we could also add support for br_if etc.).
1 parent 7c3a469 commit d7d17c9

File tree

3 files changed

+515
-3
lines changed

3 files changed

+515
-3
lines changed

src/passes/Vacuum.cpp

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,55 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum>> {
130130
}
131131

132132
void visitBlock(Block* curr) {
133+
auto& list = curr->list;
134+
135+
// If traps are assumed to never happen, we can remove code on paths that
136+
// must reach a trap:
137+
//
138+
// (block
139+
// (i32.store ..)
140+
// (br_if ..) ;; execution branches here, so the first store remains
141+
// (i32.store ..) ;; this store can be removed
142+
// (unreachable);
143+
// )
144+
//
145+
// For this to be useful we need to have at least 2 elements: something to
146+
// remove, and an unreachable.
147+
if (getPassOptions().trapsNeverHappen && list.size() >= 2) {
148+
// Go backwards. When we find a trap, mark the things before it as heading
149+
// to a trap.
150+
auto headingToTrap = false;
151+
for (int i = list.size() - 1; i >= 0; i--) {
152+
if (list[i]->is<Unreachable>()) {
153+
headingToTrap = true;
154+
continue;
155+
}
156+
157+
if (!headingToTrap) {
158+
continue;
159+
}
160+
161+
// Check if we may no longer be heading to a trap. Two situations count
162+
// here: Control flow might branch, or we might call (since a call might
163+
// reach an import; see notes on that in pass.h:trapsNeverHappen).
164+
//
165+
// We also cannot remove a pop as it is necessary for structural
166+
// reasons.
167+
EffectAnalyzer effects(getPassOptions(), *getModule(), list[i]);
168+
if (effects.transfersControlFlow() || effects.calls ||
169+
effects.danglingPop) {
170+
headingToTrap = false;
171+
continue;
172+
}
173+
174+
// This code can be removed! Turn it into a nop, and leave it for the
175+
// code lower down to finish cleaning up.
176+
ExpressionManipulator::nop(list[i]);
177+
}
178+
}
179+
133180
// compress out nops and other dead code
134181
int skip = 0;
135-
auto& list = curr->list;
136182
size_t size = list.size();
137183
for (size_t z = 0; z < size; z++) {
138184
auto* child = list[z];
@@ -210,6 +256,38 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum>> {
210256
return;
211257
}
212258
// from here on, we can assume the condition executed
259+
260+
// In trapsNeverHappen mode, a definitely-trapping arm can be assumed to not
261+
// happen. Such conditional code can be assumed to never be reached in this
262+
// mode.
263+
//
264+
// Ignore the case of an unreachable if, such as having both arms be
265+
// unreachable. In that case we'd need to fix up the IR to avoid changing
266+
// the type; leave that for DCE to simplify first. After checking that
267+
// curr->type != unreachable, we can assume that only one of the arms is
268+
// unreachable (at most).
269+
if (getPassOptions().trapsNeverHappen && curr->type != Type::unreachable) {
270+
auto optimizeArm = [&](Expression* arm, Expression* otherArm) {
271+
if (!arm->is<Unreachable>()) {
272+
return false;
273+
}
274+
Builder builder(*getModule());
275+
Expression* rep = builder.makeDrop(curr->condition);
276+
if (otherArm) {
277+
rep = builder.makeSequence(rep, otherArm);
278+
}
279+
replaceCurrent(rep);
280+
return true;
281+
};
282+
283+
// As mentioned above, do not try to optimize both arms; leave that case
284+
// for DCE.
285+
if (optimizeArm(curr->ifTrue, curr->ifFalse) ||
286+
(curr->ifFalse && optimizeArm(curr->ifFalse, curr->ifTrue))) {
287+
return;
288+
}
289+
}
290+
213291
if (curr->ifFalse) {
214292
if (curr->ifFalse->is<Nop>()) {
215293
curr->ifFalse = nullptr;

test/lit/passes/vacuum-tnh-mvp.wast

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
2+
;; RUN: wasm-opt %s --vacuum -tnh -S -o - | filecheck %s
3+
4+
(module
5+
(memory 1 1)
6+
7+
;; CHECK: (func $block-unreachable-but-call
8+
;; CHECK-NEXT: (i32.store
9+
;; CHECK-NEXT: (i32.const 0)
10+
;; CHECK-NEXT: (i32.const 1)
11+
;; CHECK-NEXT: )
12+
;; CHECK-NEXT: (call $block-unreachable-but-call)
13+
;; CHECK-NEXT: (unreachable)
14+
;; CHECK-NEXT: )
15+
(func $block-unreachable-but-call
16+
;; A call cannot be removed, even if it leads to a trap, since it might have
17+
;; non-trap effects (like mayNotReturn). We can remove the store after it,
18+
;; though.
19+
;;
20+
;; This duplicates a test in vacuum-tnh but in MVP mode (to check for a
21+
;; possible bug with the throws effect which varies based on features).
22+
(i32.store
23+
(i32.const 0)
24+
(i32.const 1)
25+
)
26+
(call $block-unreachable-but-call)
27+
(i32.store
28+
(i32.const 2)
29+
(i32.const 3)
30+
)
31+
(unreachable)
32+
)
33+
)

0 commit comments

Comments
 (0)