| /* |
| * Copyright 2015 WebAssembly Community Group participants |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| // |
| // 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" |
| |
| using namespace cashew; |
| using namespace wasm; |
| |
| // helpers |
| |
| 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 |
| struct OptimizeForJS : public WalkerPass<PostWalker<OptimizeForJS>> { |
| bool isFunctionParallel() override { return true; } |
| |
| Pass* create() override { return new OptimizeForJS; } |
| |
| void visitBinary(Binary* curr) { |
| // x - -c (where c is a constant) is larger than x + c, in js (but not |
| // necessarily in wasm, where LEBs prefer negatives). |
| if (curr->op == SubInt32) { |
| if (auto* c = curr->right->dynCast<Const>()) { |
| if (c->value.geti32() < 0) { |
| curr->op = AddInt32; |
| c->value = c->value.neg(); |
| } |
| } |
| } |
| } |
| }; |
| |
| PassRunner runner(&wasm, options); |
| runner.add<OptimizeForJS>(); |
| runner.run(); |
| } |
| |
| 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 { |
| TraverseInfo() = default; |
| TraverseInfo(Ref node) : node(node) { |
| assert(node.get()); |
| if (node->isArray()) { |
| for (size_t i = 0; i < node->size(); i++) { |
| maybeAdd(node[i]); |
| } |
| } else if (node->isAssign()) { |
| auto assign = node->asAssign(); |
| maybeAdd(assign->target()); |
| maybeAdd(assign->value()); |
| } else if (node->isAssignName()) { |
| auto assign = node->asAssignName(); |
| maybeAdd(assign->value()); |
| } else { |
| // no children |
| } |
| } |
| Ref node; |
| bool scanned = false; |
| std::vector<Ref> children; |
| |
| private: |
| void maybeAdd(Ref child) { |
| if (child.get()) { |
| children.push_back(child); |
| } |
| } |
| }; |
| |
| // Traverse, calling visit after the children |
| 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()) { |
| TraverseInfo& back = stack.back(); |
| if (!back.scanned) { |
| back.scanned = true; |
| // This is the first time we see this. |
| visitPre(back.node); |
| for (auto child : back.children) { |
| stack.emplace_back(child); |
| } |
| continue; |
| } |
| // Time to post-visit the node itself |
| auto node = back.node; |
| stack.pop_back(); |
| visitPost(node); |
| } |
| } |
| |
| static void traversePost(Ref node, std::function<void(Ref)> visit) { |
| traversePrePost(node, [](Ref node) {}, visit); |
| } |
| |
| 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; |
| }; |
| |
| auto isPlus = [](Ref node) { |
| return node->isArray() && !node->empty() && node[0] == UNARY_PREFIX && |
| node[1] == PLUS; |
| }; |
| |
| auto isFround = [](Ref node) { |
| return node->isArray() && !node->empty() && node[0] == cashew::CALL && |
| node[1] == MATH_FROUND; |
| }; |
| |
| 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 false; |
| }; |
| |
| auto isConstantAnd = [](Ref node, int num) { |
| return node->isArray() && !node->empty() && node[0] == BINARY && |
| node[1] == AND && node[3]->isNumber() && node[3]->getNumber() == num; |
| }; |
| |
| auto removeOrZero = [&](Ref node) { |
| while (isOrZero(node)) { |
| node = node[2]; |
| } |
| return node; |
| }; |
| |
| auto removePlus = [&](Ref node) { |
| while (isPlus(node)) { |
| node = node[2]; |
| } |
| return node; |
| }; |
| |
| auto removePlusAndFround = [&](Ref node) { |
| while (1) { |
| if (isFround(node)) { |
| node = node[2][0]; |
| } else if (isPlus(node)) { |
| node = node[2]; |
| } else { |
| break; |
| } |
| } |
| return node; |
| }; |
| |
| auto getHeapFromAccess = [](Ref node) { return node[1]->getIString(); }; |
| |
| auto isIntegerHeap = [](IString heap) { |
| return heap == HEAP8 || heap == HEAPU8 || heap == HEAP16 || |
| heap == HEAPU16 || heap == HEAP32 || heap == HEAPU32; |
| }; |
| |
| auto isFloatHeap = [](IString heap) { |
| return heap == HEAPF32 || heap == HEAPF64; |
| }; |
| |
| auto isHeapAccess = [&](Ref node) { |
| if (node->isArray() && !node->empty() && node[0] == SUB && |
| node[1]->isString()) { |
| auto heap = getHeapFromAccess(node); |
| return isIntegerHeap(heap) || isFloatHeap(heap); |
| } |
| return false; |
| }; |
| |
| // Optimizations |
| |
| // x >> 0 => x | 0 |
| traversePost(ast, [](Ref node) { |
| if (node->isArray() && !node->empty() && node[0] == BINARY && |
| node[1] == RSHIFT && node[3]->isNumber()) { |
| if (node[3]->getNumber() == 0) { |
| node[1]->setString(OR); |
| } |
| } |
| }); |
| |
| traversePost(ast, [&](Ref node) { |
| // x | 0 | 0 => x | 0 |
| if (isOrZero(node)) { |
| while (isOrZero(node[2])) { |
| node[2] = node[2][2]; |
| } |
| if (isBitwise(node[2])) { |
| auto child = node[2]; |
| node[1] = child[1]; |
| node[2] = child[2]; |
| node[3] = child[3]; |
| } |
| } |
| // x | 0 going into a bitwise op => skip the | 0 |
| else if (isBitwise(node)) { |
| node[2] = removeOrZero(node[2]); |
| node[3] = removeOrZero(node[3]); |
| } |
| // +(+x) => +x |
| else if (isPlus(node)) { |
| node[2] = removePlus(node[2]); |
| } |
| // +(+x) => +x |
| else if (isFround(node)) { |
| node[2] = removePlusAndFround(node[2]); |
| } |
| // Assignment into a heap coerces. |
| else if (node->isAssign()) { |
| auto assign = node->asAssign(); |
| auto target = assign->target(); |
| if (isHeapAccess(target)) { |
| auto heap = getHeapFromAccess(target); |
| if (isIntegerHeap(heap)) { |
| if (heap == HEAP8 || heap == HEAPU8) { |
| while (isOrZero(assign->value()) || |
| isConstantAnd(assign->value(), 255)) { |
| assign->value() = assign->value()[2]; |
| } |
| } else if (heap == HEAP16 || heap == HEAPU16) { |
| while (isOrZero(assign->value()) || |
| isConstantAnd(assign->value(), 65535)) { |
| assign->value() = assign->value()[2]; |
| } |
| } else { |
| assert(heap == HEAP32 || heap == HEAPU32); |
| assign->value() = removeOrZero(assign->value()); |
| } |
| } else { |
| assert(isFloatHeap(heap)); |
| if (heap == HEAPF32) { |
| assign->value() = removePlusAndFround(assign->value()); |
| } else { |
| assign->value() = removePlus(assign->value()); |
| } |
| } |
| } |
| } |
| }); |
| |
| // Remove unnecessary break/continue labels, when referring to the top level. |
| |
| // 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_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)); |
| } |
| } 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()) { |
| 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) { |
| if (options.optimizeLevel > 0) { |
| optimizeWasm(wasm, options); |
| } |
| Wasm2JSBuilder wasm2js(flags, options); |
| auto js = wasm2js.processWasm(&wasm, name); |
| if (options.optimizeLevel >= 2) { |
| optimizeJS(js); |
| } |
| Wasm2JSGlue glue(wasm, output, flags, name); |
| glue.emitPre(); |
| printJS(js, output); |
| glue.emitPost(); |
| } |
| |
| class AssertionEmitter { |
| public: |
| AssertionEmitter(Element& root, |
| SExpressionWasmBuilder& sexpBuilder, |
| Output& out, |
| Wasm2JSBuilder::Flags flags, |
| PassOptions options) |
| : root(root), sexpBuilder(sexpBuilder), out(out), flags(flags), |
| options(options) {} |
| |
| void emit(); |
| |
| private: |
| Element& root; |
| SExpressionWasmBuilder& sexpBuilder; |
| Output& out; |
| Wasm2JSBuilder::Flags flags; |
| PassOptions options; |
| Module tempAllocationModule; |
| |
| Ref emitAssertReturnFunc(Builder& wasmBuilder, |
| Element& e, |
| Name testFuncName, |
| Name asmModule); |
| Ref emitAssertReturnNanFunc(Builder& wasmBuilder, |
| Element& e, |
| Name testFuncName, |
| Name asmModule); |
| Ref emitAssertTrapFunc(Builder& wasmBuilder, |
| Element& e, |
| Name testFuncName, |
| Name asmModule); |
| bool isAssertHandled(Element& e); |
| void fixCalls(Ref asmjs, Name asmModule); |
| |
| Ref processFunction(Function* func) { |
| Wasm2JSBuilder sub(flags, options); |
| return sub.processStandaloneFunction(&tempAllocationModule, func); |
| } |
| |
| void emitFunction(Ref func) { |
| JSPrinter jser(true, true, func); |
| jser.printAst(); |
| out << jser.buffer << std::endl; |
| } |
| }; |
| |
| Ref AssertionEmitter::emitAssertReturnFunc(Builder& wasmBuilder, |
| Element& e, |
| Name testFuncName, |
| Name asmModule) { |
| 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)))); |
| } else { |
| body = actual; |
| } |
| } else if (e.size() == 3) { |
| Expression* expected = sexpBuilder.parseExpression(e[2]); |
| Type resType = expected->type; |
| actual->type = resType; |
| switch (resType) { |
| case i32: |
| body = wasmBuilder.makeBinary(EqInt32, actual, expected); |
| break; |
| |
| case i64: |
| body = wasmBuilder.makeCall( |
| "i64Equal", |
| {actual, |
| wasmBuilder.makeCall(WASM_FETCH_HIGH_BITS, {}, i32), |
| expected}, |
| i32); |
| break; |
| |
| case f32: { |
| body = wasmBuilder.makeCall("f32Equal", {actual, expected}, i32); |
| break; |
| } |
| case f64: { |
| body = wasmBuilder.makeCall("f64Equal", {actual, expected}, i32); |
| break; |
| } |
| |
| default: { |
| std::cerr << "Unhandled type in assert: " << resType << std::endl; |
| abort(); |
| } |
| } |
| } else { |
| 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)); |
| Ref jsFunc = processFunction(testFunc.get()); |
| fixCalls(jsFunc, asmModule); |
| emitFunction(jsFunc); |
| return jsFunc; |
| } |
| |
| Ref AssertionEmitter::emitAssertReturnNanFunc(Builder& wasmBuilder, |
| Element& e, |
| Name testFuncName, |
| Name asmModule) { |
| 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)); |
| Ref jsFunc = processFunction(testFunc.get()); |
| fixCalls(jsFunc, asmModule); |
| emitFunction(jsFunc); |
| return jsFunc; |
| } |
| |
| Ref AssertionEmitter::emitAssertTrapFunc(Builder& wasmBuilder, |
| Element& e, |
| Name testFuncName, |
| Name asmModule) { |
| Name innerFuncName("f"); |
| Expression* expr = sexpBuilder.parseExpression(e[1]); |
| std::unique_ptr<Function> exprFunc( |
| wasmBuilder.makeFunction(innerFuncName, |
| std::vector<NameType>{}, |
| expr->type, |
| std::vector<NameType>{}, |
| expr)); |
| IString expectedErr = e[2]->str(); |
| Ref innerFunc = processFunction(exprFunc.get()); |
| fixCalls(innerFunc, asmModule); |
| Ref outerFunc = ValueBuilder::makeFunction(testFuncName); |
| outerFunc[3]->push_back(innerFunc); |
| Ref tryBlock = ValueBuilder::makeBlock(); |
| ValueBuilder::appendToBlock(tryBlock, ValueBuilder::makeCall(innerFuncName)); |
| 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)))); |
| outerFunc[3]->push_back(ValueBuilder::makeTry( |
| 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") || |
| 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"); |
| } |
| |
| void AssertionEmitter::fixCalls(Ref asmjs, Name asmModule) { |
| if (asmjs->isArray()) { |
| ArrayStorage& arr = asmjs->getArray(); |
| for (Ref& r : arr) { |
| fixCalls(r, asmModule); |
| } |
| 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" || |
| arr[1]->getIString() == "i64Equal" || |
| arr[1]->getIString() == "isNaN") { |
| // ... |
| } else if (arr[1]->getIString() == "Math_fround") { |
| arr[1]->setString("Math.fround"); |
| } else { |
| Ref fixed = ValueBuilder::makeDot(ValueBuilder::makeName(asmModule), |
| arr[1]->getIString()); |
| arr[1]->setArray(fixed->getArray()); |
| } |
| } |
| } |
| |
| if (asmjs->isAssign()) { |
| fixCalls(asmjs->asAssign()->target(), asmModule); |
| fixCalls(asmjs->asAssign()->value(), asmModule); |
| } |
| if (asmjs->isAssignName()) { |
| fixCalls(asmjs->asAssignName()->value(), asmModule); |
| } |
| } |
| |
| void AssertionEmitter::emit() { |
| // TODO: nan and infinity shouldn't be needed once literal asm.js code isn't |
| // generated |
| out << R"( |
| var nan = NaN; |
| var infinity = Infinity; |
| )"; |
| |
| // When equating floating point values in spec tests we want to use bitwise |
| // equality like wasm does. Unfortunately though NaN makes this tricky. JS |
| // implementations like Spidermonkey and JSC will canonicalize NaN loads from |
| // `Float32Array`, but V8 will not. This means that NaN representations are |
| // kind of all over the place and difficult to bitwise equate. |
| // |
| // To work around this problem we just use a small shim which considers all |
| // NaN representations equivalent and otherwise tests for bitwise equality. |
| out << R"( |
| function f32Equal(a, b) { |
| var i = new Int32Array(1); |
| var f = new Float32Array(i.buffer); |
| f[0] = a; |
| var ai = f[0]; |
| f[0] = b; |
| var bi = f[0]; |
| |
| return (isNaN(a) && isNaN(b)) || a == b; |
| } |
| |
| function f64Equal(a, b) { |
| var i = new Int32Array(2); |
| var f = new Float64Array(i.buffer); |
| f[0] = a; |
| var ai1 = i[0]; |
| var ai2 = i[1]; |
| f[0] = b; |
| var bi1 = i[0]; |
| var bi2 = i[1]; |
| |
| return (isNaN(a) && isNaN(b)) || (ai1 == bi1 && ai2 == bi2); |
| } |
| |
| function i64Equal(actual_lo, actual_hi, expected_lo, expected_hi) { |
| return (actual_lo | 0) == (expected_lo | 0) && (actual_hi | 0) == (expected_hi | 0); |
| } |
| )"; |
| |
| Builder wasmBuilder(sexpBuilder.getAllocator()); |
| 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")) { |
| std::stringstream funcNameS; |
| funcNameS << ASM_FUNC.c_str() << i; |
| std::stringstream moduleNameS; |
| moduleNameS << "ret" << ASM_FUNC.c_str() << i; |
| Name funcName(funcNameS.str().c_str()); |
| asmModule = Name(moduleNameS.str().c_str()); |
| Module wasm; |
| SExpressionWasmBuilder builder(wasm, e); |
| emitWasm(wasm, out, flags, options, funcName); |
| continue; |
| } |
| if (!isAssertHandled(e)) { |
| std::cerr << "skipping " << e << std::endl; |
| continue; |
| } |
| Name testFuncName(IString(("check" + std::to_string(i)).c_str(), false)); |
| bool isReturn = (e[0]->str() == Name("assert_return")); |
| bool isReturnNan = (e[0]->str() == Name("assert_return_nan")); |
| Element& testOp = *e[1]; |
| // Replace "invoke" with "call" |
| testOp[0]->setString(IString("call"), false, false); |
| // Need to claim dollared to get string as function target |
| testOp[1]->setString(testOp[1]->str(), /*dollared=*/true, false); |
| |
| if (isReturn) { |
| emitAssertReturnFunc(wasmBuilder, e, testFuncName, asmModule); |
| } else if (isReturnNan) { |
| emitAssertReturnNanFunc(wasmBuilder, e, testFuncName, asmModule); |
| } else { |
| emitAssertTrapFunc(wasmBuilder, e, testFuncName, asmModule); |
| } |
| |
| out << "if (!" << testFuncName.str << "()) throw 'assertion failed: " << e |
| << "';\n"; |
| } |
| } |
| |
| } // anonymous namespace |
| |
| // Main |
| |
| int main(int argc, const char* argv[]) { |
| Wasm2JSBuilder::Flags flags; |
| 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; |
| }); |
| options.parse(argc, argv); |
| if (options.debug) { |
| flags.debug = true; |
| } |
| |
| Element* root = nullptr; |
| Module wasm; |
| Ref js; |
| std::unique_ptr<SExpressionParser> sexprParser; |
| std::unique_ptr<SExpressionWasmBuilder> sexprBuilder; |
| |
| 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; |
| |
| try { |
| // If the input filename ends in `.wasm`, then parse it in binary form, |
| // otherwise assume it's a `*.wast` file and go from there. |
| // |
| // Note that we're not using the built-in `ModuleReader` which will also do |
| // similar logic here because when testing JS files we use the |
| // `--allow-asserts` flag which means we need to parse the extra |
| // s-expressions that come at the end of the `*.wast` file after the module |
| // is defined. |
| if (binaryInput) { |
| ModuleReader reader; |
| reader.setDebug(options.debug); |
| 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; |
| } |
| sexprParser = make_unique<SExpressionParser>(input.data()); |
| root = sexprParser->root; |
| |
| 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)"; |
| } |
| |
| if (options.passOptions.validate) { |
| if (!WasmValidator().validate(wasm)) { |
| WasmPrinter::printModule(&wasm); |
| Fatal() << "error in validating input"; |
| } |
| } |
| |
| 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(); |
| } else { |
| emitWasm(wasm, output, flags, options.passOptions, "asmFunc"); |
| } |
| |
| if (options.debug) { |
| std::cerr << "done." << std::endl; |
| } |
| } |