diff --git a/.gitignore b/.gitignore index 28042929af5..b6219851acf 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ trace.cpp test/wasm-binaries-*.tbz2 test/wasm-torture-s-*.tbz2 test/wasm-install/ +test/validator/*.wasm *.pyc CMakeFiles/ diff --git a/check.py b/check.py index e0d56937336..4d025eb1bdb 100755 --- a/check.py +++ b/check.py @@ -22,7 +22,7 @@ from scripts.test.support import run_command, split_wast from scripts.test.shared import ( - ASM2WASM, BIN_DIR, EMCC, MOZJS, NATIVECC, NATIVEXX, NODEJS, S2WASM_EXE, + BIN_DIR, EMCC, MOZJS, NATIVECC, NATIVEXX, NODEJS, S2WASM_EXE, WASM_AS, WASM_CTOR_EVAL, WASM_OPT, WASM_SHELL, WASM_MERGE, WASM_SHELL_EXE, WASM_DIS, WASM_REDUCE, binary_format_check, delete_from_orbit, fail, fail_with_error, fail_if_not_identical, fail_if_not_contained, has_vanilla_emcc, @@ -30,6 +30,7 @@ requested, warnings, has_shell_timeout ) +import scripts.test.asm2wasm as asm2wasm import scripts.test.s2wasm as s2wasm import scripts.test.wasm2asm as wasm2asm @@ -144,6 +145,7 @@ def run_wasm_opt_tests(): actual = actual.replace('printing before:\n', '') expected = open(f, 'rb').read() + if actual != expected: fail(actual, expected) @@ -152,126 +154,6 @@ def run_wasm_opt_tests(): 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' @@ -717,7 +599,8 @@ def execute(): # Run all the tests run_help_tests() run_wasm_opt_tests() -run_asm2wasm_tests() +asm2wasm.test_asm2wasm() +asm2wasm.test_asm2wasm_binary() run_wasm_dis_tests() run_wasm_merge_tests() run_ctor_eval_tests() diff --git a/scripts/test/asm2wasm.py b/scripts/test/asm2wasm.py new file mode 100644 index 00000000000..db37a237aff --- /dev/null +++ b/scripts/test/asm2wasm.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python + +# Copyright 2017 WebAssembly Community Group participants +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import subprocess + +from support import run_command +from shared import ( + ASM2WASM, WASM_OPT, binary_format_check, delete_from_orbit, + fail, fail_with_error, fail_if_not_identical, options, tests +) + + +def test_asm2wasm(): + print '[ checking asm2wasm testcases... ]\n' + + for asm in tests: + if not asm.endswith('.asm.js'): + continue + 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' + print "With BINARYEN_PASS_DEBUG=1:" + do_asm2wasm_test() + del os.environ['BINARYEN_PASS_DEBUG'] + print "With BINARYEN_PASS_DEBUG disabled:" + 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: + # failed to pretty-print + fail_with_error('wasm interpreter error: ' + err) + 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') + url = 'http://example.org/' + jsmap + assert len(url) < 256, 'name too long' + url_section_contents = bytearray([len(url)]) + bytearray(url) + 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') + url_section_index = binary_contents.index(url_section_name) + if url_section_contents not in binary_contents[url_section_index:]: + fail_with_error('source map url not found in url section') + + +def test_asm2wasm_binary(): + 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' + + +if __name__ == '__main__': + test_asm2wasm() + test_asm2wasm_binary() diff --git a/scripts/test/s2wasm.py b/scripts/test/s2wasm.py index 3f38acbace0..fcba17d1836 100755 --- a/scripts/test/s2wasm.py +++ b/scripts/test/s2wasm.py @@ -32,38 +32,48 @@ def test_s2wasm(): fail_if_not_contained( output, '(import "env" "memory" (memory $0 1))') + extension_arg_map = { + '.wast': [], + '.clamp.wast': ['--emit-clamped-potential-traps'], + '.js.wast': ['--emit-jsified-potential-traps'], + } for dot_s_dir in ['dot_s', 'llvm_autogenerated']: dot_s_path = os.path.join(options.binaryen_test, dot_s_dir) for s in sorted(os.listdir(dot_s_path)): if not s.endswith('.s'): continue print '..', s - wasm = s.replace('.s', '.wast') - full = os.path.join(options.binaryen_test, dot_s_dir, s) - stack_alloc = (['--allocate-stack=1024'] - if dot_s_dir == 'llvm_autogenerated' - else []) - cmd = S2WASM + [full, '--emscripten-glue'] + stack_alloc - if s.startswith('start_'): - cmd.append('--start') - actual = run_command(cmd) - - # verify output - expected_file = os.path.join(options.binaryen_test, dot_s_dir, wasm) - if not os.path.exists(expected_file): - print actual - fail_with_error('output ' + expected_file + ' does not exist') - expected = open(expected_file, 'rb').read() - if actual != expected: - fail(actual, expected) - - # verify with options - cmd = S2WASM + [full, '--global-base=1024'] + stack_alloc - run_command(cmd) - - # run wasm-shell on the .wast to verify that it parses - cmd = WASM_SHELL + [expected_file] - run_command(cmd) + for ext, ext_args in extension_arg_map.iteritems(): + wasm = s.replace('.s', ext) + expected_file = os.path.join(options.binaryen_test, dot_s_dir, wasm) + expected_exists = os.path.exists(expected_file) + if ext != '.wast' and not expected_exists: + continue + + full = os.path.join(options.binaryen_test, dot_s_dir, s) + stack_alloc = (['--allocate-stack=1024'] + if dot_s_dir == 'llvm_autogenerated' + else []) + cmd = S2WASM + [full, '--emscripten-glue'] + stack_alloc + ext_args + if s.startswith('start_'): + cmd.append('--start') + actual = run_command(cmd) + + # verify output + if not expected_exists: + print actual + fail_with_error('output ' + expected_file + ' does not exist') + expected = open(expected_file, 'rb').read() + if actual != expected: + fail(actual, expected) + + # verify with options + cmd = S2WASM + [full, '--global-base=1024'] + stack_alloc + run_command(cmd) + + # run wasm-shell on the .wast to verify that it parses + cmd = WASM_SHELL + [expected_file] + run_command(cmd) def test_linker(): diff --git a/src/asm2wasm.h b/src/asm2wasm.h index 044517d12ba..7d4ce5fbeb5 100644 --- a/src/asm2wasm.h +++ b/src/asm2wasm.h @@ -33,6 +33,7 @@ #include "parsing.h" #include "ast_utils.h" #include "ast/branch-utils.h" +#include "ast/trapping.h" #include "wasm-builder.h" #include "wasm-emscripten.h" #include "wasm-module-building.h" @@ -88,10 +89,6 @@ Name I32_CTTZ("i32_cttz"), I64_CTTZ("i64_cttz"), I64_CTLZ("i64_ctlz"), I64_CTPOP("i64_ctpop"), - I64S_REM("i64s-rem"), - I64U_REM("i64u-rem"), - I64S_DIV("i64s-div"), - I64U_DIV("i64u-div"), F32_COPYSIGN("f32_copysign"), F64_COPYSIGN("f64_copysign"), LOAD1("load1"), @@ -356,12 +353,6 @@ struct AdjustDebugInfo : public WalkerPasstype == f32) { - auto conv = allocator.alloc(); - conv->op = PromoteFloat32; - conv->value = expr; - conv->type = WasmType::f64; - return conv; - } - assert(expr->type == f64); - return expr; - } - - // Some binary opts might trap, so emit them safely if necessary - Expression* makeTrappingI32Binary(BinaryOp op, Expression* left, Expression* right) { - if (trapMode == TrapMode::Allow) return builder.makeBinary(op, left, right); - // the wasm operation might trap if done over 0, so generate a safe call - auto *call = allocator.alloc(); - switch (op) { - case BinaryOp::RemSInt32: call->target = I32S_REM; break; - case BinaryOp::RemUInt32: call->target = I32U_REM; break; - case BinaryOp::DivSInt32: call->target = I32S_DIV; break; - case BinaryOp::DivUInt32: call->target = I32U_DIV; break; - default: WASM_UNREACHABLE(); - } - call->operands.push_back(left); - call->operands.push_back(right); - call->type = i32; - static std::set addedFunctions; - if (addedFunctions.count(call->target) == 0) { - Expression* result = builder.makeBinary(op, - builder.makeGetLocal(0, i32), - builder.makeGetLocal(1, i32) - ); - if (op == DivSInt32) { - // guard against signed division overflow - result = builder.makeIf( - builder.makeBinary(AndInt32, - builder.makeBinary(EqInt32, - builder.makeGetLocal(0, i32), - builder.makeConst(Literal(std::numeric_limits::min())) - ), - builder.makeBinary(EqInt32, - builder.makeGetLocal(1, i32), - builder.makeConst(Literal(int32_t(-1))) - ) - ), - builder.makeConst(Literal(int32_t(0))), - result - ); - } - addedFunctions.insert(call->target); - auto func = new Function; - func->name = call->target; - func->params.push_back(i32); - func->params.push_back(i32); - func->result = i32; - func->body = builder.makeIf( - builder.makeUnary(EqZInt32, - builder.makeGetLocal(1, i32) - ), - builder.makeConst(Literal(int32_t(0))), - result - ); - wasm.addFunction(func); - } - return call; - } - - // Some binary opts might trap, so emit them safely if necessary - Expression* makeTrappingI64Binary(BinaryOp op, Expression* left, Expression* right) { - if (trapMode == TrapMode::Allow) return builder.makeBinary(op, left, right); - // wasm operation might trap if done over 0, so generate a safe call - auto *call = allocator.alloc(); - switch (op) { - case BinaryOp::RemSInt64: call->target = I64S_REM; break; - case BinaryOp::RemUInt64: call->target = I64U_REM; break; - case BinaryOp::DivSInt64: call->target = I64S_DIV; break; - case BinaryOp::DivUInt64: call->target = I64U_DIV; break; - default: WASM_UNREACHABLE(); - } - call->operands.push_back(left); - call->operands.push_back(right); - call->type = i64; - static std::set addedFunctions; - if (addedFunctions.count(call->target) == 0) { - Expression* result = builder.makeBinary(op, - builder.makeGetLocal(0, i64), - builder.makeGetLocal(1, i64) - ); - if (op == DivSInt64) { - // guard against signed division overflow - result = builder.makeIf( - builder.makeBinary(AndInt32, - builder.makeBinary(EqInt64, - builder.makeGetLocal(0, i64), - builder.makeConst(Literal(std::numeric_limits::min())) - ), - builder.makeBinary(EqInt64, - builder.makeGetLocal(1, i64), - builder.makeConst(Literal(int64_t(-1))) - ) - ), - builder.makeConst(Literal(int64_t(0))), - result - ); - } - addedFunctions.insert(call->target); - auto func = new Function; - func->name = call->target; - func->params.push_back(i64); - func->params.push_back(i64); - func->result = i64; - func->body = builder.makeIf( - builder.makeUnary(EqZInt64, - builder.makeGetLocal(1, i64) - ), - builder.makeConst(Literal(int64_t(0))), - result - ); - wasm.addFunction(func); - } - return call; - } - - // Some conversions might trap, so emit them safely if necessary - Expression* makeTrappingFloatToInt32(bool signed_, Expression* value) { - if (trapMode == TrapMode::Allow) { - auto ret = allocator.alloc(); - ret->value = value; - bool isF64 = ret->value->type == f64; - if (signed_) { - ret->op = isF64 ? TruncSFloat64ToInt32 : TruncSFloat32ToInt32; - } else { - ret->op = isF64 ? TruncUFloat64ToInt32 : TruncUFloat32ToInt32; - } - ret->type = WasmType::i32; - return ret; - } - // WebAssembly traps on float-to-int overflows, but asm.js wouldn't, so we must do something - // First, normalize input to f64 - auto input = ensureDouble(value); - // We can handle this in one of two ways: clamping, which is fast, or JS, which - // is precisely like JS but in order to do that we do a slow ffi - if (trapMode == TrapMode::JS) { - // WebAssembly traps on float-to-int overflows, but asm.js wouldn't, so we must emulate that - CallImport *ret = allocator.alloc(); - ret->target = F64_TO_INT; - ret->operands.push_back(input); - ret->type = i32; - static bool addedImport = false; - if (!addedImport) { - addedImport = true; - auto import = new Import; // f64-to-int = asm2wasm.f64-to-int; - import->name = F64_TO_INT; - import->module = ASM2WASM; - import->base = F64_TO_INT; - import->functionType = ensureFunctionType("id", &wasm)->name; - import->kind = ExternalKind::Function; - wasm.addImport(import); - } - return ret; - } - assert(trapMode == TrapMode::Clamp); - Call *ret = allocator.alloc(); - ret->target = F64_TO_INT; - ret->operands.push_back(input); - ret->type = i32; - static bool added = false; - if (!added) { - added = true; - auto func = new Function; - func->name = ret->target; - func->params.push_back(f64); - func->result = i32; - func->body = builder.makeUnary(TruncSFloat64ToInt32, - builder.makeGetLocal(0, f64) - ); - // too small XXX this is different than asm.js, which does frem. here we clamp, which is much simpler/faster, and similar to native builds - func->body = builder.makeIf( - builder.makeBinary(LeFloat64, - builder.makeGetLocal(0, f64), - builder.makeConst(Literal(double(std::numeric_limits::min()) - 1)) - ), - builder.makeConst(Literal(int32_t(std::numeric_limits::min()))), - func->body - ); - // too big XXX see above - func->body = builder.makeIf( - builder.makeBinary(GeFloat64, - builder.makeGetLocal(0, f64), - builder.makeConst(Literal(double(std::numeric_limits::max()) + 1)) - ), - builder.makeConst(Literal(int32_t(std::numeric_limits::min()))), // NB: min here as well. anything out of range => to the min - func->body - ); - // nan - func->body = builder.makeIf( - builder.makeBinary(NeFloat64, - builder.makeGetLocal(0, f64), - builder.makeGetLocal(0, f64) - ), - builder.makeConst(Literal(int32_t(std::numeric_limits::min()))), // NB: min here as well. anything invalid => to the min - func->body - ); - wasm.addFunction(func); - } - return ret; - } - - Expression* makeTrappingFloatToInt64(bool signed_, Expression* value) { - if (trapMode == TrapMode::Allow) { - auto ret = allocator.alloc(); - ret->value = value; - bool isF64 = ret->value->type == f64; - if (signed_) { - ret->op = isF64 ? TruncSFloat64ToInt64 : TruncSFloat32ToInt64; - } else { - ret->op = isF64 ? TruncUFloat64ToInt64 : TruncUFloat32ToInt64; - } - ret->type = WasmType::i64; - return ret; - } - // WebAssembly traps on float-to-int overflows, but asm.js wouldn't, so we must do something - // First, normalize input to f64 - auto input = ensureDouble(value); - // There is no "JS" way to handle this, as no i64s in JS, so always clamp if we don't allow traps - Call *ret = allocator.alloc(); - ret->target = F64_TO_INT64; - ret->operands.push_back(input); - ret->type = i64; - static bool added = false; - if (!added) { - added = true; - auto func = new Function; - func->name = ret->target; - func->params.push_back(f64); - func->result = i64; - func->body = builder.makeUnary(TruncSFloat64ToInt64, - builder.makeGetLocal(0, f64) - ); - // too small - func->body = builder.makeIf( - builder.makeBinary(LeFloat64, - builder.makeGetLocal(0, f64), - builder.makeConst(Literal(double(std::numeric_limits::min()) - 1)) - ), - builder.makeConst(Literal(int64_t(std::numeric_limits::min()))), - func->body - ); - // too big - func->body = builder.makeIf( - builder.makeBinary(GeFloat64, - builder.makeGetLocal(0, f64), - builder.makeConst(Literal(double(std::numeric_limits::max()) + 1)) - ), - builder.makeConst(Literal(int64_t(std::numeric_limits::min()))), // NB: min here as well. anything out of range => to the min - func->body - ); - // nan - func->body = builder.makeIf( - builder.makeBinary(NeFloat64, - builder.makeGetLocal(0, f64), - builder.makeGetLocal(0, f64) - ), - builder.makeConst(Literal(int64_t(std::numeric_limits::min()))), // NB: min here as well. anything invalid => to the min - func->body - ); - wasm.addFunction(func); - } - return ret; + return wasm::ensureDouble(expr, allocator); } Expression* truncateToInt32(Expression* value) { @@ -1433,15 +1157,23 @@ void Asm2WasmBuilder::processAsm(Ref ast) { } } } - auto importResult = getModule()->getFunctionType(getModule()->getImport(curr->target)->functionType)->result; + Module* wasm = getModule(); + auto importResult = wasm->getFunctionType(wasm->getImport(curr->target)->functionType)->result; if (curr->type != importResult) { auto old = curr->type; curr->type = importResult; if (importResult == f64) { // we use a JS f64 value which is the most general, and convert to it switch (old) { - case i32: replaceCurrent(parent->makeTrappingFloatToInt32(true /* signed, asm.js ffi */, curr)); break; - case f32: replaceCurrent(parent->builder.makeUnary(DemoteFloat64, curr)); break; + case i32: { + Unary* trunc = parent->builder.makeUnary(TruncSFloat64ToInt32, curr); + replaceCurrent(makeTrappingUnary(trunc, parent->trappingFunctions)); + break; + } + case f32: { + replaceCurrent(parent->builder.makeUnary(DemoteFloat64, curr)); + break; + } case none: { // this function returns a value, but we are not using it, so it must be dropped. // autodrop will do that for us. @@ -1896,12 +1628,8 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { wasm.addImport(import); } return call; - } else if (trapMode != TrapMode::Allow && - (ret->op == BinaryOp::RemSInt32 || ret->op == BinaryOp::RemUInt32 || - ret->op == BinaryOp::DivSInt32 || ret->op == BinaryOp::DivUInt32)) { - return makeTrappingI32Binary(ret->op, ret->left, ret->right); } - return ret; + return makeTrappingBinary(ret, trappingFunctions); } else if (what == SUB) { Ref target = ast[1]; assert(target->isString()); @@ -1969,7 +1697,20 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { // ~, might be ~~ as a coercion or just a not if (ast[2]->isArray(UNARY_PREFIX) && ast[2][1] == B_NOT) { // if we have an unsigned coercion on us, it is an unsigned op - return makeTrappingFloatToInt32(!isParentUnsignedCoercion(astStackHelper.getParent()), process(ast[2][2])); + Expression* expr = process(ast[2][2]); + bool isSigned = !isParentUnsignedCoercion(astStackHelper.getParent()); + bool isF64 = expr->type == f64; + UnaryOp op; + if (isSigned && isF64) { + op = UnaryOp::TruncSFloat64ToInt32; + } else if (isSigned && !isF64) { + op = UnaryOp::TruncSFloat32ToInt32; + } else if (!isSigned && isF64) { + op = UnaryOp::TruncUFloat64ToInt32; + } else { // !isSigned && !isF64 + op = UnaryOp::TruncUFloat32ToInt32; + } + return makeTrappingUnary(builder.makeUnary(op, expr), trappingFunctions); } // no bitwise unary not, so do xor with -1 auto ret = allocator.alloc(); @@ -2216,8 +1957,22 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { if (name == I64_S2D) return builder.makeUnary(UnaryOp::ConvertSInt64ToFloat64, value); if (name == I64_U2F) return builder.makeUnary(UnaryOp::ConvertUInt64ToFloat32, value); if (name == I64_U2D) return builder.makeUnary(UnaryOp::ConvertUInt64ToFloat64, value); - if (name == I64_F2S || name == I64_D2S) return makeTrappingFloatToInt64(true /* signed */, value); - if (name == I64_F2U || name == I64_D2U) return makeTrappingFloatToInt64(false /* unsigned */, value); + if (name == I64_F2S) { + Unary* conv = builder.makeUnary(UnaryOp::TruncSFloat32ToInt64, value); + return makeTrappingUnary(conv, trappingFunctions); + } + if (name == I64_D2S) { + Unary* conv = builder.makeUnary(UnaryOp::TruncSFloat64ToInt64, value); + return makeTrappingUnary(conv, trappingFunctions); + } + if (name == I64_F2U) { + Unary* conv = builder.makeUnary(UnaryOp::TruncUFloat32ToInt64, value); + return makeTrappingUnary(conv, trappingFunctions); + } + if (name == I64_D2U) { + Unary* conv = builder.makeUnary(UnaryOp::TruncUFloat64ToInt64, value); + return makeTrappingUnary(conv, trappingFunctions); + } if (name == I64_BC2D) return builder.makeUnary(UnaryOp::ReinterpretInt64, value); if (name == I64_BC2I) return builder.makeUnary(UnaryOp::ReinterpretFloat64, value); if (name == I64_CTTZ) return builder.makeUnary(UnaryOp::CtzInt64, value); @@ -2231,10 +1986,22 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { if (name == I64_ADD) return builder.makeBinary(BinaryOp::AddInt64, left, right); if (name == I64_SUB) return builder.makeBinary(BinaryOp::SubInt64, left, right); if (name == I64_MUL) return builder.makeBinary(BinaryOp::MulInt64, left, right); - if (name == I64_UDIV) return makeTrappingI64Binary(BinaryOp::DivUInt64, left, right); - if (name == I64_SDIV) return makeTrappingI64Binary(BinaryOp::DivSInt64, left, right); - if (name == I64_UREM) return makeTrappingI64Binary(BinaryOp::RemUInt64, left, right); - if (name == I64_SREM) return makeTrappingI64Binary(BinaryOp::RemSInt64, left, right); + if (name == I64_UDIV) { + Binary* div = builder.makeBinary(BinaryOp::DivUInt64, left, right); + return makeTrappingBinary(div, trappingFunctions); + } + if (name == I64_SDIV) { + Binary* div = builder.makeBinary(BinaryOp::DivSInt64, left, right); + return makeTrappingBinary(div, trappingFunctions); + } + if (name == I64_UREM) { + Binary* rem = builder.makeBinary(BinaryOp::RemUInt64, left, right); + return makeTrappingBinary(rem, trappingFunctions); + } + if (name == I64_SREM) { + Binary* rem = builder.makeBinary(BinaryOp::RemSInt64, left, right); + return makeTrappingBinary(rem, trappingFunctions); + } if (name == I64_AND) return builder.makeBinary(BinaryOp::AndInt64, left, right); if (name == I64_OR) return builder.makeBinary(BinaryOp::OrInt64, left, right); if (name == I64_XOR) return builder.makeBinary(BinaryOp::XorInt64, left, right); diff --git a/src/asm_v_wasm.h b/src/asm_v_wasm.h index 53881861cf5..d42a1082aee 100644 --- a/src/asm_v_wasm.h +++ b/src/asm_v_wasm.h @@ -70,6 +70,9 @@ FunctionType* sigToFunctionType(std::string sig); FunctionType* ensureFunctionType(std::string sig, Module* wasm); +// converts an f32 to an f64 if necessary +Expression* ensureDouble(Expression* expr, MixedArena& allocator); + } // namespace wasm #endif // wasm_asm_v_wasm_h diff --git a/src/asmjs/asm_v_wasm.cpp b/src/asmjs/asm_v_wasm.cpp index ae7d320cacf..bfb04a9fd57 100644 --- a/src/asmjs/asm_v_wasm.cpp +++ b/src/asmjs/asm_v_wasm.cpp @@ -109,4 +109,16 @@ FunctionType* ensureFunctionType(std::string sig, Module* wasm) { return type; } +Expression* ensureDouble(Expression* expr, MixedArena& allocator) { + if (expr->type == f32) { + auto conv = allocator.alloc(); + conv->op = PromoteFloat32; + conv->value = expr; + conv->type = WasmType::f64; + return conv; + } + assert(expr->type == f64); + return expr; +} + } // namespace wasm diff --git a/src/asmjs/shared-constants.cpp b/src/asmjs/shared-constants.cpp index 43c3d106555..f62be6168b3 100644 --- a/src/asmjs/shared-constants.cpp +++ b/src/asmjs/shared-constants.cpp @@ -42,7 +42,13 @@ cashew::IString GLOBAL("global"), ASM2WASM("asm2wasm"), F64_REM("f64-rem"), F64_TO_INT("f64-to-int"), + F64_TO_UINT("f64-to-uint"), F64_TO_INT64("f64-to-int64"), + F64_TO_UINT64("f64-to-uint64"), + F32_TO_INT("f32-to-int"), + F32_TO_UINT("f32-to-uint"), + F32_TO_INT64("f32-to-int64"), + F32_TO_UINT64("f32-to-uint64"), I32S_DIV("i32s-div"), I32U_DIV("i32u-div"), I32S_REM("i32s-rem"), diff --git a/src/asmjs/shared-constants.h b/src/asmjs/shared-constants.h index ce5cd95a65e..7e4b27c8590 100644 --- a/src/asmjs/shared-constants.h +++ b/src/asmjs/shared-constants.h @@ -45,7 +45,13 @@ extern cashew::IString GLOBAL, ASM2WASM, F64_REM, F64_TO_INT, + F64_TO_UINT, F64_TO_INT64, + F64_TO_UINT64, + F32_TO_INT, + F32_TO_UINT, + F32_TO_INT64, + F32_TO_UINT64, I32S_DIV, I32U_DIV, I32S_REM, diff --git a/src/ast/trapping.h b/src/ast/trapping.h new file mode 100644 index 00000000000..80cc14da913 --- /dev/null +++ b/src/ast/trapping.h @@ -0,0 +1,100 @@ +/* + * Copyright 2017 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef wasm_ast_trapping_h +#define wasm_ast_trapping_h + +#include "pass.h" + +namespace wasm { + +enum class TrapMode { + Allow, + Clamp, + JS +}; + +inline void addTrapModePass(PassRunner& runner, TrapMode trapMode) { + if (trapMode == TrapMode::Clamp) { + runner.add("trap-mode-clamp"); + } else if (trapMode == TrapMode::JS) { + runner.add("trap-mode-js"); + } +} + +class TrappingFunctionContainer { +public: + TrappingFunctionContainer(TrapMode mode, Module &wasm, bool immediate = false) + : mode(mode), + wasm(wasm), + immediate(immediate) { } + + bool hasFunction(Name name) { + return functions.find(name) != functions.end(); + } + bool hasImport(Name name) { + return imports.find(name) != imports.end(); + } + + void addFunction(Function* function) { + functions[function->name] = function; + if (immediate) { + wasm.addFunction(function); + } + } + void addImport(Import* import) { + imports[import->name] = import; + if (immediate) { + wasm.addImport(import); + } + } + + void addToModule() { + if (!immediate) { + for (auto &pair : functions) { + wasm.addFunction(pair.second); + } + for (auto &pair : imports) { + wasm.addImport(pair.second); + } + } + functions.clear(); + imports.clear(); + } + + TrapMode getMode() { + return mode; + } + + Module& getModule() { + return wasm; + } + +private: + std::map functions; + std::map imports; + + TrapMode mode; + Module& wasm; + bool immediate; +}; + +Expression* makeTrappingBinary(Binary* curr, TrappingFunctionContainer &trappingFunctions); +Expression* makeTrappingUnary(Unary* curr, TrappingFunctionContainer &trappingFunctions); + +} // wasm + +#endif // wasm_ast_trapping_h diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index 6870259c4a1..3cb00b79644 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -34,6 +34,7 @@ SET(passes_SOURCES RemoveUnusedModuleElements.cpp ReorderLocals.cpp ReorderFunctions.cpp + TrapMode.cpp SafeHeap.cpp SimplifyLocals.cpp SSAify.cpp diff --git a/src/passes/TrapMode.cpp b/src/passes/TrapMode.cpp new file mode 100644 index 00000000000..e648f66a573 --- /dev/null +++ b/src/passes/TrapMode.cpp @@ -0,0 +1,318 @@ +/* + * Copyright 2017 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// Pass that supports potentially-trapping wasm operations. +// For example, integer division traps when dividing by zero, so this pass +// generates a check and replaces the result with zero in that case. +// + +#include "asm_v_wasm.h" +#include "asmjs/shared-constants.h" +#include "ast/trapping.h" +#include "mixed_arena.h" +#include "pass.h" +#include "wasm.h" +#include "wasm-builder.h" +#include "wasm-printing.h" +#include "wasm-type.h" +#include "support/name.h" + +namespace wasm { + +Name I64S_REM("i64s-rem"), + I64U_REM("i64u-rem"), + I64S_DIV("i64s-div"), + I64U_DIV("i64u-div"); + +Name getBinaryFuncName(Binary* curr) { + switch (curr->op) { + case RemSInt32: return I32S_REM; + case RemUInt32: return I32U_REM; + case DivSInt32: return I32S_DIV; + case DivUInt32: return I32U_DIV; + case RemSInt64: return I64S_REM; + case RemUInt64: return I64U_REM; + case DivSInt64: return I64S_DIV; + case DivUInt64: return I64U_DIV; + default: return Name(); + } +} + +Name getUnaryFuncName(Unary* curr) { + switch (curr->op) { + case TruncSFloat32ToInt32: return F32_TO_INT; + case TruncUFloat32ToInt32: return F32_TO_UINT; + case TruncSFloat32ToInt64: return F32_TO_INT64; + case TruncUFloat32ToInt64: return F32_TO_UINT64; + case TruncSFloat64ToInt32: return F64_TO_INT; + case TruncUFloat64ToInt32: return F64_TO_UINT; + case TruncSFloat64ToInt64: return F64_TO_INT64; + case TruncUFloat64ToInt64: return F64_TO_UINT64; + default: return Name(); + } +} + +bool isTruncOpSigned(UnaryOp op) { + switch (op) { + case TruncUFloat32ToInt32: + case TruncUFloat32ToInt64: + case TruncUFloat64ToInt32: + case TruncUFloat64ToInt64: return false; + default: return true; + } +} + +Function* generateBinaryFunc(Module& wasm, Binary *curr) { + BinaryOp op = curr->op; + WasmType type = curr->type; + bool isI64 = type == i64; + Builder builder(wasm); + Expression* result = builder.makeBinary(op, + builder.makeGetLocal(0, type), + builder.makeGetLocal(1, type) + ); + BinaryOp divSIntOp = isI64 ? DivSInt64 : DivSInt32; + UnaryOp eqZOp = isI64 ? EqZInt64 : EqZInt32; + Literal minLit = isI64 ? Literal(std::numeric_limits::min()) + : Literal(std::numeric_limits::min()); + Literal zeroLit = isI64 ? Literal(int64_t(0)) : Literal(int32_t(0)); + if (op == divSIntOp) { + // guard against signed division overflow + BinaryOp eqOp = isI64 ? EqInt64 : EqInt32; + Literal negLit = isI64 ? Literal(int64_t(-1)) : Literal(int32_t(-1)); + result = builder.makeIf( + builder.makeBinary(AndInt32, + builder.makeBinary(eqOp, + builder.makeGetLocal(0, type), + builder.makeConst(minLit) + ), + builder.makeBinary(eqOp, + builder.makeGetLocal(1, type), + builder.makeConst(negLit) + ) + ), + builder.makeConst(zeroLit), + result + ); + } + auto func = new Function; + func->name = getBinaryFuncName(curr); + func->params.push_back(type); + func->params.push_back(type); + func->result = type; + func->body = builder.makeIf( + builder.makeUnary(eqZOp, + builder.makeGetLocal(1, type) + ), + builder.makeConst(zeroLit), + result + ); + return func; +} + +template +void makeClampLimitLiterals(Literal& iMin, Literal& fMin, Literal& fMax) { + IntType minVal = std::numeric_limits::min(); + IntType maxVal = std::numeric_limits::max(); + iMin = Literal(minVal); + fMin = Literal(FloatType(minVal) - 1); + fMax = Literal(FloatType(maxVal) + 1); +} + +Function* generateUnaryFunc(Module& wasm, Unary *curr) { + WasmType type = curr->value->type; + WasmType retType = curr->type; + UnaryOp truncOp = curr->op; + bool isF64 = type == f64; + + Builder builder(wasm); + + BinaryOp leOp = isF64 ? LeFloat64 : LeFloat32; + BinaryOp geOp = isF64 ? GeFloat64 : GeFloat32; + BinaryOp neOp = isF64 ? NeFloat64 : NeFloat32; + + Literal iMin, fMin, fMax; + switch (truncOp) { + case TruncSFloat32ToInt32: makeClampLimitLiterals< int32_t, float>(iMin, fMin, fMax); break; + case TruncUFloat32ToInt32: makeClampLimitLiterals(iMin, fMin, fMax); break; + case TruncSFloat32ToInt64: makeClampLimitLiterals< int64_t, float>(iMin, fMin, fMax); break; + case TruncUFloat32ToInt64: makeClampLimitLiterals(iMin, fMin, fMax); break; + case TruncSFloat64ToInt32: makeClampLimitLiterals< int32_t, double>(iMin, fMin, fMax); break; + case TruncUFloat64ToInt32: makeClampLimitLiterals(iMin, fMin, fMax); break; + case TruncSFloat64ToInt64: makeClampLimitLiterals< int64_t, double>(iMin, fMin, fMax); break; + case TruncUFloat64ToInt64: makeClampLimitLiterals(iMin, fMin, fMax); break; + default: WASM_UNREACHABLE(); + } + + auto func = new Function; + func->name = getUnaryFuncName(curr); + func->params.push_back(type); + func->result = retType; + func->body = builder.makeUnary(truncOp, + builder.makeGetLocal(0, type) + ); + // too small XXX this is different than asm.js, which does frem. here we + // clamp, which is much simpler/faster, and similar to native builds + func->body = builder.makeIf( + builder.makeBinary(leOp, + builder.makeGetLocal(0, type), + builder.makeConst(fMin) + ), + builder.makeConst(iMin), + func->body + ); + // too big XXX see above + func->body = builder.makeIf( + builder.makeBinary(geOp, + builder.makeGetLocal(0, type), + builder.makeConst(fMax) + ), + // NB: min here as well. anything out of range => to the min + builder.makeConst(iMin), + func->body + ); + // nan + func->body = builder.makeIf( + builder.makeBinary(neOp, + builder.makeGetLocal(0, type), + builder.makeGetLocal(0, type) + ), + // NB: min here as well. anything invalid => to the min + builder.makeConst(iMin), + func->body + ); + return func; +} + +void ensureBinaryFunc(Binary* curr, Module& wasm, + TrappingFunctionContainer &trappingFunctions) { + Name name = getBinaryFuncName(curr); + if (trappingFunctions.hasFunction(name)) { + return; + } + trappingFunctions.addFunction(generateBinaryFunc(wasm, curr)); +} + +void ensureUnaryFunc(Unary *curr, Module& wasm, + TrappingFunctionContainer &trappingFunctions) { + Name name = getUnaryFuncName(curr); + if (trappingFunctions.hasFunction(name)) { + return; + } + trappingFunctions.addFunction(generateUnaryFunc(wasm, curr)); +} + +void ensureF64ToI64JSImport(TrappingFunctionContainer &trappingFunctions) { + if (trappingFunctions.hasImport(F64_TO_INT)) { + return; + } + + Module& wasm = trappingFunctions.getModule(); + auto import = new Import; // f64-to-int = asm2wasm.f64-to-int; + import->name = F64_TO_INT; + import->module = ASM2WASM; + import->base = F64_TO_INT; + import->functionType = ensureFunctionType("id", &wasm)->name; + import->kind = ExternalKind::Function; + trappingFunctions.addImport(import); +} + +Expression* makeTrappingBinary(Binary* curr, TrappingFunctionContainer &trappingFunctions) { + Name name = getBinaryFuncName(curr); + if (!name.is() || trappingFunctions.getMode() == TrapMode::Allow) { + return curr; + } + + // the wasm operation might trap if done over 0, so generate a safe call + WasmType type = curr->type; + Module& wasm = trappingFunctions.getModule(); + Builder builder(wasm); + ensureBinaryFunc(curr, wasm, trappingFunctions); + return builder.makeCall(name, {curr->left, curr->right}, type); +} + +Expression* makeTrappingUnary(Unary* curr, TrappingFunctionContainer &trappingFunctions) { + Name name = getUnaryFuncName(curr); + TrapMode mode = trappingFunctions.getMode(); + if (!name.is() || mode == TrapMode::Allow) { + return curr; + } + + Module& wasm = trappingFunctions.getModule(); + Builder builder(wasm); + // WebAssembly traps on float-to-int overflows, but asm.js wouldn't, so we must do something + // We can handle this in one of two ways: clamping, which is fast, or JS, which + // is precisely like JS but in order to do that we do a slow ffi + // If i64, there is no "JS" way to handle this, as no i64s in JS, so always clamp if we don't allow traps + // asm.js doesn't have unsigned f64-to-int, so just use the signed one. + if (curr->type != i64 && mode == TrapMode::JS) { + // WebAssembly traps on float-to-int overflows, but asm.js wouldn't, so we must emulate that + ensureF64ToI64JSImport(trappingFunctions); + Expression* f64Value = ensureDouble(curr->value, wasm.allocator); + return builder.makeCallImport(F64_TO_INT, {f64Value}, i32); + } + + ensureUnaryFunc(curr, wasm, trappingFunctions); + return builder.makeCall(name, {curr->value}, curr->type); +} + +struct TrapModePass : public WalkerPass> { +public: + + // Needs to be non-parallel so that visitModule gets called after visiting + // each node in the module, so we can add the functions that we created. + bool isFunctionParallel() override { return false; } + + TrapModePass(TrapMode mode) : mode(mode) { + assert(mode != TrapMode::Allow); + } + + Pass* create() override { return new TrapModePass(mode); } + + void visitUnary(Unary* curr) { + replaceCurrent(makeTrappingUnary(curr, *trappingFunctions)); + } + + void visitBinary(Binary* curr) { + replaceCurrent(makeTrappingBinary(curr, *trappingFunctions)); + } + + void visitModule(Module* curr) { + trappingFunctions->addToModule(); + } + + void doWalkModule(Module* module) { + trappingFunctions = make_unique(mode, *module); + WalkerPass>::doWalkModule(module); + } + +private: + TrapMode mode; + // Need to defer adding generated functions because adding functions while + // iterating over existing functions causes problems. + std::unique_ptr trappingFunctions; +}; + +Pass *createTrapModeClamp() { + return new TrapModePass(TrapMode::Clamp); +} + +Pass *createTrapModeJS() { + return new TrapModePass(TrapMode::JS); +} + +} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index a208a03dd28..6cfe77a0190 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -109,6 +109,8 @@ void PassRegistry::registerPasses() { registerPass("simplify-locals-nostructure", "miscellaneous locals-related optimizations", createSimplifyLocalsNoStructurePass); registerPass("simplify-locals-notee-nostructure", "miscellaneous locals-related optimizations", createSimplifyLocalsNoTeeNoStructurePass); registerPass("ssa", "ssa-ify variables so that they have a single assignment", createSSAifyPass); + registerPass("trap-mode-clamp", "replace trapping operations with clamping semantics", createTrapModeClamp); + registerPass("trap-mode-js", "replace trapping operations with js semantics", createTrapModeJS); registerPass("untee", "removes tee_locals, replacing them with sets and gets", createUnteePass); registerPass("vacuum", "removes obviously unneeded code", createVacuumPass); // registerPass("lower-i64", "lowers i64 into pairs of i32s", createLowerInt64Pass); diff --git a/src/passes/passes.h b/src/passes/passes.h index a02216083c8..58a9e2b278d 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -67,6 +67,8 @@ Pass *createSimplifyLocalsNoTeePass(); Pass *createSimplifyLocalsNoStructurePass(); Pass *createSimplifyLocalsNoTeeNoStructurePass(); Pass *createSSAifyPass(); +Pass *createTrapModeClamp(); +Pass *createTrapModeJS(); Pass *createUnteePass(); Pass *createVacuumPass(); diff --git a/src/shared-constants.h b/src/shared-constants.h index 31b5b7c1830..e8f98edb736 100644 --- a/src/shared-constants.h +++ b/src/shared-constants.h @@ -15,6 +15,7 @@ */ #ifndef wasm_shared_constants_h +#define wasm_shared_constants_h #include "wasm.h" diff --git a/src/tools/asm2wasm.cpp b/src/tools/asm2wasm.cpp index 3fa97d9815b..cbc3c486b77 100644 --- a/src/tools/asm2wasm.cpp +++ b/src/tools/asm2wasm.cpp @@ -18,6 +18,7 @@ // asm2wasm console tool // +#include "ast/trapping.h" #include "support/colors.h" #include "support/command-line.h" #include "support/file.h" @@ -34,7 +35,7 @@ using namespace wasm; int main(int argc, const char *argv[]) { bool legalizeJavaScriptFFI = true; - Asm2WasmBuilder::TrapMode trapMode = Asm2WasmBuilder::TrapMode::JS; + TrapMode trapMode = TrapMode::JS; bool wasmOnly = false; std::string sourceMapFilename; std::string sourceMapUrl; @@ -79,19 +80,19 @@ int main(int argc, const char *argv[]) { }) .add("--emit-potential-traps", "-i", "Emit instructions that might trap, like div/rem of 0", Options::Arguments::Zero, [&trapMode](Options *o, const std::string &) { - trapMode = Asm2WasmBuilder::TrapMode::Allow; + trapMode = TrapMode::Allow; }) .add("--emit-clamped-potential-traps", "-i", "Clamp instructions that might trap, like float => int", Options::Arguments::Zero, [&trapMode](Options *o, const std::string &) { - trapMode = Asm2WasmBuilder::TrapMode::Clamp; + trapMode = TrapMode::Clamp; }) .add("--emit-jsified-potential-traps", "-i", "Avoid instructions that might trap, handling them exactly like JS would", Options::Arguments::Zero, [&trapMode](Options *o, const std::string &) { - trapMode = Asm2WasmBuilder::TrapMode::JS; + trapMode = TrapMode::JS; }) .add("--imprecise", "-i", "Imprecise optimizations (old name for --emit-potential-traps)", Options::Arguments::Zero, [&trapMode](Options *o, const std::string &) { - trapMode = Asm2WasmBuilder::TrapMode::Allow; + trapMode = TrapMode::Allow; }) .add("--wasm-only", "-w", "Input is in WebAssembly-only format, and not actually valid asm.js", Options::Arguments::Zero, [&wasmOnly](Options *o, const std::string &) { diff --git a/src/tools/s2wasm.cpp b/src/tools/s2wasm.cpp index a357834796e..559188d1f00 100644 --- a/src/tools/s2wasm.cpp +++ b/src/tools/s2wasm.cpp @@ -18,6 +18,7 @@ // wasm2asm console tool // +#include "ast/trapping.h" #include "support/colors.h" #include "support/command-line.h" #include "support/file.h" @@ -37,6 +38,7 @@ int main(int argc, const char *argv[]) { bool importMemory = false; std::string startFunction; std::vector archiveLibraries; + TrapMode trapMode = TrapMode::Allow; Options options("s2wasm", "Link .s file into .wast"); options.extra["validate"] = "wasm"; options @@ -81,6 +83,24 @@ int main(int argc, const char *argv[]) { [&allowMemoryGrowth](Options *, const std::string &) { allowMemoryGrowth = true; }) + .add("--emit-potential-traps", "", + "Emit instructions that might trap, like div/rem of 0", + Options::Arguments::Zero, + [&trapMode](Options *o, const std::string &) { + trapMode = TrapMode::Allow; + }) + .add("--emit-clamped-potential-traps", "", + "Clamp instructions that might trap, like float => int", + Options::Arguments::Zero, + [&trapMode](Options *o, const std::string &) { + trapMode = TrapMode::Clamp; + }) + .add("--emit-jsified-potential-traps", "", + "Avoid instructions that might trap, handling them exactly like JS would", + Options::Arguments::Zero, + [&trapMode](Options *o, const std::string &) { + trapMode = TrapMode::JS; + }) .add("--emscripten-glue", "-e", "Generate emscripten glue", Options::Arguments::Zero, [&generateEmscriptenGlue](Options *, const std::string &) { @@ -144,6 +164,13 @@ int main(int argc, const char *argv[]) { S2WasmBuilder mainbuilder(input.c_str(), options.debug); linker.linkObject(mainbuilder); + if (trapMode != TrapMode::Allow) { + Module* wasm = &(linker.getOutput().wasm); + PassRunner runner(wasm); + addTrapModePass(runner, trapMode); + runner.run(); + } + for (const auto& m : archiveLibraries) { auto archiveFile(read_file>(m, Flags::Binary, debugFlag)); bool error; diff --git a/test/dot_s/traps.clamp.wast b/test/dot_s/traps.clamp.wast new file mode 100644 index 00000000000..3cab71072da --- /dev/null +++ b/test/dot_s/traps.clamp.wast @@ -0,0 +1,113 @@ +(module + (import "env" "memory" (memory $0 1)) + (table 0 anyfunc) + (export "stackSave" (func $stackSave)) + (export "stackAlloc" (func $stackAlloc)) + (export "stackRestore" (func $stackRestore)) + (export "test_traps" (func $test_traps)) + (func $test_traps (param $0 f32) (param $1 f64) (result i32) + (call $i32u-div + (call $f32-to-int + (get_local $0) + ) + (call $f64-to-uint + (get_local $1) + ) + ) + ) + (func $f32-to-int (param $0 f32) (result i32) + (if (result i32) + (f32.ne + (get_local $0) + (get_local $0) + ) + (i32.const -2147483648) + (if (result i32) + (f32.ge + (get_local $0) + (f32.const 2147483648) + ) + (i32.const -2147483648) + (if (result i32) + (f32.le + (get_local $0) + (f32.const -2147483648) + ) + (i32.const -2147483648) + (i32.trunc_s/f32 + (get_local $0) + ) + ) + ) + ) + ) + (func $f64-to-uint (param $0 f64) (result i32) + (if (result i32) + (f64.ne + (get_local $0) + (get_local $0) + ) + (i32.const 0) + (if (result i32) + (f64.ge + (get_local $0) + (f64.const 4294967296) + ) + (i32.const 0) + (if (result i32) + (f64.le + (get_local $0) + (f64.const -1) + ) + (i32.const 0) + (i32.trunc_u/f64 + (get_local $0) + ) + ) + ) + ) + ) + (func $i32u-div (param $0 i32) (param $1 i32) (result i32) + (if (result i32) + (i32.eqz + (get_local $1) + ) + (i32.const 0) + (i32.div_u + (get_local $0) + (get_local $1) + ) + ) + ) + (func $stackSave (result i32) + (i32.load offset=4 + (i32.const 0) + ) + ) + (func $stackAlloc (param $0 i32) (result i32) + (local $1 i32) + (set_local $1 + (i32.load offset=4 + (i32.const 0) + ) + ) + (i32.store offset=4 + (i32.const 0) + (i32.and + (i32.sub + (get_local $1) + (get_local $0) + ) + (i32.const -16) + ) + ) + (get_local $1) + ) + (func $stackRestore (param $0 i32) + (i32.store offset=4 + (i32.const 0) + (get_local $0) + ) + ) +) +;; METADATA: { "asmConsts": {},"staticBump": 12, "initializers": [] } diff --git a/test/dot_s/traps.js.wast b/test/dot_s/traps.js.wast new file mode 100644 index 00000000000..39d08f4c085 --- /dev/null +++ b/test/dot_s/traps.js.wast @@ -0,0 +1,65 @@ +(module + (type $FUNCSIG$id (func (param f64) (result i32))) + (import "asm2wasm" "f64-to-int" (func $f64-to-int (param f64) (result i32))) + (import "env" "memory" (memory $0 1)) + (table 0 anyfunc) + (export "stackSave" (func $stackSave)) + (export "stackAlloc" (func $stackAlloc)) + (export "stackRestore" (func $stackRestore)) + (export "test_traps" (func $test_traps)) + (func $test_traps (param $0 f32) (param $1 f64) (result i32) + (call $i32u-div + (call $f64-to-int + (f64.promote/f32 + (get_local $0) + ) + ) + (call $f64-to-int + (get_local $1) + ) + ) + ) + (func $i32u-div (param $0 i32) (param $1 i32) (result i32) + (if (result i32) + (i32.eqz + (get_local $1) + ) + (i32.const 0) + (i32.div_u + (get_local $0) + (get_local $1) + ) + ) + ) + (func $stackSave (result i32) + (i32.load offset=4 + (i32.const 0) + ) + ) + (func $stackAlloc (param $0 i32) (result i32) + (local $1 i32) + (set_local $1 + (i32.load offset=4 + (i32.const 0) + ) + ) + (i32.store offset=4 + (i32.const 0) + (i32.and + (i32.sub + (get_local $1) + (get_local $0) + ) + (i32.const -16) + ) + ) + (get_local $1) + ) + (func $stackRestore (param $0 i32) + (i32.store offset=4 + (i32.const 0) + (get_local $0) + ) + ) +) +;; METADATA: { "asmConsts": {},"staticBump": 12, "initializers": [] } diff --git a/test/dot_s/traps.s b/test/dot_s/traps.s new file mode 100644 index 00000000000..b75cf8d00ee --- /dev/null +++ b/test/dot_s/traps.s @@ -0,0 +1,14 @@ + .text + .file "" + .globl test_traps + .type test_traps,@function +test_traps: + .param f32, f64 + .result i32 +# BB#0: + i32.trunc_s/f32 $push0=, $0 + i32.trunc_u/f64 $push1=, $1 + i32.div_u $push2=, $pop0, $pop1 + .endfunc +.Lfunc_end0: + .size test_traps, .Lfunc_end0-test_traps diff --git a/test/dot_s/traps.wast b/test/dot_s/traps.wast new file mode 100644 index 00000000000..2ade5362a7c --- /dev/null +++ b/test/dot_s/traps.wast @@ -0,0 +1,49 @@ +(module + (import "env" "memory" (memory $0 1)) + (table 0 anyfunc) + (export "stackSave" (func $stackSave)) + (export "stackAlloc" (func $stackAlloc)) + (export "stackRestore" (func $stackRestore)) + (export "test_traps" (func $test_traps)) + (func $test_traps (param $0 f32) (param $1 f64) (result i32) + (i32.div_u + (i32.trunc_s/f32 + (get_local $0) + ) + (i32.trunc_u/f64 + (get_local $1) + ) + ) + ) + (func $stackSave (result i32) + (i32.load offset=4 + (i32.const 0) + ) + ) + (func $stackAlloc (param $0 i32) (result i32) + (local $1 i32) + (set_local $1 + (i32.load offset=4 + (i32.const 0) + ) + ) + (i32.store offset=4 + (i32.const 0) + (i32.and + (i32.sub + (get_local $1) + (get_local $0) + ) + (i32.const -16) + ) + ) + (get_local $1) + ) + (func $stackRestore (param $0 i32) + (i32.store offset=4 + (i32.const 0) + (get_local $0) + ) + ) +) +;; METADATA: { "asmConsts": {},"staticBump": 12, "initializers": [] } diff --git a/test/emcc_hello_world.fromasm.clamp b/test/emcc_hello_world.fromasm.clamp index b074afa30cd..e483ed89cf4 100644 --- a/test/emcc_hello_world.fromasm.clamp +++ b/test/emcc_hello_world.fromasm.clamp @@ -2214,6 +2214,32 @@ ) ) ) + (func $f64-to-uint (param $0 f64) (result i32) + (if (result i32) + (f64.ne + (get_local $0) + (get_local $0) + ) + (i32.const 0) + (if (result i32) + (f64.ge + (get_local $0) + (f64.const 4294967296) + ) + (i32.const 0) + (if (result i32) + (f64.le + (get_local $0) + (f64.const -1) + ) + (i32.const 0) + (i32.trunc_u/f64 + (get_local $0) + ) + ) + ) + ) + ) (func $i32s-div (param $0 i32) (param $1 i32) (result i32) (if (result i32) (get_local $1) @@ -4587,7 +4613,7 @@ (i32.store (get_local $7) (tee_local $5 - (call $f64-to-int + (call $f64-to-uint (get_local $15) ) ) diff --git a/test/emcc_hello_world.fromasm.clamp.no-opts b/test/emcc_hello_world.fromasm.clamp.no-opts index 3662e5d3617..36d34018c2c 100644 --- a/test/emcc_hello_world.fromasm.clamp.no-opts +++ b/test/emcc_hello_world.fromasm.clamp.no-opts @@ -4658,6 +4658,32 @@ ) ) ) + (func $f64-to-uint (param $0 f64) (result i32) + (if (result i32) + (f64.ne + (get_local $0) + (get_local $0) + ) + (i32.const 0) + (if (result i32) + (f64.ge + (get_local $0) + (f64.const 4294967296) + ) + (i32.const 0) + (if (result i32) + (f64.le + (get_local $0) + (f64.const -1) + ) + (i32.const 0) + (i32.trunc_u/f64 + (get_local $0) + ) + ) + ) + ) + ) (func $i32s-div (param $0 i32) (param $1 i32) (result i32) (if (result i32) (i32.eqz @@ -9934,7 +9960,7 @@ (loop $while-in60 (block $while-out59 (set_local $$conv216$i - (call $f64-to-int + (call $f64-to-uint (get_local $$y$addr$4$i) ) ) diff --git a/test/passes/trap-mode-clamp.txt b/test/passes/trap-mode-clamp.txt new file mode 100644 index 00000000000..91ba1920352 --- /dev/null +++ b/test/passes/trap-mode-clamp.txt @@ -0,0 +1,432 @@ +(module + (type $0 (func (param i32 i64))) + (type $1 (func (param f32))) + (type $2 (func (param f64))) + (memory $0 0) + (func $test_div (type $0) (param $0 i32) (param $1 i64) + (drop + (call $i32s-div + (get_local $0) + (get_local $0) + ) + ) + (drop + (call $i32u-div + (get_local $0) + (get_local $0) + ) + ) + (drop + (call $i64s-div + (get_local $1) + (get_local $1) + ) + ) + (drop + (call $i64u-div + (get_local $1) + (get_local $1) + ) + ) + ) + (func $test_rem (type $0) (param $0 i32) (param $1 i64) + (drop + (call $i32s-rem + (get_local $0) + (get_local $0) + ) + ) + (drop + (call $i32u-rem + (get_local $0) + (get_local $0) + ) + ) + (drop + (call $i64s-rem + (get_local $1) + (get_local $1) + ) + ) + (drop + (call $i64u-rem + (get_local $1) + (get_local $1) + ) + ) + ) + (func $test_f32_to_int (type $1) (param $0 f32) + (drop + (call $f32-to-int + (get_local $0) + ) + ) + (drop + (call $f32-to-uint + (get_local $0) + ) + ) + (drop + (call $f32-to-int64 + (get_local $0) + ) + ) + (drop + (call $f32-to-uint64 + (get_local $0) + ) + ) + ) + (func $test_f64_to_int (type $2) (param $0 f64) + (drop + (call $f64-to-int + (get_local $0) + ) + ) + (drop + (call $f64-to-uint + (get_local $0) + ) + ) + (drop + (call $f64-to-int64 + (get_local $0) + ) + ) + (drop + (call $f64-to-uint64 + (get_local $0) + ) + ) + ) + (func $f32-to-int (param $0 f32) (result i32) + (if (result i32) + (f32.ne + (get_local $0) + (get_local $0) + ) + (i32.const -2147483648) + (if (result i32) + (f32.ge + (get_local $0) + (f32.const 2147483648) + ) + (i32.const -2147483648) + (if (result i32) + (f32.le + (get_local $0) + (f32.const -2147483648) + ) + (i32.const -2147483648) + (i32.trunc_s/f32 + (get_local $0) + ) + ) + ) + ) + ) + (func $f32-to-int64 (param $0 f32) (result i64) + (if (result i64) + (f32.ne + (get_local $0) + (get_local $0) + ) + (i64.const -9223372036854775808) + (if (result i64) + (f32.ge + (get_local $0) + (f32.const 9223372036854775808) + ) + (i64.const -9223372036854775808) + (if (result i64) + (f32.le + (get_local $0) + (f32.const -9223372036854775808) + ) + (i64.const -9223372036854775808) + (i64.trunc_s/f32 + (get_local $0) + ) + ) + ) + ) + ) + (func $f32-to-uint (param $0 f32) (result i32) + (if (result i32) + (f32.ne + (get_local $0) + (get_local $0) + ) + (i32.const 0) + (if (result i32) + (f32.ge + (get_local $0) + (f32.const 4294967296) + ) + (i32.const 0) + (if (result i32) + (f32.le + (get_local $0) + (f32.const -1) + ) + (i32.const 0) + (i32.trunc_u/f32 + (get_local $0) + ) + ) + ) + ) + ) + (func $f32-to-uint64 (param $0 f32) (result i64) + (if (result i64) + (f32.ne + (get_local $0) + (get_local $0) + ) + (i64.const 0) + (if (result i64) + (f32.ge + (get_local $0) + (f32.const 18446744073709551615) + ) + (i64.const 0) + (if (result i64) + (f32.le + (get_local $0) + (f32.const -1) + ) + (i64.const 0) + (i64.trunc_u/f32 + (get_local $0) + ) + ) + ) + ) + ) + (func $f64-to-int (param $0 f64) (result i32) + (if (result i32) + (f64.ne + (get_local $0) + (get_local $0) + ) + (i32.const -2147483648) + (if (result i32) + (f64.ge + (get_local $0) + (f64.const 2147483648) + ) + (i32.const -2147483648) + (if (result i32) + (f64.le + (get_local $0) + (f64.const -2147483649) + ) + (i32.const -2147483648) + (i32.trunc_s/f64 + (get_local $0) + ) + ) + ) + ) + ) + (func $f64-to-int64 (param $0 f64) (result i64) + (if (result i64) + (f64.ne + (get_local $0) + (get_local $0) + ) + (i64.const -9223372036854775808) + (if (result i64) + (f64.ge + (get_local $0) + (f64.const 9223372036854775808) + ) + (i64.const -9223372036854775808) + (if (result i64) + (f64.le + (get_local $0) + (f64.const -9223372036854775808) + ) + (i64.const -9223372036854775808) + (i64.trunc_s/f64 + (get_local $0) + ) + ) + ) + ) + ) + (func $f64-to-uint (param $0 f64) (result i32) + (if (result i32) + (f64.ne + (get_local $0) + (get_local $0) + ) + (i32.const 0) + (if (result i32) + (f64.ge + (get_local $0) + (f64.const 4294967296) + ) + (i32.const 0) + (if (result i32) + (f64.le + (get_local $0) + (f64.const -1) + ) + (i32.const 0) + (i32.trunc_u/f64 + (get_local $0) + ) + ) + ) + ) + ) + (func $f64-to-uint64 (param $0 f64) (result i64) + (if (result i64) + (f64.ne + (get_local $0) + (get_local $0) + ) + (i64.const 0) + (if (result i64) + (f64.ge + (get_local $0) + (f64.const 18446744073709551615) + ) + (i64.const 0) + (if (result i64) + (f64.le + (get_local $0) + (f64.const -1) + ) + (i64.const 0) + (i64.trunc_u/f64 + (get_local $0) + ) + ) + ) + ) + ) + (func $i32s-div (param $0 i32) (param $1 i32) (result i32) + (if (result i32) + (i32.eqz + (get_local $1) + ) + (i32.const 0) + (if (result i32) + (i32.and + (i32.eq + (get_local $0) + (i32.const -2147483648) + ) + (i32.eq + (get_local $1) + (i32.const -1) + ) + ) + (i32.const 0) + (i32.div_s + (get_local $0) + (get_local $1) + ) + ) + ) + ) + (func $i32s-rem (param $0 i32) (param $1 i32) (result i32) + (if (result i32) + (i32.eqz + (get_local $1) + ) + (i32.const 0) + (i32.rem_s + (get_local $0) + (get_local $1) + ) + ) + ) + (func $i32u-div (param $0 i32) (param $1 i32) (result i32) + (if (result i32) + (i32.eqz + (get_local $1) + ) + (i32.const 0) + (i32.div_u + (get_local $0) + (get_local $1) + ) + ) + ) + (func $i32u-rem (param $0 i32) (param $1 i32) (result i32) + (if (result i32) + (i32.eqz + (get_local $1) + ) + (i32.const 0) + (i32.rem_u + (get_local $0) + (get_local $1) + ) + ) + ) + (func $i64s-div (param $0 i64) (param $1 i64) (result i64) + (if (result i64) + (i64.eqz + (get_local $1) + ) + (i64.const 0) + (if (result i64) + (i32.and + (i64.eq + (get_local $0) + (i64.const -9223372036854775808) + ) + (i64.eq + (get_local $1) + (i64.const -1) + ) + ) + (i64.const 0) + (i64.div_s + (get_local $0) + (get_local $1) + ) + ) + ) + ) + (func $i64s-rem (param $0 i64) (param $1 i64) (result i64) + (if (result i64) + (i64.eqz + (get_local $1) + ) + (i64.const 0) + (i64.rem_s + (get_local $0) + (get_local $1) + ) + ) + ) + (func $i64u-div (param $0 i64) (param $1 i64) (result i64) + (if (result i64) + (i64.eqz + (get_local $1) + ) + (i64.const 0) + (i64.div_u + (get_local $0) + (get_local $1) + ) + ) + ) + (func $i64u-rem (param $0 i64) (param $1 i64) (result i64) + (if (result i64) + (i64.eqz + (get_local $1) + ) + (i64.const 0) + (i64.rem_u + (get_local $0) + (get_local $1) + ) + ) + ) +) diff --git a/test/passes/trap-mode-clamp.wast b/test/passes/trap-mode-clamp.wast new file mode 100644 index 00000000000..5a44e50ec43 --- /dev/null +++ b/test/passes/trap-mode-clamp.wast @@ -0,0 +1,26 @@ +(module + (func $test_div (param $0 i32) (param $1 i64) + (drop (i32.div_s (get_local $0) (get_local $0))) + (drop (i32.div_u (get_local $0) (get_local $0))) + (drop (i64.div_s (get_local $1) (get_local $1))) + (drop (i64.div_u (get_local $1) (get_local $1))) + ) + (func $test_rem (param $0 i32) (param $1 i64) + (drop (i32.rem_s (get_local $0) (get_local $0))) + (drop (i32.rem_u (get_local $0) (get_local $0))) + (drop (i64.rem_s (get_local $1) (get_local $1))) + (drop (i64.rem_u (get_local $1) (get_local $1))) + ) + (func $test_f32_to_int (param $0 f32) + (drop (i32.trunc_s/f32 (get_local $0))) + (drop (i32.trunc_u/f32 (get_local $0))) + (drop (i64.trunc_s/f32 (get_local $0))) + (drop (i64.trunc_u/f32 (get_local $0))) + ) + (func $test_f64_to_int (param $0 f64) + (drop (i32.trunc_s/f64 (get_local $0))) + (drop (i32.trunc_u/f64 (get_local $0))) + (drop (i64.trunc_s/f64 (get_local $0))) + (drop (i64.trunc_u/f64 (get_local $0))) + ) +) diff --git a/test/passes/trap-mode-js.txt b/test/passes/trap-mode-js.txt new file mode 100644 index 00000000000..57ef09f9179 --- /dev/null +++ b/test/passes/trap-mode-js.txt @@ -0,0 +1,334 @@ +(module + (type $0 (func (param i32 i64))) + (type $1 (func (param f32))) + (type $2 (func (param f64))) + (type $FUNCSIG$id (func (param f64) (result i32))) + (import "asm2wasm" "f64-to-int" (func $f64-to-int (param f64) (result i32))) + (memory $0 0) + (func $test_div (type $0) (param $0 i32) (param $1 i64) + (drop + (call $i32s-div + (get_local $0) + (get_local $0) + ) + ) + (drop + (call $i32u-div + (get_local $0) + (get_local $0) + ) + ) + (drop + (call $i64s-div + (get_local $1) + (get_local $1) + ) + ) + (drop + (call $i64u-div + (get_local $1) + (get_local $1) + ) + ) + ) + (func $test_rem (type $0) (param $0 i32) (param $1 i64) + (drop + (call $i32s-rem + (get_local $0) + (get_local $0) + ) + ) + (drop + (call $i32u-rem + (get_local $0) + (get_local $0) + ) + ) + (drop + (call $i64s-rem + (get_local $1) + (get_local $1) + ) + ) + (drop + (call $i64u-rem + (get_local $1) + (get_local $1) + ) + ) + ) + (func $test_f32_to_int (type $1) (param $0 f32) + (drop + (call $f64-to-int + (f64.promote/f32 + (get_local $0) + ) + ) + ) + (drop + (call $f64-to-int + (f64.promote/f32 + (get_local $0) + ) + ) + ) + (drop + (call $f32-to-int64 + (get_local $0) + ) + ) + (drop + (call $f32-to-uint64 + (get_local $0) + ) + ) + ) + (func $test_f64_to_int (type $2) (param $0 f64) + (drop + (call $f64-to-int + (get_local $0) + ) + ) + (drop + (call $f64-to-int + (get_local $0) + ) + ) + (drop + (call $f64-to-int64 + (get_local $0) + ) + ) + (drop + (call $f64-to-uint64 + (get_local $0) + ) + ) + ) + (func $f32-to-int64 (param $0 f32) (result i64) + (if (result i64) + (f32.ne + (get_local $0) + (get_local $0) + ) + (i64.const -9223372036854775808) + (if (result i64) + (f32.ge + (get_local $0) + (f32.const 9223372036854775808) + ) + (i64.const -9223372036854775808) + (if (result i64) + (f32.le + (get_local $0) + (f32.const -9223372036854775808) + ) + (i64.const -9223372036854775808) + (i64.trunc_s/f32 + (get_local $0) + ) + ) + ) + ) + ) + (func $f32-to-uint64 (param $0 f32) (result i64) + (if (result i64) + (f32.ne + (get_local $0) + (get_local $0) + ) + (i64.const 0) + (if (result i64) + (f32.ge + (get_local $0) + (f32.const 18446744073709551615) + ) + (i64.const 0) + (if (result i64) + (f32.le + (get_local $0) + (f32.const -1) + ) + (i64.const 0) + (i64.trunc_u/f32 + (get_local $0) + ) + ) + ) + ) + ) + (func $f64-to-int64 (param $0 f64) (result i64) + (if (result i64) + (f64.ne + (get_local $0) + (get_local $0) + ) + (i64.const -9223372036854775808) + (if (result i64) + (f64.ge + (get_local $0) + (f64.const 9223372036854775808) + ) + (i64.const -9223372036854775808) + (if (result i64) + (f64.le + (get_local $0) + (f64.const -9223372036854775808) + ) + (i64.const -9223372036854775808) + (i64.trunc_s/f64 + (get_local $0) + ) + ) + ) + ) + ) + (func $f64-to-uint64 (param $0 f64) (result i64) + (if (result i64) + (f64.ne + (get_local $0) + (get_local $0) + ) + (i64.const 0) + (if (result i64) + (f64.ge + (get_local $0) + (f64.const 18446744073709551615) + ) + (i64.const 0) + (if (result i64) + (f64.le + (get_local $0) + (f64.const -1) + ) + (i64.const 0) + (i64.trunc_u/f64 + (get_local $0) + ) + ) + ) + ) + ) + (func $i32s-div (param $0 i32) (param $1 i32) (result i32) + (if (result i32) + (i32.eqz + (get_local $1) + ) + (i32.const 0) + (if (result i32) + (i32.and + (i32.eq + (get_local $0) + (i32.const -2147483648) + ) + (i32.eq + (get_local $1) + (i32.const -1) + ) + ) + (i32.const 0) + (i32.div_s + (get_local $0) + (get_local $1) + ) + ) + ) + ) + (func $i32s-rem (param $0 i32) (param $1 i32) (result i32) + (if (result i32) + (i32.eqz + (get_local $1) + ) + (i32.const 0) + (i32.rem_s + (get_local $0) + (get_local $1) + ) + ) + ) + (func $i32u-div (param $0 i32) (param $1 i32) (result i32) + (if (result i32) + (i32.eqz + (get_local $1) + ) + (i32.const 0) + (i32.div_u + (get_local $0) + (get_local $1) + ) + ) + ) + (func $i32u-rem (param $0 i32) (param $1 i32) (result i32) + (if (result i32) + (i32.eqz + (get_local $1) + ) + (i32.const 0) + (i32.rem_u + (get_local $0) + (get_local $1) + ) + ) + ) + (func $i64s-div (param $0 i64) (param $1 i64) (result i64) + (if (result i64) + (i64.eqz + (get_local $1) + ) + (i64.const 0) + (if (result i64) + (i32.and + (i64.eq + (get_local $0) + (i64.const -9223372036854775808) + ) + (i64.eq + (get_local $1) + (i64.const -1) + ) + ) + (i64.const 0) + (i64.div_s + (get_local $0) + (get_local $1) + ) + ) + ) + ) + (func $i64s-rem (param $0 i64) (param $1 i64) (result i64) + (if (result i64) + (i64.eqz + (get_local $1) + ) + (i64.const 0) + (i64.rem_s + (get_local $0) + (get_local $1) + ) + ) + ) + (func $i64u-div (param $0 i64) (param $1 i64) (result i64) + (if (result i64) + (i64.eqz + (get_local $1) + ) + (i64.const 0) + (i64.div_u + (get_local $0) + (get_local $1) + ) + ) + ) + (func $i64u-rem (param $0 i64) (param $1 i64) (result i64) + (if (result i64) + (i64.eqz + (get_local $1) + ) + (i64.const 0) + (i64.rem_u + (get_local $0) + (get_local $1) + ) + ) + ) +) diff --git a/test/passes/trap-mode-js.wast b/test/passes/trap-mode-js.wast new file mode 100644 index 00000000000..5a44e50ec43 --- /dev/null +++ b/test/passes/trap-mode-js.wast @@ -0,0 +1,26 @@ +(module + (func $test_div (param $0 i32) (param $1 i64) + (drop (i32.div_s (get_local $0) (get_local $0))) + (drop (i32.div_u (get_local $0) (get_local $0))) + (drop (i64.div_s (get_local $1) (get_local $1))) + (drop (i64.div_u (get_local $1) (get_local $1))) + ) + (func $test_rem (param $0 i32) (param $1 i64) + (drop (i32.rem_s (get_local $0) (get_local $0))) + (drop (i32.rem_u (get_local $0) (get_local $0))) + (drop (i64.rem_s (get_local $1) (get_local $1))) + (drop (i64.rem_u (get_local $1) (get_local $1))) + ) + (func $test_f32_to_int (param $0 f32) + (drop (i32.trunc_s/f32 (get_local $0))) + (drop (i32.trunc_u/f32 (get_local $0))) + (drop (i64.trunc_s/f32 (get_local $0))) + (drop (i64.trunc_u/f32 (get_local $0))) + ) + (func $test_f64_to_int (param $0 f64) + (drop (i32.trunc_s/f64 (get_local $0))) + (drop (i32.trunc_u/f64 (get_local $0))) + (drop (i64.trunc_s/f64 (get_local $0))) + (drop (i64.trunc_u/f64 (get_local $0))) + ) +) diff --git a/test/unit.fromasm.clamp b/test/unit.fromasm.clamp index 4393c362c81..c4f1334810f 100644 --- a/test/unit.fromasm.clamp +++ b/test/unit.fromasm.clamp @@ -168,6 +168,32 @@ ) ) ) + (func $f32-to-int (param $0 f32) (result i32) + (if (result i32) + (f32.ne + (get_local $0) + (get_local $0) + ) + (i32.const -2147483648) + (if (result i32) + (f32.ge + (get_local $0) + (f32.const 2147483648) + ) + (i32.const -2147483648) + (if (result i32) + (f32.le + (get_local $0) + (f32.const -2147483648) + ) + (i32.const -2147483648) + (i32.trunc_s/f32 + (get_local $0) + ) + ) + ) + ) + ) (func $conversions (param $0 i32) (param $1 f64) (param $2 f32) (drop (call $f64-to-int @@ -175,10 +201,8 @@ ) ) (drop - (call $f64-to-int - (f64.promote/f32 - (get_local $2) - ) + (call $f32-to-int + (get_local $2) ) ) ) @@ -1186,9 +1210,30 @@ ) ) ) - (func $f2u (param $0 f64) (result i32) - (call $f64-to-int - (get_local $0) + (func $f64-to-uint (param $0 f64) (result i32) + (if (result i32) + (f64.ne + (get_local $0) + (get_local $0) + ) + (i32.const 0) + (if (result i32) + (f64.ge + (get_local $0) + (f64.const 4294967296) + ) + (i32.const 0) + (if (result i32) + (f64.le + (get_local $0) + (f64.const -1) + ) + (i32.const 0) + (i32.trunc_u/f64 + (get_local $0) + ) + ) + ) ) ) (func $keepAlive @@ -1203,12 +1248,12 @@ ) ) (drop - (call $f2u + (call $f64-to-uint (f64.const 100) ) ) (drop - (call $f2u + (call $f64-to-int (f64.const 100) ) ) diff --git a/test/unit.fromasm.clamp.no-opts b/test/unit.fromasm.clamp.no-opts index 00b1814c496..b1b22cf6677 100644 --- a/test/unit.fromasm.clamp.no-opts +++ b/test/unit.fromasm.clamp.no-opts @@ -234,6 +234,32 @@ ) ) ) + (func $f32-to-int (param $0 f32) (result i32) + (if (result i32) + (f32.ne + (get_local $0) + (get_local $0) + ) + (i32.const -2147483648) + (if (result i32) + (f32.ge + (get_local $0) + (f32.const 2147483648) + ) + (i32.const -2147483648) + (if (result i32) + (f32.le + (get_local $0) + (f32.const -2147483648) + ) + (i32.const -2147483648) + (i32.trunc_s/f32 + (get_local $0) + ) + ) + ) + ) + ) (func $conversions (param $i i32) (param $d f64) (param $f f32) (set_local $i (call $f64-to-int @@ -241,10 +267,8 @@ ) ) (set_local $i - (call $f64-to-int - (f64.promote/f32 - (get_local $f) - ) + (call $f32-to-int + (get_local $f) ) ) (set_local $d @@ -1972,9 +1996,35 @@ ) ) ) + (func $f64-to-uint (param $0 f64) (result i32) + (if (result i32) + (f64.ne + (get_local $0) + (get_local $0) + ) + (i32.const 0) + (if (result i32) + (f64.ge + (get_local $0) + (f64.const 4294967296) + ) + (i32.const 0) + (if (result i32) + (f64.le + (get_local $0) + (f64.const -1) + ) + (i32.const 0) + (i32.trunc_u/f64 + (get_local $0) + ) + ) + ) + ) + ) (func $f2u (param $x f64) (result i32) (return - (call $f64-to-int + (call $f64-to-uint (get_local $x) ) ) diff --git a/test/wasm-only.fromasm b/test/wasm-only.fromasm index 2ac96b8aea9..6eb8bb5feb7 100644 --- a/test/wasm-only.fromasm +++ b/test/wasm-only.fromasm @@ -266,6 +266,32 @@ ) ) ) + (func $f32-to-int64 (param $0 f32) (result i64) + (if (result i64) + (f32.ne + (get_local $0) + (get_local $0) + ) + (i64.const -9223372036854775808) + (if (result i64) + (f32.ge + (get_local $0) + (f32.const 9223372036854775808) + ) + (i64.const -9223372036854775808) + (if (result i64) + (f32.le + (get_local $0) + (f32.const -9223372036854775808) + ) + (i64.const -9223372036854775808) + (i64.trunc_s/f32 + (get_local $0) + ) + ) + ) + ) + ) (func $f64-to-int64 (param $0 f64) (result i64) (if (result i64) (f64.ne @@ -292,6 +318,58 @@ ) ) ) + (func $f32-to-uint64 (param $0 f32) (result i64) + (if (result i64) + (f32.ne + (get_local $0) + (get_local $0) + ) + (i64.const 0) + (if (result i64) + (f32.ge + (get_local $0) + (f32.const 18446744073709551615) + ) + (i64.const 0) + (if (result i64) + (f32.le + (get_local $0) + (f32.const -1) + ) + (i64.const 0) + (i64.trunc_u/f32 + (get_local $0) + ) + ) + ) + ) + ) + (func $f64-to-uint64 (param $0 f64) (result i64) + (if (result i64) + (f64.ne + (get_local $0) + (get_local $0) + ) + (i64.const 0) + (if (result i64) + (f64.ge + (get_local $0) + (f64.const 18446744073709551615) + ) + (i64.const 0) + (if (result i64) + (f64.le + (get_local $0) + (f64.const -1) + ) + (i64.const 0) + (i64.trunc_u/f64 + (get_local $0) + ) + ) + ) + ) + ) (func $test64 (local $0 i64) (local $1 f32) @@ -376,15 +454,13 @@ (get_local $0) ) (drop - (call $f64-to-int64 - (f64.promote/f32 - (tee_local $1 - (f32.convert_u/i64 - (tee_local $0 - (i64.extend_u/i32 - (i32.wrap/i64 - (get_local $0) - ) + (call $f32-to-int64 + (tee_local $1 + (f32.convert_u/i64 + (tee_local $0 + (i64.extend_u/i32 + (i32.wrap/i64 + (get_local $0) ) ) ) @@ -402,14 +478,12 @@ ) ) (drop - (call $f64-to-int64 - (f64.promote/f32 - (get_local $1) - ) + (call $f32-to-uint64 + (get_local $1) ) ) (drop - (call $f64-to-int64 + (call $f64-to-uint64 (get_local $2) ) ) diff --git a/test/wasm-only.fromasm.clamp b/test/wasm-only.fromasm.clamp index 2ac96b8aea9..6eb8bb5feb7 100644 --- a/test/wasm-only.fromasm.clamp +++ b/test/wasm-only.fromasm.clamp @@ -266,6 +266,32 @@ ) ) ) + (func $f32-to-int64 (param $0 f32) (result i64) + (if (result i64) + (f32.ne + (get_local $0) + (get_local $0) + ) + (i64.const -9223372036854775808) + (if (result i64) + (f32.ge + (get_local $0) + (f32.const 9223372036854775808) + ) + (i64.const -9223372036854775808) + (if (result i64) + (f32.le + (get_local $0) + (f32.const -9223372036854775808) + ) + (i64.const -9223372036854775808) + (i64.trunc_s/f32 + (get_local $0) + ) + ) + ) + ) + ) (func $f64-to-int64 (param $0 f64) (result i64) (if (result i64) (f64.ne @@ -292,6 +318,58 @@ ) ) ) + (func $f32-to-uint64 (param $0 f32) (result i64) + (if (result i64) + (f32.ne + (get_local $0) + (get_local $0) + ) + (i64.const 0) + (if (result i64) + (f32.ge + (get_local $0) + (f32.const 18446744073709551615) + ) + (i64.const 0) + (if (result i64) + (f32.le + (get_local $0) + (f32.const -1) + ) + (i64.const 0) + (i64.trunc_u/f32 + (get_local $0) + ) + ) + ) + ) + ) + (func $f64-to-uint64 (param $0 f64) (result i64) + (if (result i64) + (f64.ne + (get_local $0) + (get_local $0) + ) + (i64.const 0) + (if (result i64) + (f64.ge + (get_local $0) + (f64.const 18446744073709551615) + ) + (i64.const 0) + (if (result i64) + (f64.le + (get_local $0) + (f64.const -1) + ) + (i64.const 0) + (i64.trunc_u/f64 + (get_local $0) + ) + ) + ) + ) + ) (func $test64 (local $0 i64) (local $1 f32) @@ -376,15 +454,13 @@ (get_local $0) ) (drop - (call $f64-to-int64 - (f64.promote/f32 - (tee_local $1 - (f32.convert_u/i64 - (tee_local $0 - (i64.extend_u/i32 - (i32.wrap/i64 - (get_local $0) - ) + (call $f32-to-int64 + (tee_local $1 + (f32.convert_u/i64 + (tee_local $0 + (i64.extend_u/i32 + (i32.wrap/i64 + (get_local $0) ) ) ) @@ -402,14 +478,12 @@ ) ) (drop - (call $f64-to-int64 - (f64.promote/f32 - (get_local $1) - ) + (call $f32-to-uint64 + (get_local $1) ) ) (drop - (call $f64-to-int64 + (call $f64-to-uint64 (get_local $2) ) ) diff --git a/test/wasm-only.fromasm.clamp.no-opts b/test/wasm-only.fromasm.clamp.no-opts index c0cfa4676f2..bbb32c5f317 100644 --- a/test/wasm-only.fromasm.clamp.no-opts +++ b/test/wasm-only.fromasm.clamp.no-opts @@ -343,6 +343,32 @@ ) ) ) + (func $f32-to-int64 (param $0 f32) (result i64) + (if (result i64) + (f32.ne + (get_local $0) + (get_local $0) + ) + (i64.const -9223372036854775808) + (if (result i64) + (f32.ge + (get_local $0) + (f32.const 9223372036854775808) + ) + (i64.const -9223372036854775808) + (if (result i64) + (f32.le + (get_local $0) + (f32.const -9223372036854775808) + ) + (i64.const -9223372036854775808) + (i64.trunc_s/f32 + (get_local $0) + ) + ) + ) + ) + ) (func $f64-to-int64 (param $0 f64) (result i64) (if (result i64) (f64.ne @@ -369,6 +395,58 @@ ) ) ) + (func $f32-to-uint64 (param $0 f32) (result i64) + (if (result i64) + (f32.ne + (get_local $0) + (get_local $0) + ) + (i64.const 0) + (if (result i64) + (f32.ge + (get_local $0) + (f32.const 18446744073709551615) + ) + (i64.const 0) + (if (result i64) + (f32.le + (get_local $0) + (f32.const -1) + ) + (i64.const 0) + (i64.trunc_u/f32 + (get_local $0) + ) + ) + ) + ) + ) + (func $f64-to-uint64 (param $0 f64) (result i64) + (if (result i64) + (f64.ne + (get_local $0) + (get_local $0) + ) + (i64.const 0) + (if (result i64) + (f64.ge + (get_local $0) + (f64.const 18446744073709551615) + ) + (i64.const 0) + (if (result i64) + (f64.le + (get_local $0) + (f64.const -1) + ) + (i64.const 0) + (i64.trunc_u/f64 + (get_local $0) + ) + ) + ) + ) + ) (func $test64 (local $x i64) (local $y i64) @@ -601,10 +679,8 @@ ) ) (set_local $x - (call $f64-to-int64 - (f64.promote/f32 - (get_local $float32) - ) + (call $f32-to-int64 + (get_local $float32) ) ) (set_local $x @@ -613,14 +689,12 @@ ) ) (set_local $x - (call $f64-to-int64 - (f64.promote/f32 - (get_local $float32) - ) + (call $f32-to-uint64 + (get_local $float32) ) ) (set_local $x - (call $f64-to-int64 + (call $f64-to-uint64 (get_local $float64) ) ) diff --git a/test/wasm-only.fromasm.no-opts b/test/wasm-only.fromasm.no-opts index c0cfa4676f2..bbb32c5f317 100644 --- a/test/wasm-only.fromasm.no-opts +++ b/test/wasm-only.fromasm.no-opts @@ -343,6 +343,32 @@ ) ) ) + (func $f32-to-int64 (param $0 f32) (result i64) + (if (result i64) + (f32.ne + (get_local $0) + (get_local $0) + ) + (i64.const -9223372036854775808) + (if (result i64) + (f32.ge + (get_local $0) + (f32.const 9223372036854775808) + ) + (i64.const -9223372036854775808) + (if (result i64) + (f32.le + (get_local $0) + (f32.const -9223372036854775808) + ) + (i64.const -9223372036854775808) + (i64.trunc_s/f32 + (get_local $0) + ) + ) + ) + ) + ) (func $f64-to-int64 (param $0 f64) (result i64) (if (result i64) (f64.ne @@ -369,6 +395,58 @@ ) ) ) + (func $f32-to-uint64 (param $0 f32) (result i64) + (if (result i64) + (f32.ne + (get_local $0) + (get_local $0) + ) + (i64.const 0) + (if (result i64) + (f32.ge + (get_local $0) + (f32.const 18446744073709551615) + ) + (i64.const 0) + (if (result i64) + (f32.le + (get_local $0) + (f32.const -1) + ) + (i64.const 0) + (i64.trunc_u/f32 + (get_local $0) + ) + ) + ) + ) + ) + (func $f64-to-uint64 (param $0 f64) (result i64) + (if (result i64) + (f64.ne + (get_local $0) + (get_local $0) + ) + (i64.const 0) + (if (result i64) + (f64.ge + (get_local $0) + (f64.const 18446744073709551615) + ) + (i64.const 0) + (if (result i64) + (f64.le + (get_local $0) + (f64.const -1) + ) + (i64.const 0) + (i64.trunc_u/f64 + (get_local $0) + ) + ) + ) + ) + ) (func $test64 (local $x i64) (local $y i64) @@ -601,10 +679,8 @@ ) ) (set_local $x - (call $f64-to-int64 - (f64.promote/f32 - (get_local $float32) - ) + (call $f32-to-int64 + (get_local $float32) ) ) (set_local $x @@ -613,14 +689,12 @@ ) ) (set_local $x - (call $f64-to-int64 - (f64.promote/f32 - (get_local $float32) - ) + (call $f32-to-uint64 + (get_local $float32) ) ) (set_local $x - (call $f64-to-int64 + (call $f64-to-uint64 (get_local $float64) ) )