diff options
Diffstat (limited to 'src/tools/wasm2js.cpp')
-rw-r--r-- | src/tools/wasm2js.cpp | 356 |
1 files changed, 331 insertions, 25 deletions
diff --git a/src/tools/wasm2js.cpp b/src/tools/wasm2js.cpp index 6a55c4483..76a724f86 100644 --- a/src/tools/wasm2js.cpp +++ b/src/tools/wasm2js.cpp @@ -28,8 +28,329 @@ using namespace cashew; using namespace wasm; +// helpers + +namespace { + +static void emitWasm(Module& wasm, Output& output, Wasm2JSBuilder::Flags flags, Name name) { + Wasm2JSBuilder wasm2js(flags); + auto js = wasm2js.processWasm(&wasm, name); + Wasm2JSGlue glue(wasm, output, flags, name); + glue.emitPre(); + JSPrinter jser(true, true, js); + jser.printAst(); + output << jser.buffer << std::endl; + glue.emitPost(); +} + +class AssertionEmitter { +public: + AssertionEmitter(Element& root, + SExpressionWasmBuilder& sexpBuilder, + Output& out, + Wasm2JSBuilder::Flags flags) : root(root), sexpBuilder(sexpBuilder), out(out), flags(flags) {} + + void emit(); + +private: + Element& root; + SExpressionWasmBuilder& sexpBuilder; + Output& out; + Wasm2JSBuilder::Flags flags; + + 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); + return sub.processFunction(nullptr, 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 == (expected_lo | 0) && actual_hi == (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, 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 builderFlags; + Wasm2JSBuilder::Flags flags; ToolOptions options("wasm2js", "Transform .wasm/.wast files to asm.js"); options .add("--output", "-o", "Output file (stdout if not specified)", @@ -41,24 +362,24 @@ int main(int argc, const char *argv[]) { .add("--allow-asserts", "", "Allow compilation of .wast testing asserts", Options::Arguments::Zero, [&](Options* o, const std::string& argument) { - builderFlags.allowAsserts = true; + flags.allowAsserts = true; o->extra["asserts"] = "1"; }) .add("--pedantic", "", "Emulate WebAssembly trapping behavior", Options::Arguments::Zero, [&](Options* o, const std::string& argument) { - builderFlags.pedantic = true; + flags.pedantic = true; }) .add_positional("INFILE", Options::Arguments::One, [](Options *o, const std::string& argument) { o->extra["infile"] = argument; }); options.parse(argc, argv); - if (options.debug) builderFlags.debug = true; + if (options.debug) flags.debug = true; Element* root = nullptr; Module wasm; - Ref asmjs; + Ref js; std::unique_ptr<SExpressionParser> sexprParser; std::unique_ptr<SExpressionWasmBuilder> sexprBuilder; @@ -106,28 +427,13 @@ int main(int argc, const char *argv[]) { } } - if (options.debug) std::cerr << "asming..." << std::endl; - Wasm2JSBuilder wasm2js(builderFlags); - asmjs = wasm2js.processWasm(&wasm); - - if (!binaryInput) { - if (options.extra["asserts"] == "1") { - if (options.debug) std::cerr << "asserting..." << std::endl; - flattenAppend(asmjs, wasm2js.processAsserts(&wasm, *root, *sexprBuilder)); - } - } - - if (options.debug) { - std::cerr << "a-printing..." << std::endl; - asmjs->stringify(std::cout, true); - std::cout << '\n'; - } - if (options.debug) std::cerr << "j-printing..." << std::endl; - JSPrinter jser(true, true, asmjs); - jser.printAst(); Output output(options.extra["output"], Flags::Text, options.debug ? Flags::Debug : Flags::Release); - output << jser.buffer << std::endl; + if (!binaryInput && options.extra["asserts"] == "1") { + AssertionEmitter(*root, *sexprBuilder, output, flags).emit(); + } else { + emitWasm(wasm, output, flags, "asmFunc"); + } if (options.debug) std::cerr << "done." << std::endl; } |