diff options
Diffstat (limited to 'src/asm2wasm.cpp')
-rw-r--r-- | src/asm2wasm.cpp | 1123 |
1 files changed, 0 insertions, 1123 deletions
diff --git a/src/asm2wasm.cpp b/src/asm2wasm.cpp deleted file mode 100644 index a327fb274..000000000 --- a/src/asm2wasm.cpp +++ /dev/null @@ -1,1123 +0,0 @@ - -#include "wasm.h" -#include "optimizer.h" - -#include "optimizer-shared.cpp" - -using namespace cashew; -using namespace wasm; - -int debug; - -// Utilities - -IString GLOBAL("global"), NAN_("NaN"), INFINITY_("Infinity"), - TOPMOST("topmost"), - INT8ARRAY("Int8Array"), - INT16ARRAY("Int16Array"), - INT32ARRAY("Int32Array"), - UINT8ARRAY("Uint8Array"), - UINT16ARRAY("Uint16Array"), - UINT32ARRAY("Uint32Array"), - FLOAT32ARRAY("Float32Array"), - FLOAT64ARRAY("Float64Array"), - IMPOSSIBLE_CONTINUE("impossible-continue"), - MATH("Math"), - IMUL("imul"), - CLZ32("clz32"); - - -static void abort_on(std::string why) { - std::cerr << why << '\n'; - abort(); -} -static void abort_on(std::string why, int x) { - std::cerr << why << ' ' << x << '\n'; - abort(); -} -static void abort_on(std::string why, Ref element) { - std::cerr << why << ' '; - element->stringify(std::cerr); - std::cerr << '\n'; - abort(); -} -static void abort_on(std::string why, IString element) { - std::cerr << why << ' ' << element.str << '\n'; - abort(); -} - -// useful when we need to see our parent, in an asm.js expression stack -struct AstStackHelper { - static std::vector<Ref> astStack; - AstStackHelper(Ref curr) { - astStack.push_back(curr); - } - ~AstStackHelper() { - astStack.pop_back(); - } - Ref getParent() { - assert(astStack.size() >= 2); - return astStack[astStack.size()-2]; - } -}; - -std::vector<Ref> AstStackHelper::astStack; - -// -// Asm2WasmBuilder - converts an asm.js module into WebAssembly -// - -class Asm2WasmBuilder { - Module& wasm; - - wasm::Arena allocator; - - // globals - - unsigned nextGlobal; // next place to put a global - unsigned maxGlobal; // highest address we can put a global - struct MappedGlobal { - unsigned address; - WasmType type; - bool import; // if true, this is an import - we should read the value, not just set a zero - MappedGlobal() : address(0), type(none), import(false) {} - MappedGlobal(unsigned address, WasmType type, bool import) : address(address), type(type), import(import) {} - }; - std::map<IString, MappedGlobal> mappedGlobals; - - void allocateGlobal(IString name, WasmType type, bool import) { - assert(mappedGlobals.find(name) == mappedGlobals.end()); - mappedGlobals.emplace(name, MappedGlobal(nextGlobal, type, import)); - nextGlobal += 8; - assert(nextGlobal < maxGlobal); - } - - struct View { - unsigned bytes; - bool integer, signed_; - View() : bytes(0) {} - View(unsigned bytes, bool integer, bool signed_) : bytes(bytes), integer(integer), signed_(signed_) {} - }; - - std::map<IString, View> views; // name (e.g. HEAP8) => view info - IString Math_imul; // imported name of Math.imul - IString Math_clz32; // imported name of Math.imul - - // function types. we fill in this information as we see - // uses, in the first pass - - std::map<IString, FunctionType> importedFunctionTypes; - - void noteImportedFunctionCall(Ref ast, Ref parent, AsmData *asmData) { - assert(ast[0] == CALL && ast[1][0] == NAME); - IString importName = ast[1][1]->getIString(); - FunctionType type; - type.name = IString((std::string("type$") + importName.str).c_str(), false); // TODO: make a list of such types - type.result = detectWasmType(parent, asmData); - Ref args = ast[2]; - for (unsigned i = 0; i < args->size(); i++) { - type.params.push_back(detectWasmType(args[i], asmData)); - } - // if we already saw this signature, verify it's the same (or else handle that) - if (importedFunctionTypes.find(importName) != importedFunctionTypes.end()) { - FunctionType& previous = importedFunctionTypes[importName]; -#if 0 - std::cout << "compare " << importName.str << "\nfirst: "; - type.print(std::cout, 0); - std::cout << "\nsecond: "; - previous.print(std::cout, 0) << ".\n"; -#endif - if (type != previous) { - // merge it in. we'll add on extra 0 parameters for ones not actually used, etc. - for (size_t i = 0; i < type.params.size(); i++) { - if (previous.params.size() > i) { - if (previous.params[i] == none) { - previous.params[i] = type.params[i]; // use a more concrete type - } else { - previous.params.push_back(type.params[i]); // add a new param - } - } - } - if (previous.result == none) { - previous.result = type.result; // use a more concrete type - } - } - } else { - importedFunctionTypes[importName] = type; - } - } - - char getSigFromType(WasmType type) { - switch (type) { - case i32: return 'i'; - case f64: return 'd'; - case none: return 'v'; - default: abort(); - } - } - - FunctionType *getFunctionType(Ref parent, ExpressionList& operands) { - // generate signature - WasmType result = detectWasmType(parent, nullptr); - std::string str = "FUNCSIG$"; - str += getSigFromType(result); - for (auto operand : operands) { - str += getSigFromType(operand->type); - } - IString sig(str.c_str(), false); - if (wasm.functionTypes.find(sig) == wasm.functionTypes.end()) { - // add new type - auto type = allocator.alloc<FunctionType>(); - type->name = sig; - type->result = result; - for (auto operand : operands) { - type->params.push_back(operand->type); - } - wasm.functionTypes[sig] = type; - assert(wasm.functionTypes.find(sig) != wasm.functionTypes.end()); - } - return wasm.functionTypes[sig]; - } - -public: - Asm2WasmBuilder(Module& wasm) : wasm(wasm), nextGlobal(8), maxGlobal(1000) {} - - void processAsm(Ref ast); - void optimize(); - -private: - WasmType asmToWasmType(AsmType asmType) { - switch (asmType) { - case ASM_INT: return WasmType::i32; - case ASM_DOUBLE: return WasmType::f64; - case ASM_NONE: return WasmType::none; - default: abort_on("confused asmType", asmType); - } - } - AsmType wasmToAsmType(WasmType type) { - switch (type) { - case WasmType::i32: return ASM_INT; - case WasmType::f64: return ASM_DOUBLE; - case WasmType::none: return ASM_NONE; - default: abort_on("confused wasmType", type); - } - } - - AsmType detectAsmType(Ref ast, AsmData *data) { - if (ast[0] == NAME) { - IString name = ast[1]->getIString(); - if (!data->isLocal(name)) { - // must be global - assert(mappedGlobals.find(name) != mappedGlobals.end()); - return wasmToAsmType(mappedGlobals[name].type); - } - } - return detectType(ast, data); - } - - WasmType detectWasmType(Ref ast, AsmData *data) { - return asmToWasmType(detectAsmType(ast, data)); - } - - bool isUnsignedCoercion(Ref ast) { // TODO: use detectSign? - if (ast[0] == BINARY && ast[1] == TRSHIFT) return true; - return false; - } - - // an asm.js binary op can either be a binary or a relational in wasm - bool parseAsmBinaryOp(IString op, Ref left, Ref right, BinaryOp &binary, RelationalOp &relational, AsmData *asmData) { - if (op == PLUS) { binary = BinaryOp::Add; return true; } - if (op == MINUS) { binary = BinaryOp::Sub; return true; } - if (op == MUL) { binary = BinaryOp::Mul; return true; } - if (op == AND) { binary = BinaryOp::And; return true; } - if (op == OR) { binary = BinaryOp::Or; return true; } - if (op == XOR) { binary = BinaryOp::Xor; return true; } - if (op == LSHIFT) { binary = BinaryOp::Shl; return true; } - if (op == RSHIFT) { binary = BinaryOp::ShrS; return true; } - if (op == TRSHIFT) { binary = BinaryOp::ShrU; return true; } - if (op == EQ) { relational = RelationalOp::Eq; return false; } - if (op == NE) { relational = RelationalOp::Ne; return false; } - WasmType leftType = detectWasmType(left, asmData); -#if 0 - std::cout << "CHECK\n"; - left->stringify(std::cout); - std::cout << " => " << printWasmType(leftType); - std::cout << '\n'; - right->stringify(std::cout); - std::cout << " => " << printWasmType(detectWasmType(right, asmData)) << "\n"; -#endif - bool isInteger = leftType == WasmType::i32; - bool isUnsigned = isUnsignedCoercion(left) || isUnsignedCoercion(right); - if (op == DIV) { - if (isInteger) { - { binary = isUnsigned ? BinaryOp::DivU : BinaryOp::DivS; return true; } - } - { binary = BinaryOp::Div; return true; } - } - if (op == MOD) { - if (isInteger) { - { binary = isUnsigned ? BinaryOp::RemU : BinaryOp::RemS; return true; } - } - abort_on("non-integer rem"); - } - if (op == GE) { - if (isInteger) { - { relational = isUnsigned ? RelationalOp::GeU : RelationalOp::GeS; return false; } - } - { relational = RelationalOp::Ge; return false; } - } - if (op == GT) { - if (isInteger) { - { relational = isUnsigned ? RelationalOp::GtU : RelationalOp::GtS; return false; } - } - { relational = RelationalOp::Gt; return false; } - } - if (op == LE) { - if (isInteger) { - { relational = isUnsigned ? RelationalOp::LeU : RelationalOp::LeS; return false; } - } - { relational = RelationalOp::Le; return false; } - } - if (op == LT) { - if (isInteger) { - { relational = isUnsigned ? RelationalOp::LtU : RelationalOp::LtS; return false; } - } - { relational = RelationalOp::Lt; return false; } - } - abort_on("bad wasm binary op", op); - } - - unsigned bytesToShift(unsigned bytes) { - switch (bytes) { - case 1: return 0; - case 2: return 1; - case 4: return 2; - case 8: return 3; - default: abort(); - } - } - - wasm::Arena tempAllocator; - - std::map<unsigned, Ref> tempNums; - - Literal getLiteral(Ref ast) { - if (ast[0] == NUM) { - return Literal((int32_t)ast[1]->getInteger()); - } else if (ast[0] == UNARY_PREFIX) { - if (ast[1] == MINUS && ast[2][0] == NUM) { - double num = -ast[2][1]->getNumber(); - assert(isInteger32(num)); - return Literal((int32_t)num); - } - if (ast[1] == MINUS && ast[2][0] == UNARY_PREFIX && ast[2][1] == PLUS && ast[2][2][0] == NUM) { - return Literal((double)-ast[2][2][1]->getNumber()); - } - } - abort(); - } - - Function* processFunction(Ref ast); -}; - -void Asm2WasmBuilder::processAsm(Ref ast) { - assert(ast[0] == TOPLEVEL); - Ref asmFunction = ast[1][0]; - assert(asmFunction[0] == DEFUN); - Ref body = asmFunction[3]; - assert(body[0][0] == STAT && body[0][1][0] == STRING && (body[0][1][1]->getIString() == IString("use asm") || body[0][1][1]->getIString() == IString("almost asm"))); - - auto addImport = [&](IString name, Ref imported, WasmType type) { - assert(imported[0] == DOT); - Ref module = imported[1]; - if (module[0] == DOT) { - // we can have (global.Math).floor; skip the 'Math' - assert(module[1][0] == NAME); - if (module[2] == MATH) { - if (imported[2] == IMUL) { - assert(Math_imul.isNull()); - Math_imul = name; - return; - } else if (imported[2] == CLZ32) { - assert(Math_clz32.isNull()); - Math_clz32 = name; - return; - } - } - module = module[1]; - } - assert(module[0] == NAME); - Import import; - import.name = name; - import.module = module[1]->getIString(); - import.base = imported[2]->getIString(); - // special-case some asm builtins - if (import.module == GLOBAL && (import.base == NAN_ || import.base == INFINITY_)) { - type = WasmType::f64; - } - if (type != WasmType::none) { - // wasm has no imported constants, so allocate a global, and we need to write the value into that - allocateGlobal(name, type, true); - } else { - wasm.imports.emplace(name, import); - } - }; - - // first pass - do almost everything, but function imports - - for (unsigned i = 1; i < body->size(); i++) { - Ref curr = body[i]; - if (curr[0] == VAR) { - // import, global, or table - for (unsigned j = 0; j < curr[1]->size(); j++) { - Ref pair = curr[1][j]; - IString name = pair[0]->getIString(); - Ref value = pair[1]; - if (value[0] == NUM) { - // global int - assert(value[1]->getNumber() == 0); - allocateGlobal(name, WasmType::i32, false); - } else if (value[0] == BINARY) { - // int import - assert(value[1] == OR && value[3][0] == NUM && value[3][1]->getNumber() == 0); - Ref import = value[2]; // env.what - addImport(name, import, WasmType::i32); - } else if (value[0] == UNARY_PREFIX) { - // double import or global - assert(value[1] == PLUS); - Ref import = value[2]; - if (import[0] == NUM) { - // global - assert(import[1]->getNumber() == 0); - allocateGlobal(name, WasmType::f64, false); - } else { - // import - addImport(name, import, WasmType::f64); - } - } else if (value[0] == DOT) { - // function import - addImport(name, value, WasmType::none); - } else if (value[0] == NEW) { - // ignore imports of typed arrays, but note the names of the arrays - value = value[1]; - assert(value[0] == CALL); - Ref constructor = value[1]; - assert(constructor[0] == DOT); // global.*Array - IString heap = constructor[2]->getIString(); - unsigned bytes; - bool integer, signed_; - if (heap == INT8ARRAY) { - bytes = 1; integer = true; signed_ = true; - } else if (heap == INT16ARRAY) { - bytes = 2; integer = true; signed_ = true; - } else if (heap == INT32ARRAY) { - bytes = 4; integer = true; signed_ = true; - } else if (heap == UINT8ARRAY) { - bytes = 1; integer = true; signed_ = false; - } else if (heap == UINT16ARRAY) { - bytes = 2; integer = true; signed_ = false; - } else if (heap == UINT32ARRAY) { - bytes = 4; integer = true; signed_ = false; - } else if (heap == FLOAT32ARRAY) { - bytes = 4; integer = false; signed_ = true; - } else if (heap == FLOAT64ARRAY) { - bytes = 8; integer = false; signed_ = true; - } - assert(views.find(name) == views.end()); - views.emplace(name, View(bytes, integer, signed_)); - } else if (value[0] == ARRAY) { - // function table. we "merge" them, so e.g. [foo, b1] , [b2, bar] => [foo, bar] , assuming b* are the aborting thunks - // when minified, we can't tell from the name b\d+, but null thunks appear multiple times in a table; others never do - // TODO: we can drop some b*s at the end of the table - Ref contents = value[1]; - std::map<IString, unsigned> counts; // name -> how many times seen - for (unsigned k = 0; k < contents->size(); k++) { - IString curr = contents[k][1]->getIString(); - counts[curr]++; - } - for (unsigned k = 0; k < contents->size(); k++) { - IString curr = contents[k][1]->getIString(); - if (wasm.table.names.size() <= k) { - wasm.table.names.push_back(curr); - } else { - if (counts[curr] == 1) { // if just one appearance, not a null thunk - wasm.table.names[k] = curr; - } - } - } - } else { - abort_on("invalid var element", pair); - } - } - } else if (curr[0] == DEFUN) { - // function - wasm.functions.push_back(processFunction(curr)); - } else if (curr[0] == RETURN) { - // exports - Ref object = curr[1]; - Ref contents = curr[1][1]; - for (unsigned k = 0; k < contents->size(); k++) { - Ref pair = contents[k]; - IString key = pair[0]->getIString(); - Ref value = pair[1]; - assert(value[0] == NAME); - Export export_; - export_.name = key; - export_.value = value[1]->getIString(); - wasm.exports.push_back(export_); - } - } - } - - // second pass - function imports - - std::vector<IString> toErase; - - for (auto& pair : wasm.imports) { - IString name = pair.first; - Import& import = pair.second; - if (importedFunctionTypes.find(name) != importedFunctionTypes.end()) { - import.type = importedFunctionTypes[name]; - } else { - // never actually used - toErase.push_back(name); - } - } - - for (auto curr : toErase) { - wasm.imports.erase(curr); - } - - // cleanups - - tempAllocator.clear(); -} - -Function* Asm2WasmBuilder::processFunction(Ref ast) { - //if (ast[1] != IString("qta")) return nullptr; - - if (debug) { - std::cout << "\nfunc: " << ast[1]->getIString().str << '\n'; - if (debug >= 2) { - ast->stringify(std::cout); - std::cout << '\n'; - } - } - - auto function = allocator.alloc<Function>(); - function->name = ast[1]->getIString(); - Ref params = ast[2]; - Ref body = ast[3]; - - unsigned nextId = 0; - auto getNextId = [&nextId](std::string prefix) { - return IString((prefix + '$' + std::to_string(nextId++)).c_str(), false); - }; - - // given an asm.js label, returns the wasm label for breaks or continues - auto getBreakLabelName = [](IString label) { - return IString((std::string("label$break$") + label.str).c_str(), false); - }; - auto getContinueLabelName = [](IString label) { - return IString((std::string("label$continue$") + label.str).c_str(), false); - }; - - IStringSet functionVariables; // params or locals - - IString parentLabel; // set in LABEL, then read in WHILE/DO - std::vector<IString> breakStack; // where a break will go - std::vector<IString> continueStack; // where a continue will go - - AsmData asmData; // need to know var and param types, for asm type detection - - for (unsigned i = 0; i < params->size(); i++) { - Ref curr = body[i]; - assert(curr[0] == STAT); - curr = curr[1]; - assert(curr[0] == ASSIGN && curr[2][0] == NAME); - IString name = curr[2][1]->getIString(); - AsmType asmType = detectType(curr[3]); - function->params.emplace_back(name, asmToWasmType(asmType)); - functionVariables.insert(name); - asmData.addParam(name, asmType); - } - unsigned start = params->size(); - while (start < body->size() && body[start][0] == VAR) { - Ref curr = body[start]; - for (unsigned j = 0; j < curr[1]->size(); j++) { - Ref pair = curr[1][j]; - IString name = pair[0]->getIString(); - AsmType asmType = detectType(pair[1], nullptr, true); - function->locals.emplace_back(name, asmToWasmType(asmType)); - functionVariables.insert(name); - asmData.addVar(name, asmType); - } - start++; - } - - bool seenReturn = false; // function->result is updated if we see a return - bool needTopmost = false; // we label the topmost b lock if we need one for a return - // processors - std::function<Expression* (Ref, unsigned)> processStatements; - std::function<Expression* (Ref, unsigned)> processUnshifted; - - std::function<Expression* (Ref)> process = [&](Ref ast) -> Expression* { - AstStackHelper astStackHelper(ast); // TODO: only create one when we need it? - if (debug >= 2) { - std::cout << "at: "; - ast->stringify(std::cout); - std::cout << '\n'; - } - IString what = ast[0]->getIString(); - if (what == STAT) { - return process(ast[1]); // and drop return value, if any - } else if (what == ASSIGN) { - if (ast[2][0] == NAME) { - IString name = ast[2][1]->getIString(); - if (functionVariables.has(name)) { - auto ret = allocator.alloc<SetLocal>(); - ret->name = ast[2][1]->getIString(); - ret->value = process(ast[3]); - ret->type = ret->value->type; - return ret; - } - // global var, do a store to memory - assert(mappedGlobals.find(name) != mappedGlobals.end()); - MappedGlobal global = mappedGlobals[name]; - auto ret = allocator.alloc<Store>(); - ret->bytes = getWasmTypeSize(global.type); - ret->float_ = isFloat(global.type); - ret->offset = 0; - ret->align = ret->bytes; - auto ptr = allocator.alloc<Const>(); - ptr->value.type = WasmType::i32; // XXX for wasm64, need 64 - ptr->value.i32 = global.address; - ret->ptr = ptr; - ret->value = process(ast[3]); - ret->type = global.type; - return ret; - } else if (ast[2][0] == SUB) { - Ref target = ast[2]; - assert(target[1][0] == NAME); - IString heap = target[1][1]->getIString(); - assert(views.find(heap) != views.end()); - View& view = views[heap]; - auto ret = allocator.alloc<Store>(); - ret->bytes = view.bytes; - ret->float_ = !view.integer; - ret->offset = 0; - ret->align = view.bytes; - ret->ptr = processUnshifted(target[2], view.bytes); - ret->value = process(ast[3]); - ret->type = ret->value->type; - return ret; - } - abort_on("confusing assign", ast); - } else if (what == BINARY) { - if (ast[1] == OR && ast[3][0] == NUM && ast[3][1]->getNumber() == 0) { - auto ret = process(ast[2]); // just look through the ()|0 coercion - ret->type = WasmType::i32; // we add it here for e.g. call coercions - return ret; - } - BinaryOp binary; - RelationalOp relational; - bool isBinary = parseAsmBinaryOp(ast[1]->getIString(), ast[2], ast[3], binary, relational, &asmData); - if (isBinary) { - auto ret = allocator.alloc<Binary>(); - ret->op = binary; - ret->left = process(ast[2]); - ret->right = process(ast[3]); - ret->type = ret->left->type; - return ret; - } else { - auto ret = allocator.alloc<Compare>(); - ret->op = relational; - ret->left = process(ast[2]); - ret->right = process(ast[3]); - assert(ret->left->type == ret->right->type); - ret->inputType = ret->left->type; - return ret; - } - } else if (what == NUM) { - auto ret = allocator.alloc<Const>(); - double num = ast[1]->getNumber(); - if (isInteger32(num)) { - ret->value.type = WasmType::i32; - ret->value.i32 = num; - } else { - ret->value.type = WasmType::f64; - ret->value.f64 = num; - } - ret->type = ret->value.type; - return ret; - } else if (what == NAME) { - IString name = ast[1]->getIString(); - if (functionVariables.has(name)) { - // var in scope - auto ret = allocator.alloc<GetLocal>(); - ret->name = name; - ret->type = asmToWasmType(asmData.getType(name)); - return ret; - } - // global var, do a load from memory - assert(mappedGlobals.find(name) != mappedGlobals.end()); - MappedGlobal global = mappedGlobals[name]; - auto ret = allocator.alloc<Load>(); - ret->bytes = getWasmTypeSize(global.type); - ret->signed_ = true; // but doesn't matter - ret->float_ = isFloat(global.type); - ret->offset = 0; - ret->align = ret->bytes; - auto ptr = allocator.alloc<Const>(); - ptr->value.type = WasmType::i32; // XXX for wasm64, need 64 - ptr->value.i32 = global.address; - ret->ptr = ptr; - ret->type = global.type; - return ret; - } else if (what == SUB) { - Ref target = ast[1]; - assert(target[0] == NAME); - IString heap = target[1]->getIString(); - assert(views.find(heap) != views.end()); - View& view = views[heap]; - auto ret = allocator.alloc<Load>(); - ret->bytes = view.bytes; - ret->signed_ = view.signed_; - ret->float_ = !view.integer; - ret->offset = 0; - ret->align = view.bytes; - ret->ptr = processUnshifted(ast[2], view.bytes); - ret->type = getWasmType(view.bytes, !view.integer); - return ret; - } else if (what == UNARY_PREFIX) { - if (ast[1] == PLUS) { - if (ast[2][0] == NUM) { - auto ret = allocator.alloc<Const>(); - ret->value.type = WasmType::f64; - ret->value.f64 = ast[2][1]->getNumber(); - ret->type = ret->value.type; - return ret; - } - AsmType childType = detectAsmType(ast[2], &asmData); - if (childType == ASM_INT) { - auto ret = allocator.alloc<Convert>(); - ret->op = isUnsignedCoercion(ast[2]) ? ConvertUInt32 : ConvertSInt32; - ret->value = process(ast[2]); - ret->type = WasmType::f64; - return ret; - } - assert(childType == ASM_NONE || childType == ASM_DOUBLE); // e.g. a coercion on a call or for a return - auto ret = process(ast[2]); // just look through the +() coercion - ret->type = WasmType::f64; // we add it here for e.g. call coercions - return ret; - } else if (ast[1] == MINUS) { - if (ast[2][0] == NUM || (ast[2][0] == UNARY_PREFIX && ast[2][1] == PLUS && ast[2][2][0] == NUM)) { - auto ret = allocator.alloc<Const>(); - ret->value = getLiteral(ast); - ret->type = ret->value.type; - return ret; - } - AsmType asmType = detectAsmType(ast[2], &asmData); - if (asmType == ASM_INT) { - // wasm has no unary negation for int, so do 0- - auto ret = allocator.alloc<Binary>(); - ret->op = Sub; - ret->left = allocator.alloc<Const>()->set(Literal((int32_t)0)); - ret->right = process(ast[2]); - ret->type = WasmType::i32; - return ret; - } - assert(asmType == ASM_DOUBLE); - auto ret = allocator.alloc<Unary>(); - ret->op = Neg; - ret->value = process(ast[2]); - ret->type = WasmType::f64; - return ret; - } else if (ast[1] == B_NOT) { - // ~, might be ~~ as a coercion or just a not - if (ast[2][0] == UNARY_PREFIX && ast[2][1] == B_NOT) { - auto ret = allocator.alloc<Convert>(); - ret->op = TruncSFloat64; // equivalent to U, except for error handling, which asm.js doesn't have anyhow - ret->value = process(ast[2][2]); - ret->type = WasmType::i32; - return ret; - } - // no bitwise unary not, so do xor with -1 - auto ret = allocator.alloc<Binary>(); - ret->op = Xor; - ret->left = process(ast[2]); - ret->right = allocator.alloc<Const>()->set(Literal(int32_t(-1))); - ret->type = WasmType::i32; - return ret; - } else if (ast[1] == L_NOT) { - // no logical unary not, so do == 0 - auto ret = allocator.alloc<Compare>(); - ret->op = Eq; - ret->left = process(ast[2]); - ret->right = allocator.alloc<Const>()->set(Literal(0)); - assert(ret->left->type == ret->right->type); - ret->inputType = ret->left->type; - return ret; - } - abort_on("bad unary", ast); - } else if (what == IF) { - auto ret = allocator.alloc<If>(); - ret->condition = process(ast[1]); - ret->ifTrue = process(ast[2]); - ret->ifFalse = !!ast[3] ? process(ast[3]) : nullptr; - return ret; - } else if (what == CALL) { - if (ast[1][0] == NAME) { - IString name = ast[1][1]->getIString(); - if (name == Math_imul) { - assert(ast[2]->size() == 2); - auto ret = allocator.alloc<Binary>(); - ret->op = Mul; - ret->left = process(ast[2][0]); - ret->right = process(ast[2][1]); - ret->type = WasmType::i32; - return ret; - } - if (name == Math_clz32) { - assert(ast[2]->size() == 1); - auto ret = allocator.alloc<Unary>(); - ret->op = Clz; - ret->value = process(ast[2][0]); - ret->type = WasmType::i32; - return ret; - } - Call* ret; - if (wasm.imports.find(name) != wasm.imports.end()) { - // no imports yet in reference interpreter, fake it - AsmType asmType = detectAsmType(astStackHelper.getParent(), &asmData); - if (asmType == ASM_NONE) return allocator.alloc<Nop>(); - if (asmType == ASM_INT) return allocator.alloc<Const>()->set(Literal((int32_t)0)); - if (asmType == ASM_DOUBLE) return allocator.alloc<Const>()->set(Literal((double)0.0)); - abort(); -#if 0 - ret = allocator.alloc<CallImport>(); - noteImportedFunctionCall(ast, astStackHelper.getParent(), &asmData); -#endif - } else { - ret = allocator.alloc<Call>(); - } - ret->target = name; - Ref args = ast[2]; - for (unsigned i = 0; i < args->size(); i++) { - ret->operands.push_back(process(args[i])); - } - return ret; - } - // function pointers - auto ret = allocator.alloc<CallIndirect>(); - Ref target = ast[1]; - assert(target[0] == SUB && target[1][0] == NAME && target[2][0] == BINARY && target[2][1] == AND && target[2][3][0] == NUM); // FUNCTION_TABLE[(expr) & mask] - ret->target = process(target[2][2]); - Ref args = ast[2]; - for (unsigned i = 0; i < args->size(); i++) { - ret->operands.push_back(process(args[i])); - } - ret->type = getFunctionType(astStackHelper.getParent(), ret->operands); - return ret; - } else if (what == RETURN) { - WasmType type = !!ast[1] ? detectWasmType(ast[1], &asmData) : none; - if (seenReturn) { - assert(function->result == type); - } else { - function->result = type; - } - // wasm has no return, so we just break on the topmost block - needTopmost = true; - auto ret = allocator.alloc<Break>(); - ret->name = TOPMOST; - ret->condition = nullptr; - ret->value = !!ast[1] ? process(ast[1]) : nullptr; - return ret; - } else if (what == BLOCK) { - return processStatements(ast[1], 0); - } else if (what == BREAK) { - auto ret = allocator.alloc<Break>(); - assert(breakStack.size() > 0); - ret->name = !!ast[1] ? getBreakLabelName(ast[1]->getIString()) : breakStack.back(); - ret->condition = nullptr; - ret->value = nullptr; - return ret; - } else if (what == CONTINUE) { - auto ret = allocator.alloc<Break>(); - assert(continueStack.size() > 0); - ret->name = !!ast[1] ? getContinueLabelName(ast[1]->getIString()) : continueStack.back(); - ret->condition = nullptr; - ret->value = nullptr; - return ret; - } else if (what == WHILE) { - bool forever = ast[1][0] == NUM && ast[1][1]->getInteger() == 1; - auto ret = allocator.alloc<Loop>(); - IString out, in; - if (!parentLabel.isNull()) { - out = getBreakLabelName(parentLabel); - in = getContinueLabelName(parentLabel); - parentLabel = IString(); - } else { - out = getNextId("while-out"); - in = getNextId("while-in"); - } - ret->out = out; - ret->in = in; - breakStack.push_back(out); - continueStack.push_back(in); - if (forever) { - ret->body = process(ast[2]); - } else { - Break *continueWhile = allocator.alloc<Break>(); - continueWhile->name = in; - continueWhile->condition = process(ast[1]); - continueWhile->value = nullptr; - auto body = allocator.alloc<Block>(); - body->list.push_back(continueWhile); - body->list.push_back(process(ast[2])); - ret->body = body; - } - continueStack.pop_back(); - breakStack.pop_back(); - return ret; - } else if (what == DO) { - if (ast[1][0] == NUM && ast[1][1]->getNumber() == 0) { - // one-time loop - auto block = allocator.alloc<Block>(); - IString stop; - if (!parentLabel.isNull()) { - stop = getBreakLabelName(parentLabel); - parentLabel = IString(); - } else { - stop = getNextId("do-once"); - } - block->name = stop; - breakStack.push_back(stop); - continueStack.push_back(IMPOSSIBLE_CONTINUE); - block->list.push_back(process(ast[2])); - continueStack.pop_back(); - breakStack.pop_back(); - return block; - } - // general do-while loop - auto ret = allocator.alloc<Loop>(); - IString out, in; - if (!parentLabel.isNull()) { - out = getBreakLabelName(parentLabel); - in = getContinueLabelName(parentLabel); - parentLabel = IString(); - } else { - out = getNextId("do-out"); - in = getNextId("do-in"); - } - ret->out = out; - ret->in = in; - breakStack.push_back(out); - continueStack.push_back(in); - ret->body = process(ast[2]); - continueStack.pop_back(); - breakStack.pop_back(); - Break *continueIf = allocator.alloc<Break>(); - continueIf->name = in; - continueIf->condition = process(ast[1]); - continueIf->value = nullptr; - if (Block *block = dynamic_cast<Block*>(ret->body)) { - block->list.push_back(continueIf); - } else { - auto newBody = allocator.alloc<Block>(); - newBody->list.push_back(ret->body); - newBody->list.push_back(continueIf); - ret->body = newBody; - } - return ret; - } else if (what == LABEL) { - parentLabel = ast[1]->getIString(); - return process(ast[2]); - } else if (what == CONDITIONAL) { - auto ret = allocator.alloc<If>(); - ret->condition = process(ast[1]); - ret->ifTrue = process(ast[2]); - ret->ifFalse = process(ast[3]); - ret->type = ret->ifTrue->type; - return ret; - } else if (what == SEQ) { - auto ret = allocator.alloc<Block>(); - ret->list.push_back(process(ast[1])); - ret->list.push_back(process(ast[2])); - return ret; - } else if (what == SWITCH) { - // XXX switch is still in flux in the spec repo, just emit a placeholder - return allocator.alloc<Nop>(); -#if 0 - IString name = getNextId("switch"); - breakStack.push_back(name); - auto ret = allocator.alloc<Switch>(); - ret->name = name; - ret->value = process(ast[1]); - Ref cases = ast[2]; - for (unsigned i = 0; i < cases->size(); i++) { - Ref curr = cases[i]; - Ref condition = curr[0]; - Ref body = curr[1]; - if (condition->isNull()) { - ret->default_ = processStatements(body, 0); - } else { - assert(condition[0] == NUM || condition[0] == UNARY_PREFIX); - Switch::Case case_; - case_.value = getLiteral(condition); - case_.body = processStatements(body, 0); - case_.fallthru = false; // XXX we assume no fallthru, ever - ret->cases.push_back(case_); - } - } - breakStack.pop_back(); - return ret; -#endif - } - abort_on("confusing expression", ast); - }; - - // given HEAP32[addr >> 2], we need an absolute address, and would like to remove that shift. - // if there is a shift, we can just look through it, etc. - processUnshifted = [&](Ref ptr, unsigned bytes) { - unsigned shifts = bytesToShift(bytes); - if (ptr[0] == BINARY && ptr[1] == RSHIFT && ptr[3][0] == NUM && ptr[3][1]->getInteger() == shifts) { - return process(ptr[2]); // look through it - } else if (ptr[0] == NUM) { - // constant, apply a shift (e.g. HEAP32[1] is address 4) - unsigned addr = ptr[1]->getInteger(); - unsigned shifted = addr << shifts; - auto ret = allocator.alloc<Const>(); - ret->value.type = WasmType::i32; - ret->value.i32 = shifted; - return (Expression*)ret; - } - abort_on("bad processUnshifted", ptr); - }; - - processStatements = [&](Ref ast, unsigned from) -> Expression* { - unsigned size = ast->size() - from; - if (size == 0) return allocator.alloc<Nop>(); - if (size == 1) return process(ast[from]); - auto block = allocator.alloc<Block>(); - for (unsigned i = from; i < ast->size(); i++) { - block->list.push_back(process(ast[i])); - } - return block; - }; - // body - function->body = processStatements(body, start); - if (needTopmost) { - Block* topmost = dynamic_cast<Block*>(function->body); - // if there's no block there, or there is a block but it already has a name, we need a new block. - if (!topmost || topmost->name.is()) { - topmost = allocator.alloc<Block>(); - topmost->list.push_back(function->body); - function->body = topmost; - } - topmost->name = TOPMOST; - } - // cleanups/checks - assert(breakStack.size() == 0 && continueStack.size() == 0); - - return function; -} - -void Asm2WasmBuilder::optimize() { - struct BlockRemover : public WasmWalker { - BlockRemover() : WasmWalker(nullptr) {} - - Expression* visitBlock(Block *curr) override { - if (curr->list.size() != 1) return curr; - // just one element; maybe we can return just the element - if (curr->name.isNull()) return curr->list[0]; - // we might be broken to, but if it's a trivial singleton child break, we can optimize here as well - Break *child = dynamic_cast<Break*>(curr->list[0]); - if (!child || child->name != curr->name || !child->value) return curr; - - struct BreakSeeker : public WasmWalker { - IString target; // look for this one - size_t found; - - BreakSeeker(IString target) : target(target), found(false) {} - - Expression* visitBreak(Break *curr) override { - if (curr->name == target) found++; - } - }; - - // look in the child's children to see if there are more uses of this name - BreakSeeker breakSeeker(curr->name); - breakSeeker.walk(child->condition); - breakSeeker.walk(child->value); - if (breakSeeker.found == 0) return child->value; - - return curr; // failed to optimize - } - }; - - BlockRemover blockRemover; - for (auto function : wasm.functions) { - blockRemover.startWalk(function); - } -} - -// main - -int main(int argc, char **argv) { - debug = getenv("ASM2WASM_DEBUG") ? getenv("ASM2WASM_DEBUG")[0] - '0' : 0; - - char *infile = argv[1]; - - if (debug) std::cerr << "loading '" << infile << "'...\n"; - FILE *f = fopen(argv[1], "r"); - assert(f); - fseek(f, 0, SEEK_END); - int size = ftell(f); - char *input = new char[size+1]; - rewind(f); - int num = fread(input, 1, size, f); - // On Windows, ftell() gives the byte position (\r\n counts as two bytes), but when - // reading, fread() returns the number of characters read (\r\n is read as one char \n, and counted as one), - // so return value of fread can be less than size reported by ftell, and that is normal. - assert((num > 0 || size == 0) && num <= size); - fclose(f); - input[num] = 0; - - // emcc --separate-asm modules look like - // - // Module["asm"] = (function(global, env, buffer) { - // .. - // }); - // - // we need to clean that up. - if (*input == 'M') { - while (*input != 'f') { - input++; - num--; - } - char *end = input + num - 1; - while (*end != '}') { - *end = 0; - end--; - } - } - - if (debug) std::cerr << "parsing...\n"; - cashew::Parser<Ref, DotZeroValueBuilder> builder; - Ref asmjs = builder.parseToplevel(input); - - if (debug) std::cerr << "wasming...\n"; - Module wasm; - Asm2WasmBuilder asm2wasm(wasm); - asm2wasm.processAsm(asmjs); - - if (debug) std::cerr << "optimizing...\n"; - asm2wasm.optimize(); - - if (debug) std::cerr << "printing...\n"; - std::cout << wasm; - - if (debug) std::cerr << "done.\n"; -} - |