Skip to content

Commit b79f3a4

Browse files
authored
ReFinalize fix (#1742)
Handle a corner case in ReFinalize, which incrementally re-types code after changes. The problem is that if we need to figure out the type of a block, we look to the last element flowing out, or to breaks with values. If there is no such last element, and the breaks are not taken - they have unreachable values - then they don't tell us the block's proper type. We asserted that in such a case the block still had a type, and didn't handle this. To fix it, we could look on the parent to see what type would fit. However, it seem simpler to just remove untaken breaks/switches as part of ReFinalization - they carry no useful info anyhow. After removing them, if the block has no other signal of a concrete type, it can just be unreachable. This bug existed for at least 1.5 years - I didn't look back further. I think it was noticed by the fuzzer now due to recent fuzzing improvements and optimizer improvements, as I just saw this bug found a second time.
1 parent 7e9f7f6 commit b79f3a4

File tree

6 files changed

+114
-31
lines changed

6 files changed

+114
-31
lines changed

src/ir/utils.h

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,23 @@ struct ExpressionAnalyzer {
8383
static uint32_t hash(Expression* curr);
8484
};
8585

86-
// Re-Finalizes all node types
86+
// Re-Finalizes all node types. This can be run after code was modified in
87+
// various ways that require propagating types around, and it does such an
88+
// "incremental" update. This is done under the assumption that there is
89+
// a valid assignment of types to apply.
8790
// This removes "unnecessary' block/if/loop types, i.e., that are added
8891
// specifically, as in
8992
// (block (result i32) (unreachable))
9093
// vs
9194
// (block (unreachable))
9295
// This converts to the latter form.
96+
// This also removes un-taken branches that would be a problem for
97+
// refinalization: if a block has been marked unreachable, and has
98+
// branches to it with values of type unreachable, then we don't
99+
// know the type for the block: it can't be none since the breaks
100+
// exist, but the breaks don't declare the type, rather everything
101+
// depends on the block. To avoid looking at the parent or something
102+
// else, just remove such un-taken branches.
93103
struct ReFinalize : public WalkerPass<PostWalker<ReFinalize, OverriddenVisitor<ReFinalize>>> {
94104
bool isFunctionParallel() override { return true; }
95105

@@ -108,7 +118,6 @@ struct ReFinalize : public WalkerPass<PostWalker<ReFinalize, OverriddenVisitor<R
108118
return;
109119
}
110120
// do this quickly, without any validation
111-
auto old = curr->type;
112121
// last element determines type
113122
curr->type = curr->list.back()->type;
114123
// if concrete, it doesn't matter if we have an unreachable child, and we
@@ -121,14 +130,8 @@ struct ReFinalize : public WalkerPass<PostWalker<ReFinalize, OverriddenVisitor<R
121130
if (iter != breakValues.end()) {
122131
// there is a break to here
123132
auto type = iter->second;
124-
if (type == unreachable) {
125-
// all we have are breaks with values of type unreachable, and no
126-
// concrete fallthrough either. we must have had an existing type, then
127-
curr->type = old;
128-
assert(isConcreteType(curr->type));
129-
} else {
130-
curr->type = type;
131-
}
133+
assert(type != unreachable); // we would have removed such branches
134+
curr->type = type;
132135
return;
133136
}
134137
}
@@ -147,15 +150,24 @@ struct ReFinalize : public WalkerPass<PostWalker<ReFinalize, OverriddenVisitor<R
147150
void visitLoop(Loop* curr) { curr->finalize(); }
148151
void visitBreak(Break* curr) {
149152
curr->finalize();
150-
updateBreakValueType(curr->name, getValueType(curr->value));
153+
auto valueType = getValueType(curr->value);
154+
if (valueType == unreachable) {
155+
replaceUntaken(curr->value, curr->condition);
156+
} else {
157+
updateBreakValueType(curr->name, valueType);
158+
}
151159
}
152160
void visitSwitch(Switch* curr) {
153161
curr->finalize();
154162
auto valueType = getValueType(curr->value);
155-
for (auto target : curr->targets) {
156-
updateBreakValueType(target, valueType);
163+
if (valueType == unreachable) {
164+
replaceUntaken(curr->value, curr->condition);
165+
} else {
166+
for (auto target : curr->targets) {
167+
updateBreakValueType(target, valueType);
168+
}
169+
updateBreakValueType(curr->default_, valueType);
157170
}
158-
updateBreakValueType(curr->default_, valueType);
159171
}
160172
void visitCall(Call* curr) { curr->finalize(); }
161173
void visitCallIndirect(CallIndirect* curr) { curr->finalize(); }
@@ -195,6 +207,7 @@ struct ReFinalize : public WalkerPass<PostWalker<ReFinalize, OverriddenVisitor<R
195207
void visitMemory(Memory* curr) { WASM_UNREACHABLE(); }
196208
void visitModule(Module* curr) { WASM_UNREACHABLE(); }
197209

210+
private:
198211
Type getValueType(Expression* value) {
199212
return value ? value->type : none;
200213
}
@@ -204,6 +217,31 @@ struct ReFinalize : public WalkerPass<PostWalker<ReFinalize, OverriddenVisitor<R
204217
breakValues[name] = type;
205218
}
206219
}
220+
221+
// Replace an untaken branch/switch with an unreachable value.
222+
// A condition may also exist and may or may not be unreachable.
223+
void replaceUntaken(Expression* value, Expression* condition) {
224+
assert(value->type == unreachable);
225+
auto* replacement = value;
226+
if (condition) {
227+
Builder builder(*getModule());
228+
// Even if we have
229+
// (block
230+
// (unreachable)
231+
// (i32.const 1)
232+
// )
233+
// we want the block type to be unreachable. That is valid as
234+
// the value is unreachable, and necessary since the type of
235+
// the condition did not have an impact before (the break/switch
236+
// type was unreachable), and might not fit in.
237+
if (isConcreteType(condition->type)) {
238+
condition = builder.makeDrop(condition);
239+
}
240+
replacement = builder.makeSequence(value, condition);
241+
assert(replacement->type);
242+
}
243+
replaceCurrent(replacement);
244+
}
207245
};
208246

209247
// Re-finalize a single node. This is slow, if you want to refinalize

test/passes/code-folding.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@
109109
(func $leave-inner-block-type (; 6 ;) (type $1)
110110
(block $label$1
111111
(drop
112-
(block $label$2 (result i32)
113-
(br_if $label$2
112+
(block $label$2
113+
(block
114114
(unreachable)
115115
(unreachable)
116116
)

test/passes/precompute.txt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@
162162
(func $refinalize-two-breaks-one-unreachable (; 6 ;) (type $2)
163163
(drop
164164
(block $label$0 (result i64)
165-
(br_if $label$0
165+
(block
166166
(select
167167
(i64.const 1)
168168
(block $block
@@ -175,17 +175,21 @@
175175
)
176176
(i32.const 0)
177177
)
178-
(i32.const 1)
178+
(drop
179+
(i32.const 1)
180+
)
179181
)
180182
)
181183
)
182184
)
183185
(func $one-break-value-and-it-is-unreachable (; 7 ;) (type $3) (result f64)
184186
(local $var$0 i32)
185-
(block $label$6 (result f64)
186-
(br_if $label$6
187+
(block $label$6
188+
(block
187189
(unreachable)
188-
(i32.const 0)
190+
(drop
191+
(i32.const 0)
192+
)
189193
)
190194
)
191195
)

test/passes/remove-unused-brs.txt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1922,4 +1922,28 @@
19221922
)
19231923
)
19241924
)
1925+
(func $fuzz-block-unreachable-brs-with-values (; 80 ;) (type $2) (result i32)
1926+
(local $0 i32)
1927+
(loop $label$1
1928+
(block $label$2
1929+
(br_if $label$1
1930+
(i32.eqz
1931+
(get_local $0)
1932+
)
1933+
)
1934+
(tee_local $0
1935+
(loop $label$5
1936+
(br_if $label$5
1937+
(block
1938+
(unreachable)
1939+
(drop
1940+
(i32.const 0)
1941+
)
1942+
)
1943+
)
1944+
)
1945+
)
1946+
)
1947+
)
1948+
)
19251949
)

test/passes/remove-unused-brs.wast

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1540,5 +1540,26 @@
15401540
(br $label$1)
15411541
)
15421542
)
1543+
(func $fuzz-block-unreachable-brs-with-values (result i32)
1544+
(local $0 i32)
1545+
(loop $label$1 (result i32)
1546+
(block $label$2 (result i32)
1547+
(if
1548+
(get_local $0)
1549+
(set_local $0
1550+
(loop $label$5
1551+
(br_if $label$5
1552+
(br_if $label$2
1553+
(unreachable)
1554+
(i32.const 0)
1555+
)
1556+
)
1557+
)
1558+
)
1559+
)
1560+
(br $label$1)
1561+
)
1562+
)
1563+
)
15431564
)
15441565

test/wasm2js/br.2asm.js

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ function asmFunc(global, env, buffer) {
510510
}
511511

512512
function $54() {
513-
var $0 = 0, $1_1 = 0;
513+
var $0 = 0;
514514
block : {
515515
block0 : {
516516
$0 = 8;
@@ -523,10 +523,8 @@ function asmFunc(global, env, buffer) {
523523
function $55() {
524524
var $0 = 0, $1_1 = 0;
525525
block : {
526-
block1 : {
527-
$0 = 8;
528-
break block;
529-
};
526+
$0 = 8;
527+
break block;
530528
};
531529
return 1 + $0 | 0 | 0;
532530
}
@@ -541,12 +539,10 @@ function asmFunc(global, env, buffer) {
541539
}
542540

543541
function $57() {
544-
var $0 = 0, $1_1 = 0;
542+
var $0 = 0;
545543
block : {
546-
block2 : {
547-
$0 = 8;
548-
break block;
549-
};
544+
$0 = 8;
545+
break block;
550546
};
551547
return 1 + $0 | 0 | 0;
552548
}

0 commit comments

Comments
 (0)