diff options
Diffstat (limited to 'src/tools/wasm2js.cpp')
-rw-r--r-- | src/tools/wasm2js.cpp | 321 |
1 files changed, 170 insertions, 151 deletions
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; } |