diff options
-rwxr-xr-x | check.py | 788 | ||||
-rw-r--r-- | src/ast/branch-utils.h | 22 | ||||
-rw-r--r-- | src/ast_utils.h | 2 | ||||
-rw-r--r-- | src/passes/OptimizeInstructions.cpp | 2 | ||||
-rw-r--r-- | src/passes/Print.cpp | 5 | ||||
-rw-r--r-- | src/passes/RemoveUnusedBrs.cpp | 4 | ||||
-rw-r--r-- | src/passes/SimplifyLocals.cpp | 2 | ||||
-rw-r--r-- | src/wasm-binary.h | 6 | ||||
-rw-r--r-- | src/wasm-validator.h | 1 | ||||
-rw-r--r-- | src/wasm.h | 4 | ||||
-rw-r--r-- | src/wasm/wasm-binary.cpp | 22 | ||||
-rw-r--r-- | src/wasm/wasm-s-parser.cpp | 9 | ||||
-rw-r--r-- | src/wasm/wasm-validator.cpp | 20 | ||||
-rw-r--r-- | src/wasm/wasm.cpp | 3 | ||||
-rw-r--r-- | test/signext.wast | 12 | ||||
-rw-r--r-- | test/signext.wast.from-wast | 33 | ||||
-rw-r--r-- | test/signext.wast.fromBinary | 36 | ||||
-rw-r--r-- | test/signext.wast.fromBinary.noDebugInfo | 36 |
18 files changed, 595 insertions, 412 deletions
@@ -39,136 +39,66 @@ if options.interpreter: # tests -print '[ checking --help is useful... ]\n' - -not_executable_suffix = ['.txt', '.js', '.ilk', '.pdb', '.dll'] -executables = sorted(filter(lambda x: not any(x.endswith(s) for s in - not_executable_suffix) and os.path.isfile(x), - os.listdir(options.binaryen_bin))) -for e in executables: - print '.. %s --help' % e - out, err = subprocess.Popen([os.path.join(options.binaryen_bin, e), '--help'], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE).communicate() - assert len(out) == 0, 'Expected no stdout, got:\n%s' % out - assert e.replace('.exe', '') in err, 'Expected help to contain program name, got:\n%s' % err - assert len(err.split('\n')) > 8, 'Expected some help, got:\n%s' % err - -print '\n[ checking wasm-opt -o notation... ]\n' - -wast = os.path.join(options.binaryen_test, 'hello_world.wast') -delete_from_orbit('a.wast') -cmd = WASM_OPT + [wast, '-o', 'a.wast', '-S'] -run_command(cmd) -fail_if_not_identical(open('a.wast').read(), open(wast).read()) - -print '\n[ checking wasm-opt binary reading/writing... ]\n' - -shutil.copyfile(os.path.join(options.binaryen_test, 'hello_world.wast'), 'a.wast') -delete_from_orbit('a.wasm') -delete_from_orbit('b.wast') -run_command(WASM_OPT + ['a.wast', '-o', 'a.wasm']) -assert open('a.wasm', 'rb').read()[0] == '\0', 'we emit binary by default' -run_command(WASM_OPT + ['a.wasm', '-o', 'b.wast', '-S']) -assert open('b.wast', 'rb').read()[0] != '\0', 'we emit text with -S' - -print '\n[ checking wasm-opt passes... ]\n' - -for t in sorted(os.listdir(os.path.join(options.binaryen_test, 'passes'))): - if t.endswith(('.wast', '.wasm')): - print '..', t - binary = '.wasm' in t - passname = os.path.basename(t).replace('.wast', '').replace('.wasm', '') - opts = [('--' + p if not p.startswith('O') else '-' + p) for p in passname.split('_')] - t = os.path.join(options.binaryen_test, 'passes', t) - actual = '' - for module, asserts in split_wast(t): - assert len(asserts) == 0 - with open('split.wast', 'w') as o: o.write(module) - cmd = WASM_OPT + opts + ['split.wast', '--print'] - curr = run_command(cmd) - actual += curr - # also check debug mode output is valid - debugged = run_command(cmd + ['--debug'], stderr=subprocess.PIPE) - fail_if_not_contained(actual, debugged) - # also check pass-debug mode - old_pass_debug = os.environ.get('BINARYEN_PASS_DEBUG') - try: - os.environ['BINARYEN_PASS_DEBUG'] = '1' - pass_debug = run_command(cmd) - fail_if_not_identical(curr, pass_debug) - finally: - if old_pass_debug is not None: - os.environ['BINARYEN_PASS_DEBUG'] = old_pass_debug - else: - if 'BINARYEN_PASS_DEBUG' in os.environ: - del os.environ['BINARYEN_PASS_DEBUG'] - - fail_if_not_identical(actual, open(os.path.join('test', 'passes', passname + ('.bin' if binary else '') + '.txt'), 'rb').read()) - - if 'emit-js-wrapper' in t: - with open('a.js') as actual: - with open(t + '.js') as expected: - fail_if_not_identical(actual.read(), expected.read()) - if 'emit-spec-wrapper' in t: - with open('a.wat') as actual: - with open(t + '.wat') as expected: - fail_if_not_identical(actual.read(), expected.read()) - -print '[ checking asm2wasm testcases... ]\n' - -for asm in tests: - if asm.endswith('.asm.js'): - for precise in [0, 1, 2]: - for opts in [1, 0]: - cmd = ASM2WASM + [os.path.join(options.binaryen_test, asm)] - wasm = asm.replace('.asm.js', '.fromasm') - if not precise: - cmd += ['--emit-potential-traps', '--ignore-implicit-traps'] - wasm += '.imprecise' - elif precise == 2: - cmd += ['--emit-clamped-potential-traps'] - wasm += '.clamp' - if not opts: - wasm += '.no-opts' - if precise: - cmd += ['-O0'] # test that -O0 does nothing - else: - cmd += ['-O'] - if 'debugInfo' in asm: - cmd += ['-g'] - if 'noffi' in asm: - cmd += ['--no-legalize-javascript-ffi'] - if precise and opts: - # test mem init importing - open('a.mem', 'wb').write(asm) - cmd += ['--mem-init=a.mem'] - if asm[0] == 'e': - cmd += ['--mem-base=1024'] - if 'i64' in asm or 'wasm-only' in asm or 'noffi' in asm: - cmd += ['--wasm-only'] - wasm = os.path.join(options.binaryen_test, wasm) - print '..', asm, wasm - - def do_asm2wasm_test(): - actual = run_command(cmd) - - # verify output - if not os.path.exists(wasm): - fail_with_error('output .wast file %s does not exist' % wasm) - expected = open(wasm, 'rb').read() - if actual != expected: - fail(actual, expected) - - binary_format_check(wasm, verify_final_result=False) - - # test both normally and with pass debug (so each inter-pass state is validated) +def run_help_tests(): + print '[ checking --help is useful... ]\n' + + not_executable_suffix = ['.txt', '.js', '.ilk', '.pdb', '.dll'] + executables = sorted(filter(lambda x: not any(x.endswith(s) for s in + not_executable_suffix) and os.path.isfile(x), + os.listdir(options.binaryen_bin))) + for e in executables: + print '.. %s --help' % e + out, err = subprocess.Popen([os.path.join(options.binaryen_bin, e), '--help'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE).communicate() + assert len(out) == 0, 'Expected no stdout, got:\n%s' % out + assert e.replace('.exe', '') in err, 'Expected help to contain program name, got:\n%s' % err + assert len(err.split('\n')) > 8, 'Expected some help, got:\n%s' % err + +def run_wasm_opt_tests(): + print '\n[ checking wasm-opt -o notation... ]\n' + + wast = os.path.join(options.binaryen_test, 'hello_world.wast') + delete_from_orbit('a.wast') + cmd = WASM_OPT + [wast, '-o', 'a.wast', '-S'] + run_command(cmd) + fail_if_not_identical(open('a.wast').read(), open(wast).read()) + + print '\n[ checking wasm-opt binary reading/writing... ]\n' + + shutil.copyfile(os.path.join(options.binaryen_test, 'hello_world.wast'), 'a.wast') + delete_from_orbit('a.wasm') + delete_from_orbit('b.wast') + run_command(WASM_OPT + ['a.wast', '-o', 'a.wasm']) + assert open('a.wasm', 'rb').read()[0] == '\0', 'we emit binary by default' + run_command(WASM_OPT + ['a.wasm', '-o', 'b.wast', '-S']) + assert open('b.wast', 'rb').read()[0] != '\0', 'we emit text with -S' + + print '\n[ checking wasm-opt passes... ]\n' + + for t in sorted(os.listdir(os.path.join(options.binaryen_test, 'passes'))): + if t.endswith(('.wast', '.wasm')): + print '..', t + binary = '.wasm' in t + passname = os.path.basename(t).replace('.wast', '').replace('.wasm', '') + opts = [('--' + p if not p.startswith('O') else '-' + p) for p in passname.split('_')] + t = os.path.join(options.binaryen_test, 'passes', t) + actual = '' + for module, asserts in split_wast(t): + assert len(asserts) == 0 + with open('split.wast', 'w') as o: o.write(module) + cmd = WASM_OPT + opts + ['split.wast', '--print'] + curr = run_command(cmd) + actual += curr + # also check debug mode output is valid + debugged = run_command(cmd + ['--debug'], stderr=subprocess.PIPE) + fail_if_not_contained(actual, debugged) + # also check pass-debug mode old_pass_debug = os.environ.get('BINARYEN_PASS_DEBUG') try: os.environ['BINARYEN_PASS_DEBUG'] = '1' - do_asm2wasm_test() - del os.environ['BINARYEN_PASS_DEBUG'] - do_asm2wasm_test() + pass_debug = run_command(cmd) + fail_if_not_identical(curr, pass_debug) finally: if old_pass_debug is not None: os.environ['BINARYEN_PASS_DEBUG'] = old_pass_debug @@ -176,152 +106,228 @@ for asm in tests: if 'BINARYEN_PASS_DEBUG' in os.environ: del os.environ['BINARYEN_PASS_DEBUG'] - # verify in wasm - if options.interpreter: - # remove imports, spec interpreter doesn't know what to do with them - subprocess.check_call(WASM_OPT + ['--remove-imports', wasm], stdout=open('ztemp.wast', 'w'), stderr=subprocess.PIPE) - proc = subprocess.Popen([options.interpreter, 'ztemp.wast'], stderr=subprocess.PIPE) - out, err = proc.communicate() - if proc.returncode != 0: - try: # to parse the error - reported = err.split(':')[1] - start, end = reported.split('-') - start_line, start_col = map(int, start.split('.')) - lines = open('ztemp.wast').read().split('\n') - print - print '='*80 - print lines[start_line-1] - print (' '*(start_col-1)) + '^' - print (' '*(start_col-2)) + '/_\\' - print '='*80 - print err - except Exception, e: - fail_with_error('wasm interpreter error: ' + err) # failed to pretty-print - fail_with_error('wasm interpreter error') - - # verify debug info - if 'debugInfo' in asm: - jsmap = 'a.wasm.map' - cmd += ['--source-map', jsmap, - '--source-map-url', 'http://example.org/' + jsmap, - '-o', 'a.wasm'] - run_command(cmd) - if not os.path.isfile(jsmap): - fail_with_error('Debug info map not created: %s' % jsmap) - with open(wasm + '.map', 'rb') as expected: - with open(jsmap, 'rb') as actual: - fail_if_not_identical(actual.read(), expected.read()) - with open('a.wasm', 'rb') as binary: - url_section_name = bytearray([16]) + bytearray('sourceMappingURL') - payload = 'http://example.org/' + jsmap - assert len(payload) < 256, 'name too long' - url_section_contents = bytearray([len(payload)]) + bytearray(payload) - print url_section_name - binary_contents = bytearray(binary.read()) - if url_section_name not in binary_contents: - fail_with_error('source map url section not found in binary') - if url_section_contents not in binary_contents[binary_contents.index(url_section_name):]: - fail_with_error('source map url not found in url section') - - -print '\n[ checking asm2wasm binary reading/writing... ]\n' - -asmjs = os.path.join(options.binaryen_test, 'hello_world.asm.js') -delete_from_orbit('a.wasm') -delete_from_orbit('b.wast') -run_command(ASM2WASM + [asmjs, '-o', 'a.wasm']) -assert open('a.wasm', 'rb').read()[0] == '\0', 'we emit binary by default' -run_command(ASM2WASM + [asmjs, '-o', 'b.wast', '-S']) -assert open('b.wast', 'rb').read()[0] != '\0', 'we emit text with -S' - -print '\n[ checking wasm-opt parsing & printing... ]\n' - -for t in sorted(os.listdir(os.path.join(options.binaryen_test, 'print'))): - if t.endswith('.wast'): - print '..', t - wasm = os.path.basename(t).replace('.wast', '') - cmd = WASM_OPT + [os.path.join(options.binaryen_test, 'print', t), '--print'] - print ' ', ' '.join(cmd) - actual, err = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() - fail_if_not_identical(actual, open(os.path.join(options.binaryen_test, 'print', wasm + '.txt')).read()) - cmd = WASM_OPT + [os.path.join(options.binaryen_test, 'print', t), '--print-minified'] - print ' ', ' '.join(cmd) - actual, err = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() - fail_if_not_identical(actual.strip(), open(os.path.join(options.binaryen_test, 'print', wasm + '.minified.txt')).read().strip()) - -print '\n[ checking wasm-opt testcases... ]\n' - -for t in tests: - if t.endswith('.wast') and not t.startswith('spec'): - print '..', t - t = os.path.join(options.binaryen_test, t) - f = t + '.from-wast' - cmd = WASM_OPT + [t, '--print'] - actual = run_command(cmd) - actual = actual.replace('printing before:\n', '') - - expected = open(f, 'rb').read() - if actual != expected: - fail(actual, expected) - - binary_format_check(t, wasm_as_args=['-g']) # test with debuginfo - binary_format_check(t, wasm_as_args=[], binary_suffix='.fromBinary.noDebugInfo') # test without debuginfo - - minify_check(t) - -print '\n[ checking wasm-dis on provided binaries... ]\n' - -for t in tests: - if t.endswith('.wasm') and not t.startswith('spec'): - print '..', t - t = os.path.join(options.binaryen_test, t) - cmd = WASM_DIS + [t] - if os.path.isfile(t + '.map'): cmd += ['--source-map', t + '.map'] - - actual = run_command(cmd) - - with open(t + '.fromBinary') as f: - expected = f.read() + fail_if_not_identical(actual, open(os.path.join('test', 'passes', passname + ('.bin' if binary else '') + '.txt'), 'rb').read()) + + if 'emit-js-wrapper' in t: + with open('a.js') as actual: + with open(t + '.js') as expected: + fail_if_not_identical(actual.read(), expected.read()) + if 'emit-spec-wrapper' in t: + with open('a.wat') as actual: + with open(t + '.wat') as expected: + fail_if_not_identical(actual.read(), expected.read()) + + print '\n[ checking wasm-opt parsing & printing... ]\n' + + for t in sorted(os.listdir(os.path.join(options.binaryen_test, 'print'))): + if t.endswith('.wast'): + print '..', t + wasm = os.path.basename(t).replace('.wast', '') + cmd = WASM_OPT + [os.path.join(options.binaryen_test, 'print', t), '--print'] + print ' ', ' '.join(cmd) + actual, err = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + fail_if_not_identical(actual, open(os.path.join(options.binaryen_test, 'print', wasm + '.txt')).read()) + cmd = WASM_OPT + [os.path.join(options.binaryen_test, 'print', t), '--print-minified'] + print ' ', ' '.join(cmd) + actual, err = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + fail_if_not_identical(actual.strip(), open(os.path.join(options.binaryen_test, 'print', wasm + '.minified.txt')).read().strip()) + + print '\n[ checking wasm-opt testcases... ]\n' + + for t in tests: + if t.endswith('.wast') and not t.startswith('spec'): + print '..', t + t = os.path.join(options.binaryen_test, t) + f = t + '.from-wast' + cmd = WASM_OPT + [t, '--print'] + actual = run_command(cmd) + actual = actual.replace('printing before:\n', '') + + expected = open(f, 'rb').read() if actual != expected: fail(actual, expected) -print '\n[ checking wasm-merge... ]\n' - -for t in os.listdir(os.path.join('test', 'merge')): - if t.endswith(('.wast', '.wasm')): - print '..', t - t = os.path.join('test', 'merge', t) - u = t + '.toMerge' - for finalize in [0, 1]: - for opt in [0, 1]: - cmd = WASM_MERGE + [t, u, '-o', 'a.wast', '-S', '--verbose'] - if finalize: cmd += ['--finalize-memory-base=1024', '--finalize-table-base=8'] - if opt: cmd += ['-O'] - stdout = run_command(cmd) - actual = open('a.wast').read() - out = t + '.combined' - if finalize: out += '.finalized' - if opt: out += '.opt' - with open(out) as f: - fail_if_not_identical(f.read(), actual) - with open(out + '.stdout') as f: - fail_if_not_identical(f.read(), stdout) - -print '\n[ checking wasm-ctor-eval... ]\n' - -for t in os.listdir(os.path.join('test', 'ctor-eval')): - if t.endswith(('.wast', '.wasm')): - print '..', t - t = os.path.join('test', 'ctor-eval', t) - ctors = open(t + '.ctors').read().strip() - cmd = WASM_CTOR_EVAL + [t, '-o', 'a.wast', '-S', '--ctors', ctors] - stdout = run_command(cmd) - actual = open('a.wast').read() - out = t + '.out' - with open(out) as f: - fail_if_not_identical(f.read(), actual) + binary_format_check(t, wasm_as_args=['-g']) # test with debuginfo + binary_format_check(t, wasm_as_args=[], binary_suffix='.fromBinary.noDebugInfo') # test without debuginfo + + minify_check(t) + +def run_asm2wasm_tests(): + print '[ checking asm2wasm testcases... ]\n' + + for asm in tests: + if asm.endswith('.asm.js'): + for precise in [0, 1, 2]: + for opts in [1, 0]: + cmd = ASM2WASM + [os.path.join(options.binaryen_test, asm)] + wasm = asm.replace('.asm.js', '.fromasm') + if not precise: + cmd += ['--emit-potential-traps', '--ignore-implicit-traps'] + wasm += '.imprecise' + elif precise == 2: + cmd += ['--emit-clamped-potential-traps'] + wasm += '.clamp' + if not opts: + wasm += '.no-opts' + if precise: + cmd += ['-O0'] # test that -O0 does nothing + else: + cmd += ['-O'] + if 'debugInfo' in asm: + cmd += ['-g'] + if 'noffi' in asm: + cmd += ['--no-legalize-javascript-ffi'] + if precise and opts: + # test mem init importing + open('a.mem', 'wb').write(asm) + cmd += ['--mem-init=a.mem'] + if asm[0] == 'e': + cmd += ['--mem-base=1024'] + if 'i64' in asm or 'wasm-only' in asm or 'noffi' in asm: + cmd += ['--wasm-only'] + wasm = os.path.join(options.binaryen_test, wasm) + print '..', asm, wasm + + def do_asm2wasm_test(): + actual = run_command(cmd) + + # verify output + if not os.path.exists(wasm): + fail_with_error('output .wast file %s does not exist' % wasm) + expected = open(wasm, 'rb').read() + if actual != expected: + fail(actual, expected) + + binary_format_check(wasm, verify_final_result=False) + + # test both normally and with pass debug (so each inter-pass state is validated) + old_pass_debug = os.environ.get('BINARYEN_PASS_DEBUG') + try: + os.environ['BINARYEN_PASS_DEBUG'] = '1' + do_asm2wasm_test() + del os.environ['BINARYEN_PASS_DEBUG'] + do_asm2wasm_test() + finally: + if old_pass_debug is not None: + os.environ['BINARYEN_PASS_DEBUG'] = old_pass_debug + else: + if 'BINARYEN_PASS_DEBUG' in os.environ: + del os.environ['BINARYEN_PASS_DEBUG'] + + # verify in wasm + if options.interpreter: + # remove imports, spec interpreter doesn't know what to do with them + subprocess.check_call(WASM_OPT + ['--remove-imports', wasm], stdout=open('ztemp.wast', 'w'), stderr=subprocess.PIPE) + proc = subprocess.Popen([options.interpreter, 'ztemp.wast'], stderr=subprocess.PIPE) + out, err = proc.communicate() + if proc.returncode != 0: + try: # to parse the error + reported = err.split(':')[1] + start, end = reported.split('-') + start_line, start_col = map(int, start.split('.')) + lines = open('ztemp.wast').read().split('\n') + print + print '='*80 + print lines[start_line-1] + print (' '*(start_col-1)) + '^' + print (' '*(start_col-2)) + '/_\\' + print '='*80 + print err + except Exception, e: + fail_with_error('wasm interpreter error: ' + err) # failed to pretty-print + fail_with_error('wasm interpreter error') + + # verify debug info + if 'debugInfo' in asm: + jsmap = 'a.wasm.map' + cmd += ['--source-map', jsmap, + '--source-map-url', 'http://example.org/' + jsmap, + '-o', 'a.wasm'] + run_command(cmd) + if not os.path.isfile(jsmap): + fail_with_error('Debug info map not created: %s' % jsmap) + with open(wasm + '.map', 'rb') as expected: + with open(jsmap, 'rb') as actual: + fail_if_not_identical(actual.read(), expected.read()) + with open('a.wasm', 'rb') as binary: + url_section_name = bytearray([16]) + bytearray('sourceMappingURL') + payload = 'http://example.org/' + jsmap + assert len(payload) < 256, 'name too long' + url_section_contents = bytearray([len(payload)]) + bytearray(payload) + print url_section_name + binary_contents = bytearray(binary.read()) + if url_section_name not in binary_contents: + fail_with_error('source map url section not found in binary') + if url_section_contents not in binary_contents[binary_contents.index(url_section_name):]: + fail_with_error('source map url not found in url section') + + + print '\n[ checking asm2wasm binary reading/writing... ]\n' + + asmjs = os.path.join(options.binaryen_test, 'hello_world.asm.js') + delete_from_orbit('a.wasm') + delete_from_orbit('b.wast') + run_command(ASM2WASM + [asmjs, '-o', 'a.wasm']) + assert open('a.wasm', 'rb').read()[0] == '\0', 'we emit binary by default' + run_command(ASM2WASM + [asmjs, '-o', 'b.wast', '-S']) + assert open('b.wast', 'rb').read()[0] != '\0', 'we emit text with -S' + +def run_wasm_dis_tests(): + print '\n[ checking wasm-dis on provided binaries... ]\n' + + for t in tests: + if t.endswith('.wasm') and not t.startswith('spec'): + print '..', t + t = os.path.join(options.binaryen_test, t) + cmd = WASM_DIS + [t] + if os.path.isfile(t + '.map'): cmd += ['--source-map', t + '.map'] -if has_shell_timeout(): + actual = run_command(cmd) + + with open(t + '.fromBinary') as f: + expected = f.read() + if actual != expected: + fail(actual, expected) + +def run_wasm_merge_tests(): + print '\n[ checking wasm-merge... ]\n' + + for t in os.listdir(os.path.join('test', 'merge')): + if t.endswith(('.wast', '.wasm')): + print '..', t + t = os.path.join('test', 'merge', t) + u = t + '.toMerge' + for finalize in [0, 1]: + for opt in [0, 1]: + cmd = WASM_MERGE + [t, u, '-o', 'a.wast', '-S', '--verbose'] + if finalize: cmd += ['--finalize-memory-base=1024', '--finalize-table-base=8'] + if opt: cmd += ['-O'] + stdout = run_command(cmd) + actual = open('a.wast').read() + out = t + '.combined' + if finalize: out += '.finalized' + if opt: out += '.opt' + with open(out) as f: + fail_if_not_identical(f.read(), actual) + with open(out + '.stdout') as f: + fail_if_not_identical(f.read(), stdout) + +def run_ctor_eval_tests(): + print '\n[ checking wasm-ctor-eval... ]\n' + + for t in os.listdir(os.path.join('test', 'ctor-eval')): + if t.endswith(('.wast', '.wasm')): + print '..', t + t = os.path.join('test', 'ctor-eval', t) + ctors = open(t + '.ctors').read().strip() + cmd = WASM_CTOR_EVAL + [t, '-o', 'a.wast', '-S', '--ctors', ctors] + stdout = run_command(cmd) + actual = open('a.wast').read() + out = t + '.out' + with open(out) as f: + fail_if_not_identical(f.read(), actual) + +def run_wasm_reduce_tests(): print '\n[ checking wasm-reduce ]\n' for t in os.listdir(os.path.join('test', 'reduce')): @@ -330,108 +336,109 @@ if has_shell_timeout(): t = os.path.join('test', 'reduce', t) # convert to wasm run_command(WASM_AS + [t, '-o', 'a.wasm']) - print run_command(WASM_REDUCE + ['a.wasm', '--command=bin/wasm-opt b.wasm --fuzz-exec', '-t', 'b.wasm', '-w', 'c.wasm']) + print run_command(WASM_REDUCE + ['a.wasm', '--command=%s b.wasm --fuzz-exec' % WASM_OPT[0], '-t', 'b.wasm', '-w', 'c.wasm']) expected = t + '.txt' run_command(WASM_DIS + ['c.wasm', '-o', 'a.wast']) with open('a.wast') as seen: with open(expected) as correct: fail_if_not_identical(seen.read(), correct.read()) -print '\n[ checking wasm-shell spec testcases... ]\n' +def run_spec_tests(): + print '\n[ checking wasm-shell spec testcases... ]\n' -if len(requested) == 0: - BLACKLIST = ['memory.wast', 'binary.wast'] # FIXME we support old and new memory formats, for now, until 0xc, and so can't pass this old-style test. - # FIXME to update the spec to 0xd, we need to implement (register "name") for import.wast - spec_tests = [os.path.join('spec', t) for t in sorted(os.listdir(os.path.join(options.binaryen_test, 'spec'))) if t not in BLACKLIST] -else: - spec_tests = requested[:] + if len(requested) == 0: + BLACKLIST = ['memory.wast', 'binary.wast'] # FIXME we support old and new memory formats, for now, until 0xc, and so can't pass this old-style test. + # FIXME to update the spec to 0xd, we need to implement (register "name") for import.wast + spec_tests = [os.path.join('spec', t) for t in sorted(os.listdir(os.path.join(options.binaryen_test, 'spec'))) if t not in BLACKLIST] + else: + spec_tests = requested[:] -for t in spec_tests: - if t.startswith('spec') and t.endswith('.wast'): - print '..', t - wast = os.path.join(options.binaryen_test, t) + for t in spec_tests: + if t.startswith('spec') and t.endswith('.wast'): + print '..', t + wast = os.path.join(options.binaryen_test, t) - # skip checks for some tests - if os.path.basename(wast) in ['linking.wast', 'nop.wast', 'stack.wast', 'typecheck.wast', 'unwind.wast']: # FIXME - continue + # skip checks for some tests + if os.path.basename(wast) in ['linking.wast', 'nop.wast', 'stack.wast', 'typecheck.wast', 'unwind.wast']: # FIXME + continue - def run_spec_test(wast): - cmd = WASM_SHELL + [wast] - # we must skip the stack machine portions of spec tests or apply other extra args - extra = { - } - cmd = cmd + (extra.get(os.path.basename(wast)) or []) - return run_command(cmd, stderr=subprocess.PIPE) - - def run_opt_test(wast): - # check optimization validation - cmd = WASM_OPT + [wast, '-O'] - run_command(cmd) - - def check_expected(actual, expected): - if expected and os.path.exists(expected): - expected = open(expected).read() - # fix it up, our pretty (i32.const 83) must become compared to a homely 83 : i32 - def fix(x): - x = x.strip() - if not x: return x - v, t = x.split(' : ') - if v.endswith('.'): v = v[:-1] # remove trailing '.' - return '(' + t + '.const ' + v + ')' - expected = '\n'.join(map(fix, expected.split('\n'))) - print ' (using expected output)' - actual = actual.strip() - expected = expected.strip() - if actual != expected: - fail(actual, expected) + def run_spec_test(wast): + cmd = WASM_SHELL + [wast] + # we must skip the stack machine portions of spec tests or apply other extra args + extra = { + } + cmd = cmd + (extra.get(os.path.basename(wast)) or []) + return run_command(cmd, stderr=subprocess.PIPE) + + def run_opt_test(wast): + # check optimization validation + cmd = WASM_OPT + [wast, '-O'] + run_command(cmd) + + def check_expected(actual, expected): + if expected and os.path.exists(expected): + expected = open(expected).read() + # fix it up, our pretty (i32.const 83) must become compared to a homely 83 : i32 + def fix(x): + x = x.strip() + if not x: return x + v, t = x.split(' : ') + if v.endswith('.'): v = v[:-1] # remove trailing '.' + return '(' + t + '.const ' + v + ')' + expected = '\n'.join(map(fix, expected.split('\n'))) + print ' (using expected output)' + actual = actual.strip() + expected = expected.strip() + if actual != expected: + fail(actual, expected) - expected = os.path.join(options.binaryen_test, 'spec', 'expected-output', os.path.basename(wast) + '.log') + expected = os.path.join(options.binaryen_test, 'spec', 'expected-output', os.path.basename(wast) + '.log') - # some spec tests should fail (actual process failure, not just assert_invalid) - try: - actual = run_spec_test(wast) - except Exception, e: - if ('wasm-validator error' in str(e) or 'parse exception' in str(e)) and '.fail.' in t: - print '<< test failed as expected >>' - continue # don't try all the binary format stuff TODO - else: - fail_with_error(str(e)) + # some spec tests should fail (actual process failure, not just assert_invalid) + try: + actual = run_spec_test(wast) + except Exception, e: + if ('wasm-validator error' in str(e) or 'parse exception' in str(e)) and '.fail.' in t: + print '<< test failed as expected >>' + continue # don't try all the binary format stuff TODO + else: + fail_with_error(str(e)) - check_expected(actual, expected) + check_expected(actual, expected) - # skip binary checks for tests that reuse previous modules by name, as that's a wast-only feature - if os.path.basename(wast) in ['exports.wast']: # FIXME - continue + # skip binary checks for tests that reuse previous modules by name, as that's a wast-only feature + if os.path.basename(wast) in ['exports.wast']: # FIXME + continue - # we must ignore some binary format splits - splits_to_skip = { - 'func.wast': [2], - 'return.wast': [2] - } + # we must ignore some binary format splits + splits_to_skip = { + 'func.wast': [2], + 'return.wast': [2] + } - # check binary format. here we can verify execution of the final result, no need for an output verification - split_num = 0 - if os.path.basename(wast) not in []: # avoid some tests with things still being sorted out in the spec - actual = '' - for module, asserts in split_wast(wast): - skip = splits_to_skip.get(os.path.basename(wast)) or [] - if split_num in skip: - print ' skipping split module', split_num - 1 + # check binary format. here we can verify execution of the final result, no need for an output verification + split_num = 0 + if os.path.basename(wast) not in []: # avoid some tests with things still being sorted out in the spec + actual = '' + for module, asserts in split_wast(wast): + skip = splits_to_skip.get(os.path.basename(wast)) or [] + if split_num in skip: + print ' skipping split module', split_num - 1 + split_num += 1 + continue + print ' testing split module', split_num split_num += 1 - continue - print ' testing split module', split_num - split_num += 1 - with open('split.wast', 'w') as o: o.write(module + '\n' + '\n'.join(asserts)) - run_spec_test('split.wast') # before binary stuff - just check it's still ok split out - run_opt_test('split.wast') # also that our optimizer doesn't break on it - result_wast = binary_format_check('split.wast', verify_final_result=False) - # add the asserts, and verify that the test still passes - open(result_wast, 'a').write('\n' + '\n'.join(asserts)) - actual += run_spec_test(result_wast) - # compare all the outputs to the expected output - check_expected(actual, os.path.join(options.binaryen_test, 'spec', 'expected-output', os.path.basename(wast) + '.log')) - -if MOZJS: + with open('split.wast', 'w') as o: o.write(module + '\n' + '\n'.join(asserts)) + run_spec_test('split.wast') # before binary stuff - just check it's still ok split out + run_opt_test('split.wast') # also that our optimizer doesn't break on it + result_wast = binary_format_check('split.wast', verify_final_result=False) + # add the asserts, and verify that the test still passes + open(result_wast, 'a').write('\n' + '\n'.join(asserts)) + actual += run_spec_test(result_wast) + # compare all the outputs to the expected output + check_expected(actual, os.path.join(options.binaryen_test, 'spec', 'expected-output', os.path.basename(wast) + '.log')) + +def run_binaryen_js_tests(): print '\n[ checking binaryen.js testcases... ]\n' for s in sorted(os.listdir(os.path.join(options.binaryen_test, 'binaryen.js'))): @@ -447,25 +454,21 @@ if MOZJS: if expected not in out: fail(out, expected) -s2wasm.test_s2wasm() -s2wasm.test_linker() -wasm2asm.test_wasm2asm() - -print '\n[ running validation tests... ]\n' -# Ensure the tests validate by default -cmd = WASM_AS + [os.path.join(options.binaryen_test, 'validator', 'invalid_export.wast')] -run_command(cmd) -cmd = WASM_AS + [os.path.join(options.binaryen_test, 'validator', 'invalid_import.wast')] -run_command(cmd) -cmd = WASM_AS + ['--validate=web', os.path.join(options.binaryen_test, 'validator', 'invalid_export.wast')] -run_command(cmd, expected_status=1) -cmd = WASM_AS + ['--validate=web', os.path.join(options.binaryen_test, 'validator', 'invalid_import.wast')] -run_command(cmd, expected_status=1) -cmd = WASM_AS + ['--validate=none', os.path.join(options.binaryen_test, 'validator', 'invalid_return.wast')] -run_command(cmd) - -if options.torture and options.test_waterfall: - +def run_validator_tests(): + print '\n[ running validation tests... ]\n' + # Ensure the tests validate by default + cmd = WASM_AS + [os.path.join(options.binaryen_test, 'validator', 'invalid_export.wast')] + run_command(cmd) + cmd = WASM_AS + [os.path.join(options.binaryen_test, 'validator', 'invalid_import.wast')] + run_command(cmd) + cmd = WASM_AS + ['--validate=web', os.path.join(options.binaryen_test, 'validator', 'invalid_export.wast')] + run_command(cmd, expected_status=1) + cmd = WASM_AS + ['--validate=web', os.path.join(options.binaryen_test, 'validator', 'invalid_import.wast')] + run_command(cmd, expected_status=1) + cmd = WASM_AS + ['--validate=none', os.path.join(options.binaryen_test, 'validator', 'invalid_return.wast')] + run_command(cmd) + +def run_torture_tests(): print '\n[ checking torture testcases... ]\n' unexpected_result_count = 0 @@ -494,8 +497,7 @@ if options.torture and options.test_waterfall: if unexpected_result_count: fail('%s failures' % unexpected_result_count, '0 failures') -if has_vanilla_emcc and has_vanilla_llvm and 0: - +def run_vanilla_tests(): print '\n[ checking emcc WASM_BACKEND testcases...]\n' try: @@ -535,9 +537,7 @@ if has_vanilla_emcc and has_vanilla_llvm and 0: else: del os.environ['EMCC_WASM_BACKEND'] -print '\n[ checking example testcases... ]\n' - -if options.run_gcc_tests: +def run_gcc_torture_tests(): print '\n[ checking native gcc testcases...]\n' if not NATIVECC or not NATIVEXX: fail_with_error('Native compiler (e.g. gcc/g++) was not found in PATH!') @@ -590,8 +590,7 @@ if options.run_gcc_tests: if actual != expected: fail(actual, expected) -if EMCC: - +def run_emscripten_tests(): if MOZJS and 0: print '\n[ checking native wasm support ]\n' @@ -714,6 +713,35 @@ if EMCC: shutil.copyfile(recreated, 'a.wasm.wast') execute() + +# Run all the tests +run_help_tests() +run_wasm_opt_tests() +run_asm2wasm_tests() +run_wasm_dis_tests() +run_wasm_merge_tests() +run_ctor_eval_tests() +if has_shell_timeout(): + run_wasm_reduce_tests() + +run_spec_tests() +if MOZJS: + run_binaryen_js_tests() +s2wasm.test_s2wasm() +s2wasm.test_linker() +wasm2asm.test_wasm2asm() +run_validator_tests() +if options.torture and options.test_waterfall: + run_torture_tests() +if has_vanilla_emcc and has_vanilla_llvm and 0: + run_vanilla_tests() +print '\n[ checking example testcases... ]\n' +if options.run_gcc_tests: + run_gcc_torture_tests() +if EMCC: + run_emscripten_tests() + +# Check/display the results if num_failures == 0: print '\n[ success! ]' diff --git a/src/ast/branch-utils.h b/src/ast/branch-utils.h index 05ead8571..d574945ef 100644 --- a/src/ast/branch-utils.h +++ b/src/ast/branch-utils.h @@ -24,24 +24,23 @@ namespace wasm { namespace BranchUtils { -// branches not actually taken (e.g. (br $out (unreachable))) -// are trivially ignored in our type system +// Some branches are obviously not actually reachable (e.g. (br $out (unreachable))) -inline bool isBranchTaken(Break* br) { +inline bool isBranchReachable(Break* br) { return !(br->value && br->value->type == unreachable) && !(br->condition && br->condition->type == unreachable); } -inline bool isBranchTaken(Switch* sw) { +inline bool isBranchReachable(Switch* sw) { return !(sw->value && sw->value->type == unreachable) && sw->condition->type != unreachable; } -inline bool isBranchTaken(Expression* expr) { +inline bool isBranchReachable(Expression* expr) { if (auto* br = expr->dynCast<Break>()) { - return isBranchTaken(br); + return isBranchReachable(br); } else if (auto* sw = expr->dynCast<Switch>()) { - return isBranchTaken(sw); + return isBranchReachable(sw); } WASM_UNREACHABLE(); } @@ -103,8 +102,9 @@ inline std::set<Name> getBranchTargets(Expression* ast) { // Finds if there are branches targeting a name. Note that since names are // unique in our IR, we just need to look for the name, and do not need // to analyze scoping. -// By default we consider untaken branches (so any named use). You can unset named to -// avoid that (and only note branches that are not obviously unreachable) +// By default we consider all branches, so any place there is a branch that +// names the target. You can unset 'named' to only note branches that appear +// reachable (i.e., are not obviously unreachable). struct BranchSeeker : public PostWalker<BranchSeeker> { Name target; bool named = true; @@ -144,7 +144,7 @@ struct BranchSeeker : public PostWalker<BranchSeeker> { if (curr->default_ == target) noteFound(curr->value); } - static bool hasTaken(Expression* tree, Name target) { + static bool hasReachable(Expression* tree, Name target) { if (!target.is()) return false; BranchSeeker seeker(target); seeker.named = false; @@ -152,7 +152,7 @@ struct BranchSeeker : public PostWalker<BranchSeeker> { return seeker.found > 0; } - static Index countTaken(Expression* tree, Name target) { + static Index countReachable(Expression* tree, Name target) { if (!target.is()) return 0; BranchSeeker seeker(target); seeker.named = false; diff --git a/src/ast_utils.h b/src/ast_utils.h index 852cf89a3..95f725636 100644 --- a/src/ast_utils.h +++ b/src/ast_utils.h @@ -62,7 +62,7 @@ struct ExpressionAnalyzer { if (auto* br = curr->dynCast<Break>()) { if (!br->condition) return true; } else if (auto* block = curr->dynCast<Block>()) { - if (block->list.size() > 0 && obviouslyDoesNotFlowOut(block->list.back()) && !BranchUtils::BranchSeeker::hasTaken(block, block->name)) return true; + if (block->list.size() > 0 && obviouslyDoesNotFlowOut(block->list.back()) && !BranchUtils::BranchSeeker::hasReachable(block, block->name)) return true; } return false; } diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 0b47d3398..ad667f888 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -31,6 +31,8 @@ #include <ast/properties.h> #include <ast/literal-utils.h> +// TODO: Use the new sign-extension opcodes where appropriate. This needs to be conditionalized on the availability of atomics. + namespace wasm { Name I32_EXPR = "i32.expr", diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 3c538a40e..04b649ff6 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -474,6 +474,11 @@ struct PrintSExpression : public Visitor<PrintSExpression> { case DemoteFloat64: o << "f32.demote/f64"; break; case ReinterpretInt32: o << "f32.reinterpret/i32"; break; case ReinterpretInt64: o << "f64.reinterpret/i64"; break; + case ExtendS8Int32: o << "i32.extend8_s"; break; + case ExtendS16Int32: o << "i32.extend16_s"; break; + case ExtendS8Int64: o << "i64.extend8_s"; break; + case ExtendS16Int64: o << "i64.extend16_s"; break; + case ExtendS32Int64: o << "i64.extend32_s"; break; default: abort(); } incIndent(); diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index ec7809f48..7e122b806 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -31,7 +31,7 @@ namespace wasm { // condition and possible value, and the possible value must // not have side effects (as they would run unconditionally) static bool canTurnIfIntoBrIf(Expression* ifCondition, Expression* brValue, PassOptions& options) { - // if the if isn't even taken, this is all dead code anyhow + // if the if isn't even reached, this is all dead code anyhow if (ifCondition->type == unreachable) return false; if (!brValue) return true; EffectAnalyzer value(options, brValue); @@ -512,7 +512,7 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { if (curr->name.is()) { auto* br = list[0]->dynCast<Break>(); // we seek a regular br_if; if the type is unreachable that means it is not - // actually taken, so ignore + // actually reached, so ignore if (br && br->condition && br->name == curr->name && br->type != unreachable) { assert(!br->value); // can't, it would be dropped or last in the block if (BranchUtils::BranchSeeker::countNamed(curr, curr->name) == 1) { diff --git a/src/passes/SimplifyLocals.cpp b/src/passes/SimplifyLocals.cpp index 919784cdf..618f8dead 100644 --- a/src/passes/SimplifyLocals.cpp +++ b/src/passes/SimplifyLocals.cpp @@ -348,7 +348,7 @@ struct SimplifyLocals : public WalkerPass<LinearExecutionWalker<SimplifyLocals>> auto* brp = breaks[j].brp; auto* br = (*brp)->cast<Break>(); assert(!br->value); - // if the break is conditional, then we must set the value here - if the break is not taken, we must still have the new value in the local + // if the break is conditional, then we must set the value here - if the break is not reached, we must still have the new value in the local auto* set = (*breakSetLocalPointer)->cast<SetLocal>(); if (br->condition) { br->value = set; diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 6df49b162..256cea75c 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -531,6 +531,12 @@ enum ASTNodes { F32ReinterpretI32 = 0xbe, F64ReinterpretI64 = 0xbf, + I32ExtendS8 = 0xc0, + I32ExtendS16 = 0xc1, + I64ExtendS8 = 0xc2, + I64ExtendS16 = 0xc3, + I64ExtendS32 = 0xc4, + AtomicPrefix = 0xfe }; diff --git a/src/wasm-validator.h b/src/wasm-validator.h index 35304d1c9..0851d33e8 100644 --- a/src/wasm-validator.h +++ b/src/wasm-validator.h @@ -78,7 +78,6 @@ struct WasmValidator : public PostWalker<WasmValidator> { std::map<Name, Expression*> breakTargets; std::map<Expression*, BreakInfo> breakInfos; - std::set<Name> namedBreakTargets; // even breaks not taken must not be named if they go to a place that does not exist WasmType returnType = unreachable; // type used in returns diff --git a/src/wasm.h b/src/wasm.h index 0cd5b05e8..d2bea45a1 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -98,6 +98,10 @@ enum UnaryOp { PromoteFloat32, // f32 to f64 DemoteFloat64, // f64 to f32 ReinterpretInt32, ReinterpretInt64, // reinterpret bits to float + // The following sign-extention operators go along with wasm atomics support. + // Extend signed subword-sized integer. This differs from e.g. ExtendSInt32 + // because the input integer is in an i64 value insetad of an i32 value. + ExtendS8Int32, ExtendS16Int32, ExtendS8Int64, ExtendS16Int64, ExtendS32Int64, }; enum BinaryOp { diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index f0787600e..68fb293e8 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -670,7 +670,7 @@ void WasmBinaryWriter::visitBreak(Break *curr) { // then either the condition or the value is unreachable, which is // extremely rare, and may require us to make the stack polymorphic // (if the block we branch to has a value, we may lack one as we - // are not a taken branch; the wasm spec on the other hand does + // are not a reachable branch; the wasm spec on the other hand does // presume the br_if emits a value of the right type, even if it // popped unreachable) o << int8_t(BinaryConsts::Unreachable); @@ -683,11 +683,10 @@ void WasmBinaryWriter::visitSwitch(Switch *curr) { recurse(curr->value); } recurse(curr->condition); - if (!BranchUtils::isBranchTaken(curr)) { - // if the branch is not taken, then it's dangerous to emit it, as - // wasm type checking rules are stricter than ours - we tolerate - // an untaken branch to a target with a different value, but not - // wasm. so just don't emit it + if (!BranchUtils::isBranchReachable(curr)) { + // if the branch is not reachable, then it's dangerous to emit it, as + // wasm type checking rules are different, especially in unreachable + // code. so just don't emit that unreachable code. o << int8_t(BinaryConsts::Unreachable); return; } @@ -1049,6 +1048,11 @@ void WasmBinaryWriter::visitUnary(Unary *curr) { case ReinterpretFloat64: o << int8_t(BinaryConsts::I64ReinterpretF64); break; case ReinterpretInt32: o << int8_t(BinaryConsts::F32ReinterpretI32); break; case ReinterpretInt64: o << int8_t(BinaryConsts::F64ReinterpretI64); break; + case ExtendS8Int32: o << int8_t(BinaryConsts::I32ExtendS8); break; + case ExtendS16Int32: o << int8_t(BinaryConsts::I32ExtendS16); break; + case ExtendS8Int64: o << int8_t(BinaryConsts::I64ExtendS8); break; + case ExtendS16Int64: o << int8_t(BinaryConsts::I64ExtendS16); break; + case ExtendS32Int64: o << int8_t(BinaryConsts::I64ExtendS32); break; default: abort(); } if (curr->type == unreachable) { @@ -2679,6 +2683,12 @@ bool WasmBinaryBuilder::maybeVisitUnary(Expression*& out, uint8_t code) { case BinaryConsts::F32ReinterpretI32: curr = allocator.alloc<Unary>(); curr->op = ReinterpretInt32; curr->type = f32; break; case BinaryConsts::F64ReinterpretI64: curr = allocator.alloc<Unary>(); curr->op = ReinterpretInt64; curr->type = f64; break; + case BinaryConsts::I32ExtendS8: curr = allocator.alloc<Unary>(); curr->op = ExtendS8Int32; curr->type = i32; break; + case BinaryConsts::I32ExtendS16: curr = allocator.alloc<Unary>(); curr->op = ExtendS16Int32; curr->type = i32; break; + case BinaryConsts::I64ExtendS8: curr = allocator.alloc<Unary>(); curr->op = ExtendS8Int64; curr->type = i64; break; + case BinaryConsts::I64ExtendS16: curr = allocator.alloc<Unary>(); curr->op = ExtendS16Int64; curr->type = i64; break; + case BinaryConsts::I64ExtendS32: curr = allocator.alloc<Unary>(); curr->op = ExtendS32Int64; curr->type = i64; break; + default: return false; } if (debug) std::cerr << "zz node: Unary" << std::endl; diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp index fd7c7e7ae..a95b551ea 100644 --- a/src/wasm/wasm-s-parser.cpp +++ b/src/wasm/wasm-s-parser.cpp @@ -699,7 +699,12 @@ Expression* SExpressionWasmBuilder::makeExpression(Element& s) { if (op[2] == 0) return makeBinary(s, BINARY_INT_OR_FLOAT(Eq), type); if (op[2] == 'z') return makeUnary(s, type == i32 ? UnaryOp::EqZInt32 : UnaryOp::EqZInt64, type); } - if (op[1] == 'x') return makeUnary(s, op[7] == 'u' ? UnaryOp::ExtendUInt32 : UnaryOp::ExtendSInt32, type); + if (op[1] == 'x') { + if (op[6] == '8') return makeUnary(s, type == i32 ? UnaryOp::ExtendS8Int32 : UnaryOp::ExtendS8Int64, type); + if (op[6] == '1') return makeUnary(s, type == i32 ? UnaryOp::ExtendS16Int32 : UnaryOp::ExtendS16Int64, type); + if (op[6] == '3') return makeUnary(s, UnaryOp::ExtendS32Int64, type); + return makeUnary(s, op[7] == 'u' ? UnaryOp::ExtendUInt32 : UnaryOp::ExtendSInt32, type); + } abort_on(op); } case 'f': { @@ -920,6 +925,8 @@ Expression* SExpressionWasmBuilder::makeUnary(Element& s, UnaryOp op, WasmType t break; } case ExtendSInt32: case ExtendUInt32: + case ExtendS8Int32: case ExtendS16Int32: + case ExtendS8Int64: case ExtendS16Int64: case ExtendS32Int64: case WrapInt64: case PromoteFloat32: case DemoteFloat64: diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 877232521..5534eb7b9 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -58,7 +58,6 @@ void WasmValidator::visitBlock(Block *curr) { } } breakTargets.erase(curr->name); - namedBreakTargets.erase(curr->name); } if (curr->list.size() > 1) { for (Index i = 0; i < curr->list.size() - 1; i++) { @@ -88,7 +87,6 @@ void WasmValidator::visitLoop(Loop *curr) { if (curr->name.is()) { noteLabelName(curr->name); breakTargets.erase(curr->name); - namedBreakTargets.erase(curr->name); if (breakInfos.count(curr) > 0) { auto& info = breakInfos[curr]; shouldBeEqual(info.arity, Index(0), curr, "breaks to a loop cannot pass a value"); @@ -128,7 +126,6 @@ void WasmValidator::visitIf(If *curr) { } void WasmValidator::noteBreak(Name name, Expression* value, Expression* curr) { - namedBreakTargets.insert(name); WasmType valueType = none; Index arity = 0; if (value) { @@ -433,8 +430,17 @@ void WasmValidator::visitUnary(Unary *curr) { shouldBeTrue(curr->value->type == i64, curr, "i64.eqz input must be i64"); break; } - case ExtendSInt32: shouldBeEqual(curr->value->type, i32, curr, "extend type must be correct"); break; - case ExtendUInt32: shouldBeEqual(curr->value->type, i32, curr, "extend type must be correct"); break; + case ExtendSInt32: + case ExtendUInt32: + case ExtendS8Int32: + case ExtendS16Int32: { + shouldBeEqual(curr->value->type, i32, curr, "extend type must be correct"); break; + } + case ExtendS8Int64: + case ExtendS16Int64: + case ExtendS32Int64: { + shouldBeEqual(curr->value->type, i64, curr, "extend type must be correct"); break; + } case WrapInt64: shouldBeEqual(curr->value->type, i64, curr, "wrap type must be correct"); break; case TruncSFloat32ToInt32: shouldBeEqual(curr->value->type, f32, curr, "trunc type must be correct"); break; case TruncSFloat32ToInt64: shouldBeEqual(curr->value->type, f32, curr, "trunc type must be correct"); break; @@ -550,9 +556,7 @@ void WasmValidator::visitFunction(Function *curr) { if (returnType != unreachable) { shouldBeEqual(curr->result, returnType, curr->body, "function result must match, if function has returns"); } - if (!shouldBeTrue(namedBreakTargets.empty(), curr->body, "all named break targets must exist") && !quiet) { - std::cerr << "(on label " << *namedBreakTargets.begin() << ")\n"; - } + shouldBeTrue(breakTargets.empty(), curr->body, "all named break targets must exist"); returnType = unreachable; labelNames.clear(); } diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index a781e0fa3..6db11cc7d 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -443,7 +443,8 @@ void Unary::finalize() { case SqrtFloat64: type = value->type; break; case EqZInt32: case EqZInt64: type = i32; break; - case ExtendSInt32: case ExtendUInt32: type = i64; break; + case ExtendS8Int32: case ExtendS16Int32: type = i32; break; + case ExtendSInt32: case ExtendUInt32: case ExtendS8Int64: case ExtendS16Int64: case ExtendS32Int64: type = i64; break; case WrapInt64: type = i32; break; case PromoteFloat32: type = f64; break; case DemoteFloat64: type = f32; break; diff --git a/test/signext.wast b/test/signext.wast new file mode 100644 index 000000000..3370e9b16 --- /dev/null +++ b/test/signext.wast @@ -0,0 +1,12 @@ +(module + (type $0 (func)) + (func $signext (type $0) + (local $0 i32) + (local $1 i64) + (drop (i32.extend8_s (get_local $0))) + (drop (i32.extend16_s (get_local $0))) + (drop (i64.extend8_s (get_local $1))) + (drop (i64.extend16_s (get_local $1))) + (drop (i64.extend32_s (get_local $1))) + ) +) diff --git a/test/signext.wast.from-wast b/test/signext.wast.from-wast new file mode 100644 index 000000000..6828723b5 --- /dev/null +++ b/test/signext.wast.from-wast @@ -0,0 +1,33 @@ +(module + (type $0 (func)) + (memory $0 0) + (func $signext (type $0) + (local $0 i32) + (local $1 i64) + (drop + (i32.extend8_s + (get_local $0) + ) + ) + (drop + (i32.extend16_s + (get_local $0) + ) + ) + (drop + (i64.extend8_s + (get_local $1) + ) + ) + (drop + (i64.extend16_s + (get_local $1) + ) + ) + (drop + (i64.extend32_s + (get_local $1) + ) + ) + ) +) diff --git a/test/signext.wast.fromBinary b/test/signext.wast.fromBinary new file mode 100644 index 000000000..ec9b50aa2 --- /dev/null +++ b/test/signext.wast.fromBinary @@ -0,0 +1,36 @@ +(module + (type $0 (func)) + (memory $0 0) + (func $signext (type $0) + (local $var$0 i32) + (local $var$1 i64) + (block $label$0 + (drop + (i32.extend8_s + (get_local $var$0) + ) + ) + (drop + (i32.extend16_s + (get_local $var$0) + ) + ) + (drop + (i64.extend8_s + (get_local $var$1) + ) + ) + (drop + (i64.extend16_s + (get_local $var$1) + ) + ) + (drop + (i64.extend32_s + (get_local $var$1) + ) + ) + ) + ) +) + diff --git a/test/signext.wast.fromBinary.noDebugInfo b/test/signext.wast.fromBinary.noDebugInfo new file mode 100644 index 000000000..04ada421c --- /dev/null +++ b/test/signext.wast.fromBinary.noDebugInfo @@ -0,0 +1,36 @@ +(module + (type $0 (func)) + (memory $0 0) + (func $0 (type $0) + (local $var$0 i32) + (local $var$1 i64) + (block $label$0 + (drop + (i32.extend8_s + (get_local $var$0) + ) + ) + (drop + (i32.extend16_s + (get_local $var$0) + ) + ) + (drop + (i64.extend8_s + (get_local $var$1) + ) + ) + (drop + (i64.extend16_s + (get_local $var$1) + ) + ) + (drop + (i64.extend32_s + (get_local $var$1) + ) + ) + ) + ) +) + |