diff options
Diffstat (limited to 'src/tools')
-rw-r--r-- | src/tools/asm2wasm.cpp | 271 | ||||
-rw-r--r-- | src/tools/execution-results.h | 17 | ||||
-rw-r--r-- | src/tools/fuzzing.h | 1554 | ||||
-rw-r--r-- | src/tools/js-wrapper.h | 35 | ||||
-rw-r--r-- | src/tools/optimization-options.h | 230 | ||||
-rw-r--r-- | src/tools/spec-wrapper.h | 29 | ||||
-rw-r--r-- | src/tools/tool-options.h | 117 | ||||
-rw-r--r-- | src/tools/tool-utils.h | 1 | ||||
-rw-r--r-- | src/tools/wasm-as.cpp | 115 | ||||
-rw-r--r-- | src/tools/wasm-ctor-eval.cpp | 207 | ||||
-rw-r--r-- | src/tools/wasm-dis.cpp | 54 | ||||
-rw-r--r-- | src/tools/wasm-emscripten-finalize.cpp | 150 | ||||
-rw-r--r-- | src/tools/wasm-metadce.cpp | 268 | ||||
-rw-r--r-- | src/tools/wasm-opt.cpp | 225 | ||||
-rw-r--r-- | src/tools/wasm-reduce.cpp | 562 | ||||
-rw-r--r-- | src/tools/wasm-shell.cpp | 125 | ||||
-rw-r--r-- | src/tools/wasm2js.cpp | 321 |
17 files changed, 2669 insertions, 1612 deletions
diff --git a/src/tools/asm2wasm.cpp b/src/tools/asm2wasm.cpp index 547bf4ab1..843106ffd 100644 --- a/src/tools/asm2wasm.cpp +++ b/src/tools/asm2wasm.cpp @@ -21,21 +21,21 @@ #include <exception> #include "ir/trapping.h" +#include "optimization-options.h" #include "support/colors.h" #include "support/command-line.h" #include "support/file.h" #include "wasm-builder.h" -#include "wasm-printing.h" #include "wasm-io.h" +#include "wasm-printing.h" #include "wasm-validator.h" -#include "optimization-options.h" #include "asm2wasm.h" using namespace cashew; using namespace wasm; -int main(int argc, const char *argv[]) { +int main(int argc, const char* argv[]) { bool legalizeJavaScriptFFI = true; TrapMode trapMode = TrapMode::JS; bool wasmOnly = false; @@ -44,81 +44,138 @@ int main(int argc, const char *argv[]) { std::string symbolMap; bool emitBinary = true; - OptimizationOptions options("asm2wasm", "Translate asm.js files to .wast files"); + OptimizationOptions options("asm2wasm", + "Translate asm.js files to .wast files"); options - .add("--output", "-o", "Output file (stdout if not specified)", - Options::Arguments::One, - [](Options *o, const std::string& argument) { - o->extra["output"] = argument; - Colors::disable(); - }) - .add("--mapped-globals", "-n", "Mapped globals", Options::Arguments::One, - [](Options *o, const std::string& argument) { - std::cerr << "warning: the --mapped-globals/-m option is deprecated (a mapped globals file is no longer needed as we use wasm globals)" << std::endl; - }) - .add("--mem-init", "-t", "Import a memory initialization file into the output module", Options::Arguments::One, - [](Options *o, const std::string& argument) { - o->extra["mem init"] = argument; - }) - .add("--mem-base", "-mb", "Set the location to write the memory initialization (--mem-init) file (GLOBAL_BASE in emscripten). If not provided, the __memory_base global import is used.", Options::Arguments::One, - [](Options *o, const std::string& argument) { - o->extra["mem base"] = argument; - }) - .add("--mem-max", "-mm", "Set the maximum size of memory in the wasm module (in bytes). -1 means no limit. Without this, TOTAL_MEMORY is used (as it is used for the initial value), or if memory growth is enabled, no limit is set. This overrides both of those.", Options::Arguments::One, - [](Options *o, const std::string& argument) { - o->extra["mem max"] = argument; - }) - .add("--total-memory", "-m", "Total memory size", Options::Arguments::One, - [](Options *o, const std::string& argument) { - o->extra["total memory"] = argument; - }) - .add("--table-max", "-tM", "Set the maximum size of the table. Without this, it is set depending on how many functions are in the module. -1 means no limit", Options::Arguments::One, - [](Options *o, const std::string& argument) { - o->extra["table max"] = argument; - }) - .add("--no-opts", "-n", "Disable optimization passes (deprecated)", Options::Arguments::Zero, - [](Options *o, const std::string& ) { - std::cerr << "--no-opts is deprecated (use -O0, etc.)\n"; - }) - .add("--trap-mode", "", - "Strategy for handling potentially trapping instructions. Valid " - "values are \"allow\", \"js\", and \"clamp\"", - Options::Arguments::One, - [&trapMode](Options *o, const std::string& argument) { - try { - trapMode = trapModeFromString(argument); - } catch (std::invalid_argument& e) { - std::cerr << "Error: " << e.what() << "\n"; - exit(EXIT_FAILURE); - } - }) - .add("--wasm-only", "-w", "Input is in WebAssembly-only format, and not actually valid asm.js", Options::Arguments::Zero, - [&wasmOnly](Options *o, const std::string& ) { - wasmOnly = true; - }) - .add("--no-legalize-javascript-ffi", "-nj", "Do not fully legalize (i64->i32, f32->f64) the imports and exports for interfacing with JS", Options::Arguments::Zero, - [&legalizeJavaScriptFFI](Options *o, const std::string& ) { - legalizeJavaScriptFFI = false; - }) - .add("--debuginfo", "-g", "Emit names section in wasm binary (or full debuginfo in wast)", - Options::Arguments::Zero, - [&](Options *o, const std::string& arguments) { options.passOptions.debugInfo = true; }) - .add("--source-map", "-sm", "Emit source map (if using binary output) to the specified file", - Options::Arguments::One, - [&sourceMapFilename](Options *o, const std::string& argument) { sourceMapFilename = argument; }) - .add("--source-map-url", "-su", "Use specified string as source map URL", - Options::Arguments::One, - [&sourceMapUrl](Options *o, const std::string& argument) { sourceMapUrl = argument; }) - .add("--symbolmap", "-s", "Emit a symbol map (indexes => names)", - Options::Arguments::One, - [&](Options *o, const std::string& argument) { symbolMap = argument; }) - .add("--emit-text", "-S", "Emit text instead of binary for the output file", - Options::Arguments::Zero, - [&](Options *o, const std::string& argument) { emitBinary = false; }) - .add_positional("INFILE", Options::Arguments::One, - [](Options *o, const std::string& argument) { - o->extra["infile"] = argument; - }); + .add("--output", + "-o", + "Output file (stdout if not specified)", + Options::Arguments::One, + [](Options* o, const std::string& argument) { + o->extra["output"] = argument; + Colors::disable(); + }) + .add( + "--mapped-globals", + "-n", + "Mapped globals", + Options::Arguments::One, + [](Options* o, const std::string& argument) { + std::cerr + << "warning: the --mapped-globals/-m option is deprecated (a mapped " + "globals file is no longer needed as we use wasm globals)" + << std::endl; + }) + .add("--mem-init", + "-t", + "Import a memory initialization file into the output module", + Options::Arguments::One, + [](Options* o, const std::string& argument) { + o->extra["mem init"] = argument; + }) + .add("--mem-base", + "-mb", + "Set the location to write the memory initialization (--mem-init) " + "file (GLOBAL_BASE in emscripten). If not provided, the __memory_base " + "global import is used.", + Options::Arguments::One, + [](Options* o, const std::string& argument) { + o->extra["mem base"] = argument; + }) + .add("--mem-max", + "-mm", + "Set the maximum size of memory in the wasm module (in bytes). -1 " + "means no limit. Without this, TOTAL_MEMORY is used (as it is used " + "for the initial value), or if memory growth is enabled, no limit is " + "set. This overrides both of those.", + Options::Arguments::One, + [](Options* o, const std::string& argument) { + o->extra["mem max"] = argument; + }) + .add("--total-memory", + "-m", + "Total memory size", + Options::Arguments::One, + [](Options* o, const std::string& argument) { + o->extra["total memory"] = argument; + }) + .add("--table-max", + "-tM", + "Set the maximum size of the table. Without this, it is set depending " + "on how many functions are in the module. -1 means no limit", + Options::Arguments::One, + [](Options* o, const std::string& argument) { + o->extra["table max"] = argument; + }) + .add("--no-opts", + "-n", + "Disable optimization passes (deprecated)", + Options::Arguments::Zero, + [](Options* o, const std::string&) { + std::cerr << "--no-opts is deprecated (use -O0, etc.)\n"; + }) + .add("--trap-mode", + "", + "Strategy for handling potentially trapping instructions. Valid " + "values are \"allow\", \"js\", and \"clamp\"", + Options::Arguments::One, + [&trapMode](Options* o, const std::string& argument) { + try { + trapMode = trapModeFromString(argument); + } catch (std::invalid_argument& e) { + std::cerr << "Error: " << e.what() << "\n"; + exit(EXIT_FAILURE); + } + }) + .add("--wasm-only", + "-w", + "Input is in WebAssembly-only format, and not actually valid asm.js", + Options::Arguments::Zero, + [&wasmOnly](Options* o, const std::string&) { wasmOnly = true; }) + .add("--no-legalize-javascript-ffi", + "-nj", + "Do not fully legalize (i64->i32, f32->f64) the imports and exports " + "for interfacing with JS", + Options::Arguments::Zero, + [&legalizeJavaScriptFFI](Options* o, const std::string&) { + legalizeJavaScriptFFI = false; + }) + .add("--debuginfo", + "-g", + "Emit names section in wasm binary (or full debuginfo in wast)", + Options::Arguments::Zero, + [&](Options* o, const std::string& arguments) { + options.passOptions.debugInfo = true; + }) + .add("--source-map", + "-sm", + "Emit source map (if using binary output) to the specified file", + Options::Arguments::One, + [&sourceMapFilename](Options* o, const std::string& argument) { + sourceMapFilename = argument; + }) + .add("--source-map-url", + "-su", + "Use specified string as source map URL", + Options::Arguments::One, + [&sourceMapUrl](Options* o, const std::string& argument) { + sourceMapUrl = argument; + }) + .add("--symbolmap", + "-s", + "Emit a symbol map (indexes => names)", + Options::Arguments::One, + [&](Options* o, const std::string& argument) { symbolMap = argument; }) + .add("--emit-text", + "-S", + "Emit text instead of binary for the output file", + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { emitBinary = false; }) + .add_positional("INFILE", + Options::Arguments::One, + [](Options* o, const std::string& argument) { + o->extra["infile"] = argument; + }); options.parse(argc, argv); // finalize arguments @@ -129,32 +186,39 @@ int main(int argc, const char *argv[]) { if (options.runningDefaultOptimizationPasses()) { if (options.passes.size() > 1) { - Fatal() << "asm2wasm can only run default optimization passes (-O, -Ox, etc.), and not specific additional passes"; + Fatal() << "asm2wasm can only run default optimization passes (-O, -Ox, " + "etc.), and not specific additional passes"; } } - const auto &tm_it = options.extra.find("total memory"); - size_t totalMemory = - tm_it == options.extra.end() ? 16 * 1024 * 1024 : atoll(tm_it->second.c_str()); + const auto& tm_it = options.extra.find("total memory"); + size_t totalMemory = tm_it == options.extra.end() + ? 16 * 1024 * 1024 + : atoll(tm_it->second.c_str()); if (totalMemory & ~Memory::kPageMask) { - std::cerr << "Error: total memory size " << totalMemory << - " is not a multiple of the 64k wasm page size\n"; + std::cerr << "Error: total memory size " << totalMemory + << " is not a multiple of the 64k wasm page size\n"; exit(EXIT_FAILURE); } Asm2WasmPreProcessor pre; // wasm binaries can contain a names section, but not full debug info -- // debug info is disabled if a map file is not specified with wasm binary - pre.debugInfo = options.passOptions.debugInfo && (!emitBinary || sourceMapFilename.size()); - auto input( - read_file<std::vector<char>>(options.extra["infile"], Flags::Text, options.debug ? Flags::Debug : Flags::Release)); - char *start = pre.process(input.data()); + pre.debugInfo = + options.passOptions.debugInfo && (!emitBinary || sourceMapFilename.size()); + auto input(read_file<std::vector<char>>(options.extra["infile"], + Flags::Text, + options.debug ? Flags::Debug + : Flags::Release)); + char* start = pre.process(input.data()); - if (options.debug) std::cerr << "parsing..." << std::endl; + if (options.debug) + std::cerr << "parsing..." << std::endl; cashew::Parser<Ref, DotZeroValueBuilder> builder; Ref asmjs = builder.parseToplevel(start); - if (options.debug) std::cerr << "wasming..." << std::endl; + if (options.debug) + std::cerr << "wasming..." << std::endl; Module wasm; // set up memory @@ -162,17 +226,19 @@ int main(int argc, const char *argv[]) { // import mem init file, if provided (do this before compiling the module, // since the optimizer should see the memory segments) - const auto &memInit = options.extra.find("mem init"); + const auto& memInit = options.extra.find("mem init"); if (memInit != options.extra.end()) { auto filename = memInit->second.c_str(); - auto data(read_file<std::vector<char>>(filename, Flags::Binary, options.debug ? Flags::Debug : Flags::Release)); + auto data(read_file<std::vector<char>>( + filename, Flags::Binary, options.debug ? Flags::Debug : Flags::Release)); // create the memory segment Expression* init; - const auto &memBase = options.extra.find("mem base"); + const auto& memBase = options.extra.find("mem base"); if (memBase == options.extra.end()) { init = Builder(wasm).makeGetGlobal(MEMORY_BASE, i32); } else { - init = Builder(wasm).makeConst(Literal(int32_t(atoi(memBase->second.c_str())))); + init = Builder(wasm).makeConst( + Literal(int32_t(atoi(memBase->second.c_str())))); } wasm.memory.segments.emplace_back(init, data); } @@ -181,7 +247,14 @@ int main(int argc, const char *argv[]) { options.applyFeatures(wasm); // compile the code - Asm2WasmBuilder asm2wasm(wasm, pre, options.debug, trapMode, options.passOptions, legalizeJavaScriptFFI, options.runningDefaultOptimizationPasses(), wasmOnly); + Asm2WasmBuilder asm2wasm(wasm, + pre, + options.debug, + trapMode, + options.passOptions, + legalizeJavaScriptFFI, + options.runningDefaultOptimizationPasses(), + wasmOnly); asm2wasm.processAsm(asmjs); // finalize the imported mem init @@ -194,7 +267,7 @@ int main(int argc, const char *argv[]) { } // Set the max memory size, if requested - const auto &memMax = options.extra.find("mem max"); + const auto& memMax = options.extra.find("mem max"); if (memMax != options.extra.end()) { uint64_t max = strtoull(memMax->second.c_str(), nullptr, 10); if (max != uint64_t(-1)) { @@ -204,7 +277,7 @@ int main(int argc, const char *argv[]) { } } // Set the table sizes, if requested - const auto &tableMax = options.extra.find("table max"); + const auto& tableMax = options.extra.find("table max"); if (tableMax != options.extra.end()) { int max = atoi(tableMax->second.c_str()); if (max >= 0) { @@ -221,7 +294,8 @@ int main(int argc, const char *argv[]) { } } - if (options.debug) std::cerr << "emitting..." << std::endl; + if (options.debug) + std::cerr << "emitting..." << std::endl; ModuleWriter writer; writer.setDebug(options.debug); writer.setDebugInfo(options.passOptions.debugInfo); @@ -233,5 +307,6 @@ int main(int argc, const char *argv[]) { } writer.write(wasm, options.extra["output"]); - if (options.debug) std::cerr << "done." << std::endl; + if (options.debug) + std::cerr << "done." << std::endl; } diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 554b4eb6c..2a42b941b 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -18,9 +18,9 @@ // Shared execution result checking code // -#include "wasm.h" -#include "shell-interface.h" #include "ir/import-utils.h" +#include "shell-interface.h" +#include "wasm.h" namespace wasm { @@ -59,9 +59,11 @@ struct ExecutionResults { LoggingExternalInterface interface(loggings); try { ModuleInstance instance(wasm, &interface); - // execute all exported methods (that are therefore preserved through opts) + // execute all exported methods (that are therefore preserved through + // opts) for (auto& exp : wasm.exports) { - if (exp->kind != ExternalKind::Function) continue; + if (exp->kind != ExternalKind::Function) + continue; std::cout << "[fuzz-exec] calling " << exp->name << "\n"; auto* func = wasm.getFunction(exp->value); if (func->result != none) { @@ -69,7 +71,8 @@ struct ExecutionResults { results[exp->name] = run(func, wasm, instance); // ignore the result if we hit an unreachable and returned no value if (isConcreteType(results[exp->name].type)) { - std::cout << "[fuzz-exec] note result: " << exp->name << " => " << results[exp->name] << '\n'; + std::cout << "[fuzz-exec] note result: " << exp->name << " => " + << results[exp->name] << '\n'; } } else { // no result, run it anyhow (it might modify memory etc.) @@ -111,9 +114,7 @@ struct ExecutionResults { return true; } - bool operator!=(ExecutionResults& other) { - return !((*this) == other); - } + bool operator!=(ExecutionResults& other) { return !((*this) == other); } Literal run(Function* func, Module& wasm) { LoggingExternalInterface interface(loggings); diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index b7056f964..f5b548ec6 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -25,12 +25,14 @@ high chance for set at start of loop high chance of a tee in that case => loop var */ -#include <wasm-builder.h> +#include "ir/memory-utils.h" #include <ir/find_all.h> #include <ir/literal-utils.h> #include <ir/manipulation.h> -#include "ir/memory-utils.h" #include <ir/utils.h> +#include <support/file.h> +#include <tools/optimization-options.h> +#include <wasm-builder.h> namespace wasm { @@ -38,32 +40,35 @@ namespace wasm { // evaluation, avoiding UB struct ThreeArgs { - Expression *a; - Expression *b; - Expression *c; + Expression* a; + Expression* b; + Expression* c; }; struct UnaryArgs { UnaryOp a; - Expression *b; + Expression* b; }; struct BinaryArgs { BinaryOp a; - Expression *b; - Expression *c; + Expression* b; + Expression* c; }; // main reader class TranslateToFuzzReader { public: - TranslateToFuzzReader(Module& wasm, std::string& filename) : wasm(wasm), builder(wasm) { - auto input(read_file<std::vector<char>>(filename, Flags::Binary, Flags::Release)); + TranslateToFuzzReader(Module& wasm, std::string& filename) + : wasm(wasm), builder(wasm) { + auto input( + read_file<std::vector<char>>(filename, Flags::Binary, Flags::Release)); readData(input); } - TranslateToFuzzReader(Module& wasm, std::vector<char> input) : wasm(wasm), builder(wasm) { + TranslateToFuzzReader(Module& wasm, std::vector<char> input) + : wasm(wasm), builder(wasm) { readData(input); } @@ -80,38 +85,91 @@ public: options.passOptions.shrinkLevel = upTo(4); break; } - case 5: options.passes.push_back("coalesce-locals"); break; - case 6: options.passes.push_back("code-pushing"); break; - case 7: options.passes.push_back("code-folding"); break; - case 8: options.passes.push_back("dce"); break; - case 9: options.passes.push_back("duplicate-function-elimination"); break; - case 10: options.passes.push_back("flatten"); break; - case 11: options.passes.push_back("inlining"); break; - case 12: options.passes.push_back("inlining-optimizing"); break; - case 13: options.passes.push_back("local-cse"); break; - case 14: options.passes.push_back("memory-packing"); break; - case 15: options.passes.push_back("merge-blocks"); break; - case 16: options.passes.push_back("optimize-instructions"); break; - case 17: options.passes.push_back("pick-load-signs"); break; - case 18: options.passes.push_back("precompute"); break; - case 19: options.passes.push_back("precompute-propagate"); break; - case 20: options.passes.push_back("remove-unused-brs"); break; - case 21: options.passes.push_back("remove-unused-module-elements"); break; - case 22: options.passes.push_back("remove-unused-names"); break; - case 23: options.passes.push_back("reorder-functions"); break; - case 24: options.passes.push_back("reorder-locals"); break; + case 5: + options.passes.push_back("coalesce-locals"); + break; + case 6: + options.passes.push_back("code-pushing"); + break; + case 7: + options.passes.push_back("code-folding"); + break; + case 8: + options.passes.push_back("dce"); + break; + case 9: + options.passes.push_back("duplicate-function-elimination"); + break; + case 10: + options.passes.push_back("flatten"); + break; + case 11: + options.passes.push_back("inlining"); + break; + case 12: + options.passes.push_back("inlining-optimizing"); + break; + case 13: + options.passes.push_back("local-cse"); + break; + case 14: + options.passes.push_back("memory-packing"); + break; + case 15: + options.passes.push_back("merge-blocks"); + break; + case 16: + options.passes.push_back("optimize-instructions"); + break; + case 17: + options.passes.push_back("pick-load-signs"); + break; + case 18: + options.passes.push_back("precompute"); + break; + case 19: + options.passes.push_back("precompute-propagate"); + break; + case 20: + options.passes.push_back("remove-unused-brs"); + break; + case 21: + options.passes.push_back("remove-unused-module-elements"); + break; + case 22: + options.passes.push_back("remove-unused-names"); + break; + case 23: + options.passes.push_back("reorder-functions"); + break; + case 24: + options.passes.push_back("reorder-locals"); + break; case 25: { options.passes.push_back("flatten"); options.passes.push_back("rereloop"); break; } - case 26: options.passes.push_back("simplify-locals"); break; - case 27: options.passes.push_back("simplify-locals-notee"); break; - case 28: options.passes.push_back("simplify-locals-nostructure"); break; - case 29: options.passes.push_back("simplify-locals-notee-nostructure"); break; - case 30: options.passes.push_back("ssa"); break; - case 31: options.passes.push_back("vacuum"); break; - default: WASM_UNREACHABLE(); + case 26: + options.passes.push_back("simplify-locals"); + break; + case 27: + options.passes.push_back("simplify-locals-notee"); + break; + case 28: + options.passes.push_back("simplify-locals-nostructure"); + break; + case 29: + options.passes.push_back("simplify-locals-notee-nostructure"); + break; + case 30: + options.passes.push_back("ssa"); + break; + case 31: + options.passes.push_back("vacuum"); + break; + default: + WASM_UNREACHABLE(); } } if (oneIn(2)) { @@ -124,13 +182,9 @@ public: std::cout << "shrink level: " << options.passOptions.shrinkLevel << '\n'; } - void setAllowNaNs(bool allowNaNs_) { - allowNaNs = allowNaNs_; - } + void setAllowNaNs(bool allowNaNs_) { allowNaNs = allowNaNs_; } - void setAllowMemory(bool allowMemory_) { - allowMemory = allowMemory_; - } + void setAllowMemory(bool allowMemory_) { allowMemory = allowMemory_; } void build() { if (allowMemory) { @@ -157,8 +211,10 @@ 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) + size_t pos; // the position in the input + // whether we already cycled through all the input (if so, we should try to + // finish things off) + bool finishedInput; // The maximum amount of params to each function. static const int MAX_PARAMS = 10; @@ -176,8 +232,8 @@ private: 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) - // this should be a power of 2 + // looking at writes (we also look outside of this region with small + // probability) this should be a power of 2 static const int USABLE_MEMORY = 16; // the number of runtime iterations (function calls, loop backbranches) we @@ -239,13 +295,9 @@ private: return temp | uint64_t(get32()); } - float getFloat() { - return Literal(get32()).reinterpretf32(); - } + float getFloat() { return Literal(get32()).reinterpretf32(); } - double getDouble() { - return Literal(get64()).reinterpretf64(); - } + double getDouble() { return Literal(get64()).reinterpretf64(); } void setupMemory() { // Add memory itself @@ -274,10 +326,12 @@ private: auto num = upTo(USABLE_MEMORY * 2); for (size_t i = 0; i < num; i++) { auto value = upTo(512); - wasm.memory.segments[0].data.push_back(value >= 256 ? 0 : (value & 0xff)); + wasm.memory.segments[0].data.push_back(value >= 256 ? 0 + : (value & 0xff)); } } - // Add memory hasher helper (for the hash, see hash.h). The function looks like: + // Add memory hasher helper (for the hash, see hash.h). The function looks + // like: // function hashMemory() { // hash = 5381; // hash = ((hash << 5) + hash) ^ mem[0]; @@ -287,31 +341,28 @@ private: // } std::vector<Expression*> contents; contents.push_back( - builder.makeSetLocal(0, builder.makeConst(Literal(uint32_t(5381)))) - ); + builder.makeSetLocal(0, builder.makeConst(Literal(uint32_t(5381))))); for (Index i = 0; i < USABLE_MEMORY; i++) { - contents.push_back( - builder.makeSetLocal(0, - builder.makeBinary(XorInt32, - builder.makeBinary(AddInt32, - builder.makeBinary(ShlInt32, - builder.makeGetLocal(0, i32), - builder.makeConst(Literal(uint32_t(5))) - ), - builder.makeGetLocal(0, i32) - ), - builder.makeLoad(1, false, i, 1, builder.makeConst(Literal(uint32_t(0))), i32) - ) - ) - ); - } - contents.push_back( - builder.makeGetLocal(0, i32) - ); + contents.push_back(builder.makeSetLocal( + 0, + builder.makeBinary( + XorInt32, + builder.makeBinary( + AddInt32, + builder.makeBinary(ShlInt32, + builder.makeGetLocal(0, i32), + builder.makeConst(Literal(uint32_t(5)))), + builder.makeGetLocal(0, i32)), + builder.makeLoad( + 1, false, i, 1, builder.makeConst(Literal(uint32_t(0))), i32)))); + } + contents.push_back(builder.makeGetLocal(0, i32)); auto* body = builder.makeBlock(contents); - auto* hasher = wasm.addFunction(builder.makeFunction("hashMemory", std::vector<Type>{}, i32, { i32 }, body)); + auto* hasher = wasm.addFunction(builder.makeFunction( + "hashMemory", std::vector<Type>{}, i32, {i32}, body)); hasher->type = ensureFunctionType(getSig(hasher), &wasm)->name; - wasm.addExport(builder.makeExport(hasher->name, hasher->name, ExternalKind::Function)); + wasm.addExport( + builder.makeExport(hasher->name, hasher->name, ExternalKind::Function)); } void setupTable() { @@ -323,15 +374,14 @@ private: void setupGlobals() { size_t index = 0; - for (auto type : { i32, i64, f32, f64 }) { + for (auto type : {i32, i64, f32, f64}) { auto num = upTo(3); for (size_t i = 0; i < num; i++) { - auto* glob = builder.makeGlobal( - std::string("global$") + std::to_string(index++), - type, - makeConst(type), - Builder::Mutable - ); + auto* glob = + builder.makeGlobal(std::string("global$") + std::to_string(index++), + type, + makeConst(type), + Builder::Mutable); wasm.addGlobal(glob); globalsByType[type].push_back(glob->name); } @@ -340,26 +390,25 @@ private: void finalizeTable() { wasm.table.initial = wasm.table.segments[0].data.size(); - wasm.table.max = oneIn(2) ? Address(Table::kUnlimitedSize) : wasm.table.initial; + wasm.table.max = + oneIn(2) ? Address(Table::kUnlimitedSize) : wasm.table.initial; } const Name HANG_LIMIT_GLOBAL = "hangLimit"; void addHangLimitSupport() { - auto* glob = builder.makeGlobal( - HANG_LIMIT_GLOBAL, - i32, - builder.makeConst(Literal(int32_t(HANG_LIMIT))), - Builder::Mutable - ); + auto* glob = + builder.makeGlobal(HANG_LIMIT_GLOBAL, + i32, + builder.makeConst(Literal(int32_t(HANG_LIMIT))), + Builder::Mutable); 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))) - ); + func->body = builder.makeSetGlobal( + glob->name, builder.makeConst(Literal(int32_t(HANG_LIMIT)))); wasm.addFunction(func); auto* export_ = new Export; @@ -370,7 +419,7 @@ private: } void addImportLoggingSupport() { - for (auto type : { i32, i64, f32, f64 }) { + for (auto type : {i32, i64, f32, f64}) { auto* func = new Function; Name name = std::string("log-") + printType(type); func->name = name; @@ -386,21 +435,14 @@ private: Expression* makeHangLimitCheck() { return builder.makeSequence( builder.makeIf( - builder.makeUnary( - UnaryOp::EqZInt32, - builder.makeGetGlobal(HANG_LIMIT_GLOBAL, i32) - ), - makeTrivial(unreachable) - ), + 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))) - ) - ) - ); + builder.makeBinary(BinaryOp::SubInt32, + builder.makeGetGlobal(HANG_LIMIT_GLOBAL, i32), + builder.makeConst(Literal(int32_t(1)))))); } void addDeNanSupport() { @@ -411,13 +453,9 @@ private: func->result = type; func->body = builder.makeIf( builder.makeBinary( - op, - builder.makeGetLocal(0, type), - builder.makeGetLocal(0, type) - ), + op, builder.makeGetLocal(0, type), builder.makeGetLocal(0, type)), builder.makeGetLocal(0, type), - builder.makeConst(literal) - ); + builder.makeConst(literal)); wasm.addFunction(func); }; add("deNan32", f32, Literal(float(0)), EqFloat32); @@ -425,11 +463,12 @@ private: } Expression* makeDeNanOp(Expression* expr) { - if (allowNaNs) return expr; + if (allowNaNs) + return expr; if (expr->type == f32) { - return builder.makeCall("deNan32", { expr }, f32); + return builder.makeCall("deNan32", {expr}, f32); } else if (expr->type == f64) { - return builder.makeCall("deNan64", { expr }, f64); + return builder.makeCall("deNan64", {expr}, f64); } return expr; // unreachable etc. is fine } @@ -444,7 +483,8 @@ private: // which we try to minimize the risk of std::vector<Expression*> hangStack; - std::map<Type, std::vector<Index>> typeLocals; // type => list of locals with that type + std::map<Type, std::vector<Index>> + typeLocals; // type => list of locals with that type Function* addFunction() { LOGGING_PERCENT = upToSquared(100); @@ -518,23 +558,19 @@ private: // loop limit FindAll<Loop> loops(func->body); for (auto* loop : loops.list) { - loop->body = builder.makeSequence( - makeHangLimitCheck(), - loop->body - ); + loop->body = builder.makeSequence(makeHangLimitCheck(), loop->body); } // recursion limit - func->body = builder.makeSequence( - makeHangLimitCheck(), - func->body - ); + func->body = builder.makeSequence(makeHangLimitCheck(), func->body); } void recombine(Function* func) { // Don't always do this. - if (oneIn(2)) return; + if (oneIn(2)) + return; // First, scan and group all expressions by type. - struct Scanner : public PostWalker<Scanner, UnifiedExpressionVisitor<Scanner>> { + struct Scanner + : public PostWalker<Scanner, UnifiedExpressionVisitor<Scanner>> { // A map of all expressions, categorized by type. std::map<Type, std::vector<Expression*>> exprsByType; @@ -544,10 +580,11 @@ private: }; Scanner scanner; scanner.walk(func->body); - // Potentially trim the list of possible picks, so replacements are more likely - // to collide. + // Potentially trim the list of possible picks, so replacements are more + // likely to collide. for (auto& pair : scanner.exprsByType) { - if (oneIn(2)) continue; + if (oneIn(2)) + continue; auto& list = pair.second; std::vector<Expression*> trimmed; size_t num = upToSquared(list.size()); @@ -568,19 +605,22 @@ private: // Second, with some probability replace an item with another item having // the same type. (This is not always valid due to nesting of labels, but // we'll fix that up later.) - struct Modder : public PostWalker<Modder, UnifiedExpressionVisitor<Modder>> { + struct Modder + : public PostWalker<Modder, UnifiedExpressionVisitor<Modder>> { Module& wasm; Scanner& scanner; TranslateToFuzzReader& parent; - Modder(Module& wasm, Scanner& scanner, TranslateToFuzzReader& parent) : wasm(wasm), scanner(scanner), parent(parent) {} + Modder(Module& wasm, Scanner& scanner, TranslateToFuzzReader& parent) + : wasm(wasm), scanner(scanner), parent(parent) {} void visitExpression(Expression* curr) { if (parent.oneIn(10)) { // Replace it! auto& candidates = scanner.exprsByType[curr->type]; assert(!candidates.empty()); // this expression itself must be there - replaceCurrent(ExpressionManipulator::copy(parent.vectorPick(candidates), wasm)); + replaceCurrent( + ExpressionManipulator::copy(parent.vectorPick(candidates), wasm)); } } }; @@ -590,12 +630,15 @@ private: void mutate(Function* func) { // Don't always do this. - if (oneIn(2)) return; - struct Modder : public PostWalker<Modder, UnifiedExpressionVisitor<Modder>> { + if (oneIn(2)) + return; + struct Modder + : public PostWalker<Modder, UnifiedExpressionVisitor<Modder>> { Module& wasm; TranslateToFuzzReader& parent; - Modder(Module& wasm, TranslateToFuzzReader& parent) : wasm(wasm), parent(parent) {} + Modder(Module& wasm, TranslateToFuzzReader& parent) + : wasm(wasm), parent(parent) {} void visitExpression(Expression* curr) { if (parent.oneIn(10)) { @@ -617,7 +660,8 @@ private: Module& wasm; TranslateToFuzzReader& parent; - Fixer(Module& wasm, TranslateToFuzzReader& parent) : wasm(wasm), parent(parent) {} + Fixer(Module& wasm, TranslateToFuzzReader& parent) + : wasm(wasm), parent(parent) {} // Track seen names to find duplication, which is invalid. std::set<Name> seen; @@ -644,14 +688,13 @@ private: void visitSwitch(Switch* curr) { for (auto name : curr->targets) { - if (replaceIfInvalid(name)) return; + if (replaceIfInvalid(name)) + return; } replaceIfInvalid(curr->default_); } - void visitBreak(Break* curr) { - replaceIfInvalid(curr->name); - } + void visitBreak(Break* curr) { replaceIfInvalid(curr->name); } bool replaceIfInvalid(Name target) { if (!hasBreakTarget(target)) { @@ -662,24 +705,26 @@ private: return false; } - void replace() { - replaceCurrent(parent.makeTrivial(getCurrent()->type)); - } + void replace() { replaceCurrent(parent.makeTrivial(getCurrent()->type)); } bool hasBreakTarget(Name name) { - if (controlFlowStack.empty()) return false; + if (controlFlowStack.empty()) + return false; Index i = controlFlowStack.size() - 1; while (1) { auto* curr = controlFlowStack[i]; if (Block* block = curr->template dynCast<Block>()) { - if (name == block->name) return true; + if (name == block->name) + return true; } else if (Loop* loop = curr->template dynCast<Loop>()) { - if (name == loop->name) return true; + if (name == loop->name) + return true; } else { // an if, ignorable assert(curr->template is<If>()); } - if (i == 0) return false; + if (i == 0) + return false; i--; } } @@ -709,7 +754,8 @@ private: invocations.push_back(makeMemoryHashLogging()); } } - if (invocations.empty()) return; + if (invocations.empty()) + return; auto* invoker = new Function; invoker->name = func->name.str + std::string("_invoker"); invoker->result = none; @@ -733,8 +779,7 @@ private: Expression* make(Type type) { // when we should stop, emit something small (but not necessarily trivial) - if (finishedInput || - nesting >= 5 * NESTING_LIMIT || // hard limit + if (finishedInput || nesting >= 5 * NESTING_LIMIT || // hard limit (nesting >= NESTING_LIMIT && !oneIn(3))) { if (isConcreteType(type)) { if (oneIn(2)) { @@ -759,9 +804,15 @@ private: case i64: case f32: case f64: - case v128: ret = _makeConcrete(type); break; - case none: ret = _makenone(); break; - case unreachable: ret = _makeunreachable(); break; + case v128: + ret = _makeConcrete(type); + break; + case none: + ret = _makenone(); + break; + case unreachable: + ret = _makeunreachable(); + break; } assert(ret->type == type); // we should create the right type of thing nesting--; @@ -770,31 +821,38 @@ private: Expression* _makeConcrete(Type type) { auto choice = upTo(100); - if (choice < 10) return makeConst(type); - if (choice < 30) return makeSetLocal(type); - if (choice < 50) return makeGetLocal(type); - if (choice < 60) return makeBlock(type); - if (choice < 70) return makeIf(type); - if (choice < 80) return makeLoop(type); - if (choice < 90) return makeBreak(type); + if (choice < 10) + return makeConst(type); + if (choice < 30) + return makeSetLocal(type); + if (choice < 50) + return makeGetLocal(type); + if (choice < 60) + return makeBlock(type); + if (choice < 70) + return makeIf(type); + if (choice < 80) + return makeLoop(type); + if (choice < 90) + return makeBreak(type); using Self = TranslateToFuzzReader; auto options = FeatureOptions<Expression* (Self::*)(Type)>() - .add(FeatureSet::MVP, - &Self::makeBlock, - &Self::makeIf, - &Self::makeLoop, - &Self::makeBreak, - &Self::makeCall, - &Self::makeCallIndirect, - &Self::makeGetLocal, - &Self::makeSetLocal, - &Self::makeLoad, - &Self::makeConst, - &Self::makeUnary, - &Self::makeBinary, - &Self::makeSelect, - &Self::makeGetGlobal) - .add(FeatureSet::SIMD, &Self::makeSIMD); + .add(FeatureSet::MVP, + &Self::makeBlock, + &Self::makeIf, + &Self::makeLoop, + &Self::makeBreak, + &Self::makeCall, + &Self::makeCallIndirect, + &Self::makeGetLocal, + &Self::makeSetLocal, + &Self::makeLoad, + &Self::makeConst, + &Self::makeUnary, + &Self::makeBinary, + &Self::makeSelect, + &Self::makeGetGlobal) + .add(FeatureSet::SIMD, &Self::makeSIMD); if (type == i32 || type == i64) { options.add(FeatureSet::Atomics, &Self::makeAtomic); } @@ -811,46 +869,66 @@ private: } } choice = upTo(100); - if (choice < 50) return makeSetLocal(none); - if (choice < 60) return makeBlock(none); - if (choice < 70) return makeIf(none); - if (choice < 80) return makeLoop(none); - if (choice < 90) return makeBreak(none); + if (choice < 50) + return makeSetLocal(none); + if (choice < 60) + return makeBlock(none); + if (choice < 70) + return makeIf(none); + if (choice < 80) + return makeLoop(none); + if (choice < 90) + return makeBreak(none); using Self = TranslateToFuzzReader; auto options = FeatureOptions<Expression* (Self::*)(Type)>() - .add(FeatureSet::MVP, - &Self::makeBlock, - &Self::makeIf, - &Self::makeLoop, - &Self::makeBreak, - &Self::makeCall, - &Self::makeCallIndirect, - &Self::makeSetLocal, - &Self::makeStore, - &Self::makeDrop, - &Self::makeNop, - &Self::makeSetGlobal) - .add(FeatureSet::BulkMemory, &Self::makeBulkMemory); + .add(FeatureSet::MVP, + &Self::makeBlock, + &Self::makeIf, + &Self::makeLoop, + &Self::makeBreak, + &Self::makeCall, + &Self::makeCallIndirect, + &Self::makeSetLocal, + &Self::makeStore, + &Self::makeDrop, + &Self::makeNop, + &Self::makeSetGlobal) + .add(FeatureSet::BulkMemory, &Self::makeBulkMemory); return (this->*pick(options))(none); } 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); + 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(); } @@ -932,7 +1010,8 @@ private: // ensure a branch back. also optionally create some loop vars std::vector<Expression*> list; list.push_back(makeMaybeBlock(none)); // primary contents - list.push_back(builder.makeBreak(ret->name, nullptr, makeCondition())); // possible branch back + // possible branch back + list.push_back(builder.makeBreak(ret->name, nullptr, makeCondition())); list.push_back(make(type)); // final element, so we have the right type ret->body = builder.makeBlock(list); } @@ -970,13 +1049,15 @@ private: Expression* makeIf(Type type) { auto* condition = makeCondition(); hangStack.push_back(nullptr); - auto* ret = buildIf({ condition, makeMaybeBlock(type), makeMaybeBlock(type) }); + auto* ret = + buildIf({condition, makeMaybeBlock(type), makeMaybeBlock(type)}); hangStack.pop_back(); return ret; } Expression* makeBreak(Type type) { - if (breakableStack.empty()) return makeTrivial(type); + if (breakableStack.empty()) + return makeTrivial(type); Expression* condition = nullptr; if (type != unreachable) { hangStack.push_back(nullptr); @@ -1029,15 +1110,18 @@ private: } switch (conditions) { case 0: { - if (!oneIn(4)) continue; + if (!oneIn(4)) + continue; break; } case 1: { - if (!oneIn(2)) continue; + if (!oneIn(2)) + continue; break; } default: { - if (oneIn(conditions + 1)) continue; + if (oneIn(conditions + 1)) + continue; } } return builder.makeBreak(name); @@ -1058,7 +1142,8 @@ private: if (!wasm.functions.empty() && !oneIn(wasm.functions.size())) { target = vectorPick(wasm.functions).get(); } - if (target->result != type) continue; + if (target->result != type) + continue; // we found one! std::vector<Expression*> args; for (auto argType : target->params) { @@ -1072,7 +1157,8 @@ private: Expression* makeCallIndirect(Type type) { auto& data = wasm.table.segments[0].data; - if (data.empty()) return make(type); + if (data.empty()) + return make(type); // look for a call target with the right type Index start = upTo(data.size()); Index i = start; @@ -1084,8 +1170,10 @@ private: break; } i++; - if (i == data.size()) i = 0; - if (i == start) return make(type); + if (i == data.size()) + i = 0; + if (i == start) + return make(type); } // with high probability, make sure the type is valid otherwise, most are // going to trap @@ -1100,17 +1188,13 @@ private: args.push_back(make(type)); } func->type = ensureFunctionType(getSig(func), &wasm)->name; - return builder.makeCallIndirect( - func->type, - target, - args, - func->result - ); + return builder.makeCallIndirect(func->type, target, args, func->result); } Expression* makeGetLocal(Type type) { auto& locals = typeLocals[type]; - if (locals.empty()) return makeConst(type); + if (locals.empty()) + return makeConst(type); return builder.makeGetLocal(vectorPick(locals), type); } @@ -1123,7 +1207,8 @@ private: valueType = getConcreteType(); } auto& locals = typeLocals[valueType]; - if (locals.empty()) return makeTrivial(type); + if (locals.empty()) + return makeTrivial(type); auto* value = make(valueType); if (tee) { return builder.makeTeeLocal(vectorPick(locals), value); @@ -1134,7 +1219,8 @@ private: Expression* makeGetGlobal(Type type) { auto& globals = globalsByType[type]; - if (globals.empty()) return makeConst(type); + if (globals.empty()) + return makeConst(type); return builder.makeGetGlobal(vectorPick(globals), type); } @@ -1142,7 +1228,8 @@ private: assert(type == none); type = getConcreteType(); auto& globals = globalsByType[type]; - if (globals.empty()) return makeTrivial(none); + if (globals.empty()) + return makeTrivial(none); auto* value = make(type); return builder.makeSetGlobal(vectorPick(globals), value); } @@ -1153,10 +1240,8 @@ private: // 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))) - ); + ret = builder.makeBinary( + AndInt32, ret, builder.makeConst(Literal(int32_t(USABLE_MEMORY - 1)))); } return ret; } @@ -1168,19 +1253,29 @@ private: 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); + 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); + 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(); } @@ -1194,19 +1289,24 @@ private: if (!wasm.features.hasSIMD()) { return makeTrivial(type); } - return builder.makeLoad(16, false, offset, pick(1, 2, 4, 8, 16), ptr, type); + return builder.makeLoad( + 16, false, offset, pick(1, 2, 4, 8, 16), ptr, type); } case none: - case unreachable: WASM_UNREACHABLE(); + case unreachable: + WASM_UNREACHABLE(); } WASM_UNREACHABLE(); } Expression* makeLoad(Type type) { - if (!allowMemory) return makeTrivial(type); + if (!allowMemory) + return makeTrivial(type); auto* ret = makeNonAtomicLoad(type); - if (type != i32 && type != i64) return ret; - if (!wasm.features.hasAtomics() || oneIn(2)) return ret; + if (type != i32 && type != i64) + return ret; + if (!wasm.features.hasAtomics() || oneIn(2)) + return ret; // make it atomic auto* load = ret->cast<Load>(); wasm.memory.shared = true; @@ -1221,11 +1321,19 @@ private: // make a normal store, then make it unreachable auto* ret = makeNonAtomicStore(getConcreteType()); auto* store = ret->dynCast<Store>(); - if (!store) return ret; + if (!store) + return ret; switch (upTo(3)) { - case 0: store->ptr = make(unreachable); break; - case 1: store->value = make(unreachable); break; - case 2: store->ptr = make(unreachable); store->value = make(unreachable); break; + case 0: + store->ptr = make(unreachable); + break; + case 1: + store->value = make(unreachable); + break; + case 2: + store->ptr = make(unreachable); + store->value = make(unreachable); + break; } store->finalize(); return store; @@ -1241,18 +1349,28 @@ private: 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); + 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); + 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(); } @@ -1266,21 +1384,27 @@ private: if (!wasm.features.hasSIMD()) { return makeTrivial(type); } - return builder.makeStore(16, offset, pick(1, 2, 4, 8, 16), ptr, value, type); + return builder.makeStore( + 16, offset, pick(1, 2, 4, 8, 16), ptr, value, type); } case none: - case unreachable: WASM_UNREACHABLE(); + case unreachable: + WASM_UNREACHABLE(); } WASM_UNREACHABLE(); } Expression* makeStore(Type type) { - if (!allowMemory) return makeTrivial(type); + if (!allowMemory) + return makeTrivial(type); auto* ret = makeNonAtomicStore(type); auto* store = ret->dynCast<Store>(); - if (!store) return ret; - if (store->value->type != i32 && store->value->type != i64) return store; - if (!wasm.features.hasAtomics() || oneIn(2)) return store; + if (!store) + return ret; + if (store->value->type != i32 && store->value->type != i64) + return store; + if (!wasm.features.hasAtomics() || oneIn(2)) + return store; // make it atomic wasm.memory.shared = true; store->isAtomic = true; @@ -1292,25 +1416,50 @@ private: if (type == v128) { // generate each lane individually for random lane interpretation switch (upTo(6)) { - case 0: return Literal( - std::array<Literal, 16>{{ - makeLiteral(i32), makeLiteral(i32), makeLiteral(i32), makeLiteral(i32), - makeLiteral(i32), makeLiteral(i32), makeLiteral(i32), makeLiteral(i32), - makeLiteral(i32), makeLiteral(i32), makeLiteral(i32), makeLiteral(i32), - makeLiteral(i32), makeLiteral(i32), makeLiteral(i32), makeLiteral(i32) - }} - ); - case 1: return Literal( - std::array<Literal, 8>{{ - makeLiteral(i32), makeLiteral(i32), makeLiteral(i32), makeLiteral(i32), - makeLiteral(i32), makeLiteral(i32), makeLiteral(i32), makeLiteral(i32) - }} - ); - case 2: return Literal(std::array<Literal, 4>{{makeLiteral(i32), makeLiteral(i32), makeLiteral(i32), makeLiteral(i32)}}); - case 3: return Literal(std::array<Literal, 2>{{makeLiteral(i64), makeLiteral(i64)}}); - case 4: return Literal(std::array<Literal, 4>{{makeLiteral(f32), makeLiteral(f32), makeLiteral(f32), makeLiteral(f32)}}); - case 5: return Literal(std::array<Literal, 2>{{makeLiteral(f64), makeLiteral(f64)}}); - default: WASM_UNREACHABLE(); + case 0: + return Literal(std::array<Literal, 16>{{makeLiteral(i32), + makeLiteral(i32), + makeLiteral(i32), + makeLiteral(i32), + makeLiteral(i32), + makeLiteral(i32), + makeLiteral(i32), + makeLiteral(i32), + makeLiteral(i32), + makeLiteral(i32), + makeLiteral(i32), + makeLiteral(i32), + makeLiteral(i32), + makeLiteral(i32), + makeLiteral(i32), + makeLiteral(i32)}}); + case 1: + return Literal(std::array<Literal, 8>{{makeLiteral(i32), + makeLiteral(i32), + makeLiteral(i32), + makeLiteral(i32), + makeLiteral(i32), + makeLiteral(i32), + makeLiteral(i32), + makeLiteral(i32)}}); + case 2: + return Literal(std::array<Literal, 4>{{makeLiteral(i32), + makeLiteral(i32), + makeLiteral(i32), + makeLiteral(i32)}}); + case 3: + return Literal( + std::array<Literal, 2>{{makeLiteral(i64), makeLiteral(i64)}}); + case 4: + return Literal(std::array<Literal, 4>{{makeLiteral(f32), + makeLiteral(f32), + makeLiteral(f32), + makeLiteral(f32)}}); + case 5: + return Literal( + std::array<Literal, 2>{{makeLiteral(f64), makeLiteral(f64)}}); + default: + WASM_UNREACHABLE(); } } @@ -1318,13 +1467,18 @@ private: case 0: { // totally random, entire range switch (type) { - case i32: return Literal(get32()); - case i64: return Literal(get64()); - case f32: return Literal(getFloat()); - case f64: return Literal(getDouble()); + case i32: + return Literal(get32()); + case i64: + return Literal(get64()); + case f32: + return Literal(getFloat()); + case f64: + return Literal(getDouble()); case v128: case none: - case unreachable: WASM_UNREACHABLE(); + case unreachable: + WASM_UNREACHABLE(); } break; } @@ -1332,22 +1486,40 @@ private: // small range int64_t small; switch (upTo(6)) { - 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; - case 4: small = int32_t(get32()); break; - case 5: small = uint32_t(get32()); break; - default: WASM_UNREACHABLE(); + 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; + case 4: + small = int32_t(get32()); + break; + case 5: + small = uint32_t(get32()); + break; + default: + WASM_UNREACHABLE(); } switch (type) { - case i32: return Literal(int32_t(small)); - case i64: return Literal(int64_t(small)); - case f32: return Literal(float(small)); - case f64: return Literal(double(small)); + case i32: + return Literal(int32_t(small)); + case i64: + return Literal(int64_t(small)); + case f32: + return Literal(float(small)); + case f64: + return Literal(double(small)); case v128: case none: - case unreachable: WASM_UNREACHABLE(); + case unreachable: + WASM_UNREACHABLE(); } break; } @@ -1355,38 +1527,63 @@ private: // special values Literal value; switch (type) { - case i32: value = Literal(pick<int32_t>(0, - 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, - 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, - 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, - 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; + case i32: + value = + Literal(pick<int32_t>(0, + 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, + 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, + 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, + 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; case v128: case none: - case unreachable: WASM_UNREACHABLE(); + case unreachable: + WASM_UNREACHABLE(); } // tweak around special values if (oneIn(3)) { // +- 1 @@ -1401,13 +1598,22 @@ private: // powers of 2 Literal value; switch (type) { - case i32: value = Literal(int32_t(1) << upTo(32)); break; - case i64: value = Literal(int64_t(1) << upTo(64)); break; - case f32: value = Literal(float(int64_t(1) << upTo(64))); break; - case f64: value = Literal(double(int64_t(1) << upTo(64))); break; + case i32: + value = Literal(int32_t(1) << upTo(32)); + break; + case i64: + value = Literal(int64_t(1) << upTo(64)); + break; + case f32: + value = Literal(float(int64_t(1) << upTo(64))); + break; + case f64: + value = Literal(double(int64_t(1) << upTo(64))); + break; case v128: case none: - case unreachable: WASM_UNREACHABLE(); + case unreachable: + WASM_UNREACHABLE(); } // maybe negative if (oneIn(2)) { @@ -1452,105 +1658,178 @@ private: case i32: { auto op = pick( FeatureOptions<UnaryOp>() - .add(FeatureSet::MVP, EqZInt32, ClzInt32, CtzInt32, PopcntInt32) - .add(FeatureSet::Atomics, ExtendS8Int32, ExtendS16Int32) - ); - return buildUnary({ op, make(i32) }); + .add(FeatureSet::MVP, EqZInt32, ClzInt32, CtzInt32, PopcntInt32) + .add(FeatureSet::Atomics, ExtendS8Int32, ExtendS16Int32)); + return buildUnary({op, make(i32)}); } - case i64: return buildUnary({ pick(EqZInt64, WrapInt64), make(i64) }); + case i64: + return buildUnary({pick(EqZInt64, WrapInt64), make(i64)}); case f32: { - auto op = pick( - FeatureOptions<UnaryOp>() - .add(FeatureSet::MVP, TruncSFloat32ToInt32, TruncUFloat32ToInt32, ReinterpretFloat32) - .add(FeatureSet::TruncSat, TruncSatSFloat32ToInt32, TruncSatUFloat32ToInt32) - ); - return buildUnary({ op, make(f32) }); + auto op = pick(FeatureOptions<UnaryOp>() + .add(FeatureSet::MVP, + TruncSFloat32ToInt32, + TruncUFloat32ToInt32, + ReinterpretFloat32) + .add(FeatureSet::TruncSat, + TruncSatSFloat32ToInt32, + TruncSatUFloat32ToInt32)); + return buildUnary({op, make(f32)}); } case f64: { - auto op = pick( - FeatureOptions<UnaryOp>() - .add(FeatureSet::MVP, TruncSFloat64ToInt32, TruncUFloat64ToInt32) - .add(FeatureSet::TruncSat, TruncSatSFloat64ToInt32, TruncSatUFloat64ToInt32) - ); - return buildUnary({ op, make(f64) }); + auto op = pick(FeatureOptions<UnaryOp>() + .add(FeatureSet::MVP, + TruncSFloat64ToInt32, + TruncUFloat64ToInt32) + .add(FeatureSet::TruncSat, + TruncSatSFloat64ToInt32, + TruncSatUFloat64ToInt32)); + return buildUnary({op, make(f64)}); } case v128: { assert(wasm.features.hasSIMD()); - return buildUnary({ pick(AnyTrueVecI8x16, AllTrueVecI8x16, AnyTrueVecI16x8, AllTrueVecI16x8, - AnyTrueVecI32x4, AllTrueVecI32x4, AnyTrueVecI64x2, AllTrueVecI64x2), - make(v128) }); + return buildUnary({pick(AnyTrueVecI8x16, + AllTrueVecI8x16, + AnyTrueVecI16x8, + AllTrueVecI16x8, + AnyTrueVecI32x4, + AllTrueVecI32x4, + AnyTrueVecI64x2, + AllTrueVecI64x2), + make(v128)}); } case none: - case unreachable: WASM_UNREACHABLE(); + case unreachable: + WASM_UNREACHABLE(); } WASM_UNREACHABLE(); } case i64: { switch (upTo(4)) { case 0: { - auto op = pick( - FeatureOptions<UnaryOp>() - .add(FeatureSet::MVP, ClzInt64, CtzInt64, PopcntInt64) - .add(FeatureSet::Atomics, ExtendS8Int64, ExtendS16Int64, ExtendS32Int64) - ); - return buildUnary({ op, make(i64) }); + auto op = + pick(FeatureOptions<UnaryOp>() + .add(FeatureSet::MVP, ClzInt64, CtzInt64, PopcntInt64) + .add(FeatureSet::Atomics, + ExtendS8Int64, + ExtendS16Int64, + ExtendS32Int64)); + return buildUnary({op, make(i64)}); } - case 1: return buildUnary({ pick(ExtendSInt32, ExtendUInt32), make(i32) }); + case 1: + return buildUnary({pick(ExtendSInt32, ExtendUInt32), make(i32)}); case 2: { - auto op = pick( - FeatureOptions<UnaryOp>() - .add(FeatureSet::MVP, TruncSFloat32ToInt64, TruncUFloat32ToInt64) - .add(FeatureSet::TruncSat, TruncSatSFloat32ToInt64, TruncSatUFloat32ToInt64) - ); - return buildUnary({ op, make(f32) }); + auto op = pick(FeatureOptions<UnaryOp>() + .add(FeatureSet::MVP, + TruncSFloat32ToInt64, + TruncUFloat32ToInt64) + .add(FeatureSet::TruncSat, + TruncSatSFloat32ToInt64, + TruncSatUFloat32ToInt64)); + return buildUnary({op, make(f32)}); } case 3: { - auto op = pick( - FeatureOptions<UnaryOp>() - .add(FeatureSet::MVP, TruncSFloat64ToInt64, TruncUFloat64ToInt64, ReinterpretFloat64) - .add(FeatureSet::TruncSat, TruncSatSFloat64ToInt64, TruncSatUFloat64ToInt64) - ); - return buildUnary({ op, make(f64) }); + auto op = pick(FeatureOptions<UnaryOp>() + .add(FeatureSet::MVP, + TruncSFloat64ToInt64, + TruncUFloat64ToInt64, + ReinterpretFloat64) + .add(FeatureSet::TruncSat, + TruncSatSFloat64ToInt64, + TruncSatUFloat64ToInt64)); + return buildUnary({op, make(f64)}); } } WASM_UNREACHABLE(); } case f32: { switch (upTo(4)) { - case 0: return makeDeNanOp(buildUnary({ pick(NegFloat32, AbsFloat32, CeilFloat32, FloorFloat32, TruncFloat32, NearestFloat32, SqrtFloat32), make(f32) })); - case 1: return makeDeNanOp(buildUnary({ pick(ConvertUInt32ToFloat32, ConvertSInt32ToFloat32, ReinterpretInt32), make(i32) })); - case 2: return makeDeNanOp(buildUnary({ pick(ConvertUInt64ToFloat32, ConvertSInt64ToFloat32), make(i64) })); - case 3: return makeDeNanOp(buildUnary({ DemoteFloat64, make(f64) })); + case 0: + return makeDeNanOp(buildUnary({pick(NegFloat32, + AbsFloat32, + CeilFloat32, + FloorFloat32, + TruncFloat32, + NearestFloat32, + SqrtFloat32), + make(f32)})); + case 1: + return makeDeNanOp(buildUnary({pick(ConvertUInt32ToFloat32, + ConvertSInt32ToFloat32, + ReinterpretInt32), + make(i32)})); + case 2: + return makeDeNanOp( + buildUnary({pick(ConvertUInt64ToFloat32, ConvertSInt64ToFloat32), + make(i64)})); + case 3: + return makeDeNanOp(buildUnary({DemoteFloat64, make(f64)})); } WASM_UNREACHABLE(); } case f64: { switch (upTo(4)) { - case 0: return makeDeNanOp(buildUnary({ pick(NegFloat64, AbsFloat64, CeilFloat64, FloorFloat64, TruncFloat64, NearestFloat64, SqrtFloat64), make(f64) })); - case 1: return makeDeNanOp(buildUnary({ pick(ConvertUInt32ToFloat64, ConvertSInt32ToFloat64), make(i32) })); - case 2: return makeDeNanOp(buildUnary({ pick(ConvertUInt64ToFloat64, ConvertSInt64ToFloat64, ReinterpretInt64), make(i64) })); - case 3: return makeDeNanOp(buildUnary({ PromoteFloat32, make(f32) })); + case 0: + return makeDeNanOp(buildUnary({pick(NegFloat64, + AbsFloat64, + CeilFloat64, + FloorFloat64, + TruncFloat64, + NearestFloat64, + SqrtFloat64), + make(f64)})); + case 1: + return makeDeNanOp( + buildUnary({pick(ConvertUInt32ToFloat64, ConvertSInt32ToFloat64), + make(i32)})); + case 2: + return makeDeNanOp(buildUnary({pick(ConvertUInt64ToFloat64, + ConvertSInt64ToFloat64, + ReinterpretInt64), + make(i64)})); + case 3: + return makeDeNanOp(buildUnary({PromoteFloat32, make(f32)})); } WASM_UNREACHABLE(); } case v128: { assert(wasm.features.hasSIMD()); switch (upTo(5)) { - case 0: return buildUnary({ pick(SplatVecI8x16, SplatVecI16x8, SplatVecI32x4), make(i32) }); - case 1: return buildUnary({ SplatVecI64x2, make(i64) }); - case 2: return buildUnary({ SplatVecF32x4, make(f32) }); - case 3: return buildUnary({ SplatVecF64x2, make(f64) }); - case 4: return buildUnary({ - pick(NotVec128, NegVecI8x16, NegVecI16x8, NegVecI32x4, NegVecI64x2, - AbsVecF32x4, NegVecF32x4, SqrtVecF32x4, AbsVecF64x2, NegVecF64x2, SqrtVecF64x2, - TruncSatSVecF32x4ToVecI32x4, TruncSatUVecF32x4ToVecI32x4, TruncSatSVecF64x2ToVecI64x2, TruncSatUVecF64x2ToVecI64x2, - ConvertSVecI32x4ToVecF32x4, ConvertUVecI32x4ToVecF32x4, ConvertSVecI64x2ToVecF64x2, ConvertUVecI64x2ToVecF64x2), - make(v128) }); + case 0: + return buildUnary( + {pick(SplatVecI8x16, SplatVecI16x8, SplatVecI32x4), make(i32)}); + case 1: + return buildUnary({SplatVecI64x2, make(i64)}); + case 2: + return buildUnary({SplatVecF32x4, make(f32)}); + case 3: + return buildUnary({SplatVecF64x2, make(f64)}); + case 4: + return buildUnary({pick(NotVec128, + NegVecI8x16, + NegVecI16x8, + NegVecI32x4, + NegVecI64x2, + AbsVecF32x4, + NegVecF32x4, + SqrtVecF32x4, + AbsVecF64x2, + NegVecF64x2, + SqrtVecF64x2, + TruncSatSVecF32x4ToVecI32x4, + TruncSatUVecF32x4ToVecI32x4, + TruncSatSVecF64x2ToVecI64x2, + TruncSatUVecF64x2ToVecI64x2, + ConvertSVecI32x4ToVecF32x4, + ConvertUVecI32x4ToVecF32x4, + ConvertSVecI64x2ToVecF64x2, + ConvertUVecI64x2ToVecF64x2), + make(v128)}); } WASM_UNREACHABLE(); } case none: - case unreachable: WASM_UNREACHABLE(); + case unreachable: + WASM_UNREACHABLE(); } WASM_UNREACHABLE(); } @@ -1562,7 +1841,8 @@ private: Expression* makeBinary(Type type) { if (type == unreachable) { if (auto* binary = makeBinary(getConcreteType())->dynCast<Binary>()) { - return makeDeNanOp(buildBinary({ binary->op, make(unreachable), make(unreachable) })); + return makeDeNanOp( + buildBinary({binary->op, make(unreachable), make(unreachable)})); } // give up return makeTrivial(type); @@ -1570,37 +1850,193 @@ private: switch (type) { case i32: { switch (upTo(4)) { - case 0: return buildBinary({ 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 buildBinary({ pick(EqInt64, NeInt64, LtSInt64, LtUInt64, LeSInt64, LeUInt64, GtSInt64, GtUInt64, GeSInt64, GeUInt64), make(i64), make(i64) }); - case 2: return buildBinary({ pick(EqFloat32, NeFloat32, LtFloat32, LeFloat32, GtFloat32, GeFloat32), make(f32), make(f32) }); - case 3: return buildBinary({ pick(EqFloat64, NeFloat64, LtFloat64, LeFloat64, GtFloat64, GeFloat64), make(f64), make(f64) }); + case 0: + return buildBinary({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 buildBinary({pick(EqInt64, + NeInt64, + LtSInt64, + LtUInt64, + LeSInt64, + LeUInt64, + GtSInt64, + GtUInt64, + GeSInt64, + GeUInt64), + make(i64), + make(i64)}); + case 2: + return buildBinary({pick(EqFloat32, + NeFloat32, + LtFloat32, + LeFloat32, + GtFloat32, + GeFloat32), + make(f32), + make(f32)}); + case 3: + return buildBinary({pick(EqFloat64, + NeFloat64, + LtFloat64, + LeFloat64, + GtFloat64, + GeFloat64), + make(f64), + make(f64)}); } WASM_UNREACHABLE(); } case i64: { - return buildBinary({ pick(AddInt64, SubInt64, MulInt64, DivSInt64, DivUInt64, RemSInt64, RemUInt64, AndInt64, OrInt64, XorInt64, ShlInt64, ShrUInt64, ShrSInt64, RotLInt64, RotRInt64), make(i64), make(i64) }); + return buildBinary({pick(AddInt64, + SubInt64, + MulInt64, + DivSInt64, + DivUInt64, + RemSInt64, + RemUInt64, + AndInt64, + OrInt64, + XorInt64, + ShlInt64, + ShrUInt64, + ShrSInt64, + RotLInt64, + RotRInt64), + make(i64), + make(i64)}); } case f32: { - return makeDeNanOp(buildBinary({ pick(AddFloat32, SubFloat32, MulFloat32, DivFloat32, CopySignFloat32, MinFloat32, MaxFloat32), make(f32), make(f32) })); + return makeDeNanOp(buildBinary({pick(AddFloat32, + SubFloat32, + MulFloat32, + DivFloat32, + CopySignFloat32, + MinFloat32, + MaxFloat32), + make(f32), + make(f32)})); } case f64: { - return makeDeNanOp(buildBinary({ pick(AddFloat64, SubFloat64, MulFloat64, DivFloat64, CopySignFloat64, MinFloat64, MaxFloat64), make(f64), make(f64) })); + return makeDeNanOp(buildBinary({pick(AddFloat64, + SubFloat64, + MulFloat64, + DivFloat64, + CopySignFloat64, + MinFloat64, + MaxFloat64), + make(f64), + make(f64)})); } case v128: { assert(wasm.features.hasSIMD()); - return buildBinary({ - pick(EqVecI8x16, NeVecI8x16, LtSVecI8x16, LtUVecI8x16, GtSVecI8x16, GtUVecI8x16, LeSVecI8x16, LeUVecI8x16, GeSVecI8x16, GeUVecI8x16, - EqVecI16x8, NeVecI16x8, LtSVecI16x8, LtUVecI16x8, GtSVecI16x8, GtUVecI16x8, LeSVecI16x8, LeUVecI16x8, GeSVecI16x8, GeUVecI16x8, - EqVecI32x4, NeVecI32x4, LtSVecI32x4, LtUVecI32x4, GtSVecI32x4, GtUVecI32x4, LeSVecI32x4, LeUVecI32x4, GeSVecI32x4, GeUVecI32x4, - EqVecF32x4, NeVecF32x4, LtVecF32x4, GtVecF32x4, LeVecF32x4, GeVecF32x4, EqVecF64x2, NeVecF64x2, LtVecF64x2, GtVecF64x2, LeVecF64x2, GeVecF64x2, - AndVec128, OrVec128, XorVec128, AddVecI8x16, AddSatSVecI8x16, AddSatUVecI8x16, SubVecI8x16, SubSatSVecI8x16, SubSatUVecI8x16, MulVecI8x16, - AddVecI16x8, AddSatSVecI16x8, AddSatUVecI16x8, SubVecI16x8, SubSatSVecI16x8, SubSatUVecI16x8, MulVecI16x8, AddVecI32x4, SubVecI32x4, MulVecI32x4, - AddVecI64x2, SubVecI64x2, AddVecF32x4, SubVecF32x4, MulVecF32x4, DivVecF32x4, MinVecF32x4, MaxVecF32x4, - AddVecF64x2, SubVecF64x2, MulVecF64x2, DivVecF64x2, MinVecF64x2, MaxVecF64x2), - make(v128), make(v128) }); + return buildBinary({pick(EqVecI8x16, + NeVecI8x16, + LtSVecI8x16, + LtUVecI8x16, + GtSVecI8x16, + GtUVecI8x16, + LeSVecI8x16, + LeUVecI8x16, + GeSVecI8x16, + GeUVecI8x16, + EqVecI16x8, + NeVecI16x8, + LtSVecI16x8, + LtUVecI16x8, + GtSVecI16x8, + GtUVecI16x8, + LeSVecI16x8, + LeUVecI16x8, + GeSVecI16x8, + GeUVecI16x8, + EqVecI32x4, + NeVecI32x4, + LtSVecI32x4, + LtUVecI32x4, + GtSVecI32x4, + GtUVecI32x4, + LeSVecI32x4, + LeUVecI32x4, + GeSVecI32x4, + GeUVecI32x4, + EqVecF32x4, + NeVecF32x4, + LtVecF32x4, + GtVecF32x4, + LeVecF32x4, + GeVecF32x4, + EqVecF64x2, + NeVecF64x2, + LtVecF64x2, + GtVecF64x2, + LeVecF64x2, + GeVecF64x2, + AndVec128, + OrVec128, + XorVec128, + AddVecI8x16, + AddSatSVecI8x16, + AddSatUVecI8x16, + SubVecI8x16, + SubSatSVecI8x16, + SubSatUVecI8x16, + MulVecI8x16, + AddVecI16x8, + AddSatSVecI16x8, + AddSatUVecI16x8, + SubVecI16x8, + SubSatSVecI16x8, + SubSatUVecI16x8, + MulVecI16x8, + AddVecI32x4, + SubVecI32x4, + MulVecI32x4, + AddVecI64x2, + SubVecI64x2, + AddVecF32x4, + SubVecF32x4, + MulVecF32x4, + DivVecF32x4, + MinVecF32x4, + MaxVecF32x4, + AddVecF64x2, + SubVecF64x2, + MulVecF64x2, + DivVecF64x2, + MinVecF64x2, + MaxVecF64x2), + make(v128), + make(v128)}); } case none: - case unreachable: WASM_UNREACHABLE(); + case unreachable: + WASM_UNREACHABLE(); } WASM_UNREACHABLE(); } @@ -1610,12 +2046,13 @@ private: } Expression* makeSelect(Type type) { - return makeDeNanOp(buildSelect({ make(i32), make(type), make(type) })); + return makeDeNanOp(buildSelect({make(i32), make(type), make(type)})); } Expression* makeSwitch(Type type) { assert(type == unreachable); - if (breakableStack.empty()) return make(type); + 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; @@ -1639,16 +2076,19 @@ private: } auto default_ = names.back(); names.pop_back(); - auto temp1 = make(i32), temp2 = isConcreteType(valueType) ? make(valueType) : nullptr; + auto temp1 = make(i32), + temp2 = isConcreteType(valueType) ? make(valueType) : nullptr; return builder.makeSwitch(names, default_, temp1, temp2); } Expression* makeDrop(Type type) { - return builder.makeDrop(make(type == unreachable ? type : getConcreteType())); + return builder.makeDrop( + make(type == unreachable ? type : getConcreteType())); } Expression* makeReturn(Type type) { - return builder.makeReturn(isConcreteType(func->result) ? make(func->result) : nullptr); + return builder.makeReturn(isConcreteType(func->result) ? make(func->result) + : nullptr); } Expression* makeNop(Type type) { @@ -1663,7 +2103,8 @@ private: Expression* makeAtomic(Type type) { assert(wasm.features.hasAtomics()); - if (!allowMemory) return makeTrivial(type); + if (!allowMemory) + return makeTrivial(type); wasm.memory.shared = true; if (type == i32 && oneIn(2)) { if (ATOMIC_WAITS && oneIn(2)) { @@ -1671,7 +2112,8 @@ private: auto expectedType = pick(i32, i64); auto* expected = make(expectedType); auto* timeout = make(i64); - return builder.makeAtomicWait(ptr, expected, timeout, expectedType, logify(get())); + return builder.makeAtomicWait( + ptr, expected, timeout, expectedType, logify(get())); } else { auto* ptr = makePointer(); auto* count = make(i32); @@ -1682,35 +2124,62 @@ private: switch (type) { case i32: { switch (upTo(3)) { - case 0: bytes = 1; break; - case 1: bytes = pick(1, 2); break; - case 2: bytes = pick(1, 2, 4); break; - default: WASM_UNREACHABLE(); + case 0: + bytes = 1; + break; + case 1: + bytes = pick(1, 2); + break; + case 2: + bytes = pick(1, 2, 4); + break; + default: + WASM_UNREACHABLE(); } break; } case i64: { switch (upTo(4)) { - case 0: bytes = 1; break; - case 1: bytes = pick(1, 2); break; - case 2: bytes = pick(1, 2, 4); break; - case 3: bytes = pick(1, 2, 4, 8); break; - default: WASM_UNREACHABLE(); + case 0: + bytes = 1; + break; + case 1: + bytes = pick(1, 2); + break; + case 2: + bytes = pick(1, 2, 4); + break; + case 3: + bytes = pick(1, 2, 4, 8); + break; + default: + WASM_UNREACHABLE(); } break; } - default: WASM_UNREACHABLE(); + default: + WASM_UNREACHABLE(); } auto offset = logify(get()); auto* ptr = makePointer(); if (oneIn(2)) { auto* value = make(type); - return builder.makeAtomicRMW(pick(AtomicRMWOp::Add, AtomicRMWOp::Sub, AtomicRMWOp::And, AtomicRMWOp::Or, AtomicRMWOp::Xor, AtomicRMWOp::Xchg), - bytes, offset, ptr, value, type); + return builder.makeAtomicRMW(pick(AtomicRMWOp::Add, + AtomicRMWOp::Sub, + AtomicRMWOp::And, + AtomicRMWOp::Or, + AtomicRMWOp::Xor, + AtomicRMWOp::Xchg), + bytes, + offset, + ptr, + value, + type); } else { auto* expected = make(type); auto* replacement = make(type); - return builder.makeAtomicCmpxchg(bytes, offset, ptr, expected, replacement, type); + return builder.makeAtomicCmpxchg( + bytes, offset, ptr, expected, replacement, type); } } @@ -1720,12 +2189,18 @@ private: return makeSIMDExtract(type); } switch (upTo(6)) { - case 0: return makeUnary(v128); - case 1: return makeBinary(v128); - case 2: return makeSIMDReplace(); - case 3: return makeSIMDShuffle(); - case 4: return makeSIMDBitselect(); - case 5: return makeSIMDShift(); + case 0: + return makeUnary(v128); + case 1: + return makeBinary(v128); + case 2: + return makeSIMDReplace(); + case 3: + return makeSIMDShuffle(); + case 4: + return makeSIMDBitselect(); + case 5: + return makeSIMDShift(); } WASM_UNREACHABLE(); } @@ -1733,43 +2208,87 @@ private: Expression* makeSIMDExtract(Type type) { auto op = static_cast<SIMDExtractOp>(0); switch (type) { - case i32: op = pick(ExtractLaneSVecI8x16, ExtractLaneUVecI8x16, ExtractLaneSVecI16x8, ExtractLaneUVecI16x8, ExtractLaneVecI32x4); break; - case i64: op = ExtractLaneVecI64x2; break; - case f32: op = ExtractLaneVecF32x4; break; - case f64: op = ExtractLaneVecF64x2; break; + case i32: + op = pick(ExtractLaneSVecI8x16, + ExtractLaneUVecI8x16, + ExtractLaneSVecI16x8, + ExtractLaneUVecI16x8, + ExtractLaneVecI32x4); + break; + case i64: + op = ExtractLaneVecI64x2; + break; + case f32: + op = ExtractLaneVecF32x4; + break; + case f64: + op = ExtractLaneVecF64x2; + break; case v128: case none: - case unreachable: WASM_UNREACHABLE(); + case unreachable: + WASM_UNREACHABLE(); } Expression* vec = make(v128); uint8_t index = 0; switch (op) { case ExtractLaneSVecI8x16: - case ExtractLaneUVecI8x16: index = upTo(16); break; + case ExtractLaneUVecI8x16: + index = upTo(16); + break; case ExtractLaneSVecI16x8: - case ExtractLaneUVecI16x8: index = upTo(8); break; + case ExtractLaneUVecI16x8: + index = upTo(8); + break; case ExtractLaneVecI32x4: - case ExtractLaneVecF32x4: index = upTo(4); break; + case ExtractLaneVecF32x4: + index = upTo(4); + break; case ExtractLaneVecI64x2: - case ExtractLaneVecF64x2: index = upTo(2); break; + case ExtractLaneVecF64x2: + index = upTo(2); + break; } return builder.makeSIMDExtract(op, vec, index); } Expression* makeSIMDReplace() { - SIMDReplaceOp op = pick(ReplaceLaneVecI8x16, ReplaceLaneVecI16x8, ReplaceLaneVecI32x4, - ReplaceLaneVecI64x2, ReplaceLaneVecF32x4, ReplaceLaneVecF64x2); + SIMDReplaceOp op = pick(ReplaceLaneVecI8x16, + ReplaceLaneVecI16x8, + ReplaceLaneVecI32x4, + ReplaceLaneVecI64x2, + ReplaceLaneVecF32x4, + ReplaceLaneVecF64x2); Expression* vec = make(v128); uint8_t index; Type lane_t; switch (op) { - case ReplaceLaneVecI8x16: index = upTo(16); lane_t = i32; break; - case ReplaceLaneVecI16x8: index = upTo(8); lane_t = i32; break; - case ReplaceLaneVecI32x4: index = upTo(4); lane_t = i32; break; - case ReplaceLaneVecI64x2: index = upTo(2); lane_t = i64; break; - case ReplaceLaneVecF32x4: index = upTo(4); lane_t = f32; break; - case ReplaceLaneVecF64x2: index = upTo(2); lane_t = f64; break; - default: WASM_UNREACHABLE(); + case ReplaceLaneVecI8x16: + index = upTo(16); + lane_t = i32; + break; + case ReplaceLaneVecI16x8: + index = upTo(8); + lane_t = i32; + break; + case ReplaceLaneVecI32x4: + index = upTo(4); + lane_t = i32; + break; + case ReplaceLaneVecI64x2: + index = upTo(2); + lane_t = i64; + break; + case ReplaceLaneVecF32x4: + index = upTo(4); + lane_t = f32; + break; + case ReplaceLaneVecF64x2: + index = upTo(2); + lane_t = f64; + break; + default: + WASM_UNREACHABLE(); } Expression* value = make(lane_t); return builder.makeSIMDReplace(op, vec, index, value); @@ -1793,28 +2312,44 @@ private: } Expression* makeSIMDShift() { - SIMDShiftOp op = pick(ShlVecI8x16, ShrSVecI8x16, ShrUVecI8x16, ShlVecI16x8, ShrSVecI16x8, ShrUVecI16x8, - ShlVecI32x4, ShrSVecI32x4, ShrUVecI32x4, ShlVecI64x2, ShrSVecI64x2, ShrUVecI64x2); + SIMDShiftOp op = pick(ShlVecI8x16, + ShrSVecI8x16, + ShrUVecI8x16, + ShlVecI16x8, + ShrSVecI16x8, + ShrUVecI16x8, + ShlVecI32x4, + ShrSVecI32x4, + ShrUVecI32x4, + ShlVecI64x2, + ShrSVecI64x2, + ShrUVecI64x2); Expression* vec = make(v128); Expression* shift = make(i32); return builder.makeSIMDShift(op, vec, shift); } Expression* makeBulkMemory(Type type) { - if (!allowMemory) return makeTrivial(type); + if (!allowMemory) + return makeTrivial(type); assert(wasm.features.hasBulkMemory()); assert(type == none); switch (upTo(4)) { - case 0: return makeMemoryInit(); - case 1: return makeDataDrop(); - case 2: return makeMemoryCopy(); - case 3: return makeMemoryFill(); + case 0: + return makeMemoryInit(); + case 1: + return makeDataDrop(); + case 2: + return makeMemoryCopy(); + case 3: + return makeMemoryFill(); } WASM_UNREACHABLE(); } Expression* makeMemoryInit() { - if (!allowMemory) return makeTrivial(none); + if (!allowMemory) + return makeTrivial(none); uint32_t segment = upTo(wasm.memory.segments.size()); size_t totalSize = wasm.memory.segments[segment].data.size(); size_t offsetVal = upTo(totalSize); @@ -1826,12 +2361,14 @@ private: } Expression* makeDataDrop() { - if (!allowMemory) return makeTrivial(none); + if (!allowMemory) + return makeTrivial(none); return builder.makeDataDrop(upTo(wasm.memory.segments.size())); } Expression* makeMemoryCopy() { - if (!allowMemory) return makeTrivial(none); + if (!allowMemory) + return makeTrivial(none); Expression* dest = makePointer(); Expression* source = makePointer(); Expression* size = make(i32); @@ -1839,7 +2376,8 @@ private: } Expression* makeMemoryFill() { - if (!allowMemory) return makeTrivial(none); + if (!allowMemory) + return makeTrivial(none); Expression* dest = makePointer(); Expression* value = makePointer(); Expression* size = make(i32); @@ -1850,32 +2388,33 @@ private: Expression* makeLogging() { auto type = pick(i32, i64, f32, f64); - return builder.makeCall(std::string("log-") + printType(type), { make(type) }, none); + return builder.makeCall( + std::string("log-") + printType(type), {make(type)}, none); } Expression* makeMemoryHashLogging() { auto* hash = builder.makeCall(std::string("hashMemory"), {}, i32); - return builder.makeCall(std::string("log-i32"), { hash }, none); + return builder.makeCall(std::string("log-i32"), {hash}, none); } // special getters Type getType() { return pick(FeatureOptions<Type>() - .add(FeatureSet::MVP, i32, i64, f32, f64, none, unreachable) - .add(FeatureSet::SIMD, v128)); + .add(FeatureSet::MVP, i32, i64, f32, f64, none, unreachable) + .add(FeatureSet::SIMD, v128)); } Type getReachableType() { return pick(FeatureOptions<Type>() - .add(FeatureSet::MVP, i32, i64, f32, f64, none) - .add(FeatureSet::SIMD, v128)); + .add(FeatureSet::MVP, i32, i64, f32, f64, none) + .add(FeatureSet::SIMD, v128)); } Type getConcreteType() { return pick(FeatureOptions<Type>() - .add(FeatureSet::MVP, i32, i64, f32, f64) - .add(FeatureSet::SIMD, v128)); + .add(FeatureSet::MVP, i32, i64, f32, f64) + .add(FeatureSet::SIMD, v128)); } // statistical distributions @@ -1889,7 +2428,8 @@ private: // this isn't a perfectly uniform distribution, but it's fast // and reasonable Index upTo(Index x) { - if (x == 0) return 0; + if (x == 0) + return 0; Index raw; if (x <= 255) { raw = get(); @@ -1904,9 +2444,7 @@ private: return ret; } - bool oneIn(Index x) { - return upTo(x) == 0; - } + bool oneIn(Index x) { return upTo(x) == 0; } bool onceEvery(Index x) { static int counter = 0; @@ -1916,69 +2454,60 @@ private: // apply upTo twice, generating a skewed distribution towards // low values - Index upToSquared(Index x) { - return upTo(upTo(x)); - } + Index upToSquared(Index x) { return upTo(upTo(x)); } // pick from a vector - template<typename T> - const T& vectorPick(const std::vector<T>& vec) { + template<typename T> const T& vectorPick(const std::vector<T>& vec) { 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) { + 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) { + template<typename T> T pickGivenNum(size_t num, T first) { assert(num == 0); return first; } - // Trick to avoid a bug in GCC 7.x. - // Upstream bug report: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82800 - #define GCC_VERSION (__GNUC__ * 10000 \ - + __GNUC_MINOR__ * 100 \ - + __GNUC_PATCHLEVEL__) - #if GCC_VERSION > 70000 && GCC_VERSION < 70300 - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" - #endif +// Trick to avoid a bug in GCC 7.x. +// Upstream bug report: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82800 +#define GCC_VERSION \ + (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#if GCC_VERSION > 70000 && GCC_VERSION < 70300 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif template<typename T, typename... Args> T pickGivenNum(size_t num, T first, Args... args) { - if (num == 0) return first; + if (num == 0) + return first; return pickGivenNum<T>(num - 1, args...); } - #if GCC_VERSION > 70000 && GCC_VERSION < 70300 - #pragma GCC diagnostic pop - #endif +#if GCC_VERSION > 70000 && GCC_VERSION < 70300 +#pragma GCC diagnostic pop +#endif - template<typename T> - struct FeatureOptions { - template<typename ...Ts> + template<typename T> struct FeatureOptions { + template<typename... Ts> FeatureOptions<T>& add(FeatureSet::Feature feature, T option, Ts... rest) { options[feature].push_back(option); return add(feature, rest...); } - FeatureOptions<T>& add(FeatureSet::Feature feature) { - return *this; - } + FeatureOptions<T>& add(FeatureSet::Feature feature) { return *this; } std::map<FeatureSet::Feature, std::vector<T>> options; }; - template<typename T> - const T pick(FeatureOptions<T>& picker) { + template<typename T> const T pick(FeatureOptions<T>& picker) { std::vector<T> matches; for (const auto& item : picker.options) { if (wasm.features.has(item.first)) { @@ -2012,6 +2541,7 @@ private: } // namespace wasm -// XXX Switch class has a condition?! is it real? should the node type be the value type if it exists?! +// XXX Switch class has a condition?! is it real? should the node type be the +// value type if it exists?! // TODO copy an existing function and replace just one node in it diff --git a/src/tools/js-wrapper.h b/src/tools/js-wrapper.h index 433d6fc5a..e39a015d6 100644 --- a/src/tools/js-wrapper.h +++ b/src/tools/js-wrapper.h @@ -28,7 +28,8 @@ static std::string generateJSWrapper(Module& wasm) { "}\n" "var tempRet0;\n" "var binary;\n" - "if (typeof process === 'object' && typeof require === 'function' /* node.js detection */) {\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" @@ -59,12 +60,21 @@ static std::string generateJSWrapper(Module& wasm) { " }\n" " return ret;\n" "}\n" - "var instance = new WebAssembly.Instance(new WebAssembly.Module(binary), {\n" + "var instance = new WebAssembly.Instance(new " + "WebAssembly.Module(binary), {\n" " 'fuzzing-support': {\n" - " 'log-i32': function(x) { console.log('[LoggingExternalInterface logging ' + literal(x, 'i32') + ']') },\n" - " 'log-i64': function(x, y) { console.log('[LoggingExternalInterface logging ' + literal(x, 'i32') + ' ' + literal(y, 'i32') + ']') },\n" // legalization: two i32s - " 'log-f32': function(x) { console.log('[LoggingExternalInterface logging ' + literal(x, 'f64') + ']') },\n" // legalization: an f64 - " 'log-f64': function(x) { console.log('[LoggingExternalInterface logging ' + literal(x, 'f64') + ']') },\n" + " 'log-i32': function(x) { " + "console.log('[LoggingExternalInterface logging ' + literal(x, 'i32') " + "+ ']') },\n" + " 'log-i64': function(x, y) { " + "console.log('[LoggingExternalInterface logging ' + literal(x, 'i32') " + "+ ' ' + literal(y, 'i32') + ']') },\n" // legalization: two i32s + " 'log-f32': function(x) { " + "console.log('[LoggingExternalInterface logging ' + literal(x, 'f64') " + "+ ']') },\n" // legalization: an f64 + " 'log-f64': function(x) { " + "console.log('[LoggingExternalInterface logging ' + literal(x, 'f64') " + "+ ']') },\n" " },\n" " 'env': {\n" " 'setTempRet0': function(x) { tempRet0 = x },\n" @@ -73,12 +83,16 @@ static std::string generateJSWrapper(Module& wasm) { "});\n"; for (auto& exp : wasm.exports) { auto* func = wasm.getFunctionOrNull(exp->value); - if (!func) continue; // something exported other than a function - ret += "if (instance.exports.hangLimitInitializer) instance.exports.hangLimitInitializer();\n"; + if (!func) + continue; // something exported other than a function + ret += "if (instance.exports.hangLimitInitializer) " + "instance.exports.hangLimitInitializer();\n"; ret += "try {\n"; - ret += std::string(" console.log('[fuzz-exec] calling $") + exp->name.str + "');\n"; + ret += std::string(" console.log('[fuzz-exec] calling $") + exp->name.str + + "');\n"; if (func->result != none) { - ret += std::string(" console.log('[fuzz-exec] note result: $") + exp->name.str + " => ' + literal("; + ret += std::string(" console.log('[fuzz-exec] note result: $") + + exp->name.str + " => ' + literal("; } else { ret += " "; } @@ -110,4 +124,3 @@ static std::string generateJSWrapper(Module& wasm) { } } // namespace wasm - diff --git a/src/tools/optimization-options.h b/src/tools/optimization-options.h index cf7c612ea..6fa9aac36 100644 --- a/src/tools/optimization-options.h +++ b/src/tools/optimization-options.h @@ -14,6 +14,9 @@ * limitations under the License. */ +#ifndef wasm_tools_optimization_options_h +#define wasm_tools_optimization_options_h + #include "tool-options.h" // @@ -27,101 +30,137 @@ struct OptimizationOptions : public ToolOptions { std::vector<std::string> passes; - OptimizationOptions(const std::string& command, const std::string& description) : ToolOptions(command, description) { - (*this).add("", "-O", "execute default optimization passes", - Options::Arguments::Zero, - [this](Options*, const std::string&) { - passOptions.setDefaultOptimizationOptions(); - passes.push_back(DEFAULT_OPT_PASSES); - }) - .add("", "-O0", "execute no optimization passes", - Options::Arguments::Zero, - [this](Options*, const std::string&) { - passOptions.optimizeLevel = 0; - passOptions.shrinkLevel = 0; - }) - .add("", "-O1", "execute -O1 optimization passes (quick&useful opts, useful for iteration builds)", - Options::Arguments::Zero, - [this](Options*, const std::string&) { - passOptions.optimizeLevel = 1; - passOptions.shrinkLevel = 0; - passes.push_back(DEFAULT_OPT_PASSES); - }) - .add("", "-O2", "execute -O2 optimization passes (most opts, generally gets most perf)", - Options::Arguments::Zero, - [this](Options*, const std::string&) { - passOptions.optimizeLevel = 2; - passOptions.shrinkLevel = 0; - passes.push_back(DEFAULT_OPT_PASSES); - }) - .add("", "-O3", "execute -O3 optimization passes (spends potentially a lot of time optimizing)", - Options::Arguments::Zero, - [this](Options*, const std::string&) { - passOptions.optimizeLevel = 3; - passOptions.shrinkLevel = 0; - passes.push_back(DEFAULT_OPT_PASSES); - }) - .add("", "-O4", "execute -O4 optimization passes (also flatten the IR, which can take a lot more time and memory, but is useful on more nested / complex / less-optimized input)", - Options::Arguments::Zero, - [this](Options*, const std::string&) { - passOptions.optimizeLevel = 4; - passOptions.shrinkLevel = 0; - passes.push_back(DEFAULT_OPT_PASSES); - }) - .add("", "-Os", "execute default optimization passes, focusing on code size", - Options::Arguments::Zero, - [this](Options*, const std::string&) { - passOptions.optimizeLevel = 2; - passOptions.shrinkLevel = 1; - passes.push_back(DEFAULT_OPT_PASSES); - }) - .add("", "-Oz", "execute default optimization passes, super-focusing on code size", - Options::Arguments::Zero, - [this](Options*, const std::string&) { - passOptions.optimizeLevel = 2; - passOptions.shrinkLevel = 2; - passes.push_back(DEFAULT_OPT_PASSES); - }) - .add("--optimize-level", "-ol", "How much to focus on optimizing code", - Options::Arguments::One, - [this](Options* o, const std::string& argument) { - passOptions.optimizeLevel = atoi(argument.c_str()); - }) - .add("--shrink-level", "-s", "How much to focus on shrinking code size", - Options::Arguments::One, - [this](Options* o, const std::string& argument) { - passOptions.shrinkLevel = atoi(argument.c_str()); - }) - .add("--ignore-implicit-traps", "-iit", "Optimize under the helpful assumption that no surprising traps occur (from load, div/mod, etc.)", - Options::Arguments::Zero, - [this](Options*, const std::string&) { - passOptions.ignoreImplicitTraps = true; - }) - .add("--low-memory-unused", "-lmu", "Optimize under the helpful assumption that the low 1K of memory is not used by the application", - Options::Arguments::Zero, - [this](Options*, const std::string&) { - passOptions.lowMemoryUnused = true; - }) - .add("--pass-arg", "-pa", "An argument passed along to optimization passes being run. Must be in the form KEY:VALUE", - Options::Arguments::N, - [this](Options*, const std::string& argument) { - auto colon = argument.find(':'); - if (colon == std::string::npos) { - Fatal() << "--pass-arg value must be in the form of KEY:VALUE"; - } - auto key = argument.substr(0, colon); - auto value = argument.substr(colon + 1); - passOptions.arguments[key] = value; - }); + OptimizationOptions(const std::string& command, + const std::string& description) + : ToolOptions(command, description) { + (*this) + .add("", + "-O", + "execute default optimization passes", + Options::Arguments::Zero, + [this](Options*, const std::string&) { + passOptions.setDefaultOptimizationOptions(); + passes.push_back(DEFAULT_OPT_PASSES); + }) + .add("", + "-O0", + "execute no optimization passes", + Options::Arguments::Zero, + [this](Options*, const std::string&) { + passOptions.optimizeLevel = 0; + passOptions.shrinkLevel = 0; + }) + .add("", + "-O1", + "execute -O1 optimization passes (quick&useful opts, useful for " + "iteration builds)", + Options::Arguments::Zero, + [this](Options*, const std::string&) { + passOptions.optimizeLevel = 1; + passOptions.shrinkLevel = 0; + passes.push_back(DEFAULT_OPT_PASSES); + }) + .add( + "", + "-O2", + "execute -O2 optimization passes (most opts, generally gets most perf)", + Options::Arguments::Zero, + [this](Options*, const std::string&) { + passOptions.optimizeLevel = 2; + passOptions.shrinkLevel = 0; + passes.push_back(DEFAULT_OPT_PASSES); + }) + .add("", + "-O3", + "execute -O3 optimization passes (spends potentially a lot of time " + "optimizing)", + Options::Arguments::Zero, + [this](Options*, const std::string&) { + passOptions.optimizeLevel = 3; + passOptions.shrinkLevel = 0; + passes.push_back(DEFAULT_OPT_PASSES); + }) + .add("", + "-O4", + "execute -O4 optimization passes (also flatten the IR, which can " + "take a lot more time and memory, but is useful on more nested / " + "complex / less-optimized input)", + Options::Arguments::Zero, + [this](Options*, const std::string&) { + passOptions.optimizeLevel = 4; + passOptions.shrinkLevel = 0; + passes.push_back(DEFAULT_OPT_PASSES); + }) + .add("", + "-Os", + "execute default optimization passes, focusing on code size", + Options::Arguments::Zero, + [this](Options*, const std::string&) { + passOptions.optimizeLevel = 2; + passOptions.shrinkLevel = 1; + passes.push_back(DEFAULT_OPT_PASSES); + }) + .add("", + "-Oz", + "execute default optimization passes, super-focusing on code size", + Options::Arguments::Zero, + [this](Options*, const std::string&) { + passOptions.optimizeLevel = 2; + passOptions.shrinkLevel = 2; + passes.push_back(DEFAULT_OPT_PASSES); + }) + .add("--optimize-level", + "-ol", + "How much to focus on optimizing code", + Options::Arguments::One, + [this](Options* o, const std::string& argument) { + passOptions.optimizeLevel = atoi(argument.c_str()); + }) + .add("--shrink-level", + "-s", + "How much to focus on shrinking code size", + Options::Arguments::One, + [this](Options* o, const std::string& argument) { + passOptions.shrinkLevel = atoi(argument.c_str()); + }) + .add("--ignore-implicit-traps", + "-iit", + "Optimize under the helpful assumption that no surprising traps " + "occur (from load, div/mod, etc.)", + Options::Arguments::Zero, + [this](Options*, const std::string&) { + passOptions.ignoreImplicitTraps = true; + }) + .add("--low-memory-unused", + "-lmu", + "Optimize under the helpful assumption that the low 1K of memory is " + "not used by the application", + Options::Arguments::Zero, + [this](Options*, const std::string&) { + passOptions.lowMemoryUnused = true; + }) + .add("--pass-arg", + "-pa", + "An argument passed along to optimization passes being run. Must be " + "in the form KEY:VALUE", + Options::Arguments::N, + [this](Options*, const std::string& argument) { + auto colon = argument.find(':'); + if (colon == std::string::npos) { + Fatal() << "--pass-arg value must be in the form of KEY:VALUE"; + } + auto key = argument.substr(0, colon); + auto value = argument.substr(colon + 1); + passOptions.arguments[key] = value; + }); // add passes in registry for (const auto& p : PassRegistry::get()->getRegisteredNames()) { (*this).add( - std::string("--") + p, "", PassRegistry::get()->getPassDescription(p), + std::string("--") + p, + "", + PassRegistry::get()->getPassDescription(p), Options::Arguments::Zero, - [this, p](Options*, const std::string&) { - passes.push_back(p); - } - ); + [this, p](Options*, const std::string&) { passes.push_back(p); }); } } @@ -134,13 +173,12 @@ struct OptimizationOptions : public ToolOptions { return false; } - bool runningPasses() { - return passes.size() > 0; - } + bool runningPasses() { return passes.size() > 0; } void runPasses(Module& wasm) { PassRunner passRunner(&wasm, passOptions); - if (debug) passRunner.setDebug(true); + if (debug) + passRunner.setDebug(true); for (auto& pass : passes) { if (pass == DEFAULT_OPT_PASSES) { passRunner.addDefaultOptimizationPasses(); @@ -153,3 +191,5 @@ struct OptimizationOptions : public ToolOptions { }; } // namespace wasm + +#endif diff --git a/src/tools/spec-wrapper.h b/src/tools/spec-wrapper.h index 77db8a0f4..516ce17a9 100644 --- a/src/tools/spec-wrapper.h +++ b/src/tools/spec-wrapper.h @@ -25,18 +25,31 @@ 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 + "\" "; + if (!func) + continue; // something exported other than a function + ret += std::string("(invoke \"hangLimitInitializer\") (invoke \"") + + exp->name.str + "\" "; for (Type 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; - case v128: ret += "(v128.const i32x4 0 0 0 0)"; break; + 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; + case v128: + ret += "(v128.const i32x4 0 0 0 0)"; + break; case none: - case unreachable: WASM_UNREACHABLE(); + case unreachable: + WASM_UNREACHABLE(); } ret += " "; } diff --git a/src/tools/tool-options.h b/src/tools/tool-options.h index 02572d72e..1f13063c5 100644 --- a/src/tools/tool-options.h +++ b/src/tools/tool-options.h @@ -14,9 +14,12 @@ * limitations under the License. */ +#ifndef wasm_tools_tool_options_h +#define wasm_tools_tool_options_h + #include "ir/module-utils.h" -#include "support/command-line.h" #include "pass.h" +#include "support/command-line.h" // // Shared optimization options for commandline tools @@ -28,64 +31,74 @@ struct ToolOptions : public Options { PassOptions passOptions; ToolOptions(const std::string& command, const std::string& description) - : Options(command, description) { + : Options(command, description) { (*this) - .add("--mvp-features", "-mvp", "Disable all non-MVP features", - Arguments::Zero, - [this](Options*, const std::string&) { - hasFeatureOptions = true; - enabledFeatures.makeMVP(); - disabledFeatures.setAll(); - }) - .add("--all-features", "-all", "Enable all features", - Arguments::Zero, - [this](Options*, const std::string&) { - hasFeatureOptions = true; - enabledFeatures.setAll(); - disabledFeatures.makeMVP(); - }) - .add("--detect-features", "", - "Use features from the target features section, or MVP (default)", - Arguments::Zero, - [this](Options*, const std::string&) { - hasFeatureOptions = true; - detectFeatures = true; - enabledFeatures.makeMVP(); - disabledFeatures.makeMVP(); - }); + .add("--mvp-features", + "-mvp", + "Disable all non-MVP features", + Arguments::Zero, + [this](Options*, const std::string&) { + hasFeatureOptions = true; + enabledFeatures.makeMVP(); + disabledFeatures.setAll(); + }) + .add("--all-features", + "-all", + "Enable all features", + Arguments::Zero, + [this](Options*, const std::string&) { + hasFeatureOptions = true; + enabledFeatures.setAll(); + disabledFeatures.makeMVP(); + }) + .add("--detect-features", + "", + "Use features from the target features section, or MVP (default)", + Arguments::Zero, + [this](Options*, const std::string&) { + hasFeatureOptions = true; + detectFeatures = true; + enabledFeatures.makeMVP(); + disabledFeatures.makeMVP(); + }); (*this) - .addFeature(FeatureSet::SignExt, "sign extension operations") - .addFeature(FeatureSet::Atomics, "atomic operations") - .addFeature(FeatureSet::MutableGlobals, "mutable globals") - .addFeature(FeatureSet::TruncSat, "nontrapping float-to-int operations") - .addFeature(FeatureSet::SIMD, "SIMD operations and types") - .addFeature(FeatureSet::BulkMemory, "bulk memory operations") - .add("--no-validation", "-n", - "Disables validation, assumes inputs are correct", - Options::Arguments::Zero, - [this](Options* o, const std::string& argument) { - passOptions.validate = false; - }); + .addFeature(FeatureSet::SignExt, "sign extension operations") + .addFeature(FeatureSet::Atomics, "atomic operations") + .addFeature(FeatureSet::MutableGlobals, "mutable globals") + .addFeature(FeatureSet::TruncSat, "nontrapping float-to-int operations") + .addFeature(FeatureSet::SIMD, "SIMD operations and types") + .addFeature(FeatureSet::BulkMemory, "bulk memory operations") + .add("--no-validation", + "-n", + "Disables validation, assumes inputs are correct", + Options::Arguments::Zero, + [this](Options* o, const std::string& argument) { + passOptions.validate = false; + }); } ToolOptions& addFeature(FeatureSet::Feature feature, const std::string& description) { (*this) - .add(std::string("--enable-") + FeatureSet::toString(feature), "", - std::string("Enable ") + description, Arguments::Zero, - [=](Options*, const std::string&) { - hasFeatureOptions = true; - enabledFeatures.set(feature, true); - disabledFeatures.set(feature, false); - }) + .add(std::string("--enable-") + FeatureSet::toString(feature), + "", + std::string("Enable ") + description, + Arguments::Zero, + [=](Options*, const std::string&) { + hasFeatureOptions = true; + enabledFeatures.set(feature, true); + disabledFeatures.set(feature, false); + }) - .add(std::string("--disable-") + FeatureSet::toString(feature), "", - std::string("Disable ") + description, Arguments::Zero, - [=](Options*, const std::string&) { - hasFeatureOptions = true; - enabledFeatures.set(feature, false); - disabledFeatures.set(feature, true); - }); + .add(std::string("--disable-") + FeatureSet::toString(feature), + "", + std::string("Disable ") + description, + Arguments::Zero, + [=](Options*, const std::string&) { + hasFeatureOptions = true; + enabledFeatures.set(feature, false); + disabledFeatures.set(feature, true); + }); return *this; } @@ -113,3 +126,5 @@ private: }; } // namespace wasm + +#endif diff --git a/src/tools/tool-utils.h b/src/tools/tool-utils.h index a897e01f0..9e010ae3a 100644 --- a/src/tools/tool-utils.h +++ b/src/tools/tool-utils.h @@ -34,4 +34,3 @@ inline std::string removeSpecificSuffix(std::string str, std::string suffix) { } } // namespace wasm - diff --git a/src/tools/wasm-as.cpp b/src/tools/wasm-as.cpp index 20e51e31f..253a93b3b 100644 --- a/src/tools/wasm-as.cpp +++ b/src/tools/wasm-as.cpp @@ -30,61 +30,87 @@ using namespace cashew; using namespace wasm; -int main(int argc, const char *argv[]) { +int main(int argc, const char* argv[]) { bool debugInfo = false; std::string symbolMap; std::string sourceMapFilename; std::string sourceMapUrl; - ToolOptions options("wasm-as", "Assemble a .wast (WebAssembly text format) into a .wasm (WebAssembly binary format)"); + ToolOptions options("wasm-as", + "Assemble a .wast (WebAssembly text format) into a .wasm " + "(WebAssembly binary format)"); options.extra["validate"] = "wasm"; options - .add("--output", "-o", "Output file (stdout if not specified)", - Options::Arguments::One, - [](Options *o, const std::string& argument) { - o->extra["output"] = argument; - Colors::disable(); - }) - .add("--validate", "-v", "Control validation of the output module", - Options::Arguments::One, - [](Options *o, const std::string& argument) { - if (argument != "web" && argument != "none" && argument != "wasm") { - std::cerr << "Valid arguments for --validate flag are 'wasm', 'web', and 'none'.\n"; - exit(1); - } - o->extra["validate"] = argument; - }) - .add("--debuginfo", "-g", "Emit names section and debug info", - Options::Arguments::Zero, - [&](Options *o, const std::string& arguments) { debugInfo = true; }) - .add("--source-map", "-sm", "Emit source map to the specified file", - Options::Arguments::One, - [&sourceMapFilename](Options *o, const std::string& argument) { sourceMapFilename = argument; }) - .add("--source-map-url", "-su", "Use specified string as source map URL", - Options::Arguments::One, - [&sourceMapUrl](Options *o, const std::string& argument) { sourceMapUrl = argument; }) - .add("--symbolmap", "-s", "Emit a symbol map (indexes => names)", - Options::Arguments::One, - [&](Options *o, const std::string& argument) { symbolMap = argument; }) - .add_positional("INFILE", Options::Arguments::One, - [](Options *o, const std::string& argument) { - o->extra["infile"] = argument; - }); + .add("--output", + "-o", + "Output file (stdout if not specified)", + Options::Arguments::One, + [](Options* o, const std::string& argument) { + o->extra["output"] = argument; + Colors::disable(); + }) + .add("--validate", + "-v", + "Control validation of the output module", + Options::Arguments::One, + [](Options* o, const std::string& argument) { + if (argument != "web" && argument != "none" && argument != "wasm") { + std::cerr << "Valid arguments for --validate flag are 'wasm', " + "'web', and 'none'.\n"; + exit(1); + } + o->extra["validate"] = argument; + }) + .add("--debuginfo", + "-g", + "Emit names section and debug info", + Options::Arguments::Zero, + [&](Options* o, const std::string& arguments) { debugInfo = true; }) + .add("--source-map", + "-sm", + "Emit source map to the specified file", + Options::Arguments::One, + [&sourceMapFilename](Options* o, const std::string& argument) { + sourceMapFilename = argument; + }) + .add("--source-map-url", + "-su", + "Use specified string as source map URL", + Options::Arguments::One, + [&sourceMapUrl](Options* o, const std::string& argument) { + sourceMapUrl = argument; + }) + .add("--symbolmap", + "-s", + "Emit a symbol map (indexes => names)", + Options::Arguments::One, + [&](Options* o, const std::string& argument) { symbolMap = argument; }) + .add_positional("INFILE", + Options::Arguments::One, + [](Options* o, const std::string& argument) { + o->extra["infile"] = argument; + }); options.parse(argc, argv); // default output is infile with changed suffix if (options.extra.find("output") == options.extra.end()) { - options.extra["output"] = removeSpecificSuffix(options.extra["infile"], ".wast") + ".wasm"; + options.extra["output"] = + removeSpecificSuffix(options.extra["infile"], ".wast") + ".wasm"; } - auto input(read_file<std::string>(options.extra["infile"], Flags::Text, options.debug ? Flags::Debug : Flags::Release)); + auto input( + read_file<std::string>(options.extra["infile"], + Flags::Text, + options.debug ? Flags::Debug : Flags::Release)); Module wasm; try { - if (options.debug) std::cerr << "s-parsing..." << std::endl; + if (options.debug) + std::cerr << "s-parsing..." << std::endl; SExpressionParser parser(const_cast<char*>(input.c_str())); Element& root = *parser.root; - if (options.debug) std::cerr << "w-parsing..." << std::endl; + if (options.debug) + std::cerr << "w-parsing..." << std::endl; SExpressionWasmBuilder builder(wasm, *root[0]); } catch (ParseException& p) { p.dump(std::cerr); @@ -94,15 +120,19 @@ int main(int argc, const char *argv[]) { options.applyFeatures(wasm); if (options.extra["validate"] != "none") { - if (options.debug) std::cerr << "Validating..." << std::endl; - if (!wasm::WasmValidator().validate(wasm, - WasmValidator::Globally | (options.extra["validate"] == "web" ? WasmValidator::Web : 0))) { + if (options.debug) + std::cerr << "Validating..." << std::endl; + if (!wasm::WasmValidator().validate( + wasm, + WasmValidator::Globally | + (options.extra["validate"] == "web" ? WasmValidator::Web : 0))) { WasmPrinter::printModule(&wasm); Fatal() << "Error: input module is not valid.\n"; } } - if (options.debug) std::cerr << "writing..." << std::endl; + if (options.debug) + std::cerr << "writing..." << std::endl; ModuleWriter writer; writer.setBinary(true); writer.setDebugInfo(debugInfo); @@ -115,5 +145,6 @@ int main(int argc, const char *argv[]) { } writer.write(wasm, options.extra["output"]); - if (options.debug) std::cerr << "Done." << std::endl; + if (options.debug) + std::cerr << "Done." << std::endl; } diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 420ec06ad..2050b3ebc 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -24,19 +24,19 @@ #include <memory> +#include "ir/global-utils.h" +#include "ir/import-utils.h" +#include "ir/literal-utils.h" +#include "ir/memory-utils.h" +#include "ir/module-utils.h" #include "pass.h" -#include "support/file.h" #include "support/colors.h" +#include "support/file.h" #include "tool-options.h" -#include "wasm-io.h" -#include "wasm-interpreter.h" #include "wasm-builder.h" +#include "wasm-interpreter.h" +#include "wasm-io.h" #include "wasm-validator.h" -#include "ir/memory-utils.h" -#include "ir/global-utils.h" -#include "ir/import-utils.h" -#include "ir/literal-utils.h" -#include "ir/module-utils.h" using namespace wasm; @@ -57,13 +57,9 @@ class EvallingGlobalManager { bool sealed = false; public: - void addDangerous(Name name) { - dangerousGlobals.insert(name); - } + void addDangerous(Name name) { dangerousGlobals.insert(name); } - void seal() { - sealed = true; - } + void seal() { sealed = true; } // for equality purposes, we just care about the globals // and whether they have changed @@ -78,9 +74,13 @@ public: if (dangerousGlobals.count(name) > 0) { std::string extra; if (name == "___dso_handle") { - extra = "\nrecommendation: build with -s NO_EXIT_RUNTIME=1 so that calls to atexit that use ___dso_handle are not emitted"; + extra = "\nrecommendation: build with -s NO_EXIT_RUNTIME=1 so that " + "calls to atexit that use ___dso_handle are not emitted"; } - throw FailToEvalException(std::string("tried to access a dangerous (import-initialized) global: ") + name.str + extra); + throw FailToEvalException( + std::string( + "tried to access a dangerous (import-initialized) global: ") + + name.str + extra); } return globals[name]; } @@ -91,14 +91,14 @@ public: bool found; Iterator() : found(false) {} - Iterator(Name name, Literal value) : first(name), second(value), found(true) {} + Iterator(Name name, Literal value) + : first(name), second(value), found(true) {} bool operator==(const Iterator& other) { - return first == other.first && second == other.second && found == other.found; - } - bool operator!=(const Iterator& other) { - return !(*this == other); + return first == other.first && second == other.second && + found == other.found; } + bool operator!=(const Iterator& other) { return !(*this == other); } }; Iterator find(Name name) { @@ -108,9 +108,7 @@ public: return Iterator(name, globals[name]); } - Iterator end() { - return Iterator(); - } + Iterator end() { return Iterator(); } }; // Use a ridiculously large stack size. @@ -125,25 +123,28 @@ static Index STACK_START = 1024 * 1024 * 1024 + STACK_SIZE; static Index STACK_LOWER_LIMIT = STACK_START - STACK_SIZE; static Index STACK_UPPER_LIMIT = STACK_START + STACK_SIZE; -class EvallingModuleInstance : public ModuleInstanceBase<EvallingGlobalManager, EvallingModuleInstance> { +class EvallingModuleInstance + : public ModuleInstanceBase<EvallingGlobalManager, EvallingModuleInstance> { public: - EvallingModuleInstance(Module& wasm, ExternalInterface* externalInterface) : ModuleInstanceBase(wasm, externalInterface) { - // if any global in the module has a non-const constructor, it is using a global import, - // which we don't have, and is illegal to use + EvallingModuleInstance(Module& wasm, ExternalInterface* externalInterface) + : ModuleInstanceBase(wasm, externalInterface) { + // if any global in the module has a non-const constructor, it is using a + // global import, which we don't have, and is illegal to use ModuleUtils::iterDefinedGlobals(wasm, [&](Global* global) { if (!global->init->is<Const>()) { // some constants are ok to use if (auto* get = global->init->dynCast<GetGlobal>()) { auto name = get->name; auto* import = wasm.getGlobal(name); - if (import->module == Name(ENV) && ( - import->base == STACKTOP || // stack constants are special, we handle them - import->base == STACK_MAX - )) { + if (import->module == Name(ENV) && + (import->base == + STACKTOP || // stack constants are special, we handle them + import->base == STACK_MAX)) { return; // this is fine } } - // this global is dangerously initialized by an import, so if it is used, we must fail + // this global is dangerously initialized by an import, so if it is + // used, we must fail globals.addDangerous(global->name); } }); @@ -177,13 +178,15 @@ struct CtorEvalExternalInterface : EvallingModuleInstance::ExternalInterface { ImportInfo imports(wasm_); if (auto* stackTop = imports.getImportedGlobal(ENV, STACKTOP)) { globals[stackTop->name] = Literal(int32_t(STACK_START)); - if (auto* stackTop = GlobalUtils::getGlobalInitializedToImport(wasm_, ENV, STACKTOP)) { + if (auto* stackTop = + GlobalUtils::getGlobalInitializedToImport(wasm_, ENV, STACKTOP)) { globals[stackTop->name] = Literal(int32_t(STACK_START)); } } if (auto* stackMax = imports.getImportedGlobal(ENV, STACK_MAX)) { globals[stackMax->name] = Literal(int32_t(STACK_START)); - if (auto* stackMax = GlobalUtils::getGlobalInitializedToImport(wasm_, ENV, STACK_MAX)) { + if (auto* stackMax = + GlobalUtils::getGlobalInitializedToImport(wasm_, ENV, STACK_MAX)) { globals[stackMax->name] = Literal(int32_t(STACK_START)); } } @@ -203,19 +206,26 @@ struct CtorEvalExternalInterface : EvallingModuleInstance::ExternalInterface { Literal callImport(Function* import, LiteralList& arguments) override { std::string extra; if (import->module == ENV && import->base == "___cxa_atexit") { - extra = "\nrecommendation: build with -s NO_EXIT_RUNTIME=1 so that calls to atexit are not emitted"; + extra = "\nrecommendation: build with -s NO_EXIT_RUNTIME=1 so that calls " + "to atexit are not emitted"; } - throw FailToEvalException(std::string("call import: ") + import->module.str + "." + import->base.str + extra); + throw FailToEvalException(std::string("call import: ") + + import->module.str + "." + import->base.str + + extra); } - Literal callTable(Index index, LiteralList& arguments, Type result, EvallingModuleInstance& instance) override { + Literal callTable(Index index, + LiteralList& arguments, + Type result, + EvallingModuleInstance& instance) override { // we assume the table is not modified (hmm) // look through the segments, try to find the function for (auto& segment : wasm->table.segments) { Index start; - // look for the index in this segment. if it has a constant offset, we look in - // the proper range. if it instead gets a global, we rely on the fact that when - // not dynamically linking then the table is loaded at offset 0. + // look for the index in this segment. if it has a constant offset, we + // look in the proper range. if it instead gets a global, we rely on the + // fact that when not dynamically linking then the table is loaded at + // offset 0. if (auto* c = segment.offset->dynCast<Const>()) { start = c->value.getInteger(); } else if (segment.offset->is<GetGlobal>()) { @@ -226,16 +236,20 @@ struct CtorEvalExternalInterface : EvallingModuleInstance::ExternalInterface { auto end = start + segment.data.size(); if (start <= index && index < end) { auto name = segment.data[index - start]; - // if this is one of our functions, we can call it; if it was imported, fail + // if this is one of our functions, we can call it; if it was imported, + // fail auto* func = wasm->getFunction(name); if (!func->imported()) { return instance.callFunctionInternal(name, arguments); } else { - throw FailToEvalException(std::string("callTable on imported function: ") + name.str); + throw FailToEvalException( + std::string("callTable on imported function: ") + name.str); } } } - throw FailToEvalException(std::string("callTable on index not found in static segments: ") + std::to_string(index)); + throw FailToEvalException( + std::string("callTable on index not found in static segments: ") + + std::to_string(index)); } int8_t load8s(Address addr) override { return doLoad<int8_t>(addr); } @@ -247,13 +261,21 @@ struct CtorEvalExternalInterface : EvallingModuleInstance::ExternalInterface { int64_t load64s(Address addr) override { return doLoad<int64_t>(addr); } uint64_t load64u(Address addr) override { return doLoad<uint64_t>(addr); } - void store8(Address addr, int8_t value) override { doStore<int8_t>(addr, value); } - void store16(Address addr, int16_t value) override { doStore<int16_t>(addr, value); } - void store32(Address addr, int32_t value) override { doStore<int32_t>(addr, value); } - void store64(Address addr, int64_t value) override { doStore<int64_t>(addr, value); } + void store8(Address addr, int8_t value) override { + doStore<int8_t>(addr, value); + } + void store16(Address addr, int16_t value) override { + doStore<int16_t>(addr, value); + } + void store32(Address addr, int32_t value) override { + doStore<int32_t>(addr, value); + } + void store64(Address addr, int64_t value) override { + doStore<int64_t>(addr, value); + } // called during initialization, but we don't keep track of a table - void tableStore(Address addr, Name value) override { } + void tableStore(Address addr, Name value) override {} void growMemory(Address /*oldSize*/, Address newSize) override { throw FailToEvalException("grow memory"); @@ -266,8 +288,7 @@ struct CtorEvalExternalInterface : EvallingModuleInstance::ExternalInterface { private: // TODO: handle unaligned too, see shell-interface - template<typename T> - T* getMemory(Address address) { + template<typename T> T* getMemory(Address address) { // if memory is on the stack, use the stack if (address >= STACK_LOWER_LIMIT) { if (address >= STACK_UPPER_LIMIT) { @@ -283,14 +304,11 @@ private: std::vector<char> temp; Builder builder(*wasm); wasm->memory.segments.push_back( - Memory::Segment( - builder.makeConst(Literal(int32_t(0))), - temp - ) - ); + Memory::Segment(builder.makeConst(Literal(int32_t(0))), temp)); } // memory should already have been flattened - assert(wasm->memory.segments[0].offset->cast<Const>()->value.getInteger() == 0); + assert(wasm->memory.segments[0].offset->cast<Const>()->value.getInteger() == + 0); auto max = address + sizeof(T); auto& data = wasm->memory.segments[0].data; if (max > data.size()) { @@ -299,14 +317,12 @@ private: return (T*)(&data[address]); } - template<typename T> - void doStore(Address address, T value) { + template<typename T> void doStore(Address address, T value) { // do a memcpy to avoid undefined behavior if unaligned memcpy(getMemory<T>(address), &value, sizeof(T)); } - template<typename T> - T doLoad(Address address) { + template<typename T> T doLoad(Address address) { // do a memcpy to avoid undefined behavior if unaligned T ret; memcpy(&ret, getMemory<T>(address), sizeof(T)); @@ -337,7 +353,7 @@ void evalCtors(Module& wasm, std::vector<std::string> ctors) { // snapshot globals (note that STACKTOP might be modified, but should // be returned, so that works out) auto globalsBefore = instance.globals; - Export *ex = wasm.getExportOrNull(ctor); + Export* ex = wasm.getExportOrNull(ctor); if (!ex) { Fatal() << "export not found: " << ctor; } @@ -366,7 +382,8 @@ void evalCtors(Module& wasm, std::vector<std::string> ctors) { } } catch (FailToEvalException& fail) { // that's it, we failed to even create the instance - std::cerr << " ...stopping since could not create module instance: " << fail.why << "\n"; + std::cerr << " ...stopping since could not create module instance: " + << fail.why << "\n"; return; } } @@ -382,37 +399,50 @@ int main(int argc, const char* argv[]) { bool debugInfo = false; std::string ctorsString; - ToolOptions options("wasm-ctor-eval", "Execute C++ global constructors ahead of time"); + ToolOptions options("wasm-ctor-eval", + "Execute C++ global constructors ahead of time"); options - .add("--output", "-o", "Output file (stdout if not specified)", - Options::Arguments::One, - [](Options* o, const std::string& argument) { - o->extra["output"] = argument; - Colors::disable(); - }) - .add("--emit-text", "-S", "Emit text instead of binary for the output file", - Options::Arguments::Zero, - [&](Options *o, const std::string& argument) { emitBinary = false; }) - .add("--debuginfo", "-g", "Emit names section and debug info", - Options::Arguments::Zero, - [&](Options *o, const std::string& arguments) { debugInfo = true; }) - .add("--ctors", "-c", "Comma-separated list of global constructor functions to evaluate", - Options::Arguments::One, - [&](Options* o, const std::string& argument) { - ctorsString = argument; - }) - .add_positional("INFILE", Options::Arguments::One, - [](Options* o, const std::string& argument) { - o->extra["infile"] = argument; - }); + .add("--output", + "-o", + "Output file (stdout if not specified)", + Options::Arguments::One, + [](Options* o, const std::string& argument) { + o->extra["output"] = argument; + Colors::disable(); + }) + .add("--emit-text", + "-S", + "Emit text instead of binary for the output file", + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { emitBinary = false; }) + .add("--debuginfo", + "-g", + "Emit names section and debug info", + Options::Arguments::Zero, + [&](Options* o, const std::string& arguments) { debugInfo = true; }) + .add( + "--ctors", + "-c", + "Comma-separated list of global constructor functions to evaluate", + Options::Arguments::One, + [&](Options* o, const std::string& argument) { ctorsString = argument; }) + .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)); + 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"; ModuleReader reader; reader.setDebug(options.debug); @@ -453,7 +483,8 @@ int main(int argc, const char* argv[]) { } if (options.extra.count("output") > 0) { - if (options.debug) std::cerr << "writing..." << std::endl; + if (options.debug) + std::cerr << "writing..." << std::endl; ModuleWriter writer; writer.setDebug(options.debug); writer.setBinary(emitBinary); diff --git a/src/tools/wasm-dis.cpp b/src/tools/wasm-dis.cpp index 3ff6819a5..5b9cb9804 100644 --- a/src/tools/wasm-dis.cpp +++ b/src/tools/wasm-dis.cpp @@ -27,25 +27,37 @@ using namespace cashew; using namespace wasm; -int main(int argc, const char *argv[]) { +int main(int argc, const char* argv[]) { std::string sourceMapFilename; - Options options("wasm-dis", "Un-assemble a .wasm (WebAssembly binary format) into a .wast (WebAssembly text format)"); - options.add("--output", "-o", "Output file (stdout if not specified)", - Options::Arguments::One, - [](Options *o, const std::string& argument) { - o->extra["output"] = argument; - Colors::disable(); - }) - .add("--source-map", "-sm", "Consume source map from the specified file to add location information", - Options::Arguments::One, - [&sourceMapFilename](Options *o, const std::string& argument) { sourceMapFilename = argument; }) - .add_positional("INFILE", Options::Arguments::One, - [](Options *o, const std::string& argument) { - o->extra["infile"] = argument; - }); + Options options("wasm-dis", + "Un-assemble a .wasm (WebAssembly binary format) into a " + ".wast (WebAssembly text format)"); + options + .add("--output", + "-o", + "Output file (stdout if not specified)", + Options::Arguments::One, + [](Options* o, const std::string& argument) { + o->extra["output"] = argument; + Colors::disable(); + }) + .add( + "--source-map", + "-sm", + "Consume source map from the specified file to add location information", + Options::Arguments::One, + [&sourceMapFilename](Options* o, const std::string& argument) { + sourceMapFilename = argument; + }) + .add_positional("INFILE", + Options::Arguments::One, + [](Options* o, const std::string& argument) { + o->extra["infile"] = argument; + }); options.parse(argc, argv); - if (options.debug) std::cerr << "parsing binary..." << std::endl; + if (options.debug) + std::cerr << "parsing binary..." << std::endl; Module wasm; try { ModuleReader().readBinary(options.extra["infile"], wasm, sourceMapFilename); @@ -59,10 +71,14 @@ int main(int argc, const char *argv[]) { Fatal() << "error in parsing wasm source mapping"; } - if (options.debug) std::cerr << "Printing..." << std::endl; - Output output(options.extra["output"], Flags::Text, options.debug ? Flags::Debug : Flags::Release); + if (options.debug) + std::cerr << "Printing..." << std::endl; + Output output(options.extra["output"], + Flags::Text, + options.debug ? Flags::Debug : Flags::Release); WasmPrinter::printModule(&wasm, output.getStream()); output << '\n'; - if (options.debug) std::cerr << "Done." << std::endl; + if (options.debug) + std::cerr << "Done." << std::endl; } diff --git a/src/tools/wasm-emscripten-finalize.cpp b/src/tools/wasm-emscripten-finalize.cpp index 46fb0533b..a23ba1c5a 100644 --- a/src/tools/wasm-emscripten-finalize.cpp +++ b/src/tools/wasm-emscripten-finalize.cpp @@ -21,6 +21,7 @@ #include <exception> +#include "abi/js.h" #include "ir/trapping.h" #include "support/colors.h" #include "support/file.h" @@ -30,12 +31,11 @@ #include "wasm-io.h" #include "wasm-printing.h" #include "wasm-validator.h" -#include "abi/js.h" using namespace cashew; using namespace wasm; -int main(int argc, const char *argv[]) { +int main(int argc, const char* argv[]) { const uint64_t INVALID_BASE = -1; std::string infile; @@ -53,60 +53,86 @@ int main(int argc, const char *argv[]) { ToolOptions options("wasm-emscripten-finalize", "Performs Emscripten-specific transforms on .wasm files"); options - .add("--output", "-o", "Output file", - Options::Arguments::One, - [&outfile](Options*, const std::string& argument) { - outfile = argument; - Colors::disable(); - }) - .add("--debuginfo", "-g", - "Emit names section in wasm binary (or full debuginfo in wast)", - Options::Arguments::Zero, - [&debugInfo](Options *, const std::string &) { - debugInfo = true; - }) - .add("--emit-text", "-S", "Emit text instead of binary for the output file", - Options::Arguments::Zero, - [&emitBinary](Options*, const std::string& ) { - emitBinary = false; - }) - .add("--global-base", "", "The address at which static globals were placed", - Options::Arguments::One, - [&globalBase](Options*, const std::string&argument ) { - globalBase = std::stoull(argument); - }) - .add("--initial-stack-pointer", "", "The initial location of the stack pointer", - Options::Arguments::One, - [&initialStackPointer](Options*, const std::string&argument ) { - initialStackPointer = std::stoull(argument); - }) - .add("--side-module", "", "Input is an emscripten side module", - Options::Arguments::Zero, - [&isSideModule](Options *o, const std::string& argument) { - isSideModule = true; - }) - .add("--input-source-map", "-ism", "Consume source map from the specified file", - Options::Arguments::One, - [&inputSourceMapFilename](Options *o, const std::string& argument) { inputSourceMapFilename = argument; }) - .add("--no-legalize-javascript-ffi", "-nj", "Do not fully legalize (i64->i32, " - "f32->f64) the imports and exports for interfacing with JS", - Options::Arguments::Zero, - [&legalizeJavaScriptFFI](Options *o, const std::string& ) { - legalizeJavaScriptFFI = false; - }) - .add("--output-source-map", "-osm", "Emit source map to the specified file", - Options::Arguments::One, - [&outputSourceMapFilename](Options *o, const std::string& argument) { outputSourceMapFilename = argument; }) - .add("--output-source-map-url", "-osu", "Emit specified string as source map URL", - Options::Arguments::One, - [&outputSourceMapUrl](Options *o, const std::string& argument) { outputSourceMapUrl = argument; }) - .add("--separate-data-segments", "", "Separate data segments to a file", - Options::Arguments::One, - [&dataSegmentFile](Options *o, const std::string& argument) { dataSegmentFile = argument;}) - .add_positional("INFILE", Options::Arguments::One, - [&infile](Options *o, const std::string& argument) { - infile = argument; - }); + .add("--output", + "-o", + "Output file", + Options::Arguments::One, + [&outfile](Options*, const std::string& argument) { + outfile = argument; + Colors::disable(); + }) + .add("--debuginfo", + "-g", + "Emit names section in wasm binary (or full debuginfo in wast)", + Options::Arguments::Zero, + [&debugInfo](Options*, const std::string&) { debugInfo = true; }) + .add("--emit-text", + "-S", + "Emit text instead of binary for the output file", + Options::Arguments::Zero, + [&emitBinary](Options*, const std::string&) { emitBinary = false; }) + .add("--global-base", + "", + "The address at which static globals were placed", + Options::Arguments::One, + [&globalBase](Options*, const std::string& argument) { + globalBase = std::stoull(argument); + }) + .add("--initial-stack-pointer", + "", + "The initial location of the stack pointer", + Options::Arguments::One, + [&initialStackPointer](Options*, const std::string& argument) { + initialStackPointer = std::stoull(argument); + }) + .add("--side-module", + "", + "Input is an emscripten side module", + Options::Arguments::Zero, + [&isSideModule](Options* o, const std::string& argument) { + isSideModule = true; + }) + .add("--input-source-map", + "-ism", + "Consume source map from the specified file", + Options::Arguments::One, + [&inputSourceMapFilename](Options* o, const std::string& argument) { + inputSourceMapFilename = argument; + }) + .add("--no-legalize-javascript-ffi", + "-nj", + "Do not fully legalize (i64->i32, " + "f32->f64) the imports and exports for interfacing with JS", + Options::Arguments::Zero, + [&legalizeJavaScriptFFI](Options* o, const std::string&) { + legalizeJavaScriptFFI = false; + }) + .add("--output-source-map", + "-osm", + "Emit source map to the specified file", + Options::Arguments::One, + [&outputSourceMapFilename](Options* o, const std::string& argument) { + outputSourceMapFilename = argument; + }) + .add("--output-source-map-url", + "-osu", + "Emit specified string as source map URL", + Options::Arguments::One, + [&outputSourceMapUrl](Options* o, const std::string& argument) { + outputSourceMapUrl = argument; + }) + .add("--separate-data-segments", + "", + "Separate data segments to a file", + Options::Arguments::One, + [&dataSegmentFile](Options* o, const std::string& argument) { + dataSegmentFile = argument; + }) + .add_positional("INFILE", + Options::Arguments::One, + [&infile](Options* o, const std::string& argument) { + infile = argument; + }); options.parse(argc, argv); if (infile == "") { @@ -167,10 +193,12 @@ int main(int argc, const char *argv[]) { std::vector<Name> initializerFunctions; if (wasm.table.imported()) { - if (wasm.table.base != "table") wasm.table.base = Name("table"); + if (wasm.table.base != "table") + wasm.table.base = Name("table"); } if (wasm.memory.imported()) { - if (wasm.table.base != "memory") wasm.memory.base = Name("memory"); + if (wasm.table.base != "memory") + wasm.memory.base = Name("memory"); } wasm.updateMaps(); @@ -204,13 +232,13 @@ int main(int argc, const char *argv[]) { passRunner.setDebugInfo(debugInfo); passRunner.add(ABI::getLegalizationPass( legalizeJavaScriptFFI ? ABI::LegalizationLevel::Full - : ABI::LegalizationLevel::Minimal - )); + : ABI::LegalizationLevel::Minimal)); passRunner.run(); } // Substantial changes to the wasm are done, enough to create the metadata. - std::string metadata = generator.generateEmscriptenMetadata(dataSize, initializerFunctions); + std::string metadata = + generator.generateEmscriptenMetadata(dataSize, initializerFunctions); // Finally, separate out data segments if relevant (they may have been needed // for metadata). diff --git a/src/tools/wasm-metadce.cpp b/src/tools/wasm-metadce.cpp index adb623ea0..a6f5bb012 100644 --- a/src/tools/wasm-metadce.cpp +++ b/src/tools/wasm-metadce.cpp @@ -26,14 +26,14 @@ #include <memory> +#include "ir/module-utils.h" #include "pass.h" +#include "support/colors.h" #include "support/command-line.h" #include "support/file.h" #include "support/json.h" -#include "support/colors.h" -#include "wasm-io.h" #include "wasm-builder.h" -#include "ir/module-utils.h" +#include "wasm-io.h" using namespace wasm; @@ -51,9 +51,10 @@ struct MetaDCEGraph { std::unordered_map<Name, DCENode> nodes; std::unordered_set<Name> roots; - std::unordered_map<Name, Name> exportToDCENode; // export exported name => DCE name + // export exported name => DCE name + std::unordered_map<Name, Name> exportToDCENode; std::unordered_map<Name, Name> functionToDCENode; // function name => DCE name - std::unordered_map<Name, Name> globalToDCENode; // global name => DCE name + std::unordered_map<Name, Name> globalToDCENode; // global name => DCE name std::unordered_map<Name, Name> DCENodeToExport; // reverse maps std::unordered_map<Name, Name> DCENodeToFunction; @@ -79,18 +80,20 @@ struct MetaDCEGraph { return getImportId(imp->module, imp->base); } - std::unordered_map<Name, Name> importIdToDCENode; // import module.base => DCE name + // import module.base => DCE name + std::unordered_map<Name, Name> importIdToDCENode; Module& wasm; MetaDCEGraph(Module& wasm) : wasm(wasm) {} - // populate the graph with info from the wasm, integrating with potentially-existing - // nodes for imports and exports that the graph may already contain. + // populate the graph with info from the wasm, integrating with + // potentially-existing nodes for imports and exports that the graph may + // already contain. void scanWebAssembly() { // Add an entry for everything we might need ahead of time, so parallel work - // does not alter parent state, just adds to things pointed by it, independently - // (each thread will add for one function, etc.) + // does not alter parent state, just adds to things pointed by it, + // independently (each thread will add for one function, etc.) ModuleUtils::iterDefinedFunctions(wasm, [&](Function* func) { auto dceName = getName("func", func->name.str); DCENodeToFunction[dceName] = func->name; @@ -103,7 +106,8 @@ struct MetaDCEGraph { globalToDCENode[global->name] = dceName; nodes[dceName] = DCENode(dceName); }); - // only process function and global imports - the table and memory are always there + // only process function and global imports - the table and memory are + // always there ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) { auto id = getImportId(import->module, import->base); if (importIdToDCENode.find(id) == importIdToDCENode.end()) { @@ -131,13 +135,15 @@ struct MetaDCEGraph { if (!wasm.getFunction(exp->value)->imported()) { node.reaches.push_back(functionToDCENode[exp->value]); } else { - node.reaches.push_back(importIdToDCENode[getFunctionImportId(exp->value)]); + node.reaches.push_back( + importIdToDCENode[getFunctionImportId(exp->value)]); } } else if (exp->kind == ExternalKind::Global) { if (!wasm.getGlobal(exp->value)->imported()) { node.reaches.push_back(globalToDCENode[exp->value]); } else { - node.reaches.push_back(importIdToDCENode[getGlobalImportId(exp->value)]); + node.reaches.push_back( + importIdToDCENode[getGlobalImportId(exp->value)]); } } } @@ -145,14 +151,11 @@ struct MetaDCEGraph { // if we provide a parent DCE name, that is who can reach what we see // if none is provided, then it is something we must root struct InitScanner : public PostWalker<InitScanner> { - InitScanner(MetaDCEGraph* parent, Name parentDceName) : parent(parent), parentDceName(parentDceName) {} + InitScanner(MetaDCEGraph* parent, Name parentDceName) + : parent(parent), parentDceName(parentDceName) {} - void visitGetGlobal(GetGlobal* curr) { - handleGlobal(curr->name); - } - void visitSetGlobal(SetGlobal* curr) { - handleGlobal(curr->name); - } + void visitGetGlobal(GetGlobal* curr) { handleGlobal(curr->name); } + void visitSetGlobal(SetGlobal* curr) { handleGlobal(curr->name); } private: MetaDCEGraph* parent; @@ -206,34 +209,29 @@ struct MetaDCEGraph { Scanner(MetaDCEGraph* parent) : parent(parent) {} - Scanner* create() override { - return new Scanner(parent); - } + Scanner* create() override { return new Scanner(parent); } void visitCall(Call* curr) { if (!getModule()->getFunction(curr->target)->imported()) { - parent->nodes[parent->functionToDCENode[getFunction()->name]].reaches.push_back( - parent->functionToDCENode[curr->target] - ); + parent->nodes[parent->functionToDCENode[getFunction()->name]] + .reaches.push_back(parent->functionToDCENode[curr->target]); } else { assert(parent->functionToDCENode.count(getFunction()->name) > 0); - parent->nodes[parent->functionToDCENode[getFunction()->name]].reaches.push_back( - parent->importIdToDCENode[parent->getFunctionImportId(curr->target)] - ); + parent->nodes[parent->functionToDCENode[getFunction()->name]] + .reaches.push_back( + parent + ->importIdToDCENode[parent->getFunctionImportId(curr->target)]); } } - void visitGetGlobal(GetGlobal* curr) { - handleGlobal(curr->name); - } - void visitSetGlobal(SetGlobal* curr) { - handleGlobal(curr->name); - } + void visitGetGlobal(GetGlobal* curr) { handleGlobal(curr->name); } + void visitSetGlobal(SetGlobal* curr) { handleGlobal(curr->name); } private: MetaDCEGraph* parent; void handleGlobal(Name name) { - if (!getFunction()) return; // non-function stuff (initializers) are handled separately + if (!getFunction()) + return; // non-function stuff (initializers) are handled separately Name dceName; if (!getModule()->getGlobal(name)->imported()) { // its a global @@ -242,7 +240,8 @@ struct MetaDCEGraph { // it's an import. dceName = parent->importIdToDCENode[parent->getGlobalImportId(name)]; } - parent->nodes[parent->functionToDCENode[getFunction()->name]].reaches.push_back(dceName); + parent->nodes[parent->functionToDCENode[getFunction()->name]] + .reaches.push_back(dceName); } }; @@ -256,7 +255,8 @@ private: // gets a unique name for the graph Name getName(std::string prefix1, std::string prefix2) { while (1) { - auto curr = Name(prefix1 + '$' + prefix2 + '$' + std::to_string(nameIndex++)); + auto curr = + Name(prefix1 + '$' + prefix2 + '$' + std::to_string(nameIndex++)); if (nodes.find(curr) == nodes.end()) { return curr; } @@ -305,7 +305,8 @@ public: // Now they are gone, standard optimization passes can do the rest! PassRunner passRunner(&wasm); passRunner.add("remove-unused-module-elements"); - passRunner.add("reorder-functions"); // removing functions may alter the optimum order, as # of calls can change + // removing functions may alter the optimum order, as # of calls can change + passRunner.add("reorder-functions"); passRunner.run(); } @@ -344,7 +345,8 @@ public: std::cout << " is import " << importMap[name] << '\n'; } if (DCENodeToExport.find(name) != DCENodeToExport.end()) { - std::cout << " is export " << DCENodeToExport[name].str << ", " << wasm.getExport(DCENodeToExport[name])->value << '\n'; + std::cout << " is export " << DCENodeToExport[name].str << ", " + << wasm.getExport(DCENodeToExport[name])->value << '\n'; } if (DCENodeToFunction.find(name) != DCENodeToFunction.end()) { std::cout << " is function " << DCENodeToFunction[name] << '\n'; @@ -372,86 +374,99 @@ int main(int argc, const char* argv[]) { std::string graphFile; bool dump = false; - Options options("wasm-metadce", "This tool performs dead code elimination (DCE) on a larger space " - "that the wasm module is just a part of. For example, if you have " - "JS and wasm that are connected, this can DCE the combined graph. " - "By doing so, it is able to eliminate wasm module exports, which " - "otherwise regular optimizations cannot.\n\n" - "This tool receives a representation of the reachability graph " - "that the wasm module resides in, which contains abstract nodes " - "and connections showing what they reach. Some of those nodes " - "can represent the wasm module's imports and exports. The tool " - "then completes the graph by adding the internal parts of the " - "module, and does DCE on the entire thing.\n\n" - "This tool will output a wasm module with dead code eliminated, " - "and metadata describing the things in the rest of the graph " - "that can be eliminated as well.\n\n" - "The graph description file should represent the graph in the following " - "JSON-like notation (note, this is not true JSON, things like " - "comments, escaping, single-quotes, etc. are not supported):\n\n" - " [\n" - " {\n" - " \"name\": \"entity1\",\n" - " \"reaches\": [\"entity2, \"entity3\"],\n" - " \"root\": true\n" - " },\n" - " {\n" - " \"name\": \"entity2\",\n" - " \"reaches\": [\"entity1, \"entity4\"]\n" - " },\n" - " {\n" - " \"name\": \"entity3\",\n" - " \"reaches\": [\"entity1\"],\n" - " \"export\": \"export1\"\n" - " },\n" - " {\n" - " \"name\": \"entity4\",\n" - " \"import\": [\"module\", \"import1\"]\n" - " },\n" - " ]\n\n" - "Each entity has a name and an optional list of the other " - "entities it reaches. It can also be marked as a root, " - "export (with the export string), or import (with the " - "module and import strings). DCE then computes what is " - "reachable from the roots."); + Options options( + "wasm-metadce", + "This tool performs dead code elimination (DCE) on a larger space " + "that the wasm module is just a part of. For example, if you have " + "JS and wasm that are connected, this can DCE the combined graph. " + "By doing so, it is able to eliminate wasm module exports, which " + "otherwise regular optimizations cannot.\n\n" + "This tool receives a representation of the reachability graph " + "that the wasm module resides in, which contains abstract nodes " + "and connections showing what they reach. Some of those nodes " + "can represent the wasm module's imports and exports. The tool " + "then completes the graph by adding the internal parts of the " + "module, and does DCE on the entire thing.\n\n" + "This tool will output a wasm module with dead code eliminated, " + "and metadata describing the things in the rest of the graph " + "that can be eliminated as well.\n\n" + "The graph description file should represent the graph in the following " + "JSON-like notation (note, this is not true JSON, things like " + "comments, escaping, single-quotes, etc. are not supported):\n\n" + " [\n" + " {\n" + " \"name\": \"entity1\",\n" + " \"reaches\": [\"entity2, \"entity3\"],\n" + " \"root\": true\n" + " },\n" + " {\n" + " \"name\": \"entity2\",\n" + " \"reaches\": [\"entity1, \"entity4\"]\n" + " },\n" + " {\n" + " \"name\": \"entity3\",\n" + " \"reaches\": [\"entity1\"],\n" + " \"export\": \"export1\"\n" + " },\n" + " {\n" + " \"name\": \"entity4\",\n" + " \"import\": [\"module\", \"import1\"]\n" + " },\n" + " ]\n\n" + "Each entity has a name and an optional list of the other " + "entities it reaches. It can also be marked as a root, " + "export (with the export string), or import (with the " + "module and import strings). DCE then computes what is " + "reachable from the roots."); options - .add("--output", "-o", "Output file (stdout if not specified)", - Options::Arguments::One, - [](Options* o, const std::string& argument) { - o->extra["output"] = argument; - Colors::disable(); - }) - .add("--emit-text", "-S", "Emit text instead of binary for the output file", - Options::Arguments::Zero, - [&](Options *o, const std::string& argument) { emitBinary = false; }) - .add("--debuginfo", "-g", "Emit names section and debug info", - Options::Arguments::Zero, - [&](Options *o, const std::string& arguments) { debugInfo = true; }) - .add("--graph-file", "-f", "Filename of the graph description file", - Options::Arguments::One, - [&](Options* o, const std::string& argument) { - graphFile = argument; - }) - .add("--dump", "-d", "Dump the combined graph file (useful for debugging)", - Options::Arguments::Zero, - [&](Options *o, const std::string& arguments) { dump = true; }) - .add_positional("INFILE", Options::Arguments::One, - [](Options* o, const std::string& argument) { - o->extra["infile"] = argument; - }); + .add("--output", + "-o", + "Output file (stdout if not specified)", + Options::Arguments::One, + [](Options* o, const std::string& argument) { + o->extra["output"] = argument; + Colors::disable(); + }) + .add("--emit-text", + "-S", + "Emit text instead of binary for the output file", + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { emitBinary = false; }) + .add("--debuginfo", + "-g", + "Emit names section and debug info", + Options::Arguments::Zero, + [&](Options* o, const std::string& arguments) { debugInfo = true; }) + .add("--graph-file", + "-f", + "Filename of the graph description file", + Options::Arguments::One, + [&](Options* o, const std::string& argument) { graphFile = argument; }) + .add("--dump", + "-d", + "Dump the combined graph file (useful for debugging)", + Options::Arguments::Zero, + [&](Options* o, const std::string& arguments) { dump = true; }) + .add_positional("INFILE", + Options::Arguments::One, + [](Options* o, const std::string& argument) { + o->extra["infile"] = argument; + }); options.parse(argc, argv); if (graphFile.size() == 0) { Fatal() << "no graph file provided."; } - auto input(read_file<std::string>(options.extra["infile"], Flags::Text, Flags::Release)); + auto input(read_file<std::string>( + options.extra["infile"], Flags::Text, Flags::Release)); Module wasm; { - if (options.debug) std::cerr << "reading...\n"; + if (options.debug) + std::cerr << "reading...\n"; ModuleReader reader; reader.setDebug(options.debug); @@ -463,32 +478,36 @@ int main(int argc, const char* argv[]) { } } - auto graphInput(read_file<std::string>(graphFile, Flags::Text, Flags::Release)); + auto graphInput( + read_file<std::string>(graphFile, Flags::Text, Flags::Release)); auto* copy = strdup(graphInput.c_str()); json::Value outside; outside.parse(copy); // parse the JSON into our graph, doing all the JSON parsing here, leaving // the abstract computation for the class itself - const json::IString NAME("name"), - REACHES("reaches"), - ROOT("root"), - EXPORT("export"), - IMPORT("import"); + const json::IString NAME("name"); + const json::IString REACHES("reaches"); + const json::IString ROOT("root"); + const json::IString EXPORT("export"); + const json::IString IMPORT("import"); MetaDCEGraph graph(wasm); if (!outside.isArray()) { - Fatal() << "input graph must be a JSON array of nodes. see --help for the form"; + Fatal() + << "input graph must be a JSON array of nodes. see --help for the form"; } auto size = outside.size(); for (size_t i = 0; i < size; i++) { json::Ref ref = outside[i]; if (!ref->isObject()) { - Fatal() << "nodes in input graph must be JSON objects. see --help for the form"; + Fatal() + << "nodes in input graph must be JSON objects. see --help for the form"; } if (!ref->has(NAME)) { - Fatal() << "nodes in input graph must have a name. see --help for the form"; + Fatal() + << "nodes in input graph must have a name. see --help for the form"; } DCENode node(ref[NAME]->getIString()); if (ref->has(REACHES)) { @@ -500,7 +519,8 @@ int main(int argc, const char* argv[]) { for (size_t j = 0; j < size; j++) { json::Ref name = reaches[j]; if (!name->isString()) { - Fatal() << "node.reaches items must be strings. see --help for the form"; + Fatal() + << "node.reaches items must be strings. see --help for the form"; } node.reaches.push_back(name->getIString()); } @@ -508,22 +528,26 @@ int main(int argc, const char* argv[]) { if (ref->has(ROOT)) { json::Ref root = ref[ROOT]; if (!root->isBool() || !root->getBool()) { - Fatal() << "node.root, if it exists, must be true. see --help for the form"; + Fatal() + << "node.root, if it exists, must be true. see --help for the form"; } graph.roots.insert(node.name); } if (ref->has(EXPORT)) { json::Ref exp = ref[EXPORT]; if (!exp->isString()) { - Fatal() << "node.export, if it exists, must be a string. see --help for the form"; + Fatal() << "node.export, if it exists, must be a string. see --help " + "for the form"; } graph.exportToDCENode[exp->getIString()] = node.name; graph.DCENodeToExport[node.name] = exp->getIString(); } if (ref->has(IMPORT)) { json::Ref imp = ref[IMPORT]; - if (!imp->isArray() || imp->size() != 2 || !imp[0]->isString() || !imp[1]->isString()) { - Fatal() << "node.import, if it exists, must be an array of two strings. see --help for the form"; + if (!imp->isArray() || imp->size() != 2 || !imp[0]->isString() || + !imp[1]->isString()) { + Fatal() << "node.import, if it exists, must be an array of two " + "strings. see --help for the form"; } auto id = graph.getImportId(imp[0]->getIString(), imp[1]->getIString()); graph.importIdToDCENode[id] = node.name; diff --git a/src/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp index 519713298..e9f369d22 100644 --- a/src/tools/wasm-opt.cpp +++ b/src/tools/wasm-opt.cpp @@ -21,21 +21,21 @@ #include <memory> +#include "execution-results.h" +#include "fuzzing.h" +#include "js-wrapper.h" +#include "optimization-options.h" #include "pass.h" +#include "shell-interface.h" +#include "spec-wrapper.h" #include "support/command-line.h" #include "support/file.h" +#include "wasm-binary.h" +#include "wasm-interpreter.h" +#include "wasm-io.h" #include "wasm-printing.h" #include "wasm-s-parser.h" #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 "fuzzing.h" -#include "js-wrapper.h" -#include "spec-wrapper.h" using namespace wasm; @@ -45,7 +45,7 @@ std::string runCommand(std::string command) { std::string output; const int MAX_BUFFER = 1024; char buffer[MAX_BUFFER]; - FILE *stream = popen(command.c_str(), "r"); + FILE* stream = popen(command.c_str(), "r"); while (fgets(buffer, MAX_BUFFER, stream) != NULL) { output.append(buffer); } @@ -81,69 +81,130 @@ int main(int argc, const char* argv[]) { OptimizationOptions options("wasm-opt", "Read, write, and optimize files"); options - .add("--output", "-o", "Output file (stdout if not specified)", - Options::Arguments::One, - [](Options* o, const std::string& argument) { - o->extra["output"] = argument; - Colors::disable(); - }) - .add("--emit-text", "-S", "Emit text instead of binary for the output file", - Options::Arguments::Zero, - [&](Options *o, const std::string& argument) { emitBinary = false; }) - .add("--debuginfo", "-g", "Emit names section and debug info", - Options::Arguments::Zero, - [&](Options *o, const std::string& arguments) { debugInfo = true; }) - .add("--converge", "-c", "Run passes to convergence, continuing while binary size decreases", - Options::Arguments::Zero, - [&](Options *o, const std::string& arguments) { converge = true; }) - .add("--fuzz-exec-before", "-feh", "Execute functions before optimization, helping fuzzing find bugs", - Options::Arguments::Zero, - [&](Options *o, const std::string& arguments) { fuzzExecBefore = true; }) - .add("--fuzz-exec", "-fe", "Execute functions before and after optimization, helping fuzzing find bugs", - Options::Arguments::Zero, - [&](Options *o, const std::string& arguments) { fuzzExecBefore = fuzzExecAfter = 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("--extra-fuzz-command", "-efc", "An extra command to run on the output before and after optimizing. The output is compared between the two, and an error occurs if they are not equal", - Options::Arguments::One, - [&](Options *o, const std::string& arguments) { extraFuzzCommand = arguments; }) - .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("--fuzz-passes", "-fp", "Pick a random set of passes to run, useful for fuzzing. this depends on translate-to-fuzz (it picks the passes from the input)", - Options::Arguments::Zero, - [&](Options *o, const std::string& arguments) { fuzzPasses = true; }) - .add("--no-fuzz-nans", "", "don't emit NaNs when fuzzing, and remove them at runtime as well (helps avoid nondeterminism between VMs)", - Options::Arguments::Zero, - [&](Options *o, const std::string& arguments) { fuzzNaNs = false; }) - .add("--no-fuzz-memory", "", "don't emit memory ops when fuzzing", - Options::Arguments::Zero, - [&](Options *o, const std::string& arguments) { fuzzMemory = false; }) - .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("--input-source-map", "-ism", "Consume source map from the specified file", - Options::Arguments::One, - [&inputSourceMapFilename](Options *o, const std::string& argument) { inputSourceMapFilename = argument; }) - .add("--output-source-map", "-osm", "Emit source map to the specified file", - Options::Arguments::One, - [&outputSourceMapFilename](Options *o, const std::string& argument) { outputSourceMapFilename = argument; }) - .add("--output-source-map-url", "-osu", "Emit specified string as source map URL", - Options::Arguments::One, - [&outputSourceMapUrl](Options *o, const std::string& argument) { outputSourceMapUrl = argument; }) - .add_positional("INFILE", Options::Arguments::One, - [](Options* o, const std::string& argument) { - o->extra["infile"] = argument; - }); + .add("--output", + "-o", + "Output file (stdout if not specified)", + Options::Arguments::One, + [](Options* o, const std::string& argument) { + o->extra["output"] = argument; + Colors::disable(); + }) + .add("--emit-text", + "-S", + "Emit text instead of binary for the output file", + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { emitBinary = false; }) + .add("--debuginfo", + "-g", + "Emit names section and debug info", + Options::Arguments::Zero, + [&](Options* o, const std::string& arguments) { debugInfo = true; }) + .add("--converge", + "-c", + "Run passes to convergence, continuing while binary size decreases", + Options::Arguments::Zero, + [&](Options* o, const std::string& arguments) { converge = true; }) + .add( + "--fuzz-exec-before", + "-feh", + "Execute functions before optimization, helping fuzzing find bugs", + Options::Arguments::Zero, + [&](Options* o, const std::string& arguments) { fuzzExecBefore = true; }) + .add("--fuzz-exec", + "-fe", + "Execute functions before and after optimization, helping fuzzing " + "find bugs", + Options::Arguments::Zero, + [&](Options* o, const std::string& arguments) { + fuzzExecBefore = fuzzExecAfter = 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("--extra-fuzz-command", + "-efc", + "An extra command to run on the output before and after optimizing. " + "The output is compared between the two, and an error occurs if they " + "are not equal", + Options::Arguments::One, + [&](Options* o, const std::string& arguments) { + extraFuzzCommand = arguments; + }) + .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("--fuzz-passes", + "-fp", + "Pick a random set of passes to run, useful for fuzzing. this depends " + "on translate-to-fuzz (it picks the passes from the input)", + Options::Arguments::Zero, + [&](Options* o, const std::string& arguments) { fuzzPasses = true; }) + .add("--no-fuzz-nans", + "", + "don't emit NaNs when fuzzing, and remove them at runtime as well " + "(helps avoid nondeterminism between VMs)", + Options::Arguments::Zero, + [&](Options* o, const std::string& arguments) { fuzzNaNs = false; }) + .add("--no-fuzz-memory", + "", + "don't emit memory ops when fuzzing", + Options::Arguments::Zero, + [&](Options* o, const std::string& arguments) { fuzzMemory = false; }) + .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("--input-source-map", + "-ism", + "Consume source map from the specified file", + Options::Arguments::One, + [&inputSourceMapFilename](Options* o, const std::string& argument) { + inputSourceMapFilename = argument; + }) + .add("--output-source-map", + "-osm", + "Emit source map to the specified file", + Options::Arguments::One, + [&outputSourceMapFilename](Options* o, const std::string& argument) { + outputSourceMapFilename = argument; + }) + .add("--output-source-map-url", + "-osu", + "Emit specified string as source map URL", + Options::Arguments::One, + [&outputSourceMapUrl](Options* o, const std::string& argument) { + outputSourceMapUrl = argument; + }) + .add_positional("INFILE", + Options::Arguments::One, + [](Options* o, const std::string& argument) { + o->extra["infile"] = argument; + }); options.parse(argc, argv); Module wasm; - if (options.debug) std::cerr << "reading...\n"; + if (options.debug) + std::cerr << "reading...\n"; if (!translateToFuzz) { ModuleReader reader; @@ -159,7 +220,8 @@ int main(int argc, const char* argv[]) { std::cerr << '\n'; Fatal() << "error in parsing wasm source map"; } catch (std::bad_alloc&) { - Fatal() << "error in building module, std::bad_alloc (possibly invalid request for silly amounts of memory)"; + Fatal() << "error in building module, std::bad_alloc (possibly invalid " + "request for silly amounts of memory)"; } options.applyFeatures(wasm); @@ -218,7 +280,9 @@ int main(int argc, const char* argv[]) { std::string firstOutput; if (extraFuzzCommand.size() > 0 && options.extra.count("output") > 0) { - if (options.debug) std::cerr << "writing binary before opts, for extra fuzz command..." << std::endl; + if (options.debug) + std::cerr << "writing binary before opts, for extra fuzz command..." + << std::endl; ModuleWriter writer; writer.setDebug(options.debug); writer.setBinary(emitBinary); @@ -252,12 +316,12 @@ int main(int argc, const char* argv[]) { } if (options.runningPasses()) { - if (options.debug) std::cerr << "running passes...\n"; + if (options.debug) + std::cerr << "running passes...\n"; auto runPasses = [&]() { options.runPasses(*curr); if (options.passOptions.validate) { - bool valid = - WasmValidator().validate(*curr); + bool valid = WasmValidator().validate(*curr); if (!valid) { WasmPrinter::printModule(&*curr); } @@ -276,10 +340,13 @@ int main(int argc, const char* argv[]) { }; auto lastSize = getSize(); while (1) { - if (options.debug) std::cerr << "running iteration for convergence (" << lastSize << ")...\n"; + if (options.debug) + std::cerr << "running iteration for convergence (" << lastSize + << ")...\n"; runPasses(); auto currSize = getSize(); - if (currSize >= lastSize) break; + if (currSize >= lastSize) + break; lastSize = currSize; } } @@ -292,7 +359,8 @@ int main(int argc, const char* argv[]) { if (options.extra.count("output") == 0) { std::cerr << "(no output file specified, not emitting output)\n"; } else { - if (options.debug) std::cerr << "writing..." << std::endl; + if (options.debug) + std::cerr << "writing..." << std::endl; ModuleWriter writer; writer.setDebug(options.debug); writer.setBinary(emitBinary); @@ -305,7 +373,8 @@ int main(int argc, const char* argv[]) { if (extraFuzzCommand.size() > 0) { auto secondOutput = runCommand(extraFuzzCommand); - std::cout << "[extra-fuzz-command second output:]\n" << firstOutput << '\n'; + std::cout << "[extra-fuzz-command second output:]\n" + << firstOutput << '\n'; if (firstOutput != secondOutput) { std::cerr << "extra fuzz command output differs\n"; abort(); diff --git a/src/tools/wasm-reduce.cpp b/src/tools/wasm-reduce.cpp index e064abbcb..87f64c1ae 100644 --- a/src/tools/wasm-reduce.cpp +++ b/src/tools/wasm-reduce.cpp @@ -23,22 +23,22 @@ // much more debuggable manner). // -#include <memory> #include <cstdio> #include <cstdlib> +#include <memory> +#include "ir/branch-utils.h" +#include "ir/iteration.h" +#include "ir/literal-utils.h" +#include "ir/properties.h" #include "pass.h" -#include "support/command-line.h" #include "support/colors.h" +#include "support/command-line.h" #include "support/file.h" #include "support/path.h" #include "support/timing.h" -#include "wasm-io.h" #include "wasm-builder.h" -#include "ir/branch-utils.h" -#include "ir/iteration.h" -#include "ir/literal-utils.h" -#include "ir/properties.h" +#include "wasm-io.h" #include "wasm-validator.h" #ifdef _WIN32 #ifndef NOMINMAX @@ -50,18 +50,18 @@ std::string GetLastErrorStdStr() { DWORD error = GetLastError(); if (error) { LPVOID lpMsgBuf; - DWORD bufLen = FormatMessage( - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - error, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR) &lpMsgBuf, - 0, NULL ); + DWORD bufLen = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&lpMsgBuf, + 0, + NULL); if (bufLen) { LPCSTR lpMsgStr = (LPCSTR)lpMsgBuf; - std::string result(lpMsgStr, lpMsgStr+bufLen); + std::string result(lpMsgStr, lpMsgStr + bufLen); LocalFree(lpMsgBuf); return result; } @@ -80,9 +80,7 @@ struct ProgramResult { double time; ProgramResult() = default; - ProgramResult(std::string command) { - getFromExecution(command); - } + ProgramResult(std::string command) { getFromExecution(command); } #ifdef _WIN32 void getFromExecution(std::string command) { @@ -100,9 +98,9 @@ struct ProgramResult { // Create a pipe for the child process's STDOUT. !CreatePipe(&hChildStd_OUT_Rd, &hChildStd_OUT_Wr, &saAttr, 0) || // Ensure the read handle to the pipe for STDOUT is not inherited. - !SetHandleInformation(hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0) - ) { - Fatal() << "CreatePipe \"" << command << "\" failed: " << GetLastErrorStdStr() << ".\n"; + !SetHandleInformation(hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0)) { + Fatal() << "CreatePipe \"" << command + << "\" failed: " << GetLastErrorStdStr() << ".\n"; } STARTUPINFO si; @@ -116,18 +114,19 @@ struct ProgramResult { ZeroMemory(&pi, sizeof(pi)); // Start the child process. - if (!CreateProcess(NULL, // No module name (use command line) - (LPSTR)command.c_str(),// Command line - NULL, // Process handle not inheritable - NULL, // Thread handle not inheritable - TRUE, // Set handle inheritance to TRUE - 0, // No creation flags - NULL, // Use parent's environment block - NULL, // Use parent's starting directory - &si, // Pointer to STARTUPINFO structure - &pi ) // Pointer to PROCESS_INFORMATION structure + if (!CreateProcess(NULL, // No module name (use command line) + (LPSTR)command.c_str(), // Command line + NULL, // Process handle not inheritable + NULL, // Thread handle not inheritable + TRUE, // Set handle inheritance to TRUE + 0, // No creation flags + NULL, // Use parent's environment block + NULL, // Use parent's starting directory + &si, // Pointer to STARTUPINFO structure + &pi) // Pointer to PROCESS_INFORMATION structure ) { - Fatal() << "CreateProcess \"" << command << "\" failed: " << GetLastErrorStdStr() << ".\n"; + Fatal() << "CreateProcess \"" << command + << "\" failed: " << GetLastErrorStdStr() << ".\n"; } // Wait until child process exits. @@ -156,8 +155,10 @@ struct ProgramResult { PeekNamedPipe(hChildStd_OUT_Rd, NULL, 0, NULL, &dwTotal, NULL); while (dwTotalRead < dwTotal) { - bSuccess = ReadFile(hChildStd_OUT_Rd, chBuf, BUFSIZE - 1, &dwRead, NULL); - if (!bSuccess || dwRead == 0) break; + bSuccess = + ReadFile(hChildStd_OUT_Rd, chBuf, BUFSIZE - 1, &dwRead, NULL); + if (!bSuccess || dwRead == 0) + break; chBuf[dwRead] = 0; dwTotalRead += dwRead; output.append(chBuf); @@ -166,7 +167,7 @@ struct ProgramResult { timer.stop(); time = timer.getTotal(); } -#else // POSIX +#else // POSIX // runs the command and notes the output // TODO: also stderr, not just stdout? void getFromExecution(std::string command) { @@ -174,10 +175,15 @@ struct ProgramResult { timer.start(); // do this using just core stdio.h and stdlib.h, for portability // sadly this requires two invokes - code = system(("timeout " + std::to_string(timeout) + "s " + command + " > /dev/null 2> /dev/null").c_str()); + code = system(("timeout " + std::to_string(timeout) + "s " + command + + " > /dev/null 2> /dev/null") + .c_str()); const int MAX_BUFFER = 1024; char buffer[MAX_BUFFER]; - FILE *stream = popen(("timeout " + std::to_string(timeout) + "s " + command + " 2> /dev/null").c_str(), "r"); + FILE* stream = popen( + ("timeout " + std::to_string(timeout) + "s " + command + " 2> /dev/null") + .c_str(), + "r"); while (fgets(buffer, MAX_BUFFER, stream) != NULL) { output.append(buffer); } @@ -190,16 +196,13 @@ struct ProgramResult { bool operator==(ProgramResult& other) { return code == other.code && output == other.output; } - bool operator!=(ProgramResult& other) { - return !(*this == other); - } + bool operator!=(ProgramResult& other) { return !(*this == other); } - bool failed() { - return code != 0; - } + bool failed() { return code != 0; } void dump(std::ostream& o) { - o << "[ProgramResult] code: " << code << " stdout: \n" << output << "[====]\nin " << time << " seconds\n[/ProgramResult]\n"; + o << "[ProgramResult] code: " << code << " stdout: \n" + << output << "[====]\nin " << time << " seconds\n[/ProgramResult]\n"; } }; @@ -210,7 +213,7 @@ inline std::ostream& operator<<(std::ostream& o, ProgramResult& result) { return o; } -} +} // namespace std ProgramResult expected; @@ -219,14 +222,22 @@ ProgramResult expected; // case we may try again but much later. static std::unordered_set<Name> functionsWeTriedToRemove; -struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor<Reducer>>> { +struct Reducer + : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor<Reducer>>> { std::string command, test, working; bool binary, deNan, verbose, debugInfo; // test is the file we write to that the command will operate on // working is the current temporary state, the reduction so far - Reducer(std::string command, std::string test, std::string working, bool binary, bool deNan, bool verbose, bool debugInfo) : - command(command), test(test), working(working), binary(binary), deNan(deNan), verbose(verbose), debugInfo(debugInfo) {} + Reducer(std::string command, + std::string test, + std::string working, + bool binary, + bool deNan, + bool verbose, + bool debugInfo) + : command(command), test(test), working(working), binary(binary), + deNan(deNan), verbose(verbose), debugInfo(debugInfo) {} // runs passes in order to reduce, until we can't reduce any more // the criterion here is wasm binary size @@ -261,28 +272,32 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< "--reorder-locals", "--simplify-locals --vacuum", "--strip", - "--vacuum" - }; + "--vacuum"}; auto oldSize = file_size(working); bool more = true; while (more) { - //std::cerr << "| starting passes loop iteration\n"; + // std::cerr << "| starting passes loop iteration\n"; more = false; - // try both combining with a generic shrink (so minor pass overhead is compensated for), and without + // try both combining with a generic shrink (so minor pass overhead is + // compensated for), and without for (auto pass : passes) { std::string currCommand = Path::getBinaryenBinaryTool("wasm-opt") + " "; // TODO(tlively): -all should be replaced with an option to use the // existing feature set, once implemented. currCommand += working + " -all -o " + test + " " + pass; - if (debugInfo) currCommand += " -g "; - if (verbose) std::cerr << "| trying pass command: " << currCommand << "\n"; + if (debugInfo) + currCommand += " -g "; + if (verbose) + std::cerr << "| trying pass command: " << currCommand << "\n"; if (!ProgramResult(currCommand).failed()) { auto newSize = file_size(test); if (newSize < oldSize) { // the pass didn't fail, and the size looks smaller, so promising // see if it is still has the property we are preserving if (ProgramResult(command) == expected) { - std::cerr << "| command \"" << currCommand << "\" succeeded, reduced size to " << newSize << ", and preserved the property\n"; + std::cerr << "| command \"" << currCommand + << "\" succeeded, reduced size to " << newSize + << ", and preserved the property\n"; copy_file(test, working); more = true; oldSize = newSize; @@ -291,7 +306,8 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< } } } - if (verbose) std::cerr << "| done with passes for now\n"; + if (verbose) + std::cerr << "| done with passes for now\n"; } // does one pass of slow and destructive reduction. returns whether it @@ -311,7 +327,9 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< // size should be as expected, and output should be as expected ProgramResult result; if (!writeAndTestReduction(result)) { - std::cerr << "\n|! WARNING: writing before destructive reduction fails, very unlikely reduction can work\n" << result << '\n'; + std::cerr << "\n|! WARNING: writing before destructive reduction fails, " + "very unlikely reduction can work\n" + << result << '\n'; } // destroy! walkModule(getModule()); @@ -381,15 +399,18 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< return false; } auto* curr = getCurrent(); - //std::cerr << "try " << curr << " => " << with << '\n'; - if (curr->type != with->type) return false; - if (!shouldTryToReduce()) return false; + // std::cerr << "try " << curr << " => " << with << '\n'; + if (curr->type != with->type) + return false; + if (!shouldTryToReduce()) + return false; replaceCurrent(with); if (!writeAndTestReduction()) { replaceCurrent(curr); return false; } - std::cerr << "| tryToReplaceCurrent succeeded (in " << getLocation() << ")\n"; + std::cerr << "| tryToReplaceCurrent succeeded (in " << getLocation() + << ")\n"; noteReduction(); return true; } @@ -404,38 +425,45 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< if (!isOkReplacement(with)) { return false; } - if (child->type != with->type) return false; - if (!shouldTryToReduce()) return false; + if (child->type != with->type) + return false; + if (!shouldTryToReduce()) + return false; auto* before = child; child = with; if (!writeAndTestReduction()) { child = before; return false; } - std::cerr << "| tryToReplaceChild succeeded (in " << getLocation() << ")\n"; - //std::cerr << "| " << before << " => " << with << '\n'; + std::cerr << "| tryToReplaceChild succeeded (in " << getLocation() + << ")\n"; + // std::cerr << "| " << before << " => " << with << '\n'; noteReduction(); return true; } std::string getLocation() { - if (getFunction()) return getFunction()->name.str; + if (getFunction()) + return getFunction()->name.str; return "(non-function context)"; } - // visitors. in each we try to remove code in a destructive and nontrivial way. - // "nontrivial" means something that optimization passes can't achieve, since we - // don't need to duplicate work that they do + // visitors. in each we try to remove code in a destructive and nontrivial + // way. "nontrivial" means something that optimization passes can't achieve, + // since we don't need to duplicate work that they do void visitExpression(Expression* curr) { // type-based reductions if (curr->type == none) { - if (tryToReduceCurrentToNop()) return; + if (tryToReduceCurrentToNop()) + return; } else if (isConcreteType(curr->type)) { - if (tryToReduceCurrentToConst()) return; + if (tryToReduceCurrentToConst()) + return; } else { assert(curr->type == unreachable); - if (tryToReduceCurrentToUnreachable()) return; + if (tryToReduceCurrentToUnreachable()) + return; } // specific reductions if (auto* iff = curr->dynCast<If>()) { @@ -472,11 +500,14 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< } } } else if (auto* block = curr->dynCast<Block>()) { - if (!shouldTryToReduce()) return; + if (!shouldTryToReduce()) + return; // replace a singleton auto& list = block->list; - if (list.size() == 1 && !BranchUtils::BranchSeeker::hasNamed(block, block->name)) { - if (tryToReplaceCurrent(block->list[0])) return; + if (list.size() == 1 && + !BranchUtils::BranchSeeker::hasNamed(block, block->name)) { + if (tryToReplaceCurrent(block->list[0])) + return; } // try to get rid of nops Index i = 0; @@ -504,7 +535,8 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< } return; // nothing more to do } else if (auto* loop = curr->dynCast<Loop>()) { - if (shouldTryToReduce() && !BranchUtils::BranchSeeker::hasNamed(loop, loop->name)) { + if (shouldTryToReduce() && + !BranchUtils::BranchSeeker::hasNamed(loop, loop->name)) { tryToReplaceCurrent(loop->body); } return; // nothing more to do @@ -512,73 +544,116 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< // Finally, try to replace with a child. for (auto* child : ChildIterator(curr)) { if (isConcreteType(child->type) && curr->type == none) { - if (tryToReplaceCurrent(builder->makeDrop(child))) return; + if (tryToReplaceCurrent(builder->makeDrop(child))) + return; } else { - if (tryToReplaceCurrent(child)) return; + if (tryToReplaceCurrent(child)) + return; } } // If that didn't work, try to replace with a child + a unary conversion if (isConcreteType(curr->type) && !curr->is<Unary>()) { // but not if it's already unary for (auto* child : ChildIterator(curr)) { - if (child->type == curr->type) continue; // already tried - if (!isConcreteType(child->type)) continue; // no conversion + if (child->type == curr->type) + continue; // already tried + if (!isConcreteType(child->type)) + continue; // no conversion Expression* fixed = nullptr; switch (curr->type) { case i32: { switch (child->type) { - case i32: WASM_UNREACHABLE(); - case i64: fixed = builder->makeUnary(WrapInt64, child); break; - case f32: fixed = builder->makeUnary(TruncSFloat32ToInt32, child); break; - case f64: fixed = builder->makeUnary(TruncSFloat64ToInt32, child); break; - case v128: continue; // v128 not implemented yet + case i32: + WASM_UNREACHABLE(); + case i64: + fixed = builder->makeUnary(WrapInt64, child); + break; + case f32: + fixed = builder->makeUnary(TruncSFloat32ToInt32, child); + break; + case f64: + fixed = builder->makeUnary(TruncSFloat64ToInt32, child); + break; + case v128: + continue; // v128 not implemented yet case none: - case unreachable: WASM_UNREACHABLE(); + case unreachable: + WASM_UNREACHABLE(); } break; } case i64: { switch (child->type) { - case i32: fixed = builder->makeUnary(ExtendSInt32, child); break; - case i64: WASM_UNREACHABLE(); - case f32: fixed = builder->makeUnary(TruncSFloat32ToInt64, child); break; - case f64: fixed = builder->makeUnary(TruncSFloat64ToInt64, child); break; - case v128: continue; // v128 not implemented yet + case i32: + fixed = builder->makeUnary(ExtendSInt32, child); + break; + case i64: + WASM_UNREACHABLE(); + case f32: + fixed = builder->makeUnary(TruncSFloat32ToInt64, child); + break; + case f64: + fixed = builder->makeUnary(TruncSFloat64ToInt64, child); + break; + case v128: + continue; // v128 not implemented yet case none: - case unreachable: WASM_UNREACHABLE(); + case unreachable: + WASM_UNREACHABLE(); } break; } case f32: { switch (child->type) { - case i32: fixed = builder->makeUnary(ConvertSInt32ToFloat32, child); break; - case i64: fixed = builder->makeUnary(ConvertSInt64ToFloat32, child); break; - case f32: WASM_UNREACHABLE(); - case f64: fixed = builder->makeUnary(DemoteFloat64, child); break; - case v128: continue; // v128 not implemented yet + case i32: + fixed = builder->makeUnary(ConvertSInt32ToFloat32, child); + break; + case i64: + fixed = builder->makeUnary(ConvertSInt64ToFloat32, child); + break; + case f32: + WASM_UNREACHABLE(); + case f64: + fixed = builder->makeUnary(DemoteFloat64, child); + break; + case v128: + continue; // v128 not implemented yet case none: - case unreachable: WASM_UNREACHABLE(); + case unreachable: + WASM_UNREACHABLE(); } break; } case f64: { switch (child->type) { - case i32: fixed = builder->makeUnary(ConvertSInt32ToFloat64, child); break; - case i64: fixed = builder->makeUnary(ConvertSInt64ToFloat64, child); break; - case f32: fixed = builder->makeUnary(PromoteFloat32, child); break; - case f64: WASM_UNREACHABLE(); - case v128: continue; // v128 not implemented yet + case i32: + fixed = builder->makeUnary(ConvertSInt32ToFloat64, child); + break; + case i64: + fixed = builder->makeUnary(ConvertSInt64ToFloat64, child); + break; + case f32: + fixed = builder->makeUnary(PromoteFloat32, child); + break; + case f64: + WASM_UNREACHABLE(); + case v128: + continue; // v128 not implemented yet case none: - case unreachable: WASM_UNREACHABLE(); + case unreachable: + WASM_UNREACHABLE(); } break; } - case v128: continue; // v128 not implemented yet + case v128: + continue; // v128 not implemented yet case none: - case unreachable: WASM_UNREACHABLE(); + case unreachable: + WASM_UNREACHABLE(); } assert(fixed->type == curr->type); - if (tryToReplaceCurrent(fixed)) return; + if (tryToReplaceCurrent(fixed)) + return; } } } @@ -609,7 +684,8 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< first = item; break; } - if (!first.isNull()) break; + if (!first.isNull()) + break; } visitSegmented(curr, first, 100); } @@ -627,12 +703,15 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< bool shrank = false; for (auto& segment : curr->segments) { auto& data = segment.data; - size_t skip = 1; // when we succeed, try to shrink by more and more, similar to bisection + // when we succeed, try to shrink by more and more, similar to bisection + size_t skip = 1; for (size_t i = 0; i < data.size() && !data.empty(); i++) { - if (!justShrank && !shouldTryToReduce(bonus)) continue; + if (!justShrank && !shouldTryToReduce(bonus)) + continue; auto save = data; for (size_t j = 0; j < skip; j++) { - if (!data.empty()) data.pop_back(); + if (!data.empty()) + data.pop_back(); } auto justShrank = writeAndTestReduction(); if (justShrank) { @@ -648,10 +727,13 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< } // the "opposite" of shrinking: copy a 'zero' element for (auto& segment : curr->segments) { - if (segment.data.empty()) continue; + if (segment.data.empty()) + continue; for (auto& item : segment.data) { - if (!shouldTryToReduce(bonus)) continue; - if (item == zero) continue; + if (!shouldTryToReduce(bonus)) + continue; + if (item == zero) + continue; auto save = item; item = zero; if (writeAndTestReduction()) { @@ -678,23 +760,27 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< functionNames.push_back(func->name); } size_t skip = 1; - // If we just removed some functions in the previous iteration, keep trying to remove more - // as this is one of the most efficient ways to reduce. + // If we just removed some functions in the previous iteration, keep trying + // to remove more as this is one of the most efficient ways to reduce. bool justRemoved = false; for (size_t i = 0; i < functionNames.size(); i++) { if (!justRemoved && functionsWeTriedToRemove.count(functionNames[i]) == 1 && - !shouldTryToReduce(std::max((factor / 100) + 1, 1000))) continue; + !shouldTryToReduce(std::max((factor / 100) + 1, 1000))) + continue; std::vector<Name> names; - for (size_t j = 0; names.size() < skip && i + j < functionNames.size(); j++) { + for (size_t j = 0; names.size() < skip && i + j < functionNames.size(); + j++) { auto name = functionNames[i + j]; if (module->getFunctionOrNull(name)) { names.push_back(name); functionsWeTriedToRemove.insert(name); } } - if (names.size() == 0) continue; - std::cout << "| try to remove " << names.size() << " functions (skip: " << skip << ")\n"; + if (names.size() == 0) + continue; + std::cout << "| try to remove " << names.size() + << " functions (skip: " << skip << ")\n"; justRemoved = tryToRemoveFunctions(names); if (justRemoved) { noteReduction(names.size()); @@ -712,9 +798,11 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< } skip = 1; for (size_t i = 0; i < exports.size(); i++) { - if (!shouldTryToReduce(std::max((factor / 100) + 1, 1000))) continue; + if (!shouldTryToReduce(std::max((factor / 100) + 1, 1000))) + continue; std::vector<Export> currExports; - for (size_t j = 0; currExports.size() < skip && i + j < exports.size(); j++) { + for (size_t j = 0; currExports.size() < skip && i + j < exports.size(); + j++) { auto exp = exports[i + j]; if (module->getExportOrNull(exp.name)) { currExports.push_back(exp); @@ -736,7 +824,8 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< } // If we are left with a single function that is not exported or used in // a table, that is useful as then we can change the return type. - if (module->functions.size() == 1 && module->exports.empty() && module->table.segments.empty()) { + if (module->functions.size() == 1 && module->exports.empty() && + module->table.segments.empty()) { auto* func = module->functions[0].get(); // We can't remove something that might have breaks to it. if (!func->imported() && !Properties::isNamedControlFlow(func->body)) { @@ -773,7 +862,8 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< } // remove all references to them - struct FunctionReferenceRemover : public PostWalker<FunctionReferenceRemover> { + struct FunctionReferenceRemover + : public PostWalker<FunctionReferenceRemover> { std::unordered_set<Name> names; std::vector<Name> exportsToRemove; @@ -801,9 +891,11 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< break; } } - if (!other.isNull()) break; + if (!other.isNull()) + break; } - if (other.isNull()) return; // we failed to find a replacement + if (other.isNull()) + return; // we failed to find a replacement for (auto& segment : curr->segments) { for (auto& name : segment.data) { if (names.count(name)) { @@ -822,7 +914,8 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< FunctionReferenceRemover referenceRemover(names); referenceRemover.walkModule(module.get()); - if (WasmValidator().validate(*module, WasmValidator::Globally | WasmValidator::Quiet) && + if (WasmValidator().validate( + *module, WasmValidator::Globally | WasmValidator::Quiet) && writeAndTestReduction()) { std::cerr << "| removed " << names.size() << " functions\n"; return true; @@ -836,8 +929,10 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< // try to replace condition with always true and always false void handleCondition(Expression*& condition) { - if (!condition) return; - if (condition->is<Const>()) return; + if (!condition) + return; + if (condition->is<Const>()) + return; auto* c = builder->makeConst(Literal(int32_t(0))); if (!tryToReplaceChild(condition, c)) { c->value = Literal(int32_t(1)); @@ -847,7 +942,8 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< bool tryToReduceCurrentToNop() { auto* curr = getCurrent(); - if (curr->is<Nop>()) return false; + if (curr->is<Nop>()) + return false; // try to replace with a trivial value Nop nop; if (tryToReplaceCurrent(&nop)) { @@ -860,10 +956,12 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< // try to replace a concrete value with a trivial constant bool tryToReduceCurrentToConst() { auto* curr = getCurrent(); - if (curr->is<Const>()) return false; + if (curr->is<Const>()) + return false; // try to replace with a trivial value Const* c = builder->makeConst(Literal(int32_t(0))); - if (tryToReplaceCurrent(c)) return true; + if (tryToReplaceCurrent(c)) + return true; c->value = Literal::makeFromInt32(1, curr->type); c->type = curr->type; return tryToReplaceCurrent(c); @@ -871,7 +969,8 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< bool tryToReduceCurrentToUnreachable() { auto* curr = getCurrent(); - if (curr->is<Unreachable>()) return false; + if (curr->is<Unreachable>()) + return false; // try to replace with a trivial value Unreachable un; if (tryToReplaceCurrent(&un)) { @@ -889,76 +988,87 @@ struct Reducer : public WalkerPass<PostWalker<Reducer, UnifiedExpressionVisitor< int main(int argc, const char* argv[]) { std::string input, test, working, command; - bool binary = true, - deNan = false, - verbose = false, - debugInfo = false, + bool binary = true, deNan = false, verbose = false, debugInfo = false, force = false; - Options options("wasm-reduce", "Reduce a wasm file to a smaller one that has the same behavior on a given command"); + Options options("wasm-reduce", + "Reduce a wasm file to a smaller one that has the same " + "behavior on a given command"); options - .add("--command", "-cmd", "The command to run on the test, that we want to reduce while keeping the command's output identical. " - "We look at the command's return code and stdout here (TODO: stderr), " - "and we reduce while keeping those unchanged.", - Options::Arguments::One, - [&](Options* o, const std::string& argument) { - command = argument; - }) - .add("--test", "-t", "Test file (this will be written to to test, the given command should read it when we call it)", - Options::Arguments::One, - [&](Options* o, const std::string& argument) { - test = argument; - }) - .add("--working", "-w", "Working file (this will contain the current good state while doing temporary computations, " - "and will contain the final best result at the end)", - Options::Arguments::One, - [&](Options* o, const std::string& argument) { - working = argument; - }) - .add("--binaries", "-b", "binaryen binaries location (bin/ directory)", - Options::Arguments::One, - [&](Options* o, const std::string& argument) { - // Add separator just in case - Path::setBinaryenBinDir(argument + Path::getPathSeparator()); - }) - .add("--text", "-S", "Emit intermediate files as text, instead of binary (also make sure the test and working files have a .wat or .wast suffix)", - Options::Arguments::Zero, - [&](Options* o, const std::string& argument) { - binary = false; - }) - .add("--denan", "", "Avoid nans when reducing", - Options::Arguments::Zero, - [&](Options* o, const std::string& argument) { - deNan = true; - }) - .add("--verbose", "-v", "Verbose output mode", - Options::Arguments::Zero, - [&](Options* o, const std::string& argument) { - verbose = true; - }) - .add("--debugInfo", "-g", "Keep debug info in binaries", - Options::Arguments::Zero, - [&](Options* o, const std::string& argument) { - debugInfo = true; - }) - .add("--force", "-f", "Force the reduction attempt, ignoring problems that imply it is unlikely to succeed", - Options::Arguments::Zero, - [&](Options* o, const std::string& argument) { - force = true; - }) - .add("--timeout", "-to", "A timeout to apply to each execution of the command, in seconds (default: 2)", - Options::Arguments::One, - [&](Options* o, const std::string& argument) { - timeout = atoi(argument.c_str()); - std::cout << "|applying timeout: " << timeout << "\n"; - }) - .add_positional("INFILE", Options::Arguments::One, - [&](Options* o, const std::string& argument) { - input = argument; - }); + .add("--command", + "-cmd", + "The command to run on the test, that we want to reduce while keeping " + "the command's output identical. " + "We look at the command's return code and stdout here (TODO: stderr), " + "and we reduce while keeping those unchanged.", + Options::Arguments::One, + [&](Options* o, const std::string& argument) { command = argument; }) + .add("--test", + "-t", + "Test file (this will be written to to test, the given command should " + "read it when we call it)", + Options::Arguments::One, + [&](Options* o, const std::string& argument) { test = argument; }) + .add("--working", + "-w", + "Working file (this will contain the current good state while doing " + "temporary computations, " + "and will contain the final best result at the end)", + Options::Arguments::One, + [&](Options* o, const std::string& argument) { working = argument; }) + .add("--binaries", + "-b", + "binaryen binaries location (bin/ directory)", + Options::Arguments::One, + [&](Options* o, const std::string& argument) { + // Add separator just in case + Path::setBinaryenBinDir(argument + Path::getPathSeparator()); + }) + .add("--text", + "-S", + "Emit intermediate files as text, instead of binary (also make sure " + "the test and working files have a .wat or .wast suffix)", + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { binary = false; }) + .add("--denan", + "", + "Avoid nans when reducing", + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { deNan = true; }) + .add("--verbose", + "-v", + "Verbose output mode", + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { verbose = true; }) + .add("--debugInfo", + "-g", + "Keep debug info in binaries", + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { debugInfo = true; }) + .add("--force", + "-f", + "Force the reduction attempt, ignoring problems that imply it is " + "unlikely to succeed", + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { force = true; }) + .add("--timeout", + "-to", + "A timeout to apply to each execution of the command, in seconds " + "(default: 2)", + Options::Arguments::One, + [&](Options* o, const std::string& argument) { + timeout = atoi(argument.c_str()); + std::cout << "|applying timeout: " << timeout << "\n"; + }) + .add_positional( + "INFILE", + Options::Arguments::One, + [&](Options* o, const std::string& argument) { input = argument; }); options.parse(argc, argv); - if (test.size() == 0) Fatal() << "test file not provided\n"; - if (working.size() == 0) Fatal() << "working file not provided\n"; + if (test.size() == 0) + Fatal() << "test file not provided\n"; + if (working.size() == 0) + Fatal() << "working file not provided\n"; if (!binary) { Colors::disable(); @@ -979,15 +1089,20 @@ int main(int argc, const char* argv[]) { auto stopIfNotForced = [&](std::string message, ProgramResult& result) { std::cerr << "|! " << message << '\n' << result << '\n'; if (!force) { - Fatal() << "|! stopping, as it is very unlikely reduction can succeed (use -f to ignore this check)"; + Fatal() << "|! stopping, as it is very unlikely reduction can succeed " + "(use -f to ignore this check)"; } }; if (expected.time + 1 >= timeout) { - stopIfNotForced("execution time is dangerously close to the timeout - you should probably increase the timeout", expected); + stopIfNotForced("execution time is dangerously close to the timeout - you " + "should probably increase the timeout", + expected); } - std::cerr << "|checking that command has different behavior on invalid binary (this verifies that the test file is used by the command)\n"; + std::cerr + << "|checking that command has different behavior on invalid binary (this " + "verifies that the test file is used by the command)\n"; { { std::ofstream dst(test, std::ios::binary); @@ -995,24 +1110,31 @@ int main(int argc, const char* argv[]) { } ProgramResult result(command); if (result == expected) { - stopIfNotForced("running command on an invalid module should give different results", result); + stopIfNotForced( + "running command on an invalid module should give different results", + result); } } - std::cerr << "|checking that command has expected behavior on canonicalized (read-written) binary\n"; + std::cerr << "|checking that command has expected behavior on canonicalized " + "(read-written) binary\n"; { // read and write it // TODO(tlively): -all should be replaced with an option to use the existing // feature set, once implemented. - auto cmd = Path::getBinaryenBinaryTool("wasm-opt") + " " + input + " -all -o " + test; - if (!binary) cmd += " -S"; + auto cmd = Path::getBinaryenBinaryTool("wasm-opt") + " " + input + + " -all -o " + test; + if (!binary) + cmd += " -S"; ProgramResult readWrite(cmd); if (readWrite.failed()) { stopIfNotForced("failed to read and write the binary", readWrite); } else { ProgramResult result(command); if (result != expected) { - stopIfNotForced("running command on the canonicalized module should give the same results", result); + stopIfNotForced("running command on the canonicalized module should " + "give the same results", + result); } } } @@ -1044,13 +1166,15 @@ int main(int argc, const char* argv[]) { std::cerr << "| after pass reduction: " << newSize << "\n"; // always stop after a pass reduction attempt, for final cleanup - if (stopping) break; + if (stopping) + break; // check if the full cycle (destructive/passes) has helped or not if (lastPostPassesSize && newSize >= lastPostPassesSize) { std::cerr << "| progress has stopped, skipping to the end\n"; if (factor == 1) { - // this is after doing work with factor 1, so after the remaining work, stop + // this is after doing work with factor 1, so after the remaining work, + // stop stopping = true; } else { // just try to remove all we can and finish up @@ -1059,9 +1183,10 @@ int main(int argc, const char* argv[]) { } lastPostPassesSize = newSize; - // if destructive reductions lead to useful proportionate pass reductions, keep - // going at the same factor, as pass reductions are far faster - std::cerr << "| pass progress: " << passProgress << ", last destructive: " << lastDestructiveReductions << '\n'; + // if destructive reductions lead to useful proportionate pass reductions, + // keep going at the same factor, as pass reductions are far faster + std::cerr << "| pass progress: " << passProgress + << ", last destructive: " << lastDestructiveReductions << '\n'; if (passProgress >= 4 * lastDestructiveReductions) { // don't change std::cerr << "| progress is good, do not quickly decrease factor\n"; @@ -1083,16 +1208,19 @@ int main(int argc, const char* argv[]) { while (1) { std::cerr << "| reduce destructively... (factor: " << factor << ")\n"; lastDestructiveReductions = reducer.reduceDestructively(factor); - if (lastDestructiveReductions > 0) break; + if (lastDestructiveReductions > 0) + break; // we failed to reduce destructively if (factor == 1) { stopping = true; break; } - factor = std::max(1, factor / 4); // quickly now, try to find *something* we can reduce + factor = std::max( + 1, factor / 4); // quickly now, try to find *something* we can reduce } - std::cerr << "| destructive reduction led to size: " << file_size(working) << '\n'; + std::cerr << "| destructive reduction led to size: " << file_size(working) + << '\n'; } std::cerr << "|finished, final size: " << file_size(working) << "\n"; copy_file(working, test); // just to avoid confusion diff --git a/src/tools/wasm-shell.cpp b/src/tools/wasm-shell.cpp index 0c95b39ea..6141b1a37 100644 --- a/src/tools/wasm-shell.cpp +++ b/src/tools/wasm-shell.cpp @@ -35,13 +35,13 @@ using namespace cashew; using namespace wasm; -Name ASSERT_RETURN("assert_return"), - ASSERT_TRAP("assert_trap"), - ASSERT_INVALID("assert_invalid"), - ASSERT_MALFORMED("assert_malformed"), - ASSERT_UNLINKABLE("assert_unlinkable"), - INVOKE("invoke"), - GET("get"); +Name ASSERT_RETURN("assert_return"); +Name ASSERT_TRAP("assert_trap"); +Name ASSERT_INVALID("assert_invalid"); +Name ASSERT_MALFORMED("assert_malformed"); +Name ASSERT_UNLINKABLE("assert_unlinkable"); +Name INVOKE("invoke"); +Name GET("get"); // Modules named in the file @@ -60,7 +60,10 @@ struct Operation { Name name; LiteralList arguments; - Operation(Element& element, ModuleInstance* instanceInit, SExpressionWasmBuilder& builder) : instance(instanceInit) { + Operation(Element& element, + ModuleInstance* instanceInit, + SExpressionWasmBuilder& builder) + : instance(instanceInit) { operation = element[0]->str(); Index i = 1; if (element.size() >= 3 && element[2]->isStr()) { @@ -87,14 +90,19 @@ struct Operation { } }; -static void run_asserts(Name moduleName, size_t* i, bool* checked, Module* wasm, +static void run_asserts(Name moduleName, + size_t* i, + bool* checked, + Module* wasm, Element* root, SExpressionWasmBuilder* builder, Name entry) { ModuleInstance* instance = nullptr; if (wasm) { - auto tempInterface = wasm::make_unique<ShellExternalInterface>(); // prefix make_unique to work around visual studio bugs - auto tempInstance = wasm::make_unique<ModuleInstance>(*wasm, tempInterface.get()); + // prefix make_unique to work around visual studio bugs + auto tempInterface = wasm::make_unique<ShellExternalInterface>(); + auto tempInstance = + wasm::make_unique<ModuleInstance>(*wasm, tempInterface.get()); interfaces[moduleName].swap(tempInterface); instances[moduleName].swap(tempInstance); instance = instances[moduleName].get(); @@ -117,7 +125,8 @@ static void run_asserts(Name moduleName, size_t* i, bool* checked, Module* wasm, while (*i < root->size()) { Element& curr = *(*root)[*i]; IString id = curr[0]->str(); - if (id == MODULE) break; + if (id == MODULE) + break; *checked = true; Colors::red(std::cerr); std::cerr << *i << '/' << (root->size() - 1); @@ -128,15 +137,15 @@ static void run_asserts(Name moduleName, size_t* i, bool* checked, Module* wasm, Colors::green(std::cerr); std::cerr << " [line: " << curr.line << "]\n"; Colors::normal(std::cerr); - if (id == ASSERT_INVALID || id == ASSERT_MALFORMED || id == ASSERT_UNLINKABLE) { + if (id == ASSERT_INVALID || id == ASSERT_MALFORMED || + id == ASSERT_UNLINKABLE) { // a module invalidity test Module wasm; bool invalid = false; std::unique_ptr<SExpressionWasmBuilder> builder; try { builder = std::unique_ptr<SExpressionWasmBuilder>( - new SExpressionWasmBuilder(wasm, *curr[1]) - ); + new SExpressionWasmBuilder(wasm, *curr[1])); } catch (const ParseException&) { invalid = true; } @@ -147,7 +156,8 @@ static void run_asserts(Name moduleName, size_t* i, bool* checked, Module* wasm, if (!invalid && id == ASSERT_UNLINKABLE) { // validate "instantiating" the mdoule auto reportUnknownImport = [&](Importable* import) { - std::cerr << "unknown import: " << import->module << '.' << import->base << '\n'; + std::cerr << "unknown import: " << import->module << '.' + << import->base << '\n'; invalid = true; }; ModuleUtils::iterImportedGlobals(wasm, reportUnknownImport); @@ -168,7 +178,8 @@ static void run_asserts(Name moduleName, size_t* i, bool* checked, Module* wasm, for (auto name : segment.data) { // spec tests consider it illegal to use spectest.print in a table if (auto* import = wasm.getFunction(name)) { - if (import->imported() && import->module == SPECTEST && import->base == PRINT) { + if (import->imported() && import->module == SPECTEST && + import->base == PRINT) { std::cerr << "cannot put spectest.print in table\n"; invalid = true; } @@ -201,10 +212,8 @@ static void run_asserts(Name moduleName, size_t* i, bool* checked, Module* wasm, if (id == ASSERT_RETURN) { assert(!trapped); if (curr.size() >= 3) { - Literal expected = builder - ->parseExpression(*curr[2]) - ->dynCast<Const>() - ->value; + Literal expected = + builder->parseExpression(*curr[2])->dynCast<Const>()->value; std::cerr << "seen " << result << ", expected " << expected << '\n'; if (expected != result) { std::cout << "unexpected, should be identical\n"; @@ -219,7 +228,8 @@ static void run_asserts(Name moduleName, size_t* i, bool* checked, Module* wasm, } } } - if (id == ASSERT_TRAP) assert(trapped); + if (id == ASSERT_TRAP) + assert(trapped); } *i += 1; } @@ -235,37 +245,44 @@ int main(int argc, const char* argv[]) { Options options("wasm-shell", "Execute .wast files"); options - .add( - "--entry", "-e", "Call the entry point after parsing the module", - Options::Arguments::One, - [&entry](Options*, const std::string& argument) { entry = argument; }) - .add( - "--skip", "-s", "Skip input on certain lines (comma-separated-list)", - Options::Arguments::One, - [&skipped](Options*, const std::string& argument) { - size_t i = 0; - while (i < argument.size()) { - auto ending = argument.find(',', i); - if (ending == std::string::npos) { - ending = argument.size(); - } - auto sub = argument.substr(i, ending - i); - skipped.insert(atoi(sub.c_str())); - i = ending + 1; - } - }) - .add_positional("INFILE", Options::Arguments::One, - [](Options* o, const std::string& argument) { - o->extra["infile"] = argument; - }); + .add("--entry", + "-e", + "Call the entry point after parsing the module", + Options::Arguments::One, + [&entry](Options*, const std::string& argument) { entry = argument; }) + .add("--skip", + "-s", + "Skip input on certain lines (comma-separated-list)", + Options::Arguments::One, + [&skipped](Options*, const std::string& argument) { + size_t i = 0; + while (i < argument.size()) { + auto ending = argument.find(',', i); + if (ending == std::string::npos) { + ending = argument.size(); + } + auto sub = argument.substr(i, ending - i); + skipped.insert(atoi(sub.c_str())); + i = ending + 1; + } + }) + .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::vector<char>>(options.extra["infile"], Flags::Text, options.debug ? Flags::Debug : Flags::Release)); + auto input(read_file<std::vector<char>>(options.extra["infile"], + Flags::Text, + options.debug ? Flags::Debug + : Flags::Release)); bool checked = false; try { - if (options.debug) std::cerr << "parsing text to s-expressions...\n"; + if (options.debug) + std::cerr << "parsing text to s-expressions...\n"; SExpressionParser parser(input.data()); Element& root = *parser.root; @@ -282,13 +299,15 @@ int main(int argc, const char* argv[]) { } IString id = curr[0]->str(); if (id == MODULE) { - if (options.debug) std::cerr << "parsing s-expressions to wasm...\n"; + if (options.debug) + std::cerr << "parsing s-expressions to wasm...\n"; Colors::green(std::cerr); std::cerr << "BUILDING MODULE [line: " << curr.line << "]\n"; Colors::normal(std::cerr); auto module = wasm::make_unique<Module>(); Name moduleName; - auto builder = wasm::make_unique<SExpressionWasmBuilder>(*module, *root[i], &moduleName); + auto builder = wasm::make_unique<SExpressionWasmBuilder>( + *module, *root[i], &moduleName); builders[moduleName].swap(builder); modules[moduleName].swap(module); i++; @@ -298,7 +317,13 @@ int main(int argc, const char* argv[]) { WasmPrinter::printModule(modules[moduleName].get()); } assert(valid); - run_asserts(moduleName, &i, &checked, modules[moduleName].get(), &root, builders[moduleName].get(), entry); + run_asserts(moduleName, + &i, + &checked, + modules[moduleName].get(), + &root, + builders[moduleName].get(), + entry); } else { run_asserts(Name(), &i, &checked, nullptr, &root, nullptr, entry); } diff --git a/src/tools/wasm2js.cpp b/src/tools/wasm2js.cpp index 84aa9f024..3d5f484da 100644 --- a/src/tools/wasm2js.cpp +++ b/src/tools/wasm2js.cpp @@ -18,13 +18,14 @@ // wasm2js console tool // +#include "wasm2js.h" +#include "optimization-options.h" +#include "pass.h" #include "support/colors.h" #include "support/command-line.h" #include "support/file.h" #include "wasm-s-parser.h" #include "wasm2js.h" -#include "optimization-options.h" -#include "pass.h" using namespace cashew; using namespace wasm; @@ -34,8 +35,8 @@ using namespace wasm; namespace { static void optimizeWasm(Module& wasm, PassOptions options) { - // Perform various optimizations that will be good for JS, but would not be great - // for wasm in general + // Perform various optimizations that will be good for JS, but would not be + // great for wasm in general struct OptimizeForJS : public WalkerPass<PostWalker<OptimizeForJS>> { bool isFunctionParallel() override { return true; } @@ -60,14 +61,12 @@ static void optimizeWasm(Module& wasm, PassOptions options) { runner.run(); } -template<typename T> -static void printJS(Ref ast, T& output) { +template<typename T> static void printJS(Ref ast, T& output) { JSPrinter jser(true, true, ast); jser.printAst(); output << jser.buffer << std::endl; } - // Traversals struct TraverseInfo { @@ -102,7 +101,9 @@ private: }; // Traverse, calling visit after the children -static void traversePrePost(Ref node, std::function<void (Ref)> visitPre, std::function<void (Ref)> visitPost) { +static void traversePrePost(Ref node, + std::function<void(Ref)> visitPre, + std::function<void(Ref)> visitPost) { std::vector<TraverseInfo> stack; stack.push_back(TraverseInfo(node)); while (!stack.empty()) { @@ -123,7 +124,7 @@ static void traversePrePost(Ref node, std::function<void (Ref)> visitPre, std::f } } -static void traversePost(Ref node, std::function<void (Ref)> visit) { +static void traversePost(Ref node, std::function<void(Ref)> visit) { traversePrePost(node, [](Ref node) {}, visit); } @@ -131,24 +132,28 @@ static void optimizeJS(Ref ast) { // helpers auto isOrZero = [](Ref node) { - return node->isArray() && !node->empty() && node[0] == BINARY && node[1] == OR && node[3]->isNumber() && node[3]->getNumber() == 0; + return node->isArray() && !node->empty() && node[0] == BINARY && + node[1] == OR && node[3]->isNumber() && node[3]->getNumber() == 0; }; auto isPlus = [](Ref node) { - return node->isArray() && !node->empty() && node[0] == UNARY_PREFIX && node[1] == PLUS; + return node->isArray() && !node->empty() && node[0] == UNARY_PREFIX && + node[1] == PLUS; }; auto isBitwise = [](Ref node) { if (node->isArray() && !node->empty() && node[0] == BINARY) { auto op = node[1]; - return op == OR || op == AND || op == XOR || op == RSHIFT || op == TRSHIFT || op == LSHIFT; + return op == OR || op == AND || op == XOR || op == RSHIFT || + op == TRSHIFT || op == LSHIFT; } return false; }; // x >> 0 => x | 0 traversePost(ast, [](Ref node) { - if (node->isArray() && !node->empty() && node[0] == BINARY && node[1] == RSHIFT && node[3]->isNumber()) { + if (node->isArray() && !node->empty() && node[0] == BINARY && + node[1] == RSHIFT && node[3]->isNumber()) { if (node[3]->getNumber() == 0) { node[1]->setString(OR); } @@ -190,60 +195,69 @@ static void optimizeJS(Ref ast) { // XXX IString invalid("__wasm2js$INVALID_LABEL__"); std::vector<Ref> breakCapturers; std::vector<Ref> continueCapturers; - std::unordered_map<IString, Ref> labelToValue; // maps the label to the loop/etc. + std::unordered_map<IString, Ref> + labelToValue; // maps the label to the loop/etc. std::unordered_set<Value*> labelled; // all things with a label on them. Value INVALID; - traversePrePost(ast, [&](Ref node) { - if (node->isArray() && !node->empty()) { - if (node[0] == LABEL) { - auto label = node[1]->getIString(); - labelToValue[label] = node[2]; - labelled.insert(node[2].get()); - } else if (node[0] == WHILE || node[0] == DO || node[0] == FOR) { - breakCapturers.push_back(node); - continueCapturers.push_back(node); - } else if (node[0] == cashew::BLOCK) { - if (labelled.count(node.get())) { - // Cannot break to a block without the label. - breakCapturers.push_back(Ref(&INVALID)); + traversePrePost( + ast, + [&](Ref node) { + if (node->isArray() && !node->empty()) { + if (node[0] == LABEL) { + auto label = node[1]->getIString(); + labelToValue[label] = node[2]; + labelled.insert(node[2].get()); + } else if (node[0] == WHILE || node[0] == DO || node[0] == FOR) { + breakCapturers.push_back(node); + continueCapturers.push_back(node); + } else if (node[0] == cashew::BLOCK) { + if (labelled.count(node.get())) { + // Cannot break to a block without the label. + breakCapturers.push_back(Ref(&INVALID)); + } + } else if (node[0] == SWITCH) { + breakCapturers.push_back(node); } - } else if (node[0] == SWITCH) { - breakCapturers.push_back(node); } - } - }, [&](Ref node) { - if (node->isArray() && !node->empty()) { - if (node[0] == LABEL) { - auto label = node[1]->getIString(); - labelToValue.erase(label); - labelled.erase(node[2].get()); - } else if (node[0] == WHILE || node[0] == DO || node[0] == FOR) { - breakCapturers.pop_back(); - continueCapturers.pop_back(); - } else if (node[0] == cashew::BLOCK) { - if (labelled.count(node.get())) { - breakCapturers.pop_back(); - } - } else if (node[0] == SWITCH) { - breakCapturers.pop_back(); - } else if (node[0] == BREAK || node[0] == CONTINUE) { - if (!node[1]->isNull()) { + }, + [&](Ref node) { + if (node->isArray() && !node->empty()) { + if (node[0] == LABEL) { auto label = node[1]->getIString(); - assert(labelToValue.count(label)); - auto& capturers = node[0] == BREAK ? breakCapturers : continueCapturers; - assert(!capturers.empty()); - if (capturers.back() == labelToValue[label]) { - // Success, the break/continue goes exactly where we would if we - // didn't have the label! - node[1]->setNull(); + labelToValue.erase(label); + labelled.erase(node[2].get()); + } else if (node[0] == WHILE || node[0] == DO || node[0] == FOR) { + breakCapturers.pop_back(); + continueCapturers.pop_back(); + } else if (node[0] == cashew::BLOCK) { + if (labelled.count(node.get())) { + breakCapturers.pop_back(); + } + } else if (node[0] == SWITCH) { + breakCapturers.pop_back(); + } else if (node[0] == BREAK || node[0] == CONTINUE) { + if (!node[1]->isNull()) { + auto label = node[1]->getIString(); + assert(labelToValue.count(label)); + auto& capturers = + node[0] == BREAK ? breakCapturers : continueCapturers; + assert(!capturers.empty()); + if (capturers.back() == labelToValue[label]) { + // Success, the break/continue goes exactly where we would if we + // didn't have the label! + node[1]->setNull(); + } } } } - } - }); + }); } -static void emitWasm(Module& wasm, Output& output, Wasm2JSBuilder::Flags flags, PassOptions options, Name name) { +static void emitWasm(Module& wasm, + Output& output, + Wasm2JSBuilder::Flags flags, + PassOptions options, + Name name) { if (options.optimizeLevel > 0) { optimizeWasm(wasm, options); } @@ -264,7 +278,9 @@ public: SExpressionWasmBuilder& sexpBuilder, Output& out, Wasm2JSBuilder::Flags flags, - PassOptions options) : root(root), sexpBuilder(sexpBuilder), out(out), flags(flags), options(options) {} + PassOptions options) + : root(root), sexpBuilder(sexpBuilder), out(out), flags(flags), + options(options) {} void emit(); @@ -310,11 +326,9 @@ Ref AssertionEmitter::emitAssertReturnFunc(Builder& wasmBuilder, Expression* actual = sexpBuilder.parseExpression(e[1]); Expression* body = nullptr; if (e.size() == 2) { - if (actual->type == none) { - body = wasmBuilder.blockify( - actual, - wasmBuilder.makeConst(Literal(uint32_t(1))) - ); + if (actual->type == none) { + body = wasmBuilder.blockify(actual, + wasmBuilder.makeConst(Literal(uint32_t(1)))); } else { body = actual; } @@ -330,9 +344,10 @@ Ref AssertionEmitter::emitAssertReturnFunc(Builder& wasmBuilder, case i64: body = wasmBuilder.makeCall( "i64Equal", - {actual, wasmBuilder.makeCall(WASM_FETCH_HIGH_BITS, {}, i32), expected}, - i32 - ); + {actual, + wasmBuilder.makeCall(WASM_FETCH_HIGH_BITS, {}, i32), + expected}, + i32); break; case f32: { @@ -353,14 +368,11 @@ Ref AssertionEmitter::emitAssertReturnFunc(Builder& wasmBuilder, assert(false && "Unexpected number of parameters in assert_return"); } std::unique_ptr<Function> testFunc( - wasmBuilder.makeFunction( - testFuncName, - std::vector<NameType>{}, - body->type, - std::vector<NameType>{}, - body - ) - ); + wasmBuilder.makeFunction(testFuncName, + std::vector<NameType>{}, + body->type, + std::vector<NameType>{}, + body)); Ref jsFunc = processFunction(testFunc.get()); fixCalls(jsFunc, asmModule); emitFunction(jsFunc); @@ -374,14 +386,11 @@ Ref AssertionEmitter::emitAssertReturnNanFunc(Builder& wasmBuilder, Expression* actual = sexpBuilder.parseExpression(e[1]); Expression* body = wasmBuilder.makeCall("isNaN", {actual}, i32); std::unique_ptr<Function> testFunc( - wasmBuilder.makeFunction( - testFuncName, - std::vector<NameType>{}, - body->type, - std::vector<NameType>{}, - body - ) - ); + wasmBuilder.makeFunction(testFuncName, + std::vector<NameType>{}, + body->type, + std::vector<NameType>{}, + body)); Ref jsFunc = processFunction(testFunc.get()); fixCalls(jsFunc, asmModule); emitFunction(jsFunc); @@ -399,8 +408,7 @@ Ref AssertionEmitter::emitAssertTrapFunc(Builder& wasmBuilder, std::vector<NameType>{}, expr->type, std::vector<NameType>{}, - expr) - ); + expr)); IString expectedErr = e[2]->str(); Ref innerFunc = processFunction(exprFunc.get()); fixCalls(innerFunc, asmModule); @@ -411,33 +419,25 @@ Ref AssertionEmitter::emitAssertTrapFunc(Builder& wasmBuilder, Ref catchBlock = ValueBuilder::makeBlock(); ValueBuilder::appendToBlock( catchBlock, - ValueBuilder::makeReturn( - ValueBuilder::makeCall( - ValueBuilder::makeDot( - ValueBuilder::makeName(IString("e")), - ValueBuilder::makeName(IString("message")), - ValueBuilder::makeName(IString("includes")) - ), - ValueBuilder::makeString(expectedErr) - ) - ) - ); + ValueBuilder::makeReturn(ValueBuilder::makeCall( + ValueBuilder::makeDot(ValueBuilder::makeName(IString("e")), + ValueBuilder::makeName(IString("message")), + ValueBuilder::makeName(IString("includes"))), + ValueBuilder::makeString(expectedErr)))); outerFunc[3]->push_back(ValueBuilder::makeTry( - tryBlock, - ValueBuilder::makeName((IString("e"))), - catchBlock)); + tryBlock, ValueBuilder::makeName((IString("e"))), catchBlock)); outerFunc[3]->push_back(ValueBuilder::makeReturn(ValueBuilder::makeInt(0))); emitFunction(outerFunc); return outerFunc; } bool AssertionEmitter::isAssertHandled(Element& e) { - return e.isList() && e.size() >= 2 && e[0]->isStr() - && (e[0]->str() == Name("assert_return") || + return e.isList() && e.size() >= 2 && e[0]->isStr() && + (e[0]->str() == Name("assert_return") || e[0]->str() == Name("assert_return_nan") || - (flags.pedantic && e[0]->str() == Name("assert_trap"))) - && e[1]->isList() && e[1]->size() >= 2 && (*e[1])[0]->isStr() - && (*e[1])[0]->str() == Name("invoke"); + (flags.pedantic && e[0]->str() == Name("assert_trap"))) && + e[1]->isList() && e[1]->size() >= 2 && (*e[1])[0]->isStr() && + (*e[1])[0]->str() == Name("invoke"); } void AssertionEmitter::fixCalls(Ref asmjs, Name asmModule) { @@ -446,7 +446,8 @@ void AssertionEmitter::fixCalls(Ref asmjs, Name asmModule) { for (Ref& r : arr) { fixCalls(r, asmModule); } - if (arr.size() > 0 && arr[0]->isString() && arr[0]->getIString() == cashew::CALL) { + if (arr.size() > 0 && arr[0]->isString() && + arr[0]->getIString() == cashew::CALL) { assert(arr.size() >= 2); if (arr[1]->getIString() == "f32Equal" || arr[1]->getIString() == "f64Equal" || @@ -457,7 +458,7 @@ void AssertionEmitter::fixCalls(Ref asmjs, Name asmModule) { arr[1]->setString("Math.fround"); } else { Ref fixed = ValueBuilder::makeDot(ValueBuilder::makeName(asmModule), - arr[1]->getIString()); + arr[1]->getIString()); arr[1]->setArray(fixed->getArray()); } } @@ -522,7 +523,8 @@ void AssertionEmitter::emit() { Name asmModule = std::string("ret") + ASM_FUNC.str; for (size_t i = 0; i < root.size(); ++i) { Element& e = *root[i]; - if (e.isList() && e.size() >= 1 && e[0]->isStr() && e[0]->str() == Name("module")) { + if (e.isList() && e.size() >= 1 && e[0]->isStr() && + e[0]->str() == Name("module")) { std::stringstream funcNameS; funcNameS << ASM_FUNC.c_str() << i; std::stringstream moduleNameS; @@ -555,10 +557,7 @@ void AssertionEmitter::emit() { emitAssertTrapFunc(wasmBuilder, e, testFuncName, asmModule); } - out << "if (!" - << testFuncName.str - << "()) throw 'assertion failed: " - << e + out << "if (!" << testFuncName.str << "()) throw 'assertion failed: " << e << "';\n"; } } @@ -567,38 +566,48 @@ void AssertionEmitter::emit() { // Main -int main(int argc, const char *argv[]) { +int main(int argc, const char* argv[]) { Wasm2JSBuilder::Flags flags; - OptimizationOptions options("wasm2js", "Transform .wasm/.wast files to asm.js"); + OptimizationOptions options("wasm2js", + "Transform .wasm/.wast files to asm.js"); options - .add("--output", "-o", "Output file (stdout if not specified)", - Options::Arguments::One, - [](Options* o, const std::string& argument) { - o->extra["output"] = argument; - Colors::disable(); - }) - .add("--allow-asserts", "", "Allow compilation of .wast testing asserts", - Options::Arguments::Zero, - [&](Options* o, const std::string& argument) { - flags.allowAsserts = true; - o->extra["asserts"] = "1"; - }) - .add("--pedantic", "", "Emulate WebAssembly trapping behavior", - Options::Arguments::Zero, - [&](Options* o, const std::string& argument) { - flags.pedantic = true; - }) - .add("--emscripten", "", "Emulate the glue in emscripten-compatible form (and not ES6 module form)", - Options::Arguments::Zero, - [&](Options* o, const std::string& argument) { - flags.emscripten = true; - }) - .add_positional("INFILE", Options::Arguments::One, - [](Options *o, const std::string& argument) { - o->extra["infile"] = argument; - }); + .add("--output", + "-o", + "Output file (stdout if not specified)", + Options::Arguments::One, + [](Options* o, const std::string& argument) { + o->extra["output"] = argument; + Colors::disable(); + }) + .add("--allow-asserts", + "", + "Allow compilation of .wast testing asserts", + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { + flags.allowAsserts = true; + o->extra["asserts"] = "1"; + }) + .add( + "--pedantic", + "", + "Emulate WebAssembly trapping behavior", + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { flags.pedantic = true; }) + .add( + "--emscripten", + "", + "Emulate the glue in emscripten-compatible form (and not ES6 module " + "form)", + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { flags.emscripten = true; }) + .add_positional("INFILE", + Options::Arguments::One, + [](Options* o, const std::string& argument) { + o->extra["infile"] = argument; + }); options.parse(argc, argv); - if (options.debug) flags.debug = true; + if (options.debug) + flags.debug = true; Element* root = nullptr; Module wasm; @@ -606,11 +615,11 @@ int main(int argc, const char *argv[]) { std::unique_ptr<SExpressionParser> sexprParser; std::unique_ptr<SExpressionWasmBuilder> sexprBuilder; - auto &input = options.extra["infile"]; + auto& input = options.extra["infile"]; std::string suffix(".wasm"); bool binaryInput = - input.size() >= suffix.size() && - input.compare(input.size() - suffix.size(), suffix.size(), suffix) == 0; + input.size() >= suffix.size() && + input.compare(input.size() - suffix.size(), suffix.size(), suffix) == 0; try { // If the input filename ends in `.wasm`, then parse it in binary form, @@ -627,20 +636,25 @@ int main(int argc, const char *argv[]) { reader.read(input, wasm, ""); options.applyFeatures(wasm); } else { - auto input( - read_file<std::vector<char>>(options.extra["infile"], Flags::Text, options.debug ? Flags::Debug : Flags::Release)); - if (options.debug) std::cerr << "s-parsing..." << std::endl; + auto input(read_file<std::vector<char>>(options.extra["infile"], + Flags::Text, + options.debug ? Flags::Debug + : Flags::Release)); + if (options.debug) + std::cerr << "s-parsing..." << std::endl; sexprParser = make_unique<SExpressionParser>(input.data()); root = sexprParser->root; - if (options.debug) std::cerr << "w-parsing..." << std::endl; + if (options.debug) + std::cerr << "w-parsing..." << std::endl; sexprBuilder = make_unique<SExpressionWasmBuilder>(wasm, *(*root)[0]); } } catch (ParseException& p) { p.dump(std::cerr); Fatal() << "error in parsing input"; } catch (std::bad_alloc&) { - Fatal() << "error in building module, std::bad_alloc (possibly invalid request for silly amounts of memory)"; + Fatal() << "error in building module, std::bad_alloc (possibly invalid " + "request for silly amounts of memory)"; } if (options.passOptions.validate) { @@ -650,13 +664,18 @@ int main(int argc, const char *argv[]) { } } - if (options.debug) std::cerr << "j-printing..." << std::endl; - Output output(options.extra["output"], Flags::Text, options.debug ? Flags::Debug : Flags::Release); + if (options.debug) + std::cerr << "j-printing..." << std::endl; + Output output(options.extra["output"], + Flags::Text, + options.debug ? Flags::Debug : Flags::Release); if (!binaryInput && options.extra["asserts"] == "1") { - AssertionEmitter(*root, *sexprBuilder, output, flags, options.passOptions).emit(); + AssertionEmitter(*root, *sexprBuilder, output, flags, options.passOptions) + .emit(); } else { emitWasm(wasm, output, flags, options.passOptions, "asmFunc"); } - if (options.debug) std::cerr << "done." << std::endl; + if (options.debug) + std::cerr << "done." << std::endl; } |