diff options
-rwxr-xr-x | auto_update_tests.py | 9 | ||||
-rwxr-xr-x | check.py | 9 | ||||
-rw-r--r-- | src/tools/execution-results.h | 117 | ||||
-rw-r--r-- | src/tools/js-wrapper.h | 89 | ||||
-rw-r--r-- | src/tools/spec-wrapper.h | 47 | ||||
-rw-r--r-- | src/tools/translate-to-fuzz.h | 1115 | ||||
-rw-r--r-- | src/tools/wasm-opt.cpp | 132 | ||||
-rw-r--r-- | src/tools/wasm-shell.cpp | 24 | ||||
-rw-r--r-- | src/wasm-binary.h | 7 | ||||
-rw-r--r-- | test/passes/emit-js-wrapper=a.js.txt | 42 | ||||
-rw-r--r-- | test/passes/emit-js-wrapper=a.js.wast | 37 | ||||
-rw-r--r-- | test/passes/emit-js-wrapper=a.js.wast.js | 44 | ||||
-rw-r--r-- | test/passes/emit-spec-wrapper=a.wat.txt | 42 | ||||
-rw-r--r-- | test/passes/emit-spec-wrapper=a.wat.wast | 37 | ||||
-rw-r--r-- | test/passes/emit-spec-wrapper=a.wat.wast.wat | 1 | ||||
-rw-r--r-- | test/passes/fuzz-exec_O.txt | 8 | ||||
-rw-r--r-- | test/passes/translate-to-fuzz.txt | 1126 | ||||
-rw-r--r-- | test/passes/translate-to-fuzz.wast | 99 |
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' @@ -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 |