diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/asm2wasm.h | 281 | ||||
-rw-r--r-- | src/asmjs/asm_v_wasm.cpp | 2 | ||||
-rw-r--r-- | src/ast_utils.h | 48 | ||||
-rw-r--r-- | src/emscripten-optimizer/optimizer-shared.cpp | 11 | ||||
-rw-r--r-- | src/emscripten-optimizer/optimizer.h | 3 | ||||
-rw-r--r-- | src/emscripten-optimizer/parser.cpp | 2 | ||||
-rw-r--r-- | src/emscripten-optimizer/parser.h | 12 | ||||
-rw-r--r-- | src/passes/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/passes/LegalizeJSInterface.cpp | 211 | ||||
-rw-r--r-- | src/passes/pass.cpp | 1 | ||||
-rw-r--r-- | src/passes/passes.h | 5 | ||||
-rw-r--r-- | src/tools/asm2wasm.cpp | 7 | ||||
-rw-r--r-- | src/wasm-builder.h | 27 | ||||
-rw-r--r-- | src/wasm-js.cpp | 2 | ||||
-rw-r--r-- | src/wasm-validator.h | 3 |
15 files changed, 536 insertions, 80 deletions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h index e926e381f..bf42a8a0e 100644 --- a/src/asm2wasm.h +++ b/src/asm2wasm.h @@ -40,6 +40,55 @@ namespace wasm { using namespace cashew; +// Names + +Name I64("i64"), + I64_CONST("i64_const"), + I64_ADD("i64_add"), + I64_SUB("i64_sub"), + I64_MUL("i64_mul"), + I64_UDIV("i64_udiv"), + I64_SDIV("i64_sdiv"), + I64_UREM("i64_urem"), + I64_SREM("i64_srem"), + I64_AND("i64_and"), + I64_OR("i64_or"), + I64_XOR("i64_xor"), + I64_SHL("i64_shl"), + I64_ASHR("i64_ashr"), + I64_LSHR("i64_lshr"), + I64_LOAD("i64_load"), + I64_STORE("i64_store"), + I64_EQ("i64_eq"), + I64_NE("i64_ne"), + I64_ULE("i64_ule"), + I64_SLE("i64_sle"), + I64_UGE("i64_uge"), + I64_SGE("i64_sge"), + I64_ULT("i64_ult"), + I64_SLT("i64_slt"), + I64_UGT("i64_ugt"), + I64_SGT("i64_sgt"), + I64_TRUNC("i64_trunc"), + I64_SEXT("i64_sext"), + I64_ZEXT("i64_zext"), + I64_S2F("i64_s2f"), + I64_S2D("i64_s2d"), + I64_U2F("i64_u2f"), + I64_U2D("i64_u2d"), + I64_F2S("i64_f2s"), + I64_D2S("i64_d2s"), + I64_F2U("i64_f2u"), + I64_D2U("i64_d2u"), + I64_BC2D("i64_bc2d"), + I64_BC2I("i64_bc2i"), + I64_CTTZ("i64_cttz"), + I64_CTLZ("i64_ctlz"), + I64S_REM("i64s-rem"), + I64U_REM("i64u-rem"), + I64S_DIV("i64s-div"), + I64U_DIV("i64u-div"); + // Utilities static void abort_on(std::string why, Ref element) { @@ -53,6 +102,10 @@ static void abort_on(std::string why, IString element) { abort(); } +Index indexOr(Index x, Index y) { + return x ? x : y; +} + // useful when we need to see our parent, in an asm.js expression stack struct AstStackHelper { static std::vector<Ref> astStack; @@ -160,6 +213,7 @@ class Asm2WasmBuilder { bool debug; bool imprecise; bool optimize; + bool wasmOnly; public: std::map<IString, MappedGlobal> mappedGlobals; @@ -213,15 +267,14 @@ private: std::map<IString, std::unique_ptr<FunctionType>> importedFunctionTypes; - void noteImportedFunctionCall(Ref ast, WasmType resultType, AsmData *asmData, CallImport* call) { + void noteImportedFunctionCall(Ref ast, WasmType resultType, CallImport* call) { assert(ast[0] == CALL && ast[1][0] == NAME); IString importName = ast[1][1]->getIString(); auto type = make_unique<FunctionType>(); type->name = IString((std::string("type$") + importName.str).c_str(), false); // TODO: make a list of such types type->result = resultType; - Ref args = ast[2]; - for (unsigned i = 0; i < args->size(); i++) { - type->params.push_back(detectWasmType(args[i], asmData)); + for (auto* operand : call->operands) { + type->params.push_back(operand->type); } // if we already saw this signature, verify it's the same (or else handle that) if (importedFunctionTypes.find(importName) != importedFunctionTypes.end()) { @@ -259,14 +312,15 @@ private: } public: - Asm2WasmBuilder(Module& wasm, bool memoryGrowth, bool debug, bool imprecise, bool optimize) + Asm2WasmBuilder(Module& wasm, bool memoryGrowth, bool debug, bool imprecise, bool optimize, bool wasmOnly) : wasm(wasm), allocator(wasm.allocator), builder(wasm), memoryGrowth(memoryGrowth), debug(debug), imprecise(imprecise), - optimize(optimize) {} + optimize(optimize), + wasmOnly(wasmOnly) {} void processAsm(Ref ast); @@ -286,7 +340,7 @@ private: return view->second.type; } } - return detectType(ast, data, false, Math_fround); + return detectType(ast, data, false, Math_fround, wasmOnly); } WasmType detectWasmType(Ref ast, AsmData *data) { @@ -367,6 +421,10 @@ private: return -1; // avoid warning } + bool maybeWasmInt64Intrinsic(Name name) { + return strncmp(name.str, "i64", 3) == 0; + } + std::map<unsigned, Ref> tempNums; Literal checkLiteral(Ref ast) { @@ -388,6 +446,10 @@ private: 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()); } + } else if (wasmOnly && ast[0] == CALL && ast[1][0] == NAME && ast[1][1] == I64_CONST) { + uint64_t low = ast[2][0][1]->getNumber(); + uint64_t high = ast[2][1][1]->getNumber(); + return Literal(uint64_t(low + (high << 32))); } return Literal(); } @@ -426,6 +488,44 @@ private: return ret; } + // Some binary opts might trap, so emit them safely if we are precise + Expression* makeDangerousI64Binary(BinaryOp op, Expression* left, Expression* right) { + if (imprecise) return builder.makeBinary(op, left, right); + // we are precise, and the wasm operation might trap if done over 0, so generate a safe call + auto *call = allocator.alloc<Call>(); + switch (op) { + case BinaryOp::RemSInt64: call->target = I64S_REM; break; + case BinaryOp::RemUInt64: call->target = I64U_REM; break; + case BinaryOp::DivSInt64: call->target = I64S_DIV; break; + case BinaryOp::DivUInt64: call->target = I64U_DIV; break; + default: WASM_UNREACHABLE(); + } + call->operands.push_back(left); + call->operands.push_back(right); + call->type = i64; + static std::set<Name> addedFunctions; + if (addedFunctions.count(call->target) == 0) { + addedFunctions.insert(call->target); + auto func = new Function; + func->name = call->target; + func->params.push_back(i64); + func->params.push_back(i64); + func->result = i64; + func->body = builder.makeIf( + builder.makeUnary(EqZInt64, + builder.makeGetLocal(1, i64) + ), + builder.makeConst(Literal(int64_t(0))), + builder.makeBinary(op, + builder.makeGetLocal(0, i64), + builder.makeGetLocal(1, i64) + ) + ); + wasm.addFunction(func); + } + return call; + } + Function* processFunction(Ref ast); }; @@ -762,7 +862,14 @@ void Asm2WasmBuilder::processAsm(Ref ast) { FinalizeCalls(Asm2WasmBuilder* parent) : parent(parent) {} void visitCall(Call* curr) { - assert(getModule()->checkFunction(curr->target) ? true : (std::cerr << curr->target << '\n', false)); + if (!getModule()->checkFunction(curr->target)) { + std::cerr << "invalid call target: " << curr->target << '\n'; + if (parent->maybeWasmInt64Intrinsic(curr->target)) { + std::cerr << " - perhaps this is a wasm-only i64() method, and you should run asm2wasm with --wasm-only?\n"; + if (parent->wasmOnly) std::cerr << " - wait, you *did*. so this is an internal compiler error, please file an issue!\n"; + } + WASM_UNREACHABLE(); + } auto result = getModule()->getFunction(curr->target)->result; if (curr->type != result) { curr->type = result; @@ -826,6 +933,10 @@ void Asm2WasmBuilder::processAsm(Ref ast) { passRunner.add<FinalizeCalls>(this); passRunner.add<ReFinalize>(); // FinalizeCalls changes call types, need to percolate passRunner.add<AutoDrop>(); // FinalizeCalls may cause us to require additional drops + if (wasmOnly) { + // we didn't legalize i64s in fastcomp, and so must legalize the interface to the outside + passRunner.add("legalize-js-interface"); + } if (optimize) { // autodrop can add some garbage passRunner.add("vacuum"); @@ -913,28 +1024,8 @@ void Asm2WasmBuilder::processAsm(Ref ast) { x64 = Builder::addVar(func, "x64", i64), y64 = Builder::addVar(func, "y64", i64); auto* body = allocator.alloc<Block>(); - auto recreateI64 = [&](Index target, Index low, Index high) { - return builder.makeSetLocal( - target, - builder.makeBinary( - OrInt64, - builder.makeUnary( - ExtendUInt32, - builder.makeGetLocal(low, i32) - ), - builder.makeBinary( - ShlInt64, - builder.makeUnary( - ExtendUInt32, - builder.makeGetLocal(high, i32) - ), - builder.makeConst(Literal(int64_t(32))) - ) - ) - ); - }; - body->list.push_back(recreateI64(x64, xl, xh)); - body->list.push_back(recreateI64(y64, yl, yh)); + body->list.push_back(builder.makeSetLocal(x64, I64Utilities::recreateI64(builder, xl, xh))); + body->list.push_back(builder.makeSetLocal(y64, I64Utilities::recreateI64(builder, yl, yh))); body->list.push_back( builder.makeIf( builder.makeGetLocal(r, i32), @@ -963,22 +1054,10 @@ void Asm2WasmBuilder::processAsm(Ref ast) { body->list.push_back( builder.makeSetGlobal( tempRet0, - builder.makeUnary( - WrapInt64, - builder.makeBinary( - ShrUInt64, - builder.makeGetLocal(x64, i64), - builder.makeConst(Literal(int64_t(32))) - ) - ) - ) - ); - body->list.push_back( - builder.makeUnary( - WrapInt64, - builder.makeGetLocal(x64, i64) + I64Utilities::getI64High(builder, x64) ) ); + body->list.push_back(I64Utilities::getI64Low(builder, x64)); body->finalize(); func->body = body; } @@ -1027,7 +1106,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { curr = curr[1]; assert(curr[0] == ASSIGN && curr[2][0] == NAME); IString name = curr[2][1]->getIString(); - AsmType asmType = detectType(curr[3], nullptr, false, Math_fround); + AsmType asmType = detectType(curr[3], nullptr, false, Math_fround, wasmOnly); Builder::addParam(function, name, asmToWasmType(asmType)); functionVariables.insert(name); asmData.addParam(name, asmType); @@ -1038,7 +1117,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { 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, Math_fround); + AsmType asmType = detectType(pair[1], nullptr, true, Math_fround, wasmOnly); Builder::addVar(function, name, asmToWasmType(asmType)); functionVariables.insert(name); asmData.addVar(name, asmType); @@ -1110,7 +1189,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { conv->type = WasmType::f32; ret->value = conv; } else { - abort(); + abort_on("bad subtract types", ast); } } return ret; @@ -1446,13 +1525,74 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { } return ret; } + if (wasmOnly && maybeWasmInt64Intrinsic(name)) { + auto num = ast[2]->size(); + if (num == 1) { + auto* value = process(ast[2][0]); + if (name == I64) { + // no-op "coercion" / "cast", although we also tolerate i64(0) for constants that fit in i32 + if (value->type == i32) { + return builder.makeConst(Literal(int64_t(value->cast<Const>()->value.geti32()))); + } else { + fixCallType(value, i64); + return value; + } + } + if (name == I64_TRUNC) return builder.makeUnary(UnaryOp::WrapInt64, value); + if (name == I64_SEXT) return builder.makeUnary(UnaryOp::ExtendSInt32, value); + if (name == I64_ZEXT) return builder.makeUnary(UnaryOp::ExtendUInt32, value); + if (name == I64_S2F) return builder.makeUnary(UnaryOp::ConvertSInt64ToFloat32, value); + if (name == I64_S2D) return builder.makeUnary(UnaryOp::ConvertSInt64ToFloat64, value); + if (name == I64_U2F) return builder.makeUnary(UnaryOp::ConvertUInt64ToFloat32, value); + if (name == I64_U2D) return builder.makeUnary(UnaryOp::ConvertUInt64ToFloat64, value); + if (name == I64_F2S) return builder.makeUnary(UnaryOp::TruncSFloat32ToInt64, value); + if (name == I64_D2S) return builder.makeUnary(UnaryOp::TruncSFloat64ToInt64, value); + if (name == I64_F2U) return builder.makeUnary(UnaryOp::TruncUFloat32ToInt64, value); + if (name == I64_D2U) return builder.makeUnary(UnaryOp::TruncUFloat64ToInt64, value); + if (name == I64_BC2D) return builder.makeUnary(UnaryOp::ReinterpretInt64, value); + if (name == I64_BC2I) return builder.makeUnary(UnaryOp::ReinterpretFloat64, value); + if (name == I64_CTTZ) return builder.makeUnary(UnaryOp::CtzInt64, value); + if (name == I64_CTLZ) return builder.makeUnary(UnaryOp::ClzInt64, value); + } else if (num == 2) { // 2 params,binary + if (name == I64_CONST) return builder.makeConst(getLiteral(ast)); + if (name == I64_LOAD) return builder.makeLoad(8, true, 0, indexOr(ast[2][1][1]->getInteger(), 8), process(ast[2][0]), i64); + auto* left = process(ast[2][0]); + auto* right = process(ast[2][1]); + // maths + if (name == I64_ADD) return builder.makeBinary(BinaryOp::AddInt64, left, right); + if (name == I64_SUB) return builder.makeBinary(BinaryOp::SubInt64, left, right); + if (name == I64_MUL) return builder.makeBinary(BinaryOp::MulInt64, left, right); + if (name == I64_UDIV) return makeDangerousI64Binary(BinaryOp::DivUInt64, left, right); + if (name == I64_SDIV) return makeDangerousI64Binary(BinaryOp::DivSInt64, left, right); + if (name == I64_UREM) return makeDangerousI64Binary(BinaryOp::RemUInt64, left, right); + if (name == I64_SREM) return makeDangerousI64Binary(BinaryOp::RemSInt64, left, right); + if (name == I64_AND) return builder.makeBinary(BinaryOp::AndInt64, left, right); + if (name == I64_OR) return builder.makeBinary(BinaryOp::OrInt64, left, right); + if (name == I64_XOR) return builder.makeBinary(BinaryOp::XorInt64, left, right); + if (name == I64_SHL) return builder.makeBinary(BinaryOp::ShlInt64, left, right); + if (name == I64_ASHR) return builder.makeBinary(BinaryOp::ShrSInt64, left, right); + if (name == I64_LSHR) return builder.makeBinary(BinaryOp::ShrUInt64, left, right); + // comps + if (name == I64_EQ) return builder.makeBinary(BinaryOp::EqInt64, left, right); + if (name == I64_NE) return builder.makeBinary(BinaryOp::NeInt64, left, right); + if (name == I64_ULE) return builder.makeBinary(BinaryOp::LeUInt64, left, right); + if (name == I64_SLE) return builder.makeBinary(BinaryOp::LeSInt64, left, right); + if (name == I64_UGE) return builder.makeBinary(BinaryOp::GeUInt64, left, right); + if (name == I64_SGE) return builder.makeBinary(BinaryOp::GeSInt64, left, right); + if (name == I64_ULT) return builder.makeBinary(BinaryOp::LtUInt64, left, right); + if (name == I64_SLT) return builder.makeBinary(BinaryOp::LtSInt64, left, right); + if (name == I64_UGT) return builder.makeBinary(BinaryOp::GtUInt64, left, right); + if (name == I64_SGT) return builder.makeBinary(BinaryOp::GtSInt64, left, right); + } else if (num == 3) { // 3 params + if (name == I64_STORE) return builder.makeStore(8, 0, indexOr(ast[2][2][1]->getInteger(), 8), process(ast[2][0]), process(ast[2][1]), i64); + } + } Expression* ret; ExpressionList* operands; + bool import = false; if (wasm.checkImport(name)) { - Ref parent = astStackHelper.getParent(); - WasmType type = !!parent ? detectWasmType(parent, &asmData) : none; + import = true; auto specific = allocator.alloc<CallImport>(); - noteImportedFunctionCall(ast, type, &asmData, specific); specific->target = name; operands = &specific->operands; ret = specific; @@ -1466,6 +1606,11 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { for (unsigned i = 0; i < args->size(); i++) { operands->push_back(process(args[i])); } + if (import) { + Ref parent = astStackHelper.getParent(); + WasmType type = !!parent ? detectWasmType(parent, &asmData) : none; + noteImportedFunctionCall(ast, type, ret->cast<CallImport>()); + } return ret; } // function pointers @@ -1755,17 +1900,15 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { auto br = allocator.alloc<Switch>(); br->condition = process(ast[1]); - assert(br->condition->type == i32); Ref cases = ast[2]; bool seen = false; - int min = 0; // the lowest index we see; we will offset to it + int64_t min = 0; // the lowest index we see; we will offset to it for (unsigned i = 0; i < cases->size(); i++) { Ref curr = cases[i]; Ref condition = curr[0]; if (!condition->isNull()) { - assert(condition[0] == NUM || condition[0] == UNARY_PREFIX); - int32_t index = getLiteral(condition).geti32(); + int64_t index = getLiteral(condition).getInteger(); if (!seen) { seen = true; min = index; @@ -1774,12 +1917,23 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { } } } - Binary* offsetor = allocator.alloc<Binary>(); - offsetor->op = BinaryOp::SubInt32; - offsetor->left = br->condition; - offsetor->right = builder.makeConst(Literal(min)); - offsetor->type = i32; - br->condition = offsetor; + if (br->condition->type == i32) { + Binary* offsetor = allocator.alloc<Binary>(); + offsetor->op = BinaryOp::SubInt32; + offsetor->left = br->condition; + offsetor->right = builder.makeConst(Literal(int32_t(min))); + offsetor->type = i32; + br->condition = offsetor; + } else { + assert(br->condition->type == i64); + // 64-bit condition. after offsetting it must be in a reasonable range, but the offsetting itself must be 64-bit + Binary* offsetor = allocator.alloc<Binary>(); + offsetor->op = BinaryOp::SubInt64; + offsetor->left = br->condition; + offsetor->right = builder.makeConst(Literal(int64_t(min))); + offsetor->type = i64; + br->condition = builder.makeUnary(UnaryOp::WrapInt64, offsetor); // TODO: check this fits in 32 bits + } auto top = allocator.alloc<Block>(); top->list.push_back(br); @@ -1794,15 +1948,14 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { if (condition->isNull()) { name = br->default_ = getNextId("switch-default"); } else { - assert(condition[0] == NUM || condition[0] == UNARY_PREFIX); - int32_t index = getLiteral(condition).geti32(); + auto index = getLiteral(condition).getInteger(); assert(index >= min); index -= min; assert(index >= 0); - size_t index_s = index; + uint64_t index_s = index; name = getNextId("switch-case"); if (br->targets.size() <= index_s) { - br->targets.resize(index_s+1); + br->targets.resize(index_s + 1); } br->targets[index_s] = name; } diff --git a/src/asmjs/asm_v_wasm.cpp b/src/asmjs/asm_v_wasm.cpp index 648871100..2bf2b2bd3 100644 --- a/src/asmjs/asm_v_wasm.cpp +++ b/src/asmjs/asm_v_wasm.cpp @@ -25,6 +25,7 @@ WasmType asmToWasmType(AsmType asmType) { case ASM_INT: return WasmType::i32; case ASM_DOUBLE: return WasmType::f64; case ASM_FLOAT: return WasmType::f32; + case ASM_INT64: return WasmType::i64; case ASM_NONE: return WasmType::none; default: {} } @@ -36,6 +37,7 @@ AsmType wasmToAsmType(WasmType type) { case WasmType::i32: return ASM_INT; case WasmType::f32: return ASM_FLOAT; case WasmType::f64: return ASM_DOUBLE; + case WasmType::i64: return ASM_INT64; case WasmType::none: return ASM_NONE; default: {} } diff --git a/src/ast_utils.h b/src/ast_utils.h index 9741faa25..8b2ae0d59 100644 --- a/src/ast_utils.h +++ b/src/ast_utils.h @@ -970,6 +970,54 @@ struct AutoDrop : public WalkerPass<ExpressionStackWalker<AutoDrop, Visitor<Auto } }; +struct I64Utilities { + static Expression* recreateI64(Builder& builder, Expression* low, Expression* high) { + return + builder.makeBinary( + OrInt64, + builder.makeUnary( + ExtendUInt32, + low + ), + builder.makeBinary( + ShlInt64, + builder.makeUnary( + ExtendUInt32, + high + ), + builder.makeConst(Literal(int64_t(32))) + ) + ) + ; + }; + + static Expression* recreateI64(Builder& builder, Index low, Index high) { + return recreateI64(builder, builder.makeGetLocal(low, i32), builder.makeGetLocal(high, i32)); + }; + + static Expression* getI64High(Builder& builder, Index index) { + return + builder.makeUnary( + WrapInt64, + builder.makeBinary( + ShrUInt64, + builder.makeGetLocal(index, i64), + builder.makeConst(Literal(int64_t(32))) + ) + ) + ; + } + + static Expression* getI64Low(Builder& builder, Index index) { + return + builder.makeUnary( + WrapInt64, + builder.makeGetLocal(index, i64) + ) + ; + } +}; + } // namespace wasm #endif // wasm_ast_utils_h diff --git a/src/emscripten-optimizer/optimizer-shared.cpp b/src/emscripten-optimizer/optimizer-shared.cpp index 6831d81b0..57b7921fa 100644 --- a/src/emscripten-optimizer/optimizer-shared.cpp +++ b/src/emscripten-optimizer/optimizer-shared.cpp @@ -52,7 +52,7 @@ HeapInfo parseHeap(const char *name) { return ret; } -AsmType detectType(Ref node, AsmData *asmData, bool inVarDef, IString minifiedFround) { +AsmType detectType(Ref node, AsmData *asmData, bool inVarDef, IString minifiedFround, bool allowI64) { switch (node[0]->getCString()[0]) { case 'n': { if (node[0] == NUM) { @@ -79,7 +79,7 @@ AsmType detectType(Ref node, AsmData *asmData, bool inVarDef, IString minifiedFr if (node[0] == UNARY_PREFIX) { switch (node[1]->getCString()[0]) { case '+': return ASM_DOUBLE; - case '-': return detectType(node[2], asmData, inVarDef, minifiedFround); + case '-': return detectType(node[2], asmData, inVarDef, minifiedFround, allowI64); case '!': case '~': return ASM_INT; } break; @@ -91,6 +91,7 @@ AsmType detectType(Ref node, AsmData *asmData, bool inVarDef, IString minifiedFr if (node[1][0] == NAME) { IString name = node[1][1]->getIString(); if (name == MATH_FROUND || name == minifiedFround) return ASM_FLOAT; + else if (allowI64 && (name == INT64 || name == INT64_CONST)) return ASM_INT64; else if (name == SIMD_FLOAT32X4 || name == SIMD_FLOAT32X4_CHECK) return ASM_FLOAT32X4; else if (name == SIMD_FLOAT64X2 || name == SIMD_FLOAT64X2_CHECK) return ASM_FLOAT64X2; else if (name == SIMD_INT8X16 || name == SIMD_INT8X16_CHECK) return ASM_INT8X16; @@ -99,7 +100,7 @@ AsmType detectType(Ref node, AsmData *asmData, bool inVarDef, IString minifiedFr } return ASM_NONE; } else if (node[0] == CONDITIONAL) { - return detectType(node[2], asmData, inVarDef, minifiedFround); + return detectType(node[2], asmData, inVarDef, minifiedFround, allowI64); } break; } @@ -107,7 +108,7 @@ AsmType detectType(Ref node, AsmData *asmData, bool inVarDef, IString minifiedFr if (node[0] == BINARY) { switch (node[1]->getCString()[0]) { case '+': case '-': - case '*': case '/': case '%': return detectType(node[2], asmData, inVarDef, minifiedFround); + case '*': case '/': case '%': return detectType(node[2], asmData, inVarDef, minifiedFround, allowI64); case '|': case '&': case '^': case '<': case '>': // handles <<, >>, >>=, <=, >= case '=': case '!': { // handles ==, != return ASM_INT; @@ -118,7 +119,7 @@ AsmType detectType(Ref node, AsmData *asmData, bool inVarDef, IString minifiedFr } case 's': { if (node[0] == SEQ) { - return detectType(node[2], asmData, inVarDef, minifiedFround); + return detectType(node[2], asmData, inVarDef, minifiedFround, allowI64); } else if (node[0] == SUB) { assert(node[1][0] == NAME); HeapInfo info = parseHeap(node[1][1]->getCString()); diff --git a/src/emscripten-optimizer/optimizer.h b/src/emscripten-optimizer/optimizer.h index 684fc0164..dc73962c5 100644 --- a/src/emscripten-optimizer/optimizer.h +++ b/src/emscripten-optimizer/optimizer.h @@ -49,12 +49,13 @@ enum AsmType { ASM_INT8X16, ASM_INT16X8, ASM_INT32X4, + ASM_INT64, // non-asm.js ASM_NONE // number of types }; struct AsmData; -AsmType detectType(cashew::Ref node, AsmData *asmData=nullptr, bool inVarDef=false, cashew::IString minifiedFround=cashew::IString()); +AsmType detectType(cashew::Ref node, AsmData *asmData=nullptr, bool inVarDef=false, cashew::IString minifiedFround=cashew::IString(), bool allowI64=false); struct AsmData { struct Local { diff --git a/src/emscripten-optimizer/parser.cpp b/src/emscripten-optimizer/parser.cpp index ef2891941..064c0efcf 100644 --- a/src/emscripten-optimizer/parser.cpp +++ b/src/emscripten-optimizer/parser.cpp @@ -54,6 +54,8 @@ IString TOPLEVEL("toplevel"), UNARY_PREFIX("unary-prefix"), UNARY_POSTFIX("unary-postfix"), MATH_FROUND("Math_fround"), + INT64("i64"), + INT64_CONST("i64_const"), SIMD_FLOAT32X4("SIMD_Float32x4"), SIMD_FLOAT64X2("SIMD_Float64x2"), SIMD_INT8X16("SIMD_Int8x16"), diff --git a/src/emscripten-optimizer/parser.h b/src/emscripten-optimizer/parser.h index 9429ff87f..060b71272 100644 --- a/src/emscripten-optimizer/parser.h +++ b/src/emscripten-optimizer/parser.h @@ -69,6 +69,8 @@ extern IString TOPLEVEL, UNARY_PREFIX, UNARY_POSTFIX, MATH_FROUND, + INT64, + INT64_CONST, SIMD_FLOAT32X4, SIMD_FLOAT64X2, SIMD_INT8X16, @@ -549,8 +551,8 @@ class Parser { if (value.isNumber()) { arg = parseFrag(value); src += value.size; - } else { - assert(value.type == OPERATOR); + } else if (value.type == OPERATOR) { + // negative number assert(value.str == MINUS); src += value.size; skipSpace(src); @@ -558,6 +560,12 @@ class Parser { assert(value2.isNumber()); arg = Builder::makePrefix(MINUS, parseFrag(value2)); src += value2.size; + } else { + // identifier and function call + assert(value.type == IDENT); + src += value.size; + skipSpace(src); + arg = parseCall(parseFrag(value), src); } Builder::appendCaseToSwitch(ret, arg); skipSpace(src); diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index 30f63c880..3f6574ec4 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -4,6 +4,7 @@ SET(passes_SOURCES DeadCodeElimination.cpp DuplicateFunctionElimination.cpp ExtractFunction.cpp + LegalizeJSInterface.cpp MergeBlocks.cpp Metrics.cpp NameManager.cpp diff --git a/src/passes/LegalizeJSInterface.cpp b/src/passes/LegalizeJSInterface.cpp new file mode 100644 index 000000000..996197701 --- /dev/null +++ b/src/passes/LegalizeJSInterface.cpp @@ -0,0 +1,211 @@ +/* + * Copyright 2016 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. + */ + +// +// i64 values are not valid in JS, and must be handled in some other +// way. This pass transforms all i64s in params and results in imports +// and exports into pairs of i32, i32 (low, high). If JS on the outside +// calls with that ABI, then everything should then just work, using +// stub methods added in this pass, that thunk i64s into i32, i32 and +// vice versa as necessary. +// + +#include <wasm.h> +#include <pass.h> +#include <wasm-builder.h> +#include <ast_utils.h> + +namespace wasm { + +Name TEMP_RET_0("tempRet0"); + +struct LegalizeJSInterface : public Pass { + void run(PassRunner* runner, Module* module) override { + // for each illegal export, we must export a legalized stub instead + for (auto& ex : module->exports) { + if (ex->kind == Export::Function) { + // if it's an import, ignore it + if (auto* func = module->checkFunction(ex->value)) { + if (isIllegal(func)) { + auto legalName = makeLegalStub(func, module); + ex->value = legalName; + } + } + } + } + // for each illegal import, we must call a legalized stub instead + std::vector<Import*> newImports; // add them at the end, to not invalidate the iter + for (auto& im : module->imports) { + if (im->kind == Import::Function && isIllegal(im->functionType)) { + Name funcName; + auto* legal = makeLegalStub(im.get(), module, funcName); + illegalToLegal[im->name] = funcName; + newImports.push_back(legal); + } + } + if (illegalToLegal.size() > 0) { + for (auto* im : newImports) { + module->addImport(im); + } + + // fix up imports: call_import of an illegal must be turned to a call of a legal + + struct FixImports : public WalkerPass<PostWalker<FixImports, Visitor<FixImports>>> { + bool isFunctionParallel() override { return true; } + + Pass* create() override { return new FixImports(illegalToLegal); } + + std::map<Name, Name>* illegalToLegal; + + FixImports(std::map<Name, Name>* illegalToLegal) : illegalToLegal(illegalToLegal) {} + + void visitCallImport(CallImport* curr) { + auto iter = illegalToLegal->find(curr->target); + if (iter == illegalToLegal->end()) return; + + if (iter->second == getFunction()->name) return; // inside the stub function itself, is the one safe place to do the call + replaceCurrent(Builder(*getModule()).makeCall(iter->second, curr->operands, curr->type)); + } + }; + + PassRunner passRunner(module); + passRunner.add<FixImports>(&illegalToLegal); + passRunner.run(); + } + } + +private: + // map of illegal to legal names for imports + std::map<Name, Name> illegalToLegal; + + template<typename T> + bool isIllegal(T* t) { + for (auto param : t->params) { + if (param == i64) return true; + } + if (t->result == i64) return true; + return false; + } + + // JS calls the export, so it must call a legal stub that calls the actual wasm function + Name makeLegalStub(Function* func, Module* module) { + Builder builder(*module); + auto* legal = new Function(); + legal->name = Name(std::string("legalstub$") + func->name.str); + + auto* call = module->allocator.alloc<Call>(); + call->target = func->name; + call->type = func->result; + + for (auto param : func->params) { + if (param == i64) { + call->operands.push_back(I64Utilities::recreateI64(builder, legal->params.size(), legal->params.size() + 1)); + legal->params.push_back(i32); + legal->params.push_back(i32); + } else { + call->operands.push_back(builder.makeGetLocal(legal->params.size(), param)); + legal->params.push_back(param); + } + } + + if (func->result == i64) { + legal->result = i32; + auto index = builder.addVar(legal, Name(), i64); + auto* block = builder.makeBlock(); + block->list.push_back(builder.makeSetLocal(index, call)); + if (module->checkGlobal(TEMP_RET_0)) { + block->list.push_back(builder.makeSetGlobal( + TEMP_RET_0, + I64Utilities::getI64High(builder, index) + )); + } else { + block->list.push_back(builder.makeUnreachable()); // no way to emit the high bits :( + } + block->list.push_back(I64Utilities::getI64Low(builder, index)); + block->finalize(); + legal->body = block; + } else { + legal->result = func->result; + legal->body = call; + } + + // a method may be exported multiple times + if (!module->checkFunction(legal->name)) { + module->addFunction(legal); + } + return legal->name; + } + + // wasm calls the import, so it must call a stub that calls the actual legal JS import + Import* makeLegalStub(Import* im, Module* module, Name& funcName) { + Builder builder(*module); + auto* type = new FunctionType(); + type->name = Name(std::string("legaltype$") + im->name.str); + auto* legal = new Import(); + legal->name = Name(std::string("legalimport$") + im->name.str); + legal->module = im->module; + legal->base = im->base; + legal->kind = Import::Function; + legal->functionType = type; + auto* func = new Function(); + func->name = Name(std::string("legalfunc$") + im->name.str); + funcName = func->name; + + auto* call = module->allocator.alloc<CallImport>(); + call->target = legal->name; + + for (auto param : im->functionType->params) { + if (param == i64) { + call->operands.push_back(I64Utilities::getI64Low(builder, func->params.size())); + call->operands.push_back(I64Utilities::getI64High(builder, func->params.size())); + type->params.push_back(i32); + type->params.push_back(i32); + } else { + call->operands.push_back(builder.makeGetLocal(func->params.size(), param)); + type->params.push_back(param); + } + func->params.push_back(param); + } + + if (im->functionType->result == i64) { + call->type = i32; + Expression* get; + if (module->checkGlobal(TEMP_RET_0)) { + get = builder.makeGetGlobal(TEMP_RET_0, i32); + } else { + get = builder.makeUnreachable(); // no way to emit the high bits :( + } + func->body = I64Utilities::recreateI64(builder, call, get); + type->result = i32; + } else { + call->type = im->functionType->result; + func->body = call; + type->result = im->functionType->result; + } + func->result = im->functionType->result; + + module->addFunction(func); + module->addFunctionType(type); + return legal; + } +}; + +Pass *createLegalizeJSInterfacePass() { + return new LegalizeJSInterface(); +} + +} // namespace wasm + diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index f3bc6d4c7..ac5ad04c3 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -67,6 +67,7 @@ void PassRegistry::registerPasses() { registerPass("dce", "removes unreachable code", createDeadCodeEliminationPass); registerPass("duplicate-function-elimination", "removes duplicate functions", createDuplicateFunctionEliminationPass); registerPass("extract-function", "leaves just one function (useful for debugging)", createExtractFunctionPass); + registerPass("legalize-js-interface", "legalizes i64 types on the import/export boundary", createLegalizeJSInterfacePass); registerPass("merge-blocks", "merges blocks to their parents", createMergeBlocksPass); registerPass("metrics", "reports metrics", createMetricsPass); registerPass("nm", "name list", createNameListPass); diff --git a/src/passes/passes.h b/src/passes/passes.h index 80fa394e1..13193bc4c 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -27,7 +27,10 @@ Pass *createCoalesceLocalsWithLearningPass(); Pass *createDeadCodeEliminationPass(); Pass *createDuplicateFunctionEliminationPass(); Pass *createExtractFunctionPass(); +Pass *createFullPrinterPass(); +Pass *createLegalizeJSInterfacePass(); Pass *createLowerIfElsePass(); +Pass *createMinifiedPrinterPass(); Pass *createMergeBlocksPass(); Pass *createMetricsPass(); Pass *createNameListPass(); @@ -35,8 +38,6 @@ Pass *createNameManagerPass(); Pass *createOptimizeInstructionsPass(); Pass *createPostEmscriptenPass(); Pass *createPrinterPass(); -Pass *createMinifiedPrinterPass(); -Pass *createFullPrinterPass(); Pass *createRelooperJumpThreadingPass(); Pass *createRemoveImportsPass(); Pass *createRemoveMemoryPass(); diff --git a/src/tools/asm2wasm.cpp b/src/tools/asm2wasm.cpp index 031b1fd23..49e04470b 100644 --- a/src/tools/asm2wasm.cpp +++ b/src/tools/asm2wasm.cpp @@ -32,6 +32,7 @@ using namespace wasm; int main(int argc, const char *argv[]) { bool opts = true; bool imprecise = false; + bool wasmOnly = false; Options options("asm2wasm", "Translate asm.js files to .wast files"); options @@ -61,6 +62,10 @@ int main(int argc, const char *argv[]) { [&imprecise](Options *o, const std::string &) { imprecise = true; }) + .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_positional("INFILE", Options::Arguments::One, [](Options *o, const std::string &argument) { o->extra["infile"] = argument; @@ -93,7 +98,7 @@ int main(int argc, const char *argv[]) { if (options.debug) std::cerr << "wasming..." << std::endl; Module wasm; wasm.memory.initial = wasm.memory.max = totalMemory / Memory::kPageSize; - Asm2WasmBuilder asm2wasm(wasm, pre.memoryGrowth, options.debug, imprecise, opts); + Asm2WasmBuilder asm2wasm(wasm, pre.memoryGrowth, options.debug, imprecise, opts, wasmOnly); asm2wasm.processAsm(asmjs); // import mem init file, if provided diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 7cb195d08..a8c7bc4d1 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -115,6 +115,22 @@ public: call->operands.set(args); return call; } + template<typename T> + Call* makeCall(Name target, const T& args, WasmType type) { + auto* call = allocator.alloc<Call>(); + call->type = type; // not all functions may exist yet, so type must be provided + call->target = target; + call->operands.set(args); + return call; + } + template<typename T> + CallImport* makeCallImport(Name target, const T& args, WasmType type) { + auto* call = allocator.alloc<CallImport>(); + call->type = type; // similar to makeCall, for consistency + call->target = target; + call->operands.set(args); + return call; + } CallIndirect* makeCallIndirect(FunctionType* type, Expression* target, const std::vector<Expression*>& args) { auto* call = allocator.alloc<CallIndirect>(); call->fullType = type->name; @@ -242,11 +258,14 @@ public: static Index addVar(Function* func, Name name, WasmType type) { // always ok to add a var, it does not affect other indices - assert(func->localIndices.size() == func->params.size() + func->vars.size()); + Index index = func->getNumLocals(); + if (name.is()) { + // if there is a name, apply it, but here we assume all the rest have names too FIXME + assert(func->localIndices.size() == func->params.size() + func->vars.size()); + func->localIndices[name] = index; + func->localNames.push_back(name); + } func->vars.emplace_back(type); - Index index = func->localNames.size(); - func->localIndices[name] = index; - func->localNames.push_back(name); return index; } diff --git a/src/wasm-js.cpp b/src/wasm-js.cpp index 7d71cec3c..652e5621f 100644 --- a/src/wasm-js.cpp +++ b/src/wasm-js.cpp @@ -79,7 +79,7 @@ extern "C" void EMSCRIPTEN_KEEPALIVE load_asm2wasm(char *input) { module->memory.max = pre.memoryGrowth ? Address(Memory::kMaxSize) : module->memory.initial; if (wasmJSDebug) std::cerr << "wasming...\n"; - asm2wasm = new Asm2WasmBuilder(*module, pre.memoryGrowth, debug, false /* TODO: support imprecise? */, false /* TODO: support optimizing? */); + asm2wasm = new Asm2WasmBuilder(*module, pre.memoryGrowth, debug, false /* TODO: support imprecise? */, false /* TODO: support optimizing? */, false /* TODO: support asm2wasm-i64? */); asm2wasm->processAsm(asmjs); } diff --git a/src/wasm-validator.h b/src/wasm-validator.h index 3c580ce51..bdc075946 100644 --- a/src/wasm-validator.h +++ b/src/wasm-validator.h @@ -46,6 +46,9 @@ public: bool validate(Module& module, bool validateWeb=false) { validateWebConstraints = validateWeb; walkModule(&module); + if (!valid) { + WasmPrinter::printModule(&module, std::cerr); + } return valid; } |