Skip to content

Commit e201819

Browse files
authored
[Wasm EH] Optimize away _ref from try_table catches when unused (#6996)
If we have (drop (block $b (result exnref) (try_table (catch_all_ref $b) then we don't really need to send the ref: it is dropped, so we can just replace catch_all_ref with catch_all and then remove the drop and the block value. MergeBlocks already had logic to remove block values, so it is the natural place to add this.
1 parent 31b4558 commit e201819

File tree

2 files changed

+267
-4
lines changed

2 files changed

+267
-4
lines changed

src/passes/MergeBlocks.cpp

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,11 @@
8383

8484
namespace wasm {
8585

86-
// Looks for reasons we can't remove the values from breaks to an origin
87-
// For example, if there is a switch targeting us, we can't do it - we can't
88-
// remove the value from other targets
86+
// Looks for reasons we can't remove the values from breaks to an origin. This
87+
// is run when we know the value sent to that block is dropped, so the value is
88+
// not needed, but some corner cases stop us (for example, if there is a switch
89+
// targeting us, we can't do it - we can't remove the value from the switch's
90+
// other targets).
8991
struct ProblemFinder
9092
: public ControlFlowWalker<ProblemFinder,
9193
UnifiedExpressionVisitor<ProblemFinder>> {
@@ -123,6 +125,39 @@ struct ProblemFinder
123125
return;
124126
}
125127

128+
if (auto* tryy = curr->dynCast<TryTable>()) {
129+
auto num = tryy->catchTags.size();
130+
for (Index i = 0; i < num; i++) {
131+
if (tryy->catchDests[i] == origin) {
132+
// This try_table branches to the origin we care about. We know the
133+
// value being sent to the block is dropped, so we'd like to stop
134+
// anything from being sent to it. We can stop a ref from being sent,
135+
// so if that is enough to remove all the values, then we can
136+
// optimize here. In other words, if this is a catch_all_ref (which
137+
// can only send a ref) or this is a catch_ref of a specific tag that
138+
// has no contents (so if we remove the ref, nothing remains), then we
139+
// can optimize, but if this is is a catch of a tag *with* contents
140+
// then those contents stop us.
141+
//
142+
// TODO: We could also support cases where the target block has
143+
// multiple values, and remove just ref at the end. That might
144+
// make more sense in TupleOptimization though as it would need
145+
// to track uses of parts of a tuple.
146+
if (!tryy->catchTags[i] ||
147+
getModule()->getTag(tryy->catchTags[i])->sig.params.size() == 0) {
148+
// There must be a ref here, otherwise there is no value being sent
149+
// at all, and we should not be running ProblemFinder at all.
150+
assert(tryy->catchRefs[i]);
151+
} else {
152+
// Anything else is a problem.
153+
foundProblem = true;
154+
return;
155+
}
156+
}
157+
}
158+
return;
159+
}
160+
126161
// Any other branch type - switch, br_on, etc. - is not handled yet.
127162
BranchUtils::operateOnScopeNameUses(curr, [&](Name& name) {
128163
if (name == origin) {
@@ -174,6 +209,18 @@ struct BreakValueDropper : public ControlFlowWalker<BreakValueDropper> {
174209
replaceCurrent(curr->value);
175210
}
176211
}
212+
213+
void visitTryTable(TryTable* curr) {
214+
auto num = curr->catchTags.size();
215+
for (Index i = 0; i < num; i++) {
216+
if (curr->catchDests[i] == origin) {
217+
// Remove the existing ref being sent.
218+
assert(curr->catchRefs[i]);
219+
curr->catchRefs[i] = false;
220+
curr->sentTypes[i] = Type::none;
221+
}
222+
}
223+
}
177224
};
178225

179226
// Checks for code after an unreachable element.
@@ -223,7 +270,8 @@ static bool optimizeDroppedBlock(Drop* drop,
223270
drop->finalize();
224271
block->list.back() = drop;
225272
}
226-
block->finalize();
273+
// Remove the old type, which was from the value we just removed.
274+
block->finalize(Type::none);
227275
return true;
228276
}
229277

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
2+
;; RUN: wasm-opt %s -all --merge-blocks -all -S -o - | filecheck %s
3+
4+
(module
5+
;; CHECK: (import "a" "b" (func $import (type $0)))
6+
(import "a" "b" (func $import))
7+
8+
;; CHECK: (tag $empty)
9+
(tag $empty)
10+
11+
;; CHECK: (tag $i32 (param i32))
12+
(tag $i32 (param i32))
13+
14+
;; CHECK: (tag $exnref (param exnref))
15+
(tag $exnref (param exnref))
16+
17+
;; CHECK: (func $drop-block-try_catch_all_ref (type $0)
18+
;; CHECK-NEXT: (block $catch
19+
;; CHECK-NEXT: (try_table (catch_all $catch)
20+
;; CHECK-NEXT: (call $import)
21+
;; CHECK-NEXT: (unreachable)
22+
;; CHECK-NEXT: )
23+
;; CHECK-NEXT: )
24+
;; CHECK-NEXT: )
25+
(func $drop-block-try_catch_all_ref
26+
;; This block is dropped, so the try_table's exnref value can be removed
27+
;; by replacing catch_all_ref with catch_all.
28+
(drop
29+
(block $catch (result exnref)
30+
(try_table (catch_all_ref $catch)
31+
(call $import)
32+
(unreachable)
33+
)
34+
)
35+
)
36+
)
37+
38+
;; CHECK: (func $drop-block-try_catch_ref (type $0)
39+
;; CHECK-NEXT: (block $catch
40+
;; CHECK-NEXT: (try_table (catch $empty $catch)
41+
;; CHECK-NEXT: (call $import)
42+
;; CHECK-NEXT: (unreachable)
43+
;; CHECK-NEXT: )
44+
;; CHECK-NEXT: )
45+
;; CHECK-NEXT: )
46+
(func $drop-block-try_catch_ref
47+
;; As above, but with catch_ref instead of catch_all_ref. We can still
48+
;; optimize.
49+
(drop
50+
(block $catch (result exnref)
51+
(try_table (catch_ref $empty $catch)
52+
(call $import)
53+
(unreachable)
54+
)
55+
)
56+
)
57+
)
58+
59+
;; CHECK: (func $drop-block-try_catch_multi (type $0)
60+
;; CHECK-NEXT: (block $catch
61+
;; CHECK-NEXT: (try_table (catch $empty $catch) (catch_all $catch)
62+
;; CHECK-NEXT: (call $import)
63+
;; CHECK-NEXT: (unreachable)
64+
;; CHECK-NEXT: )
65+
;; CHECK-NEXT: )
66+
;; CHECK-NEXT: )
67+
(func $drop-block-try_catch_multi
68+
;; As above, but with two catches, both of whom can be optimized.
69+
(drop
70+
(block $catch (result exnref)
71+
(try_table (catch_ref $empty $catch) (catch_all_ref $catch)
72+
(call $import)
73+
(unreachable)
74+
)
75+
)
76+
)
77+
)
78+
79+
;; CHECK: (func $drop-block-try_catch_all_i32 (type $0)
80+
;; CHECK-NEXT: (tuple.drop 2
81+
;; CHECK-NEXT: (block $catch (type $1) (result i32 exnref)
82+
;; CHECK-NEXT: (try_table (catch_ref $i32 $catch)
83+
;; CHECK-NEXT: (call $import)
84+
;; CHECK-NEXT: (unreachable)
85+
;; CHECK-NEXT: )
86+
;; CHECK-NEXT: )
87+
;; CHECK-NEXT: )
88+
;; CHECK-NEXT: )
89+
(func $drop-block-try_catch_all_i32
90+
;; Send a tag value + exnref. We don't handle this yet TODO
91+
(tuple.drop 2
92+
(block $catch (result i32 exnref)
93+
(try_table (catch_ref $i32 $catch)
94+
(call $import)
95+
(unreachable)
96+
)
97+
)
98+
)
99+
)
100+
101+
;; CHECK: (func $drop-block-try_catch_multi_partial (type $0)
102+
;; CHECK-NEXT: (tuple.drop 2
103+
;; CHECK-NEXT: (block $outer (type $1) (result i32 exnref)
104+
;; CHECK-NEXT: (block $inner
105+
;; CHECK-NEXT: (try_table (catch_ref $i32 $outer) (catch_all $inner)
106+
;; CHECK-NEXT: (call $import)
107+
;; CHECK-NEXT: (unreachable)
108+
;; CHECK-NEXT: )
109+
;; CHECK-NEXT: )
110+
;; CHECK-NEXT: (unreachable)
111+
;; CHECK-NEXT: )
112+
;; CHECK-NEXT: )
113+
;; CHECK-NEXT: )
114+
(func $drop-block-try_catch_multi_partial
115+
;; Two catches, one of which we can optimize, but not both.
116+
(tuple.drop 2
117+
(block $outer (result i32 exnref)
118+
(drop
119+
(block $inner (result exnref)
120+
(try_table (catch_ref $i32 $outer) (catch_all_ref $inner)
121+
(call $import)
122+
(unreachable)
123+
)
124+
)
125+
)
126+
(unreachable)
127+
)
128+
)
129+
)
130+
131+
;; CHECK: (func $drop-block_try_catch_multi-fail (type $0)
132+
;; CHECK-NEXT: (drop
133+
;; CHECK-NEXT: (block $catch (result exnref)
134+
;; CHECK-NEXT: (try_table (catch $exnref $catch) (catch_all_ref $catch)
135+
;; CHECK-NEXT: (call $import)
136+
;; CHECK-NEXT: (unreachable)
137+
;; CHECK-NEXT: )
138+
;; CHECK-NEXT: )
139+
;; CHECK-NEXT: )
140+
;; CHECK-NEXT: )
141+
(func $drop-block_try_catch_multi-fail
142+
(drop
143+
;; This block is sent an exnref in two ways: once as the contents of a tag
144+
;; and once as the ref of a catch_all_ref. We can remove the latter but
145+
;; not the former, and they go to the same block, so we cannot optimize.
146+
(block $catch (result exnref)
147+
(try_table (catch $exnref $catch) (catch_all_ref $catch)
148+
(call $import)
149+
(unreachable)
150+
)
151+
)
152+
)
153+
)
154+
155+
;; CHECK: (func $drop-block-try_catch_all (type $0)
156+
;; CHECK-NEXT: (block $catch
157+
;; CHECK-NEXT: (try_table (catch_all $catch)
158+
;; CHECK-NEXT: (call $import)
159+
;; CHECK-NEXT: (unreachable)
160+
;; CHECK-NEXT: )
161+
;; CHECK-NEXT: )
162+
;; CHECK-NEXT: )
163+
(func $drop-block-try_catch_all
164+
;; Without _ref, there is nothing to optimize (and we should not error).
165+
;; Also since there is no ref, there is no drop anyhow.
166+
(block $catch
167+
(try_table (catch_all $catch)
168+
(call $import)
169+
(unreachable)
170+
)
171+
)
172+
)
173+
174+
;; CHECK: (func $drop-block-try_catch (type $0)
175+
;; CHECK-NEXT: (block $catch
176+
;; CHECK-NEXT: (try_table (catch $empty $catch)
177+
;; CHECK-NEXT: (call $import)
178+
;; CHECK-NEXT: (unreachable)
179+
;; CHECK-NEXT: )
180+
;; CHECK-NEXT: )
181+
;; CHECK-NEXT: )
182+
(func $drop-block-try_catch
183+
;; As above, but with a catch instead of catch_all.
184+
(block $catch
185+
(try_table (catch $empty $catch)
186+
(call $import)
187+
(unreachable)
188+
)
189+
)
190+
)
191+
192+
;; CHECK: (func $drop-block-try_catch_i32 (type $0)
193+
;; CHECK-NEXT: (drop
194+
;; CHECK-NEXT: (block $catch (result i32)
195+
;; CHECK-NEXT: (try_table (catch $i32 $catch)
196+
;; CHECK-NEXT: (call $import)
197+
;; CHECK-NEXT: (unreachable)
198+
;; CHECK-NEXT: )
199+
;; CHECK-NEXT: )
200+
;; CHECK-NEXT: )
201+
;; CHECK-NEXT: )
202+
(func $drop-block-try_catch_i32
203+
;; Send an i32 without a ref. We can't optimize here, as we can't prevent
204+
;; the i32 from being sent.
205+
(drop
206+
(block $catch (result i32)
207+
(try_table (catch $i32 $catch)
208+
(call $import)
209+
(unreachable)
210+
)
211+
)
212+
)
213+
)
214+
)
215+

0 commit comments

Comments
 (0)