Skip to content

Commit 9c6b8e0

Browse files
authored
Validate finalization (#1014)
* validate that types are properly finalized, when in pass-debug mode (BINARYEN_PASS_DEBUG env var): check after each pass is run that the type of each node is equal to the proper type (when finalizing it, i.e., fully recomputing the type). * fix many fuzz bugs found by that. * in particular, fix dce bugs with type changes not being fully updated during code removal. add a new TypeUpdater helper class that lets a pass update types efficiently, by the helper tracking deps between blocks and branches etc., and updating/propagating type changes only as necessary.
1 parent bb1c44a commit 9c6b8e0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+4275
-363
lines changed

check.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,16 +125,29 @@
125125
cmd += ['--wasm-only']
126126
wasm = os.path.join(options.binaryen_test, wasm)
127127
print '..', asm, wasm
128-
actual = run_command(cmd)
129128

130-
# verify output
131-
if not os.path.exists(wasm):
132-
fail_with_error('output .wast file %s does not exist' % wasm)
133-
expected = open(wasm, 'rb').read()
134-
if actual != expected:
135-
fail(actual, expected)
136-
137-
binary_format_check(wasm, verify_final_result=False)
129+
def do_asm2wasm_test():
130+
actual = run_command(cmd)
131+
132+
# verify output
133+
if not os.path.exists(wasm):
134+
fail_with_error('output .wast file %s does not exist' % wasm)
135+
expected = open(wasm, 'rb').read()
136+
if actual != expected:
137+
fail(actual, expected)
138+
139+
binary_format_check(wasm, verify_final_result=False)
140+
141+
# test both normally and with pass debug (so each inter-pass state is validated)
142+
old_pass_debug = os.environ.get('BINARYEN_PASS_DEBUG')
143+
try:
144+
os.environ['BINARYEN_PASS_DEBUG'] = '1'
145+
do_asm2wasm_test()
146+
del os.environ['BINARYEN_PASS_DEBUG']
147+
do_asm2wasm_test()
148+
finally:
149+
if old_pass_debug is not None:
150+
os.environ['BINARYEN_PASS_DEBUG'] = old_pass_debug
138151

139152
# verify in wasm
140153
if options.interpreter:
@@ -280,6 +293,11 @@ def run_spec_test(wast):
280293
cmd = cmd + (extra.get(os.path.basename(wast)) or [])
281294
return run_command(cmd, stderr=subprocess.PIPE)
282295

296+
def run_opt_test(wast):
297+
# check optimization validation
298+
cmd = WASM_OPT + [wast, '-O']
299+
run_command(cmd)
300+
283301
def check_expected(actual, expected):
284302
if expected and os.path.exists(expected):
285303
expected = open(expected).read()
@@ -335,6 +353,7 @@ def fix(x):
335353
split_num += 1
336354
with open('split.wast', 'w') as o: o.write(module + '\n' + '\n'.join(asserts))
337355
run_spec_test('split.wast') # before binary stuff - just check it's still ok split out
356+
run_opt_test('split.wast') # also that our optimizer doesn't break on it
338357
result_wast = binary_format_check('split.wast', verify_final_result=False)
339358
# add the asserts, and verify that the test still passes
340359
open(result_wast, 'a').write('\n' + '\n'.join(asserts))

src/asm2wasm.h

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
#include "wasm-builder.h"
3636
#include "wasm-emscripten.h"
3737
#include "wasm-printing.h"
38-
#include "wasm-validator.h"
3938
#include "wasm-module-building.h"
4039

4140
namespace wasm {
@@ -1339,6 +1338,12 @@ void Asm2WasmBuilder::processAsm(Ref ast) {
13391338
add->left = parent->builder.makeConst(Literal((int32_t)parent->functionTableStarts[tableName]));
13401339
}
13411340
}
1341+
1342+
void visitFunction(Function* curr) {
1343+
// changing call types requires we percolate types, and drop stuff.
1344+
// we do this in this pass so that we don't look broken between passes
1345+
AutoDrop().walkFunctionInModule(curr, getModule());
1346+
}
13421347
};
13431348

13441349
// apply debug info, reducing intrinsic calls into annotations on the ast nodes
@@ -1400,9 +1405,9 @@ void Asm2WasmBuilder::processAsm(Ref ast) {
14001405
passRunner.setDebug(true);
14011406
passRunner.setValidateGlobally(false);
14021407
}
1408+
// finalizeCalls also does autoDrop, which is crucial for the non-optimizing case,
1409+
// so that the output of the first pass is valid
14031410
passRunner.add<FinalizeCalls>(this);
1404-
passRunner.add<ReFinalize>(); // FinalizeCalls changes call types, need to percolate
1405-
passRunner.add<AutoDrop>(); // FinalizeCalls may cause us to require additional drops
14061411
if (legalizeJavaScriptFFI) {
14071412
passRunner.add("legalize-js-interface");
14081413
}
@@ -1551,8 +1556,6 @@ void Asm2WasmBuilder::processAsm(Ref ast) {
15511556
body->finalize();
15521557
func->body = body;
15531558
}
1554-
1555-
assert(WasmValidator().validate(wasm));
15561559
}
15571560

15581561
Function* Asm2WasmBuilder::processFunction(Ref ast) {
@@ -2489,7 +2492,6 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
24892492
}
24902493

24912494
top->list.push_back(br);
2492-
top->finalize();
24932495

24942496
for (unsigned i = 0; i < cases->size(); i++) {
24952497
Ref curr = cases[i];
@@ -2564,7 +2566,6 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
25642566
top->name = name;
25652567
next->list.push_back(top);
25662568
next->list.push_back(case_);
2567-
next->finalize();
25682569
top = next;
25692570
nameMapper.popLabelName(name);
25702571
}
@@ -2580,7 +2581,6 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
25802581
first->ifFalse = builder.makeBreak(br->default_);
25812582

25822583
brHolder->list.push_back(chain);
2583-
brHolder->finalize();
25842584
}
25852585

25862586
breakStack.pop_back();

src/ast/ExpressionManipulator.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ void ExpressionManipulator::spliceIntoBlock(Block* block, Index index, Expressio
147147
}
148148
list[index] = add;
149149
}
150+
block->finalize(block->type);
150151
}
151152

152153
} // namespace wasm

src/ast/block-utils.h

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2017 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef wasm_ast_block_h
18+
#define wasm_ast_block_h
19+
20+
#include "literal.h"
21+
#include "wasm.h"
22+
#include "ast_utils.h"
23+
24+
namespace wasm {
25+
26+
namespace BlockUtils {
27+
// if a block has just one element, it can often be replaced
28+
// with that content
29+
template<typename T>
30+
inline Expression* simplifyToContents(Block* block, T* parent, bool allowTypeChange = false) {
31+
auto& list = block->list;
32+
if (list.size() == 1 && !BreakSeeker::has(list[0], block->name)) {
33+
// just one element. try to replace the block
34+
auto* singleton = list[0];
35+
auto sideEffects = EffectAnalyzer(parent->getPassOptions(), singleton).hasSideEffects();
36+
if (!sideEffects && !isConcreteWasmType(singleton->type)) {
37+
// no side effects, and singleton is not returning a value, so we can throw away
38+
// the block and its contents, basically
39+
return Builder(*parent->getModule()).replaceWithIdenticalType(block);
40+
} else if (block->type == singleton->type || allowTypeChange) {
41+
return singleton;
42+
} else {
43+
// (side effects +) type change, must be block with declared value but inside is unreachable
44+
// (if both concrete, must match, and since no name on block, we can't be
45+
// branched to, so if singleton is unreachable, so is the block)
46+
assert(isConcreteWasmType(block->type) && singleton->type == unreachable);
47+
// we could replace with unreachable, but would need to update all
48+
// the parent's types
49+
}
50+
} else if (list.size() == 0) {
51+
ExpressionManipulator::nop(block);
52+
}
53+
return block;
54+
}
55+
56+
// similar, but when we allow the type to change while doing so
57+
template<typename T>
58+
inline Expression* simplifyToContentsWithPossibleTypeChange(Block* block, T* parent) {
59+
return simplifyToContents(block, parent, true);
60+
}
61+
};
62+
63+
} // namespace wasm
64+
65+
#endif // wasm_ast_block_h
66+

src/ast/manipulation.h

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2017 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef wasm_ast_manipulation_h
18+
#define wasm_ast_manipulation_h
19+
20+
#include "wasm.h"
21+
22+
namespace wasm {
23+
24+
struct ExpressionManipulator {
25+
// Re-use a node's memory. This helps avoid allocation when optimizing.
26+
template<typename InputType, typename OutputType>
27+
static OutputType* convert(InputType *input) {
28+
static_assert(sizeof(OutputType) <= sizeof(InputType),
29+
"Can only convert to a smaller size Expression node");
30+
input->~InputType(); // arena-allocaed, so no destructor, but avoid UB.
31+
OutputType* output = (OutputType*)(input);
32+
new (output) OutputType;
33+
return output;
34+
}
35+
36+
// Convenience method for nop, which is a common conversion
37+
template<typename InputType>
38+
static Nop* nop(InputType* target) {
39+
return convert<InputType, Nop>(target);
40+
}
41+
42+
// Convert a node that allocates
43+
template<typename InputType, typename OutputType>
44+
static OutputType* convert(InputType *input, MixedArena& allocator) {
45+
assert(sizeof(OutputType) <= sizeof(InputType));
46+
input->~InputType(); // arena-allocaed, so no destructor, but avoid UB.
47+
OutputType* output = (OutputType*)(input);
48+
new (output) OutputType(allocator);
49+
return output;
50+
}
51+
52+
using CustomCopier = std::function<Expression*(Expression*)>;
53+
static Expression* flexibleCopy(Expression* original, Module& wasm, CustomCopier custom);
54+
55+
static Expression* copy(Expression* original, Module& wasm) {
56+
auto copy = [](Expression* curr) {
57+
return nullptr;
58+
};
59+
return flexibleCopy(original, wasm, copy);
60+
}
61+
62+
// Splice an item into the middle of a block's list
63+
static void spliceIntoBlock(Block* block, Index index, Expression* add);
64+
};
65+
66+
} // wasm
67+
68+
#endif // wams_ast_manipulation_h
69+

0 commit comments

Comments
 (0)