summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--scripts/test/wasm2js.py4
-rw-r--r--src/tools/wasm2js.cpp33
-rw-r--r--src/wasm2js.h2
-rw-r--r--test/wasm2js/deterministic.2asm.js44
-rw-r--r--test/wasm2js/deterministic.2asm.js.opt43
-rw-r--r--test/wasm2js/deterministic.wast14
6 files changed, 136 insertions, 4 deletions
diff --git a/scripts/test/wasm2js.py b/scripts/test/wasm2js.py
index 8a1c99975..fec0d26db 100644
--- a/scripts/test/wasm2js.py
+++ b/scripts/test/wasm2js.py
@@ -56,6 +56,8 @@ def test_wasm2js_output():
cmd += ['-O']
if 'emscripten' in t:
cmd += ['--emscripten']
+ if 'deterministic' in t:
+ cmd += ['--deterministic']
out = support.run_command(cmd)
all_out.append(out)
@@ -156,6 +158,8 @@ def update_wasm2js_tests():
cmd += ['-O']
if 'emscripten' in wasm:
cmd += ['--emscripten']
+ if 'deterministic' in t:
+ cmd += ['--deterministic']
out = support.run_command(cmd)
all_out.append(out)
diff --git a/src/tools/wasm2js.cpp b/src/tools/wasm2js.cpp
index e7b62c16a..7f3433bb7 100644
--- a/src/tools/wasm2js.cpp
+++ b/src/tools/wasm2js.cpp
@@ -141,7 +141,7 @@ static void replaceInPlaceIfPossible(Ref target, Ref value) {
}
}
-static void optimizeJS(Ref ast) {
+static void optimizeJS(Ref ast, Wasm2JSBuilder::Flags flags) {
// Helpers
auto isBinary = [](Ref node, IString op) {
@@ -262,8 +262,21 @@ static void optimizeJS(Ref ast) {
node[1]->setString(L_NOT);
node[3]->setNull();
} else if (isOrZero(node) || isTrshiftZero(node)) {
- // Just being different from 0 is enough, casts don't matter.
- return node[2];
+ // Just being different from 0 is enough, casts don't matter. However,
+ // in deterministic mode we care about corner cases that would trap in
+ // wasm, like an integer divide by zero:
+ //
+ // if ((1 / 0) | 0) => condition is Infinity | 0 = 0 which is falsey
+ //
+ // while
+ //
+ // if (1 / 0) => condition is Infinity which is truthy
+ //
+ // Thankfully this is not common, and does not occur on % (1 % 0 is a NaN
+ // which has the right truthiness).
+ if (!(flags.deterministic && isBinary(node[2], DIV))) {
+ return node[2];
+ }
}
return node;
};
@@ -511,7 +524,7 @@ static void emitWasm(Module& wasm,
Wasm2JSBuilder wasm2js(flags, options);
auto js = wasm2js.processWasm(&wasm, name);
if (options.optimizeLevel >= 2) {
- optimizeJS(js);
+ optimizeJS(js, flags);
}
Wasm2JSGlue glue(wasm, output, flags, name);
glue.emitPre();
@@ -849,6 +862,18 @@ int main(int argc, const char* argv[]) {
Options::Arguments::Zero,
[&](Options* o, const std::string& argument) { flags.emscripten = true; })
.add(
+ "--deterministic",
+ "",
+ "Replace WebAssembly trapping behavior deterministically "
+ "(the default is to not care about what would trap in wasm, like a load "
+ "out of bounds or integer divide by zero; with this flag, we try to be "
+ "deterministic at least in what happens, which might or might not be "
+ "to trap like wasm, but at least should not vary)",
+ Options::Arguments::Zero,
+ [&](Options* o, const std::string& argument) {
+ flags.deterministic = true;
+ })
+ .add(
"--symbols-file",
"",
"Emit a symbols file that maps function indexes to their original names",
diff --git a/src/wasm2js.h b/src/wasm2js.h
index 4db734203..1d8003323 100644
--- a/src/wasm2js.h
+++ b/src/wasm2js.h
@@ -120,10 +120,12 @@ class Wasm2JSBuilder {
public:
struct Flags {
+ // see wasm2js.cpp for details
bool debug = false;
bool pedantic = false;
bool allowAsserts = false;
bool emscripten = false;
+ bool deterministic = false;
std::string symbolsFile;
};
diff --git a/test/wasm2js/deterministic.2asm.js b/test/wasm2js/deterministic.2asm.js
new file mode 100644
index 000000000..8954443cf
--- /dev/null
+++ b/test/wasm2js/deterministic.2asm.js
@@ -0,0 +1,44 @@
+
+function asmFunc(global, env, buffer) {
+ var memory = env.memory;
+ var HEAP8 = new global.Int8Array(buffer);
+ var HEAP16 = new global.Int16Array(buffer);
+ var HEAP32 = new global.Int32Array(buffer);
+ var HEAPU8 = new global.Uint8Array(buffer);
+ var HEAPU16 = new global.Uint16Array(buffer);
+ var HEAPU32 = new global.Uint32Array(buffer);
+ var HEAPF32 = new global.Float32Array(buffer);
+ var HEAPF64 = new global.Float64Array(buffer);
+ var Math_imul = global.Math.imul;
+ var Math_fround = global.Math.fround;
+ var Math_abs = global.Math.abs;
+ var Math_clz32 = global.Math.clz32;
+ var Math_min = global.Math.min;
+ var Math_max = global.Math.max;
+ var Math_floor = global.Math.floor;
+ var Math_ceil = global.Math.ceil;
+ var Math_sqrt = global.Math.sqrt;
+ var abort = env.abort;
+ var nan = global.NaN;
+ var infinity = global.Infinity;
+ var global$0 = -44;
+ function $0() {
+ if ((global$0 >>> 0) / ((HEAP32[0 >> 2] | 0) >>> 0) | 0) {
+ abort()
+ }
+ return 1 | 0;
+ }
+
+ var FUNCTION_TABLE = [];
+ function __wasm_memory_size() {
+ return buffer.byteLength / 65536 | 0;
+ }
+
+ return {
+ "foo": $0
+ };
+}
+
+var memasmFunc = new ArrayBuffer(65536);
+var retasmFunc = asmFunc({Math,Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,NaN,Infinity}, {abort:function() { throw new Error('abort'); }},memasmFunc);
+export var foo = retasmFunc.foo;
diff --git a/test/wasm2js/deterministic.2asm.js.opt b/test/wasm2js/deterministic.2asm.js.opt
new file mode 100644
index 000000000..7a49ec1fb
--- /dev/null
+++ b/test/wasm2js/deterministic.2asm.js.opt
@@ -0,0 +1,43 @@
+
+function asmFunc(global, env, buffer) {
+ var memory = env.memory;
+ var HEAP8 = new global.Int8Array(buffer);
+ var HEAP16 = new global.Int16Array(buffer);
+ var HEAP32 = new global.Int32Array(buffer);
+ var HEAPU8 = new global.Uint8Array(buffer);
+ var HEAPU16 = new global.Uint16Array(buffer);
+ var HEAPU32 = new global.Uint32Array(buffer);
+ var HEAPF32 = new global.Float32Array(buffer);
+ var HEAPF64 = new global.Float64Array(buffer);
+ var Math_imul = global.Math.imul;
+ var Math_fround = global.Math.fround;
+ var Math_abs = global.Math.abs;
+ var Math_clz32 = global.Math.clz32;
+ var Math_min = global.Math.min;
+ var Math_max = global.Math.max;
+ var Math_floor = global.Math.floor;
+ var Math_ceil = global.Math.ceil;
+ var Math_sqrt = global.Math.sqrt;
+ var abort = env.abort;
+ var nan = global.NaN;
+ var infinity = global.Infinity;
+ function $0() {
+ if (4294967252 / HEAPU32[0] | 0) {
+ abort()
+ }
+ return 1;
+ }
+
+ var FUNCTION_TABLE = [];
+ function __wasm_memory_size() {
+ return buffer.byteLength / 65536 | 0;
+ }
+
+ return {
+ "foo": $0
+ };
+}
+
+var memasmFunc = new ArrayBuffer(65536);
+var retasmFunc = asmFunc({Math,Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,NaN,Infinity}, {abort:function() { throw new Error('abort'); }},memasmFunc);
+export var foo = retasmFunc.foo;
diff --git a/test/wasm2js/deterministic.wast b/test/wasm2js/deterministic.wast
new file mode 100644
index 000000000..d1f39ba06
--- /dev/null
+++ b/test/wasm2js/deterministic.wast
@@ -0,0 +1,14 @@
+(module
+ (global $global$0 (mut i32) (i32.const -44))
+ (import "env" "memory" (memory $0 1 1))
+ (func "foo" (result i32)
+ (if
+ (i32.div_u
+ (global.get $global$0)
+ (i32.load (i32.const 0))
+ )
+ (unreachable)
+ )
+ (i32.const 1)
+ )
+)