summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xauto_update_tests.py9
-rwxr-xr-xcheck.py9
-rw-r--r--src/tools/execution-results.h117
-rw-r--r--src/tools/js-wrapper.h89
-rw-r--r--src/tools/spec-wrapper.h47
-rw-r--r--src/tools/translate-to-fuzz.h1115
-rw-r--r--src/tools/wasm-opt.cpp132
-rw-r--r--src/tools/wasm-shell.cpp24
-rw-r--r--src/wasm-binary.h7
-rw-r--r--test/passes/emit-js-wrapper=a.js.txt42
-rw-r--r--test/passes/emit-js-wrapper=a.js.wast37
-rw-r--r--test/passes/emit-js-wrapper=a.js.wast.js44
-rw-r--r--test/passes/emit-spec-wrapper=a.wat.txt42
-rw-r--r--test/passes/emit-spec-wrapper=a.wat.wast37
-rw-r--r--test/passes/emit-spec-wrapper=a.wat.wast.wat1
-rw-r--r--test/passes/fuzz-exec_O.txt8
-rw-r--r--test/passes/translate-to-fuzz.txt1126
-rw-r--r--test/passes/translate-to-fuzz.wast99
18 files changed, 2900 insertions, 85 deletions
diff --git a/auto_update_tests.py b/auto_update_tests.py
index 7aab8ea4e..fd5999cad 100755
--- a/auto_update_tests.py
+++ b/auto_update_tests.py
@@ -45,7 +45,6 @@ for asm in sorted(os.listdir('test')):
cmd += ['--source-map', os.path.join('test', wasm + '.map'), '-o', 'a.wasm']
run_command(cmd)
-
for dot_s_dir in ['dot_s', 'llvm_autogenerated']:
for s in sorted(os.listdir(os.path.join('test', dot_s_dir))):
if not s.endswith('.s'): continue
@@ -102,6 +101,14 @@ for t in sorted(os.listdir(os.path.join('test', 'passes'))):
cmd = WASM_OPT + opts + ['split.wast', '--print']
actual += run_command(cmd)
with open(os.path.join('test', 'passes', passname + ('.bin' if binary else '') + '.txt'), 'w') as o: o.write(actual)
+ if 'emit-js-wrapper' in t:
+ with open('a.js') as i:
+ with open(t + '.js', 'w') as o:
+ o.write(i.read())
+ if 'emit-spec-wrapper' in t:
+ with open('a.wat') as i:
+ with open(t + '.wat', 'w') as o:
+ o.write(i.read())
print '\n[ checking wasm-opt -o notation... ]\n'
diff --git a/check.py b/check.py
index 6f6f23138..befb6f04e 100755
--- a/check.py
+++ b/check.py
@@ -106,6 +106,15 @@ for t in sorted(os.listdir(os.path.join(options.binaryen_test, 'passes'))):
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:
diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h
new file mode 100644
index 000000000..163e70abb
--- /dev/null
+++ b/src/tools/execution-results.h
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+
+//
+// Shared execution result checking code
+//
+
+#include "wasm.h"
+#include "shell-interface.h"
+
+namespace wasm {
+
+static bool areBitwiseEqual(Literal a, Literal b) {
+ if (a == b) return true;
+ // accept equal nans if equal in all bits
+ if (a.type != b.type) return false;
+ if (a.type == f32) {
+ return a.reinterpreti32() == b.reinterpreti32();
+ } else if (a.type == f64) {
+ return a.reinterpreti64() == b.reinterpreti64();
+ }
+ return false;
+}
+
+// gets execution results from a wasm module. this is useful for fuzzing
+//
+// we can only get results when there are no imports. we then call each method
+// that has a result, with some values
+struct ExecutionResults {
+ std::map<Name, Literal> results;
+
+ // get results of execution
+ void get(Module& wasm) {
+ if (wasm.imports.size() > 0) {
+ std::cout << "[fuzz-exec] imports, so quitting\n";
+ return;
+ }
+ for (auto& func : wasm.functions) {
+ if (func->result != none) {
+ // this has a result
+ results[func->name] = run(func.get(), wasm);
+ std::cout << "[fuzz-exec] note result: " << func->name.str << " => " << results[func->name] << '\n';
+ } else {
+ // no result, run it anyhow (it might modify memory etc.)
+ run(func.get(), wasm);
+ std::cout << "[fuzz-exec] no result for void func: " << func->name.str << '\n';
+ }
+ }
+ std::cout << "[fuzz-exec] " << results.size() << " results noted\n";
+ }
+
+ // get current results and check them against previous ones
+ void check(Module& wasm) {
+ ExecutionResults optimizedResults;
+ optimizedResults.get(wasm);
+ if (optimizedResults != *this) {
+ std::cout << "[fuzz-exec] optimization passes changed execution results";
+ abort();
+ }
+ std::cout << "[fuzz-exec] " << results.size() << " results match\n";
+ }
+
+ bool operator==(ExecutionResults& other) {
+ for (auto& iter : results) {
+ auto name = iter.first;
+ if (other.results.find(name) != other.results.end()) {
+ std::cout << "[fuzz-exec] comparing " << name << '\n';
+ if (!areBitwiseEqual(results[name], other.results[name])) {
+ std::cout << "not identical!\n";
+ abort();
+ }
+ }
+ }
+ return true;
+ }
+
+ bool operator!=(ExecutionResults& other) {
+ return !((*this) == other);
+ }
+
+ Literal run(Function* func, Module& wasm) {
+ ShellExternalInterface interface;
+ try {
+ ModuleInstance instance(wasm, &interface);
+ LiteralList arguments;
+ // init hang support, if present
+ if (wasm.getFunctionOrNull("hangLimitInitializer")) {
+ instance.callFunction("hangLimitInitializer", arguments);
+ }
+ // call the method
+ for (WasmType param : func->params) {
+ // zeros in arguments TODO: more?
+ arguments.push_back(Literal(param));
+ }
+ return instance.callFunction(func->name, arguments);
+ } catch (const TrapException&) {
+ // may throw in instance creation (init of offsets) or call itself
+ return Literal();
+ }
+ }
+};
+
+} // namespace wasm
+
diff --git a/src/tools/js-wrapper.h b/src/tools/js-wrapper.h
new file mode 100644
index 000000000..90453eb35
--- /dev/null
+++ b/src/tools/js-wrapper.h
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+//
+// Emit a JavaScript wrapper to run a wasm module with some test
+// values, useful for fuzzing.
+//
+
+namespace wasm {
+
+static std::string generateJSWrapper(Module& wasm) {
+ std::string ret;
+ ret += "if (typeof console === 'undefined') {\n"
+ " console = { log: print };\n"
+ "}\n"
+ "var binary;\n"
+ "if (typeof process === 'object' && typeof require === 'function' /* node.js detection */) {\n"
+ " var args = process.argv.slice(2);\n"
+ " binary = require('fs').readFileSync(args[0]);\n"
+ " if (!binary.buffer) binary = new Uint8Array(binary);\n"
+ "} else {\n"
+ " var args;\n"
+ " if (typeof scriptArgs != 'undefined') {\n"
+ " args = scriptArgs;\n"
+ " } else if (typeof arguments != 'undefined') {\n"
+ " args = arguments;\n"
+ " }\n"
+ " if (typeof readbuffer === 'function') {\n"
+ " binary = new Uint8Array(readbuffer(args[0]));\n"
+ " } else {\n"
+ " binary = read(args[0], 'binary');\n"
+ " }\n"
+ "}\n"
+ "var instance = new WebAssembly.Instance(new WebAssembly.Module(binary), {});\n";
+ for (auto& exp : wasm.exports) {
+ auto* func = wasm.getFunctionOrNull(exp->value);
+ if (!func) continue; // something exported other than a function
+ auto bad = false; // check for things we can't support
+ for (WasmType param : func->params) {
+ if (param == i64) bad = true;
+ }
+ if (func->result == i64) bad = true;
+ if (bad) continue;
+ ret += "if (instance.exports.hangLimitInitializer) instance.exports.hangLimitInitializer();\n";
+ ret += "try {\n";
+ ret += std::string(" console.log('calling: ") + exp->name.str + "');\n";
+ if (func->result != none) {
+ ret += " console.log(' result: ' + ";
+ }
+ ret += std::string("instance.exports.") + exp->name.str + "(";
+ bool first = true;
+ for (WasmType param : func->params) {
+ WASM_UNUSED(param);
+ // zeros in arguments TODO more?
+ if (first) {
+ first = false;
+ } else {
+ ret += ", ";
+ }
+ ret += "0";
+ }
+ ret += ")";
+ if (func->result != none) {
+ ret += ")"; // for console.log
+ }
+ ret += ";\n";
+ ret += "} catch (e) {\n";
+ ret += " console.log(' exception: ' + e);\n";
+ ret += "}\n";
+ }
+ ret += "console.log('done.')\n";
+ return ret;
+}
+
+} // namespace wasm
+
diff --git a/src/tools/spec-wrapper.h b/src/tools/spec-wrapper.h
new file mode 100644
index 000000000..4da746a5d
--- /dev/null
+++ b/src/tools/spec-wrapper.h
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+//
+// Emit a wasm spec interpreter wrapper to run a wasm module with some test
+// values, useful for fuzzing.
+//
+
+namespace wasm {
+
+static std::string generateSpecWrapper(Module& wasm) {
+ std::string ret;
+ for (auto& exp : wasm.exports) {
+ auto* func = wasm.getFunctionOrNull(exp->value);
+ if (!func) continue; // something exported other than a function
+ ret += std::string("(invoke \"hangLimitInitializer\") (invoke \"") + exp->name.str + "\" ";
+ for (WasmType param : func->params) {
+ // zeros in arguments TODO more?
+ switch (param) {
+ case i32: ret += "(i32.const 0)"; break;
+ case i64: ret += "(i64.const 0)"; break;
+ case f32: ret += "(f32.const 0)"; break;
+ case f64: ret += "(f64.const 0)"; break;
+ default: WASM_UNREACHABLE();
+ }
+ ret += " ";
+ }
+ ret += ") ";
+ }
+ return ret;
+}
+
+} // namespace wasm
+
diff --git a/src/tools/translate-to-fuzz.h b/src/tools/translate-to-fuzz.h
new file mode 100644
index 000000000..a7f86af13
--- /dev/null
+++ b/src/tools/translate-to-fuzz.h
@@ -0,0 +1,1115 @@
+/*
+ * 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.
+ */
+
+//
+// Translate a binary stream of bytes into a valid wasm module, *somehow*.
+// This is helpful for fuzzing.
+//
+
+#include <wasm-builder.h>
+
+namespace wasm {
+
+// helper structs, since list initialization has a fixed order of
+// evaluation, avoiding UB
+
+struct ThreeArgs {
+ Expression *a;
+ Expression *b;
+ Expression *c;
+};
+
+struct UnaryArgs {
+ UnaryOp a;
+ Expression *b;
+};
+
+struct BinaryArgs {
+ BinaryOp a;
+ Expression *b;
+ Expression *c;
+};
+
+// main reader
+
+class TranslateToFuzzReader {
+public:
+ TranslateToFuzzReader(Module& wasm) : wasm(wasm), builder(wasm) {}
+
+ void read(std::string& filename) {
+ auto input(read_file<std::vector<char>>(filename, Flags::Binary, Flags::Release));
+ bytes.swap(input);
+ pos = 0;
+ finishedInput = false;
+ // ensure *some* input to be read
+ if (bytes.size() == 0) {
+ bytes.push_back(0);
+ }
+ build();
+ }
+
+private:
+ Module& wasm;
+ Builder builder;
+ std::vector<char> bytes; // the input bytes
+ size_t pos; // the position in the input
+ bool finishedInput; // whether we already cycled through all the input (if so, we should try to finish things off)
+
+ // some things require luck, try them a few times
+ static const int TRIES = 10;
+
+ // beyond a nesting limit, greatly decrease the chance to continue to nest
+ static const int NESTING_LIMIT = 11;
+
+ // reduce the chance for a function to call itself by this factor
+ static const int RECURSION_FACTOR = 10;
+
+ // the maximum size of a block
+ static const int BLOCK_FACTOR = 5;
+
+ // the memory that we use, a small portion so that we have a good chance of
+ // looking at writes (we also look outside of this region with small probability)
+ static const int USABLE_MEMORY = 32;
+
+ // the number of runtime iterations (function calls, loop backbranches) we
+ // allow before we stop execution with a trap, to prevent hangs. 0 means
+ // no hang protection.
+ static const int HANG_LIMIT = 25;
+
+ // Optionally remove NaNs, which are a source of nondeterminism (which makes
+ // cross-VM comparisons harder)
+ static const bool DE_NAN = true;
+
+ // after we finish the input, we start going through it again, but xoring
+ // so it's not identical
+ int xorFactor = 0;
+
+ int8_t get() {
+ if (pos == bytes.size()) {
+ // we ran out of input, go to the start for more stuff
+ finishedInput = true;
+ pos = 0;
+ xorFactor++;
+ }
+ return bytes[pos++] ^ xorFactor;
+ }
+
+ int16_t get16() {
+ auto temp = uint16_t(get()) << 8;
+ return temp | uint16_t(get());
+ }
+
+ int32_t get32() {
+ auto temp = uint32_t(get16()) << 16;
+ return temp | uint32_t(get16());
+ }
+
+ int64_t get64() {
+ auto temp = uint64_t(get32()) << 32;
+ return temp | uint64_t(get32());
+ }
+
+ float getFloat() {
+ return Literal(get32()).reinterpretf32();
+ }
+
+ double getDouble() {
+ return Literal(get64()).reinterpretf64();
+ }
+
+ void build() {
+ setupMemory();
+ // keep adding functions until we run out of input
+ while (!finishedInput) {
+ addFunction();
+ }
+ if (HANG_LIMIT > 0) {
+ addHangLimitSupport();
+ }
+ if (DE_NAN) {
+ addDeNanSupport();
+ }
+ }
+
+ void setupMemory() {
+ wasm.memory.exists = true;
+ // use one page
+ wasm.memory.initial = wasm.memory.max = 1;
+ }
+
+ const Name HANG_LIMIT_GLOBAL = "hangLimit";
+
+ void addHangLimitSupport() {
+ auto* glob = new Global;
+ glob->name = HANG_LIMIT_GLOBAL;
+ glob->type = i32;
+ glob->init = builder.makeConst(Literal(int32_t(HANG_LIMIT)));
+ glob->mutable_ = true;
+ wasm.addGlobal(glob);
+
+ auto* func = new Function;
+ func->name = "hangLimitInitializer";
+ func->result = none;
+ func->body = builder.makeSetGlobal(glob->name,
+ builder.makeConst(Literal(int32_t(HANG_LIMIT)))
+ );
+ wasm.addFunction(func);
+
+ auto* export_ = new Export;
+ export_->name = func->name;
+ export_->value = func->name;
+ export_->kind = ExternalKind::Function;
+ wasm.addExport(export_);
+ }
+
+ Expression* makeHangLimitCheck() {
+ return builder.makeSequence(
+ builder.makeIf(
+ builder.makeUnary(
+ UnaryOp::EqZInt32,
+ builder.makeGetGlobal(HANG_LIMIT_GLOBAL, i32)
+ ),
+ makeTrivial(unreachable)
+ ),
+ builder.makeSetGlobal(
+ HANG_LIMIT_GLOBAL,
+ builder.makeBinary(
+ BinaryOp::SubInt32,
+ builder.makeGetGlobal(HANG_LIMIT_GLOBAL, i32),
+ builder.makeConst(Literal(int32_t(1)))
+ )
+ )
+ );
+ }
+
+ void addDeNanSupport() {
+ auto add = [&](Name name, WasmType type, Literal literal, BinaryOp op) {
+ auto* func = new Function;
+ func->name = name;
+ func->params.push_back(type);
+ func->result = type;
+ func->body = builder.makeIf(
+ builder.makeBinary(
+ op,
+ builder.makeGetLocal(0, type),
+ builder.makeGetLocal(0, type)
+ ),
+ builder.makeGetLocal(0, type),
+ builder.makeConst(literal)
+ );
+ wasm.addFunction(func);
+ };
+ add("deNan32", f32, Literal(float(0)), EqFloat32);
+ add("deNan64", f64, Literal(double(0)), EqFloat64);
+ }
+
+ Expression* makeDeNanOp(Expression* expr) {
+ if (!DE_NAN) return expr;
+ if (expr->type == f32) {
+ return builder.makeCall("deNan32", { expr }, f32);
+ } else if (expr->type == f64) {
+ return builder.makeCall("deNan64", { expr }, f64);
+ }
+ return expr; // unreachable etc. is fine
+ }
+
+ // function generation state
+
+ Function* func;
+ std::vector<Expression*> breakableStack; // things we can break to
+ Index labelIndex;
+
+ // a list of things relevant to computing the odds of an infinite loop,
+ // which we try to minimize the risk of
+ std::vector<Expression*> hangStack;
+
+ std::map<WasmType, std::vector<Index>> typeLocals; // type => list of locals with that type
+
+ void addFunction() {
+ Index num = wasm.functions.size();
+ func = new Function;
+ func->name = std::string("func_") + std::to_string(num);
+ func->result = getReachableType();
+ assert(typeLocals.empty());
+ Index numParams = upToSquared(5);
+ for (Index i = 0; i < numParams; i++) {
+ auto type = getConcreteType();
+ typeLocals[type].push_back(func->params.size());
+ func->params.push_back(type);
+ }
+ Index numVars = upToSquared(10);
+ for (Index i = 0; i < numVars; i++) {
+ auto type = getConcreteType();
+ typeLocals[type].push_back(func->params.size() + func->vars.size());
+ func->vars.push_back(type);
+ }
+ labelIndex = 0;
+ assert(breakableStack.empty());
+ assert(hangStack.empty());
+ // with reasonable chance make the body a block
+ if (oneIn(2)) {
+ func->body = makeBlock(func->result);
+ } else {
+ // with very small chance, make the body unreachable
+ if (oneIn(20)) {
+ func->body = make(unreachable);
+ } else {
+ func->body = make(func->result);
+ }
+ }
+ if (HANG_LIMIT > 0) {
+ func->body = builder.makeSequence(
+ makeHangLimitCheck(),
+ func->body
+ );
+ }
+ assert(breakableStack.empty());
+ assert(hangStack.empty());
+ wasm.addFunction(func);
+ // export some, but not all (to allow inlining etc.). make sure to
+ // export at least one, though, to keep each testcase interesting
+ if (num == 0 || oneIn(2)) {
+ auto* export_ = new Export;
+ export_->name = func->name;
+ export_->value = func->name;
+ export_->kind = ExternalKind::Function;
+ wasm.addExport(export_);
+ }
+ // cleanup
+ typeLocals.clear();
+ }
+
+ Name makeLabel() {
+ return std::string("label$") + std::to_string(labelIndex++);
+ }
+
+ // always call the toplevel make(type) command, not the internal specific ones
+
+ int nesting = 0;
+
+ Expression* make(WasmType type) {
+ // when we should stop, emit something small (but not necessarily trivial)
+ if (finishedInput ||
+ nesting >= 5 * NESTING_LIMIT || // hard limit
+ (nesting >= NESTING_LIMIT && !oneIn(3))) {
+ if (isConcreteWasmType(type)) {
+ if (oneIn(2)) {
+ return makeConst(type);
+ } else {
+ return makeGetLocal(type);
+ }
+ } else if (type == none) {
+ if (oneIn(2)) {
+ return makeNop(type);
+ } else {
+ return makeSetLocal(type);
+ }
+ }
+ assert(type == unreachable);
+ return makeTrivial(type);
+ }
+ nesting++;
+ Expression* ret;
+ switch (type) {
+ case i32: ret = _makei32(); break;
+ case i64: ret = _makei64(); break;
+ case f32: ret = _makef32(); break;
+ case f64: ret = _makef64(); break;
+ case none: ret = _makenone(); break;
+ case unreachable: ret = _makeunreachable(); break;
+ default: WASM_UNREACHABLE();
+ }
+ nesting--;
+ return ret;
+ }
+
+ Expression* _makei32() {
+ switch (upTo(13)) {
+ case 0: return makeBlock(i32);
+ case 1: return makeIf(i32);
+ case 2: return makeLoop(i32);
+ case 3: return makeBreak(i32);
+ case 4: return makeCall(i32);
+ case 5: return makeCallIndirect(i32);
+ case 6: return makeGetLocal(i32);
+ case 7: return makeSetLocal(i32);
+ case 8: return makeLoad(i32);
+ case 9: return makeConst(i32);
+ case 10: return makeUnary(i32);
+ case 11: return makeBinary(i32);
+ case 12: return makeSelect(i32);
+ }
+ WASM_UNREACHABLE();
+ }
+
+ Expression* _makei64() {
+ switch (upTo(13)) {
+ case 0: return makeBlock(i64);
+ case 1: return makeIf(i64);
+ case 2: return makeLoop(i64);
+ case 3: return makeBreak(i64);
+ case 4: return makeCall(i64);
+ case 5: return makeCallIndirect(i64);
+ case 6: return makeGetLocal(i64);
+ case 7: return makeSetLocal(i64);
+ case 8: return makeLoad(i64);
+ case 9: return makeConst(i64);
+ case 10: return makeUnary(i64);
+ case 11: return makeBinary(i64);
+ case 12: return makeSelect(i64);
+ }
+ WASM_UNREACHABLE();
+ }
+
+ Expression* _makef32() {
+ switch (upTo(13)) {
+ case 0: return makeBlock(f32);
+ case 1: return makeIf(f32);
+ case 2: return makeLoop(f32);
+ case 3: return makeBreak(f32);
+ case 4: return makeCall(f32);
+ case 5: return makeCallIndirect(f32);
+ case 6: return makeGetLocal(f32);
+ case 7: return makeSetLocal(f32);
+ case 8: return makeLoad(f32);
+ case 9: return makeConst(f32);
+ case 10: return makeUnary(f32);
+ case 11: return makeBinary(f32);
+ case 12: return makeSelect(f32);
+ }
+ WASM_UNREACHABLE();
+ }
+
+ Expression* _makef64() {
+ switch (upTo(13)) {
+ case 0: return makeBlock(f64);
+ case 1: return makeIf(f64);
+ case 2: return makeLoop(f64);
+ case 3: return makeBreak(f64);
+ case 4: return makeCall(f64);
+ case 5: return makeCallIndirect(f64);
+ case 6: return makeGetLocal(f64);
+ case 7: return makeSetLocal(f64);
+ case 8: return makeLoad(f64);
+ case 9: return makeConst(f64);
+ case 10: return makeUnary(f64);
+ case 11: return makeBinary(f64);
+ case 12: return makeSelect(f64);
+ }
+ WASM_UNREACHABLE();
+ }
+
+ Expression* _makenone() {
+ switch (upTo(10)) {
+ case 0: return makeBlock(none);
+ case 1: return makeIf(none);
+ case 2: return makeLoop(none);
+ case 3: return makeBreak(none);
+ case 4: return makeCall(none);
+ case 5: return makeCallIndirect(none);
+ case 6: return makeSetLocal(none);
+ case 7: return makeStore(none);
+ case 8: return makeDrop(none);
+ case 9: return makeNop(none);
+ }
+ WASM_UNREACHABLE();
+ }
+
+ Expression* _makeunreachable() {
+ switch (upTo(15)) {
+ case 0: return makeBlock(unreachable);
+ case 1: return makeIf(unreachable);
+ case 2: return makeLoop(unreachable);
+ case 3: return makeBreak(unreachable);
+ case 4: return makeCall(unreachable);
+ case 5: return makeCallIndirect(unreachable);
+ case 6: return makeSetLocal(unreachable);
+ case 7: return makeStore(unreachable);
+ case 8: return makeUnary(unreachable);
+ case 9: return makeBinary(unreachable);
+ case 10: return makeSelect(unreachable);
+ case 11: return makeSwitch(unreachable);
+ case 12: return makeDrop(unreachable);
+ case 13: return makeReturn(unreachable);
+ case 14: return makeUnreachable(unreachable);
+ }
+ WASM_UNREACHABLE();
+ }
+
+ // make something with no chance of infinite recursion
+ Expression* makeTrivial(WasmType type) {
+ if (isConcreteWasmType(type)) {
+ return makeConst(type);
+ } else if (type == none) {
+ return makeNop(type);
+ }
+ assert(type == unreachable);
+ return builder.makeReturn(
+ isConcreteWasmType(func->result) ? makeConst(func->result) : nullptr
+ );
+ }
+
+ // specific expression creators
+
+ Expression* makeBlock(WasmType type) {
+ auto* ret = builder.makeBlock();
+ ret->type = type; // so we have it during child creation
+ ret->name = makeLabel();
+ breakableStack.push_back(ret);
+ Index num = upToSquared(BLOCK_FACTOR - 1); // we add another later
+ if (nesting >= NESTING_LIMIT / 2) {
+ // smaller blocks past the limit
+ num /= 2;
+ if (nesting >= NESTING_LIMIT && oneIn(2)) {
+ // smaller blocks past the limit
+ num /= 2;
+ }
+ }
+ while (num > 0 && !finishedInput) {
+ ret->list.push_back(make(none));
+ num--;
+ }
+ // give a chance to make the final element an unreachable break, instead
+ // of concrete - a common pattern (branch to the top of a loop etc.)
+ if (!finishedInput && isConcreteWasmType(type) && oneIn(2)) {
+ ret->list.push_back(makeBreak(unreachable));
+ } else {
+ ret->list.push_back(make(type));
+ }
+ breakableStack.pop_back();
+ if (isConcreteWasmType(type)) {
+ ret->finalize(type);
+ } else {
+ ret->finalize();
+ }
+ if (ret->type != type) {
+ // e.g. we might want an unreachable block, but a child breaks to it
+ assert(type == unreachable && ret->type == none);
+ return builder.makeSequence(ret, make(unreachable));
+ }
+ return ret;
+ }
+
+ Expression* makeLoop(WasmType type) {
+ auto* ret = wasm.allocator.alloc<Loop>();
+ ret->type = type; // so we have it during child creation
+ ret->name = makeLabel();
+ breakableStack.push_back(ret);
+ hangStack.push_back(ret);
+ ret->body = makeMaybeBlock(type);
+ breakableStack.pop_back();
+ hangStack.pop_back();
+ if (HANG_LIMIT > 0) {
+ ret->body = builder.makeSequence(
+ makeHangLimitCheck(),
+ ret->body
+ );
+ }
+ ret->finalize();
+ return ret;
+ }
+
+ Expression* makeCondition() {
+ // we want a 50-50 chance for the condition to be taken, for interesting
+ // execution paths. by itself, there is bias (e.g. most consts are "yes")
+ // so even that out with noise
+ auto* ret = make(i32);
+ if (oneIn(2)) {
+ ret = builder.makeUnary(UnaryOp::EqZInt32, ret);
+ }
+ return ret;
+ }
+
+ // make something, with a good chance of it being a block
+ Expression* makeMaybeBlock(WasmType type) {
+ // if past the limit, prefer not to emit blocks
+ if (nesting >= NESTING_LIMIT || oneIn(3)) {
+ return make(type);
+ } else {
+ return makeBlock(type);
+ }
+ }
+
+ Expression* makeIf(WasmType type) {
+ auto* condition = makeCondition();
+ hangStack.push_back(nullptr);
+ auto* ret = makeIf({ condition, makeMaybeBlock(type), makeMaybeBlock(type) });
+ hangStack.pop_back();
+ return ret;
+ }
+
+ Expression* makeIf(const struct ThreeArgs& args) {
+ return builder.makeIf(args.a, args.b, args.c);
+ }
+
+ Expression* makeBreak(WasmType type) {
+ if (breakableStack.empty()) return makeTrivial(type);
+ Expression* condition = nullptr;
+ if (type != unreachable) {
+ hangStack.push_back(nullptr);
+ condition = makeCondition();
+ }
+ // we need to find a proper target to break to; try a few times
+ int tries = TRIES;
+ while (tries-- > 0) {
+ auto* target = vectorPick(breakableStack);
+ auto name = getTargetName(target);
+ auto valueType = getTargetType(target);
+ if (isConcreteWasmType(type)) {
+ // we are flowing out a value
+ if (valueType != type) {
+ // we need to break to a proper place
+ continue;
+ }
+ auto* ret = builder.makeBreak(name, make(type), condition);
+ hangStack.pop_back();
+ return ret;
+ } else if (type == none) {
+ if (valueType != none) {
+ // we need to break to a proper place
+ continue;
+ }
+ auto* ret = builder.makeBreak(name, nullptr, condition);
+ hangStack.pop_back();
+ return ret;
+ } else {
+ assert(type == unreachable);
+ if (valueType != none) {
+ // we need to break to a proper place
+ continue;
+ }
+ // we are about to make an *un*conditional break. if it is
+ // to a loop, we prefer there to be a condition along the
+ // way, to reduce the chance of infinite looping
+ size_t conditions = 0;
+ int i = hangStack.size();
+ while (--i >= 0) {
+ auto* item = hangStack[i];
+ if (item == nullptr) {
+ conditions++;
+ } else if (auto* loop = item->cast<Loop>()) {
+ if (loop->name == name) {
+ // we found the target, no more conditions matter
+ break;
+ }
+ }
+ }
+ switch (conditions) {
+ case 0: if (!oneIn(4)) continue;
+ case 1: if (!oneIn(2)) continue;
+ default: if (oneIn(conditions + 1)) continue;
+ }
+ return builder.makeBreak(name);
+ }
+ }
+ // we failed to find something
+ if (type != unreachable) {
+ hangStack.pop_back();
+ }
+ return makeTrivial(type);
+ }
+
+ Expression* makeCall(WasmType type) {
+ // seems ok, go on
+ int tries = TRIES;
+ while (tries-- > 0) {
+ Function* target = func;
+ if (!wasm.functions.empty() && !oneIn(wasm.functions.size())) {
+ target = vectorPick(wasm.functions).get();
+ }
+ if (target->result != type) continue;
+ // reduce the odds of recursion dramatically, to limit infinite loops
+ if (target == func && !oneIn(RECURSION_FACTOR * TRIES)) continue;
+ // we found one!
+ std::vector<Expression*> args;
+ for (auto argType : target->params) {
+ args.push_back(make(argType));
+ }
+ return builder.makeCall(target->name, args, type);
+ }
+ // we failed to find something
+ return make(type);
+ }
+
+ Expression* makeCallIndirect(WasmType type) {
+ return make(type); // TODO
+ }
+
+ Expression* makeGetLocal(WasmType type) {
+ auto& locals = typeLocals[type];
+ if (locals.empty()) return makeTrivial(type);
+ return builder.makeGetLocal(vectorPick(locals), type);
+ }
+
+ Expression* makeSetLocal(WasmType type) {
+ bool tee = type != none;
+ WasmType valueType;
+ if (tee) {
+ valueType = type;
+ } else {
+ valueType = getConcreteType();
+ }
+ auto& locals = typeLocals[valueType];
+ if (locals.empty()) return makeTrivial(type);
+ auto* value = make(valueType);
+ if (tee) {
+ return builder.makeTeeLocal(vectorPick(locals), value);
+ } else {
+ return builder.makeSetLocal(vectorPick(locals), value);
+ }
+ }
+
+ Expression* makePointer() {
+ auto* ret = make(i32);
+ // with high probability, mask the pointer so it's in a reasonable
+ // range. otherwise, most pointers are going to be out of range and
+ // most memory ops will just trap
+ if (!oneIn(10)) {
+ ret = builder.makeBinary(AndInt32,
+ ret,
+ builder.makeConst(Literal(int32_t(USABLE_MEMORY - 1)))
+ );
+ }
+ return ret;
+ }
+
+ Expression* makeLoad(WasmType type) {
+ auto offset = logify(get());
+ auto ptr = makePointer();
+ switch (type) {
+ case i32: {
+ bool signed_ = get() & 1;
+ switch (upTo(3)) {
+ case 0: return builder.makeLoad(1, signed_, offset, 1, ptr, type);
+ case 1: return builder.makeLoad(2, signed_, offset, pick(1, 2), ptr, type);
+ case 2: return builder.makeLoad(4, signed_, offset, pick(1, 2, 4), ptr, type);
+ }
+ WASM_UNREACHABLE();
+ }
+ case i64: {
+ bool signed_ = get() & 1;
+ switch (upTo(4)) {
+ case 0: return builder.makeLoad(1, signed_, offset, 1, ptr, type);
+ case 1: return builder.makeLoad(2, signed_, offset, pick(1, 2), ptr, type);
+ case 2: return builder.makeLoad(4, signed_, offset, pick(1, 2, 4), ptr, type);
+ case 3: return builder.makeLoad(8, signed_, offset, pick(1, 2, 4, 8), ptr, type);
+ }
+ WASM_UNREACHABLE();
+ }
+ case f32: {
+ return builder.makeLoad(4, false, offset, pick(1, 2, 4), ptr, type);
+ }
+ case f64: {
+ return builder.makeLoad(8, false, offset, pick(1, 2, 4, 8), ptr, type);
+ }
+ default: WASM_UNREACHABLE();
+ }
+ }
+
+ Store* makeStore(WasmType type) {
+ if (type == unreachable) {
+ // make a normal store, then make it unreachable
+ auto* ret = makeStore(getConcreteType());
+ switch (upTo(3)) {
+ case 0: ret->ptr = make(unreachable); break;
+ case 1: ret->value = make(unreachable); break;
+ case 2: ret->ptr = make(unreachable); ret->value = make(unreachable); break;
+ }
+ ret->finalize();
+ return ret;
+ }
+ // the type is none or unreachable. we also need to pick the value
+ // type.
+ if (type == none) {
+ type = getConcreteType();
+ }
+ auto offset = logify(get());
+ auto ptr = makePointer();
+ auto value = make(type);
+ switch (type) {
+ case i32: {
+ switch (upTo(3)) {
+ case 0: return builder.makeStore(1, offset, 1, ptr, value, type);
+ case 1: return builder.makeStore(2, offset, pick(1, 2), ptr, value, type);
+ case 2: return builder.makeStore(4, offset, pick(1, 2, 4), ptr, value, type);
+ }
+ WASM_UNREACHABLE();
+ }
+ case i64: {
+ switch (upTo(4)) {
+ case 0: return builder.makeStore(1, offset, 1, ptr, value, type);
+ case 1: return builder.makeStore(2, offset, pick(1, 2), ptr, value, type);
+ case 2: return builder.makeStore(4, offset, pick(1, 2, 4), ptr, value, type);
+ case 3: return builder.makeStore(8, offset, pick(1, 2, 4, 8), ptr, value, type);
+ }
+ WASM_UNREACHABLE();
+ }
+ case f32: {
+ return builder.makeStore(4, offset, pick(1, 2, 4), ptr, value, type);
+ }
+ case f64: {
+ return builder.makeStore(8, offset, pick(1, 2, 4, 8), ptr, value, type);
+ }
+ default: WASM_UNREACHABLE();
+ }
+ }
+
+ Expression* makeConst(WasmType type) {
+ Literal value;
+ switch (upTo(3)) {
+ case 0: {
+ // totally random, entire range
+ switch (type) {
+ case i32: value = Literal(get32()); break;
+ case i64: value = Literal(get64()); break;
+ case f32: value = Literal(getFloat()); break;
+ case f64: value = Literal(getDouble()); break;
+ default: WASM_UNREACHABLE();
+ }
+ break;
+ }
+ case 1: {
+ // small range
+ int32_t small;
+ switch (upTo(4)) {
+ case 0: small = int8_t(get()); break;
+ case 1: small = uint8_t(get()); break;
+ case 2: small = int16_t(get16()); break;
+ case 3: small = uint16_t(get16()); break;
+ default: WASM_UNREACHABLE();
+ }
+ switch (type) {
+ case i32: value = Literal(int32_t(small)); break;
+ case i64: value = Literal(int64_t(small)); break;
+ case f32: value = Literal(float(small)); break;
+ case f64: value = Literal(double(small)); break;
+ default: WASM_UNREACHABLE();
+ }
+ break;
+ }
+ case 2: {
+ // special values
+ switch (type) {
+ case i32: value = Literal(pick<int32_t>(0, -1, 1,
+ std::numeric_limits<int8_t>::min(), std::numeric_limits<int8_t>::max(),
+ std::numeric_limits<int16_t>::min(), std::numeric_limits<int16_t>::max(),
+ std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::max(),
+ std::numeric_limits<uint8_t>::max(),
+ std::numeric_limits<uint16_t>::max(),
+ std::numeric_limits<uint32_t>::max())); break;
+ case i64: value = Literal(pick<int64_t>(0, -1, 1,
+ std::numeric_limits<int8_t>::min(), std::numeric_limits<int8_t>::max(),
+ std::numeric_limits<int16_t>::min(), std::numeric_limits<int16_t>::max(),
+ std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::max(),
+ std::numeric_limits<int64_t>::min(), std::numeric_limits<int64_t>::max(),
+ std::numeric_limits<uint8_t>::max(),
+ std::numeric_limits<uint16_t>::max(),
+ std::numeric_limits<uint32_t>::max(),
+ std::numeric_limits<uint64_t>::max())); break;
+ case f32: value = Literal(pick<float>(0, -1, 1,
+ std::numeric_limits<float>::min(), std::numeric_limits<float>::max(),
+ std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::max(),
+ std::numeric_limits<int64_t>::min(), std::numeric_limits<int64_t>::max(),
+ std::numeric_limits<uint32_t>::max(),
+ std::numeric_limits<uint64_t>::max())); break;
+ case f64: value = Literal(pick<double>(0, -1, 1,
+ std::numeric_limits<float>::min(), std::numeric_limits<float>::max(),
+ std::numeric_limits<double>::min(), std::numeric_limits<double>::max(),
+ std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::max(),
+ std::numeric_limits<int64_t>::min(), std::numeric_limits<int64_t>::max(),
+ std::numeric_limits<uint32_t>::max(),
+ std::numeric_limits<uint64_t>::max())); break;
+ default: WASM_UNREACHABLE();
+ }
+ break;
+ }
+ }
+ auto* ret = wasm.allocator.alloc<Const>();
+ ret->value = value;
+ ret->type = value.type;
+ return ret;
+ }
+
+ Expression* makeUnary(const UnaryArgs& args) {
+ return builder.makeUnary(args.a, args.b);
+ }
+
+ Expression* makeUnary(WasmType type) {
+ if (type == unreachable) {
+ if (auto* unary = makeUnary(getConcreteType())->dynCast<Unary>()) {
+ return makeDeNanOp(builder.makeUnary(unary->op, make(unreachable)));
+ }
+ // give up
+ return makeTrivial(type);
+ }
+ switch (type) {
+ case i32: {
+ switch (upTo(4)) {
+ case 0: return makeUnary({ pick(EqZInt32, ClzInt32, CtzInt32, PopcntInt32), make(i32) });
+ case 1: return makeUnary({ pick(EqZInt64, WrapInt64), make(i64) });
+ case 2: return makeUnary({ pick(TruncSFloat32ToInt32, TruncUFloat32ToInt32, ReinterpretFloat32), make(f32) });
+ case 3: return makeUnary({ pick(TruncSFloat64ToInt32, TruncUFloat64ToInt32), make(f64) });
+ }
+ WASM_UNREACHABLE();
+ }
+ case i64: {
+ switch (upTo(4)) {
+ case 0: return makeUnary({ pick(ClzInt64, CtzInt64, PopcntInt64), make(i64) });
+ case 1: return makeUnary({ pick(ExtendSInt32, ExtendUInt32), make(i32) });
+ case 2: return makeUnary({ pick(TruncSFloat32ToInt64, TruncUFloat32ToInt64), make(f32) });
+ case 3: return makeUnary({ pick(TruncSFloat64ToInt64, TruncUFloat64ToInt64, ReinterpretFloat64), make(f64) });
+ }
+ WASM_UNREACHABLE();
+ }
+ case f32: {
+ switch (upTo(4)) {
+ case 0: return makeDeNanOp(makeUnary({ pick(NegFloat32, AbsFloat32, CeilFloat32, FloorFloat32, TruncFloat32, NearestFloat32, SqrtFloat32), make(f32) }));
+ case 1: return makeDeNanOp(makeUnary({ pick(ConvertUInt32ToFloat32, ConvertSInt32ToFloat32, ReinterpretInt32), make(i32) }));
+ case 2: return makeDeNanOp(makeUnary({ pick(ConvertUInt64ToFloat32, ConvertSInt64ToFloat32), make(i64) }));
+ case 3: return makeDeNanOp(makeUnary({ DemoteFloat64, make(f64) }));
+ }
+ WASM_UNREACHABLE();
+ }
+ case f64: {
+ switch (upTo(4)) {
+ case 0: return makeDeNanOp(makeUnary({ pick(NegFloat64, AbsFloat64, CeilFloat64, FloorFloat64, TruncFloat64, NearestFloat64, SqrtFloat64), make(f64) }));
+ case 1: return makeDeNanOp(makeUnary({ pick(ConvertUInt32ToFloat64, ConvertSInt32ToFloat64), make(i32) }));
+ case 2: return makeDeNanOp(makeUnary({ pick(ConvertUInt64ToFloat64, ConvertSInt64ToFloat64, ReinterpretInt64), make(i64) }));
+ case 3: return makeDeNanOp(makeUnary({ PromoteFloat32, make(f32) }));
+ }
+ WASM_UNREACHABLE();
+ }
+ default: WASM_UNREACHABLE();
+ }
+ WASM_UNREACHABLE();
+ }
+
+ Expression* makeBinary(const BinaryArgs& args) {
+ return builder.makeBinary(args.a, args.b, args.c);
+ }
+
+ Expression* makeBinary(WasmType type) {
+ if (type == unreachable) {
+ if (auto* binary = makeBinary(getConcreteType())->dynCast<Binary>()) {
+ return makeDeNanOp(makeBinary({ binary->op, make(unreachable), make(unreachable) }));
+ }
+ // give up
+ return makeTrivial(type);
+ }
+ switch (type) {
+ case i32: {
+ switch (upTo(4)) {
+ case 0: return makeBinary({ pick(AddInt32, SubInt32, MulInt32, DivSInt32, DivUInt32, RemSInt32, RemUInt32, AndInt32, OrInt32, XorInt32, ShlInt32, ShrUInt32, ShrSInt32, RotLInt32, RotRInt32, EqInt32, NeInt32, LtSInt32, LtUInt32, LeSInt32, LeUInt32, GtSInt32, GtUInt32, GeSInt32, GeUInt32), make(i32), make(i32) });
+ case 1: return makeBinary({ pick(EqInt64, NeInt64, LtSInt64, LtUInt64, LeSInt64, LeUInt64, GtSInt64, GtUInt64, GeSInt64, GeUInt64), make(i64), make(i64) });
+ case 2: return makeBinary({ pick(EqFloat32, NeFloat32, LtFloat32, LeFloat32, GtFloat32, GeFloat32), make(f32), make(f32) });
+ case 3: return makeBinary({ pick(EqFloat64, NeFloat64, LtFloat64, LeFloat64, GtFloat64, GeFloat64), make(f64), make(f64) });
+ }
+ WASM_UNREACHABLE();
+ }
+ case i64: {
+ return makeBinary({ pick(AddInt64, SubInt64, MulInt64, DivSInt64, DivUInt64, RemSInt64, RemUInt64, AndInt64, OrInt64, XorInt64, ShlInt64, ShrUInt64, ShrSInt64, RotLInt64, RotRInt64), make(i64), make(i64) });
+ }
+ case f32: {
+ return makeDeNanOp(makeBinary({ pick(AddFloat32, SubFloat32, MulFloat32, DivFloat32, CopySignFloat32, MinFloat32, MaxFloat32), make(f32), make(f32) }));
+ }
+ case f64: {
+ return makeDeNanOp(makeBinary({ pick(AddFloat64, SubFloat64, MulFloat64, DivFloat64, CopySignFloat64, MinFloat64, MaxFloat64), make(f64), make(f64) }));
+ }
+ default: WASM_UNREACHABLE();
+ }
+ WASM_UNREACHABLE();
+ }
+
+ Expression* makeSelect(const ThreeArgs& args) {
+ return builder.makeSelect(args.a, args.b, args.c);
+ }
+
+ Expression* makeSelect(WasmType type) {
+ return makeDeNanOp(makeSelect({ make(i32), make(type), make(type) }));
+ }
+
+ Expression* makeSwitch(WasmType type) {
+ assert(type == unreachable);
+ if (breakableStack.empty()) return make(type);
+ // we need to find proper targets to break to; try a bunch
+ int tries = TRIES;
+ std::vector<Name> names;
+ WasmType valueType = unreachable;
+ while (tries-- > 0) {
+ auto* target = vectorPick(breakableStack);
+ auto name = getTargetName(target);
+ auto currValueType = getTargetType(target);
+ if (names.empty()) {
+ valueType = currValueType;
+ } else {
+ if (valueType != currValueType) {
+ continue; // all values must be the same
+ }
+ }
+ names.push_back(name);
+ }
+ if (names.size() < 2) {
+ // we failed to find enough
+ return make(type);
+ }
+ auto default_ = names.back();
+ names.pop_back();
+ auto temp1 = make(i32), temp2 = isConcreteWasmType(valueType) ? make(valueType) : nullptr;
+ return builder.makeSwitch(names, default_, temp1, temp2);
+ }
+
+ Expression* makeDrop(WasmType type) {
+ return builder.makeDrop(make(type == unreachable ? type : getConcreteType()));
+ }
+
+ Expression* makeReturn(WasmType type) {
+ return builder.makeReturn(isConcreteWasmType(func->result) ? make(func->result) : nullptr);
+ }
+
+ Expression* makeNop(WasmType type) {
+ assert(type == none);
+ return builder.makeNop();
+ }
+
+ Expression* makeUnreachable(WasmType type) {
+ assert(type == unreachable);
+ return builder.makeUnreachable();
+ }
+
+ // special getters
+
+ WasmType getType() {
+ switch (upTo(6)) {
+ case 0: return i32;
+ case 1: return i64;
+ case 2: return f32;
+ case 3: return f64;
+ case 4: return none;
+ case 5: return unreachable;
+ }
+ WASM_UNREACHABLE();
+ }
+
+ WasmType getReachableType() {
+ switch (upTo(5)) {
+ case 0: return i32;
+ case 1: return i64;
+ case 2: return f32;
+ case 3: return f64;
+ case 4: return none;
+ }
+ WASM_UNREACHABLE();
+ }
+
+ WasmType getConcreteType() {
+ switch (upTo(4)) {
+ case 0: return i32;
+ case 1: return i64;
+ case 2: return f32;
+ case 3: return f64;
+ }
+ WASM_UNREACHABLE();
+ }
+
+ // statistical distributions
+
+ // 0 to the limit, logarithmic scale
+ Index logify(Index x) {
+ return std::floor(std::log(std::max(Index(1) + x, Index(1))));
+ }
+
+ // one of the integer values in [0, x)
+ // this isn't a perfectly uniform distribution, but it's fast
+ // and reasonable
+ Index upTo(Index x) {
+ if (x == 0) return 0;
+ Index raw;
+ if (x <= 255) {
+ raw = get();
+ } else if (x <= 65535) {
+ raw = get16();
+ } else {
+ raw = get32();
+ }
+ auto ret = raw % x;
+ // use extra bits as "noise" for later
+ xorFactor += raw / x;
+ return ret;
+ }
+
+ bool oneIn(Index x) {
+ return upTo(x) == 0;
+ }
+
+ // apply upTo twice, generating a skewed distribution towards
+ // low values
+ Index upToSquared(Index x) {
+ return upTo(upTo(x));
+ }
+
+ // pick from a vector
+ template<typename T>
+ const T& vectorPick(const std::vector<T>& vec) {
+ // TODO: get32?
+ assert(!vec.empty());
+ auto index = upTo(vec.size());
+ return vec[index];
+ }
+
+ // pick from a fixed list
+ template<typename T, typename... Args>
+ T pick(T first, Args... args) {
+ auto num = sizeof...(Args) + 1;
+ auto temp = upTo(num);
+ return pickGivenNum<T>(temp, first, args...);
+ }
+
+ template<typename T>
+ T pickGivenNum(size_t num, T first) {
+ assert(num == 0);
+ return first;
+ }
+
+ template<typename T, typename... Args>
+ T pickGivenNum(size_t num, T first, Args... args) {
+ if (num == 0) return first;
+ return pickGivenNum<T>(num - 1, args...);
+ }
+
+ // utilities
+
+ Name getTargetName(Expression* target) {
+ if (auto* block = target->dynCast<Block>()) {
+ return block->name;
+ } else if (auto* loop = target->dynCast<Loop>()) {
+ return loop->name;
+ }
+ WASM_UNREACHABLE();
+ }
+
+ WasmType getTargetType(Expression* target) {
+ if (auto* block = target->dynCast<Block>()) {
+ return block->type;
+ } else if (target->is<Loop>()) {
+ return none;
+ }
+ WASM_UNREACHABLE();
+ }
+};
+
+} // namespace wasm
+
+// XXX Switch class has a condition?! is it real? should the node type be the value type if it exists?!
diff --git a/src/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp
index 184dbae1b..c34331366 100644
--- a/src/tools/wasm-opt.cpp
+++ b/src/tools/wasm-opt.cpp
@@ -29,65 +29,16 @@
#include "wasm-validator.h"
#include "wasm-io.h"
#include "wasm-interpreter.h"
+#include "wasm-binary.h"
#include "shell-interface.h"
#include "optimization-options.h"
+#include "execution-results.h"
+#include "translate-to-fuzz.h"
+#include "js-wrapper.h"
+#include "spec-wrapper.h"
using namespace wasm;
-// gets execution results from a wasm module. this is useful for fuzzing
-//
-// we can only get results when there are no imports. we then call each method
-// that has a result, with some values
-struct ExecutionResults {
- std::map<Name, Literal> results;
-
- void get(Module& wasm) {
- if (wasm.imports.size() > 0) {
- std::cout << "[fuzz-exec] imports, so quitting\n";
- return;
- }
- for (auto& func : wasm.functions) {
- if (func->result != none) {
- // this is good
- results[func->name] = run(func.get(), wasm);
- }
- }
- std::cout << "[fuzz-exec] " << results.size() << " results noted\n";
- }
-
- bool operator==(ExecutionResults& other) {
- for (auto& iter : results) {
- auto name = iter.first;
- if (other.results.find(name) != other.results.end()) {
- if (results[name] != other.results[name]) {
- return false;
- }
- }
- }
- return true;
- }
-
- bool operator!=(ExecutionResults& other) {
- return !((*this) == other);
- }
-
- Literal run(Function* func, Module& wasm) {
- ShellExternalInterface interface;
- try {
- ModuleInstance instance(wasm, &interface);
- LiteralList arguments;
- for (WasmType param : func->params) {
- // zeros in arguments TODO: more?
- arguments.push_back(Literal(param));
- }
- return instance.callFunction(func->name, arguments);
- } catch (const TrapException&) {
- // may throw in instance creation (init of offsets) or call itself
- return Literal();
- }
- }
-};
-
//
// main
//
@@ -98,8 +49,12 @@ int main(int argc, const char* argv[]) {
bool emitBinary = true;
bool debugInfo = false;
bool fuzzExec = false;
+ bool fuzzBinary = false;
+ bool translateToFuzz = false;
+ std::string emitJSWrapper;
+ std::string emitSpecWrapper;
- OptimizationOptions options("wasm-opt", "Optimize .wast files");
+ OptimizationOptions options("wasm-opt", "Read, write, and optimize files");
options
.add("--output", "-o", "Output file (stdout if not specified)",
Options::Arguments::One,
@@ -116,21 +71,31 @@ int main(int argc, const char* argv[]) {
.add("--fuzz-exec", "-fe", "Execute functions before and after optimization, helping fuzzing find bugs",
Options::Arguments::Zero,
[&](Options *o, const std::string &arguments) { fuzzExec = true; })
+ .add("--fuzz-binary", "-fb", "Convert to binary and back after optimizations and before fuzz-exec, helping fuzzing find binary format bugs",
+ Options::Arguments::Zero,
+ [&](Options *o, const std::string &arguments) { fuzzBinary = true; })
+ .add("--translate-to-fuzz", "-ttf", "Translate the input into a valid wasm module *somehow*, useful for fuzzing",
+ Options::Arguments::Zero,
+ [&](Options *o, const std::string &arguments) { translateToFuzz = true; })
+ .add("--emit-js-wrapper", "-ejw", "Emit a JavaScript wrapper file that can run the wasm with some test values, useful for fuzzing",
+ Options::Arguments::One,
+ [&](Options *o, const std::string &arguments) { emitJSWrapper = arguments; })
+ .add("--emit-spec-wrapper", "-esw", "Emit a wasm spec interpreter wrapper file that can run the wasm with some test values, useful for fuzzing",
+ Options::Arguments::One,
+ [&](Options *o, const std::string &arguments) { emitSpecWrapper = arguments; })
.add_positional("INFILE", Options::Arguments::One,
[](Options* o, const std::string& argument) {
o->extra["infile"] = argument;
});
options.parse(argc, argv);
- auto input(read_file<std::string>(options.extra["infile"], Flags::Text, options.debug ? Flags::Debug : Flags::Release));
-
Module wasm;
- {
- if (options.debug) std::cerr << "reading...\n";
+ if (options.debug) std::cerr << "reading...\n";
+
+ if (!translateToFuzz) {
ModuleReader reader;
reader.setDebug(options.debug);
-
try {
reader.read(options.extra["infile"], wasm);
} catch (ParseException& p) {
@@ -139,10 +104,18 @@ int main(int argc, const char* argv[]) {
} catch (std::bad_alloc& b) {
Fatal() << "error in building module, std::bad_alloc (possibly invalid request for silly amounts of memory)";
}
- }
- if (!WasmValidator().validate(wasm)) {
- Fatal() << "error in validating input";
+ if (!WasmValidator().validate(wasm)) {
+ Fatal() << "error in validating input";
+ }
+ } else {
+ // translate-to-fuzz
+ TranslateToFuzzReader reader(wasm);
+ reader.read(options.extra["infile"]);
+ if (!WasmValidator().validate(wasm)) {
+ std::cerr << "translate-to-fuzz must always generate a valid module";
+ abort();
+ }
}
ExecutionResults results;
@@ -158,12 +131,21 @@ int main(int argc, const char* argv[]) {
}
if (fuzzExec) {
- ExecutionResults optimizedResults;
- optimizedResults.get(wasm);
- if (optimizedResults != results) {
- Fatal() << "[fuzz-exec] optimization passes changed execution results";
+ auto* compare = &wasm;
+ Module second;
+ if (fuzzBinary) {
+ compare = &second;
+ BufferWithRandomAccess buffer(false);
+ // write the binary
+ WasmBinaryWriter writer(&wasm, buffer, false);
+ writer.write();
+ // read the binary
+ auto input = buffer.getAsChars();
+ WasmBinaryBuilder parser(second, input, false);
+ parser.read();
+ assert(WasmValidator().validate(second));
}
- std::cout << "[fuzz-exec] results match\n";
+ results.check(*compare);
}
if (options.extra.count("output") > 0) {
@@ -174,4 +156,18 @@ int main(int argc, const char* argv[]) {
writer.setDebugInfo(debugInfo);
writer.write(wasm, options.extra["output"]);
}
+
+ if (emitJSWrapper.size() > 0) {
+ std::ofstream outfile;
+ outfile.open(emitJSWrapper, std::ofstream::out);
+ outfile << generateJSWrapper(wasm);
+ outfile.close();
+ }
+
+ if (emitSpecWrapper.size() > 0) {
+ std::ofstream outfile;
+ outfile.open(emitSpecWrapper, std::ofstream::out);
+ outfile << generateSpecWrapper(wasm);
+ outfile.close();
+ }
}
diff --git a/src/tools/wasm-shell.cpp b/src/tools/wasm-shell.cpp
index e5736a951..b7fde3af6 100644
--- a/src/tools/wasm-shell.cpp
+++ b/src/tools/wasm-shell.cpp
@@ -22,6 +22,7 @@
#include <memory>
+#include "execution-results.h"
#include "pass.h"
#include "shell-interface.h"
#include "support/command-line.h"
@@ -86,19 +87,6 @@ struct Operation {
}
};
-static void verify_result(Literal a, Literal b) {
- if (a == b) return;
- // accept equal nans if equal in all bits
- assert(a.type == b.type);
- if (a.type == f32) {
- assert(a.reinterpreti32() == b.reinterpreti32());
- } else if (a.type == f64) {
- assert(a.reinterpreti64() == b.reinterpreti64());
- } else {
- abort();
- }
-}
-
static void run_asserts(Name moduleName, size_t* i, bool* checked, Module* wasm,
Element* root,
SExpressionWasmBuilder* builder,
@@ -213,11 +201,17 @@ static void run_asserts(Name moduleName, size_t* i, bool* checked, Module* wasm,
->dynCast<Const>()
->value;
std::cerr << "seen " << result << ", expected " << expected << '\n';
- verify_result(expected, result);
+ if (!areBitwiseEqual(expected, result)) {
+ std::cout << "unexpected, should be identical\n";
+ abort();
+ }
} else {
Literal expected;
std::cerr << "seen " << result << ", expected " << expected << '\n';
- verify_result(expected, result);
+ if (!areBitwiseEqual(expected, result)) {
+ std::cout << "unexpected, should be identical\n";
+ abort();
+ }
}
}
if (id == ASSERT_TRAP) assert(trapped);
diff --git a/src/wasm-binary.h b/src/wasm-binary.h
index 9c468b0ea..daa1dc4fa 100644
--- a/src/wasm-binary.h
+++ b/src/wasm-binary.h
@@ -267,6 +267,13 @@ public:
void writeTo(T& o) {
for (auto c : *this) o << c;
}
+
+ std::vector<char> getAsChars() {
+ std::vector<char> ret;
+ ret.resize(size());
+ std::copy(begin(), end(), ret.begin());
+ return ret;
+ }
};
namespace BinaryConsts {
diff --git a/test/passes/emit-js-wrapper=a.js.txt b/test/passes/emit-js-wrapper=a.js.txt
new file mode 100644
index 000000000..0432ccdc7
--- /dev/null
+++ b/test/passes/emit-js-wrapper=a.js.txt
@@ -0,0 +1,42 @@
+(module
+ (type $0 (func (param i32 i32) (result i32)))
+ (type $1 (func (param i32)))
+ (type $2 (func (param i32 i64 f32 f64)))
+ (type $3 (func (param i32 f32 f64)))
+ (type $4 (func (param i32 f32 f64) (result i64)))
+ (memory $0 256 256)
+ (export "add" (func $add))
+ (export "no_return" (func $no-return))
+ (export "types" (func $types))
+ (export "types2" (func $types2))
+ (export "types3" (func $types3))
+ (func $add (type $0) (param $x i32) (param $y i32) (result i32)
+ (i32.add
+ (get_local $x)
+ (get_local $y)
+ )
+ )
+ (func $unexported (type $0) (param $x i32) (param $y i32) (result i32)
+ (i32.add
+ (get_local $x)
+ (get_local $y)
+ )
+ )
+ (func $no-return (type $1) (param $x i32)
+ (drop
+ (i32.add
+ (get_local $x)
+ (get_local $x)
+ )
+ )
+ )
+ (func $types (type $2) (param $x i32) (param $y i64) (param $z f32) (param $w f64)
+ (nop)
+ )
+ (func $types2 (type $3) (param $x i32) (param $z f32) (param $w f64)
+ (nop)
+ )
+ (func $types3 (type $4) (param $x i32) (param $z f32) (param $w f64) (result i64)
+ (i64.const 1)
+ )
+)
diff --git a/test/passes/emit-js-wrapper=a.js.wast b/test/passes/emit-js-wrapper=a.js.wast
new file mode 100644
index 000000000..4ea764b61
--- /dev/null
+++ b/test/passes/emit-js-wrapper=a.js.wast
@@ -0,0 +1,37 @@
+(module
+ (memory $0 256 256)
+ (export "add" (func $add))
+ (export "no_return" (func $no-return)) ;; note exported name is slightly different
+ (export "types" (func $types))
+ (export "types2" (func $types2))
+ (export "types3" (func $types3))
+ (func $add (param $x i32) (param $y i32) (result i32)
+ (i32.add
+ (get_local $x)
+ (get_local $y)
+ )
+ )
+ (func $unexported (param $x i32) (param $y i32) (result i32)
+ (i32.add
+ (get_local $x)
+ (get_local $y)
+ )
+ )
+ (func $no-return (param $x i32)
+ (drop
+ (i32.add
+ (get_local $x)
+ (get_local $x)
+ )
+ )
+ )
+ (func $types (param $x i32) (param $y i64) (param $z f32) (param $w f64)
+ (nop)
+ )
+ (func $types2 (param $x i32) (param $z f32) (param $w f64)
+ (nop)
+ )
+ (func $types3 (param $x i32) (param $z f32) (param $w f64) (result i64)
+ (i64.const 1)
+ )
+)
diff --git a/test/passes/emit-js-wrapper=a.js.wast.js b/test/passes/emit-js-wrapper=a.js.wast.js
new file mode 100644
index 000000000..778f43827
--- /dev/null
+++ b/test/passes/emit-js-wrapper=a.js.wast.js
@@ -0,0 +1,44 @@
+if (typeof console === 'undefined') {
+ console = { log: print };
+}
+var binary;
+if (typeof process === 'object' && typeof require === 'function' /* node.js detection */) {
+ var args = process.argv.slice(2);
+ binary = require('fs').readFileSync(args[0]);
+ if (!binary.buffer) binary = new Uint8Array(binary);
+} else {
+ var args;
+ if (typeof scriptArgs != 'undefined') {
+ args = scriptArgs;
+ } else if (typeof arguments != 'undefined') {
+ args = arguments;
+ }
+ if (typeof readbuffer === 'function') {
+ binary = new Uint8Array(readbuffer(args[0]));
+ } else {
+ binary = read(args[0], 'binary');
+ }
+}
+var instance = new WebAssembly.Instance(new WebAssembly.Module(binary), {});
+if (instance.exports.hangLimitInitializer) instance.exports.hangLimitInitializer();
+try {
+ console.log('calling: add');
+ console.log(' result: ' + instance.exports.add(0, 0));
+} catch (e) {
+ console.log(' exception: ' + e);
+}
+if (instance.exports.hangLimitInitializer) instance.exports.hangLimitInitializer();
+try {
+ console.log('calling: no_return');
+instance.exports.no_return(0);
+} catch (e) {
+ console.log(' exception: ' + e);
+}
+if (instance.exports.hangLimitInitializer) instance.exports.hangLimitInitializer();
+try {
+ console.log('calling: types2');
+instance.exports.types2(0, 0, 0);
+} catch (e) {
+ console.log(' exception: ' + e);
+}
+console.log('done.')
diff --git a/test/passes/emit-spec-wrapper=a.wat.txt b/test/passes/emit-spec-wrapper=a.wat.txt
new file mode 100644
index 000000000..0432ccdc7
--- /dev/null
+++ b/test/passes/emit-spec-wrapper=a.wat.txt
@@ -0,0 +1,42 @@
+(module
+ (type $0 (func (param i32 i32) (result i32)))
+ (type $1 (func (param i32)))
+ (type $2 (func (param i32 i64 f32 f64)))
+ (type $3 (func (param i32 f32 f64)))
+ (type $4 (func (param i32 f32 f64) (result i64)))
+ (memory $0 256 256)
+ (export "add" (func $add))
+ (export "no_return" (func $no-return))
+ (export "types" (func $types))
+ (export "types2" (func $types2))
+ (export "types3" (func $types3))
+ (func $add (type $0) (param $x i32) (param $y i32) (result i32)
+ (i32.add
+ (get_local $x)
+ (get_local $y)
+ )
+ )
+ (func $unexported (type $0) (param $x i32) (param $y i32) (result i32)
+ (i32.add
+ (get_local $x)
+ (get_local $y)
+ )
+ )
+ (func $no-return (type $1) (param $x i32)
+ (drop
+ (i32.add
+ (get_local $x)
+ (get_local $x)
+ )
+ )
+ )
+ (func $types (type $2) (param $x i32) (param $y i64) (param $z f32) (param $w f64)
+ (nop)
+ )
+ (func $types2 (type $3) (param $x i32) (param $z f32) (param $w f64)
+ (nop)
+ )
+ (func $types3 (type $4) (param $x i32) (param $z f32) (param $w f64) (result i64)
+ (i64.const 1)
+ )
+)
diff --git a/test/passes/emit-spec-wrapper=a.wat.wast b/test/passes/emit-spec-wrapper=a.wat.wast
new file mode 100644
index 000000000..4ea764b61
--- /dev/null
+++ b/test/passes/emit-spec-wrapper=a.wat.wast
@@ -0,0 +1,37 @@
+(module
+ (memory $0 256 256)
+ (export "add" (func $add))
+ (export "no_return" (func $no-return)) ;; note exported name is slightly different
+ (export "types" (func $types))
+ (export "types2" (func $types2))
+ (export "types3" (func $types3))
+ (func $add (param $x i32) (param $y i32) (result i32)
+ (i32.add
+ (get_local $x)
+ (get_local $y)
+ )
+ )
+ (func $unexported (param $x i32) (param $y i32) (result i32)
+ (i32.add
+ (get_local $x)
+ (get_local $y)
+ )
+ )
+ (func $no-return (param $x i32)
+ (drop
+ (i32.add
+ (get_local $x)
+ (get_local $x)
+ )
+ )
+ )
+ (func $types (param $x i32) (param $y i64) (param $z f32) (param $w f64)
+ (nop)
+ )
+ (func $types2 (param $x i32) (param $z f32) (param $w f64)
+ (nop)
+ )
+ (func $types3 (param $x i32) (param $z f32) (param $w f64) (result i64)
+ (i64.const 1)
+ )
+)
diff --git a/test/passes/emit-spec-wrapper=a.wat.wast.wat b/test/passes/emit-spec-wrapper=a.wat.wast.wat
new file mode 100644
index 000000000..20cdac9b2
--- /dev/null
+++ b/test/passes/emit-spec-wrapper=a.wat.wast.wat
@@ -0,0 +1 @@
+(invoke "hangLimitInitializer") (invoke "add" (i32.const 0) (i32.const 0) ) (invoke "hangLimitInitializer") (invoke "no_return" (i32.const 0) ) (invoke "hangLimitInitializer") (invoke "types" (i32.const 0) (i64.const 0) (f32.const 0) (f64.const 0) ) (invoke "hangLimitInitializer") (invoke "types2" (i32.const 0) (f32.const 0) (f64.const 0) ) (invoke "hangLimitInitializer") (invoke "types3" (i32.const 0) (f32.const 0) (f64.const 0) ) \ No newline at end of file
diff --git a/test/passes/fuzz-exec_O.txt b/test/passes/fuzz-exec_O.txt
index f5f8583a3..bb6796494 100644
--- a/test/passes/fuzz-exec_O.txt
+++ b/test/passes/fuzz-exec_O.txt
@@ -1,3 +1,5 @@
+[fuzz-exec] note result: func_0 => (none.const ?)
+[fuzz-exec] note result: func_1 => (none.const ?)
[fuzz-exec] 2 results noted
(module
(type $0 (func (result i64)))
@@ -21,5 +23,9 @@
)
)
)
+[fuzz-exec] note result: func_0 => (none.const ?)
+[fuzz-exec] note result: func_1 => (none.const ?)
[fuzz-exec] 2 results noted
-[fuzz-exec] results match
+[fuzz-exec] comparing $func_0
+[fuzz-exec] comparing $func_1
+[fuzz-exec] 2 results match
diff --git a/test/passes/translate-to-fuzz.txt b/test/passes/translate-to-fuzz.txt
new file mode 100644
index 000000000..daab102ad
--- /dev/null
+++ b/test/passes/translate-to-fuzz.txt
@@ -0,0 +1,1126 @@
+(module
+ (global $hangLimit (mut i32) (i32.const 25))
+ (memory $0 1 1)
+ (export "func_0" (func $func_0))
+ (export "func_1" (func $func_1))
+ (export "func_4" (func $func_4))
+ (export "func_5" (func $func_5))
+ (export "hangLimitInitializer" (func $hangLimitInitializer))
+ (func $func_0 (result i32)
+ (local $0 f32)
+ (local $1 i64)
+ (local $2 f32)
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return
+ (i32.const -118)
+ )
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (block $label$0 (result i32)
+ (if
+ (loop $label$1 (result i32)
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return
+ (i32.const 127)
+ )
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (select
+ (i64.ne
+ (if (result i64)
+ (i32.eqz
+ (loop $label$5 (result i32)
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return
+ (i32.const 190)
+ )
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (block $label$6 (result i32)
+ (i32.const 104)
+ )
+ )
+ )
+ (block $label$7 (result i64)
+ (return
+ (i32.const 387928603)
+ )
+ )
+ (block $label$8 (result i64)
+ (br $label$1)
+ )
+ )
+ (i64.const -1)
+ )
+ (br_if $label$0
+ (br_if $label$0
+ (i32.popcnt
+ (i32.const 75)
+ )
+ (i32.eqz
+ (select
+ (if (result i32)
+ (loop $label$9 (result i32)
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return
+ (i32.const 828535924)
+ )
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (block $label$10 (result i32)
+ (block $label$11
+ (set_local $1
+ (get_local $1)
+ )
+ )
+ (br $label$1)
+ )
+ )
+ (block $label$12 (result i32)
+ (i32.const 1767927669)
+ )
+ (block $label$13 (result i32)
+ (if (result i32)
+ (i32.const -32768)
+ (i32.const 127)
+ (i32.const 269491029)
+ )
+ )
+ )
+ (if (result i32)
+ (i32.eqz
+ (loop $label$14 (result i32)
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return
+ (i32.const -71)
+ )
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (block $label$15 (result i32)
+ (br $label$1)
+ )
+ )
+ )
+ (block $label$16 (result i32)
+ (br $label$1)
+ )
+ (i32.ge_u
+ (br_if $label$0
+ (i32.const -98)
+ (i32.const 65419)
+ )
+ (i32.load offset=22 align=1
+ (i32.and
+ (i32.const 0)
+ (i32.const 31)
+ )
+ )
+ )
+ )
+ (br_if $label$0
+ (i32.load offset=22
+ (i32.and
+ (i32.const -1)
+ (i32.const 31)
+ )
+ )
+ (i32.const 1561467741)
+ )
+ )
+ )
+ )
+ (i32.const 32767)
+ )
+ (br_if $label$0
+ (loop $label$2 (result i32)
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return
+ (i32.const 469762305)
+ )
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (block $label$3 (result i32)
+ (i32.popcnt
+ (select
+ (br_if $label$0
+ (i32.const 573448231)
+ (select
+ (i32.const 1917992040)
+ (i32.const -25)
+ (i32.const -105)
+ )
+ )
+ (i32.const 65535)
+ (br_if $label$3
+ (i32.lt_u
+ (select
+ (select
+ (br_if $label$3
+ (i32.const 188)
+ (i32.const 1)
+ )
+ (loop $label$4 (result i32)
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return
+ (i32.const 255)
+ )
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (i32.const 65448)
+ )
+ (i32.const 31021)
+ )
+ (i32.const 1562845246)
+ (i32.const 1)
+ )
+ (i32.const 2147483647)
+ )
+ (i32.const 127)
+ )
+ )
+ )
+ )
+ )
+ (i32.eqz
+ (i32.const -1)
+ )
+ )
+ )
+ )
+ (block $label$17
+ (if
+ (i32.eqz
+ (select
+ (i64.gt_s
+ (tee_local $1
+ (if (result i64)
+ (select
+ (select
+ (br_if $label$0
+ (i32.const 4)
+ (i32.const 1)
+ )
+ (br_if $label$0
+ (i32.const -120)
+ (i32.const 26)
+ )
+ (i32.const 32767)
+ )
+ (select
+ (i32.const 909534506)
+ (i32.const 6424)
+ (select
+ (i32.const -2147483648)
+ (i32.const -1)
+ (i32.const 32767)
+ )
+ )
+ (i32.const 1)
+ )
+ (block $label$19 (result i64)
+ (i64.extend_u/i32
+ (i32.const 255)
+ )
+ )
+ (block $label$20 (result i64)
+ (if (result i64)
+ (block $label$21 (result i32)
+ (block $label$22 (result i32)
+ (i32.const -42)
+ )
+ )
+ (i64.div_s
+ (i64.const -9223372036854775808)
+ (loop $label$23 (result i64)
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return
+ (i32.const -15)
+ )
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (get_local $1)
+ )
+ )
+ (if (result i64)
+ (select
+ (i32.const -20)
+ (i32.const 84)
+ (i32.const 706834235)
+ )
+ (block $label$24 (result i64)
+ (br $label$17)
+ )
+ (block $label$25 (result i64)
+ (return
+ (i32.const -44)
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ (i64.popcnt
+ (block $label$26 (result i64)
+ (br $label$17)
+ )
+ )
+ )
+ (i32.reinterpret/f32
+ (block $label$27 (result f32)
+ (return
+ (i32.const 1112421928)
+ )
+ )
+ )
+ (br_if $label$0
+ (i32.const 18)
+ (block $label$18 (result i32)
+ (br $label$17)
+ )
+ )
+ )
+ )
+ (block $label$28
+ (drop
+ (f32.const -2147483648)
+ )
+ )
+ (block $label$31
+ (drop
+ (block $label$32 (result f64)
+ (f64.const 3777575208023997706127184e35)
+ )
+ )
+ (nop)
+ )
+ )
+ )
+ (block $label$33
+ (loop $label$34
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return
+ (i32.const 73)
+ )
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (block $label$35
+ (loop $label$36
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return
+ (i32.const 155802894)
+ )
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (br_if $label$36
+ (i32.eqz
+ (block $label$37 (result i32)
+ (i32.const -1)
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ (set_local $1
+ (i64.trunc_s/f32
+ (f32.const 18446744073709551615)
+ )
+ )
+ (return
+ (i32.const -57)
+ )
+ )
+ )
+ (func $func_1 (param $0 i64) (param $1 i32) (result i64)
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return
+ (i64.const -40)
+ )
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (call $func_1
+ (tee_local $0
+ (i64.shl
+ (if (result i64)
+ (i32.eqz
+ (tee_local $1
+ (get_local $1)
+ )
+ )
+ (block $label$0 (result i64)
+ (return
+ (i64.const 18469)
+ )
+ )
+ (block $label$1 (result i64)
+ (return
+ (i64.const -1)
+ )
+ )
+ )
+ (tee_local $0
+ (tee_local $0
+ (block $label$2 (result i64)
+ (block $label$3 (result i64)
+ (return
+ (i64.const 127)
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ (get_local $1)
+ )
+ )
+ (func $func_2
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return)
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (if
+ (i32.eqz
+ (if (result i32)
+ (i32.const 5662)
+ (block $label$0 (result i32)
+ (return)
+ )
+ (block $label$1 (result i32)
+ (return)
+ )
+ )
+ )
+ (nop)
+ (if
+ (i32.const 508363275)
+ (block $label$2
+ (if
+ (i32.const -1)
+ (block $label$3
+ (nop)
+ (nop)
+ )
+ (block $label$4
+ (loop $label$5
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return)
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (block $label$6
+ (if
+ (i32.eqz
+ (i64.eqz
+ (block $label$7 (result i64)
+ (block $label$8 (result i64)
+ (loop $label$9 (result i64)
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return)
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (i64.const 5787412799727686208)
+ )
+ )
+ )
+ )
+ )
+ (block $label$10
+ (loop $label$11
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return)
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (block $label$12
+ (f64.store offset=4
+ (i32.and
+ (i32.const 48)
+ (i32.const 31)
+ )
+ (f64.const -nan:0xfffffffffffae)
+ )
+ )
+ )
+ )
+ (block $label$13
+ (block $label$14
+ (loop $label$15
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return)
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (block $label$16
+ (nop)
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ (block $label$17
+ (br_if $label$17
+ (i32.eqz
+ (if (result i32)
+ (if (result i32)
+ (select
+ (block $label$19 (result i32)
+ (i32.store offset=4 align=1
+ (i32.and
+ (block $label$20 (result i32)
+ (i32.const 19534)
+ )
+ (i32.const 31)
+ )
+ (br_if $label$19
+ (i32.const 488444703)
+ (i32.eqz
+ (i32.const -128)
+ )
+ )
+ )
+ (select
+ (i32.const -97)
+ (i32.const -2147483648)
+ (i32.const -125)
+ )
+ )
+ (if (result i32)
+ (block $label$21 (result i32)
+ (br $label$17)
+ )
+ (block $label$22 (result i32)
+ (i32.load16_u offset=22
+ (select
+ (loop $label$23 (result i32)
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return)
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (i32.const 15448)
+ )
+ (i32.rem_u
+ (i32.const 1026110509)
+ (i32.const 1)
+ )
+ (i32.const 97)
+ )
+ )
+ )
+ (block $label$24 (result i32)
+ (i32.trunc_u/f64
+ (call $deNan64
+ (select
+ (f64.const 6.013471909394168e-154)
+ (f64.const -nan:0xfffffffffffbb)
+ (call $func_0)
+ )
+ )
+ )
+ )
+ )
+ (block $label$18 (result i32)
+ (br_if $label$18
+ (i32.const -76)
+ (i32.eqz
+ (i32.clz
+ (i64.le_u
+ (i64.const 7)
+ (i64.const -30)
+ )
+ )
+ )
+ )
+ )
+ )
+ (i32.const -10)
+ (block $label$25 (result i32)
+ (loop $label$26 (result i32)
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return)
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (br_if $label$25
+ (i32.const 32767)
+ (i32.eqz
+ (i32.load offset=4 align=1
+ (i32.and
+ (i32.const 32767)
+ (i32.const 31)
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ (block $label$27 (result i32)
+ (br $label$17)
+ )
+ (block $label$28 (result i32)
+ (i32.const -124)
+ )
+ )
+ )
+ )
+ (block $label$29
+ (loop $label$30
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return)
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (loop $label$31
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return)
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (block $label$32
+ (loop $label$33
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return)
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (block $label$34
+ (block $label$35
+ (drop
+ (f64.load offset=22
+ (i32.and
+ (i32.const -31)
+ (i32.const 31)
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ (block $label$36
+ (br_if $label$17
+ (i32.eqz
+ (i32.const 1)
+ )
+ )
+ (block $label$37
+ (block $label$38
+ (block $label$39
+ (block $label$40
+ (block $label$41
+ (nop)
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ (nop)
+ )
+ )
+ )
+ )
+ (func $func_3 (result f32)
+ (local $0 i32)
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return
+ (f32.const 3595217802362880)
+ )
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (f32.const 1.1754943508222875e-38)
+ )
+ (func $func_4 (result i32)
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return
+ (i32.const 11)
+ )
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (i32.const 71)
+ )
+ (func $func_5 (param $0 i64) (param $1 i32) (param $2 i32) (result i64)
+ (local $3 f32)
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return
+ (i64.const -77)
+ )
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (call $func_1
+ (if (result i64)
+ (if (result i32)
+ (i32.ctz
+ (block $label$0 (result i32)
+ (return
+ (i64.const -94)
+ )
+ )
+ )
+ (block $label$1 (result i32)
+ (return
+ (i64.const -71)
+ )
+ )
+ (block $label$2 (result i32)
+ (call $func_4)
+ )
+ )
+ (tee_local $0
+ (select
+ (select
+ (loop $label$7 (result i64)
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return
+ (i64.const -9223372036854775808)
+ )
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (block $label$8 (result i64)
+ (return
+ (i64.const 4627)
+ )
+ )
+ )
+ (i64.sub
+ (i64.const 30983)
+ (if (result i64)
+ (i32.eqz
+ (i32.const -128)
+ )
+ (block $label$9 (result i64)
+ (i64.rem_s
+ (i64.load offset=3 align=4
+ (i32.and
+ (f32.le
+ (f32.load offset=4
+ (i32.and
+ (i32.const -126)
+ (i32.const 31)
+ )
+ )
+ (tee_local $3
+ (call $deNan32
+ (select
+ (call $deNan32
+ (f32.convert_s/i32
+ (get_local $1)
+ )
+ )
+ (f32.const 7.458154094308611e-10)
+ (get_local $1)
+ )
+ )
+ )
+ )
+ (i32.const 31)
+ )
+ )
+ (block $label$10 (result i64)
+ (return
+ (i64.const -72)
+ )
+ )
+ )
+ )
+ (block $label$11 (result i64)
+ (return
+ (i64.const 106)
+ )
+ )
+ )
+ )
+ (i32.const 0)
+ )
+ (loop $label$12 (result i64)
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return
+ (i64.const 65509)
+ )
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (block $label$13 (result i64)
+ (return
+ (i64.const 127)
+ )
+ )
+ )
+ (select
+ (f64.ge
+ (f64.const 1.1754943508222875e-38)
+ (if (result f64)
+ (f32.gt
+ (get_local $3)
+ (call $deNan32
+ (f32.sub
+ (get_local $3)
+ (get_local $3)
+ )
+ )
+ )
+ (block $label$3 (result f64)
+ (return
+ (i64.const 65457)
+ )
+ )
+ (block $label$4 (result f64)
+ (return
+ (i64.const 1011356149276677899)
+ )
+ )
+ )
+ )
+ (i32.reinterpret/f32
+ (f32.load offset=2 align=2
+ (i32.and
+ (select
+ (get_local $2)
+ (tee_local $2
+ (get_local $2)
+ )
+ (i32.load8_u offset=2
+ (i32.and
+ (loop $label$5 (result i32)
+ (block
+ (if
+ (i32.eqz
+ (get_global $hangLimit)
+ )
+ (return
+ (i64.const 0)
+ )
+ )
+ (set_global $hangLimit
+ (i32.sub
+ (get_global $hangLimit)
+ (i32.const 1)
+ )
+ )
+ )
+ (block $label$6 (result i32)
+ (return
+ (i64.const -65)
+ )
+ )
+ )
+ (i32.const 31)
+ )
+ )
+ )
+ (i32.const 31)
+ )
+ )
+ )
+ (i32.const 10853)
+ )
+ )
+ )
+ (block $label$14 (result i64)
+ (block $label$15
+ (nop)
+ )
+ (return
+ (i64.const -89)
+ )
+ )
+ )
+ (block $label$16 (result i32)
+ (block $label$17
+ (nop)
+ )
+ (nop)
+ (get_local $1)
+ )
+ )
+ )
+ (func $hangLimitInitializer
+ (set_global $hangLimit
+ (i32.const 25)
+ )
+ )
+ (func $deNan32 (param $0 f32) (result f32)
+ (if (result f32)
+ (f32.eq
+ (get_local $0)
+ (get_local $0)
+ )
+ (get_local $0)
+ (f32.const 0)
+ )
+ )
+ (func $deNan64 (param $0 f64) (result f64)
+ (if (result f64)
+ (f64.eq
+ (get_local $0)
+ (get_local $0)
+ )
+ (get_local $0)
+ (f64.const 0)
+ )
+ )
+)
diff --git a/test/passes/translate-to-fuzz.wast b/test/passes/translate-to-fuzz.wast
new file mode 100644
index 000000000..cbf25fde1
--- /dev/null
+++ b/test/passes/translate-to-fuzz.wast
@@ -0,0 +1,99 @@
+(module # fake module here, for test harness, but it's really not needed
+..
+any
+3INPUT
+h e r e
+*will*
+d0
+0.753538467597066
+2.2339337309978227
+.................
+lorem ipsum whatever
+
+through the darkness of future past
+the magician longs to see
+one [chants|chance] out between two worlds
+fire, walk with me
+
+
+h e r e
+*will*
+d0
+0.753538467597066
+2.2339337309978227
+.................
+lorem ipsum whatever
+
+through the darkness of future past
+the magician longs to see
+one [chants|chance] out between two worlds
+fire, walk with me
+
+
+(&!*^@$*&@!^*&@#^$*&@#$*&@#$^*&@^#$)(&)(!&$(*&^@&#*$
+
+MOAR testing09237861235980723894570389yfskdjhgfm13jo847rtnjcsjjdhfgnc12o387456vb1p98364vlaisutfvlKUYASDOV*&Q@$%VOUAYFROVLUKSYDFP(*A^*&%DFASF________
+<>?><?><?><>?>>?<>??><A?S>D<?A>S<D?><G?S><DG?S><G
+
+2.2339337309978227
+.................
+lorem ipsum whatever
+
+through the darkness of future past
+the magician longs to see
+one [chants|chance] out between two worlds
+fire, walk with me
+
+
+(&!*^@$*&@!^*&@#^$*&@#$*&@#$^*&@^#$)(&)(!&$(*&^@&#*$
+
+MOAR testing09237861235980723894570389yfskdjhgfm13jo847rtnjcsjjdhfgnc12o387456vb1p98364vlaisutfvlKUYASDOV*&Q@$%VOUAYFROVLUKSYDFP(*A^*&%DFASF________
+<>?><?><?><>?>>?<>??><A?S>D<?A>S<D?><G?S><DG?S><G
+
+INPUT
+h e r e
+*will*
+d0
+0.753538467597066
+2.2339337309978227
+.................
+lorem ipsum whatever
+
+through the darkness of future past
+the magician longs to see
+one [chants|chance] out between two worlds
+fire, walk with me
+
+
+h e r e
+*will*
+d0
+0.753538467597066
+2.2339337309978227
+.................
+lorem ipsum whatever
+
+through the darkness of future past
+the magician longs to see
+one [chants|chance] out between two worlds
+fire, walk with me
+
+
+(&!*^@$*&@!^*&@#^$*&@#$*&@#$^*&@^#$)(&)(!&$(*&^@&#*$
+
+MOAR testing09237861235980723894570389yfskdjhgfm13jo847rtnjcsjjdhfgnc12o387456vb1p98364vlaisutfvlKUYASDOV*&Q@$%VOUAYFROVLUKSYDFP(*A^*&%DFASF________
+<>?><?><?><>?>>?<>??><A?S>D<?A>S<D?><G?S><DG?S><G
+
+2.2339337309978227
+.................
+lorem ipsum whatever
+
+through the darkness of future past
+the magician longs to see
+one [chants|chance] out between two worlds
+fire, walk with me
+
+
+(&!*^@$*&@!^*&@#^$*&@#$*&@#$^*&@^#$)(&)(!&$(*&^@&#*$
+
+) # this isn't really needed either