diff options
Diffstat (limited to 'src/asm2wasm.h')
-rw-r--r-- | src/asm2wasm.h | 262 |
1 files changed, 142 insertions, 120 deletions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h index 353f80413..4c37b7705 100644 --- a/src/asm2wasm.h +++ b/src/asm2wasm.h @@ -28,6 +28,7 @@ #include "shared-constants.h" #include "asm_v_wasm.h" #include "pass.h" +#include "ast_utils.h" namespace wasm { @@ -149,7 +150,8 @@ class Asm2WasmBuilder { std::map<CallIndirect*, IString> callIndirects; // track these, as we need to fix them after we know the functionTableStarts. this maps call => its function table bool memoryGrowth; - int debug; + bool debug; + bool imprecise; public: std::map<IString, MappedGlobal> mappedGlobals; @@ -204,8 +206,9 @@ private: // uses, in the first pass std::map<IString, FunctionType> importedFunctionTypes; + std::map<IString, std::vector<CallImport*>> importedFunctionCalls; - void noteImportedFunctionCall(Ref ast, WasmType resultType, AsmData *asmData) { + void noteImportedFunctionCall(Ref ast, WasmType resultType, AsmData *asmData, CallImport* call) { assert(ast[0] == CALL && ast[1][0] == NAME); IString importName = ast[1][1]->getIString(); FunctionType type; @@ -242,6 +245,7 @@ private: } else { importedFunctionTypes[importName] = type; } + importedFunctionCalls[importName].push_back(call); } FunctionType* getFunctionType(Ref parent, ExpressionList& operands) { @@ -251,13 +255,14 @@ private: } public: - Asm2WasmBuilder(AllocatingModule& wasm, bool memoryGrowth, int debug) + Asm2WasmBuilder(AllocatingModule& wasm, bool memoryGrowth, bool debug, bool imprecise) : wasm(wasm), allocator(wasm.allocator), nextGlobal(8), maxGlobal(1000), memoryGrowth(memoryGrowth), - debug(debug) {} + debug(debug), + imprecise(imprecise) {} void processAsm(Ref ast); void optimize(); @@ -414,10 +419,12 @@ private: return nullptr; } + // ensure a nameless block Block* blockify(Expression* expression) { - if (expression->is<Block>()) return expression->dyn_cast<Block>(); + if (expression->is<Block>() && !expression->cast<Block>()->name.is()) return expression->dyn_cast<Block>(); auto ret = allocator.alloc<Block>(); ret->list.push_back(expression); + ret->finalize(); return ret; } @@ -635,11 +642,17 @@ void Asm2WasmBuilder::processAsm(Ref ast) { 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); + assert(pair[1][0] == NAME); + IString value = pair[1][1]->getIString(); + if (key == Name("_emscripten_replace_memory")) { + // asm.js memory growth provides this special non-asm function, which we don't need (we use grow_memory) + assert(wasm.functionsMap.find(value) == wasm.functionsMap.end()); + continue; + } + assert(wasm.functionsMap.find(value) != wasm.functionsMap.end()); auto export_ = allocator.alloc<Export>(); export_->name = key; - export_->value = value[1]->getIString(); + export_->value = value; wasm.addExport(export_); } } @@ -670,6 +683,20 @@ void Asm2WasmBuilder::processAsm(Ref ast) { wasm.removeImport(curr); } + // fill out call_import - add extra params as needed. asm tolerates ffi overloading, wasm does not + for (auto& pair : importedFunctionCalls) { + IString name = pair.first; + auto& list = pair.second; + auto type = importedFunctionTypes[name]; + for (auto* call : list) { + for (size_t i = call->operands.size(); i < type.params.size(); i++) { + auto val = allocator.alloc<Const>(); + val->type = val->value.type = type.params[i]; + call->operands.push_back(val); + } + } + } + // finalize indirect calls for (auto& pair : callIndirects) { @@ -703,6 +730,7 @@ void Asm2WasmBuilder::processAsm(Ref ast) { wasm.addExport(export_); } + wasm.memory.exportName = MEMORY; } Function* Asm2WasmBuilder::processFunction(Ref ast) { @@ -710,10 +738,8 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { if (debug) { std::cout << "\nfunc: " << ast[1]->getIString().str << '\n'; - if (debug >= 2) { - ast->stringify(std::cout); - std::cout << '\n'; - } + ast->stringify(std::cout); + std::cout << '\n'; } auto function = allocator.alloc<Function>(); @@ -783,7 +809,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { std::function<Expression* (Ref)> process = [&](Ref ast) -> Expression* { AstStackHelper astStackHelper(ast); // TODO: only create one when we need it? - if (debug >= 2) { + if (debug) { std::cout << "at: "; ast->stringify(std::cout); std::cout << '\n'; @@ -992,29 +1018,38 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { } 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) { -#if 0 - auto ret = allocator.alloc<Unary>(); - 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; -#endif - // WebAssembly traps on float-to-int overflows, but asm.js wouldn't, so we must emulate that - CallImport *ret = allocator.alloc<CallImport>(); - ret->target = F64_TO_INT; - ret->operands.push_back(process(ast[2][2])); - ret->type = i32; - static bool addedImport = false; - if (!addedImport) { - addedImport = true; - auto import = allocator.alloc<Import>(); // f64-to-int = asm2wasm.f64-to-int; - import->name = F64_TO_INT; - import->module = ASM2WASM; - import->base = F64_TO_INT; - import->type = ensureFunctionType("id", &wasm, allocator); - wasm.addImport(import); + if (imprecise) { + auto ret = allocator.alloc<Unary>(); + ret->value = process(ast[2][2]); + ret->op = ret->value->type == f64 ? TruncSFloat64 : TruncSFloat32; // imprecise, because this wasm thing might trap, while asm.js never would + ret->type = WasmType::i32; + return ret; + } else { + // WebAssembly traps on float-to-int overflows, but asm.js wouldn't, so we must emulate that + CallImport *ret = allocator.alloc<CallImport>(); + ret->target = F64_TO_INT; + auto input = process(ast[2][2]); + if (input->type == f32) { + auto conv = allocator.alloc<Unary>(); + conv->op = PromoteFloat32; + conv->value = input; + conv->type = WasmType::f64; + input = conv; + } + ret->operands.push_back(input); + ret->type = i32; + static bool addedImport = false; + if (!addedImport) { + addedImport = true; + auto import = allocator.alloc<Import>(); // f64-to-int = asm2wasm.f64-to-int; + import->name = F64_TO_INT; + import->module = ASM2WASM; + import->base = F64_TO_INT; + import->type = ensureFunctionType("id", &wasm, allocator); + wasm.addImport(import); + } + return ret; } - return ret; } // no bitwise unary not, so do xor with -1 auto ret = allocator.alloc<Binary>(); @@ -1024,13 +1059,10 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { ret->type = WasmType::i32; return ret; } else if (ast[1] == L_NOT) { - // no logical unary not, so do == 0 - auto ret = allocator.alloc<Binary>(); - ret->op = Eq; - ret->left = process(ast[2]); - ret->right = allocator.alloc<Const>()->set(Literal(0)); - assert(ret->left->type == ret->right->type); - ret->finalize(); + auto ret = allocator.alloc<Unary>(); + ret->op = EqZ; + ret->value = process(ast[2]); + ret->type = i32; return ret; } abort_on("bad unary", ast); @@ -1119,7 +1151,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { select->condition = isNegative; select->type = i32; block->list.push_back(select); - block->type = i32; + block->finalize(); return block; } else if (value->type == f32 || value->type == f64) { auto ret = allocator.alloc<Unary>(); @@ -1148,8 +1180,9 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { if (wasm.importsMap.find(name) != wasm.importsMap.end()) { Ref parent = astStackHelper.getParent(); WasmType type = !!parent ? detectWasmType(parent, &asmData) : none; - ret = allocator.alloc<CallImport>(); - noteImportedFunctionCall(ast, type, &asmData); + auto specific = allocator.alloc<CallImport>(); + noteImportedFunctionCall(ast, type, &asmData, specific); + ret = specific; } else { ret = allocator.alloc<Call>(); } @@ -1201,6 +1234,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { block = allocator.alloc<Block>(); block->name = name; block->list.push_back(ret); + block->finalize(); ret = block; } } @@ -1243,6 +1277,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { auto body = allocator.alloc<Block>(); body->list.push_back(condition); body->list.push_back(process(ast[2])); + body->finalize(); ret->body = body; } // loops do not automatically loop, add a branch back @@ -1256,8 +1291,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { return ret; } else if (what == DO) { if (ast[1][0] == NUM && ast[1][1]->getNumber() == 0) { - // one-time loop - auto block = allocator.alloc<Block>(); + // one-time loop, unless there is a continue IString stop; if (!parentLabel.isNull()) { stop = getBreakLabelName(parentLabel); @@ -1265,13 +1299,28 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { } else { stop = getNextId("do-once"); } - block->name = stop; + IString more = getNextId("unlikely-continue"); breakStack.push_back(stop); - continueStack.push_back(IMPOSSIBLE_CONTINUE); - block->list.push_back(process(ast[2])); + continueStack.push_back(more); + auto child = process(ast[2]); continueStack.pop_back(); breakStack.pop_back(); - return block; + // if we never continued, we don't need a loop + BreakSeeker breakSeeker(more); + breakSeeker.walk(child); + if (breakSeeker.found == 0) { + auto block = allocator.alloc<Block>(); + block->list.push_back(child); + block->name = stop; + block->finalize(); + return block; + } else { + auto loop = allocator.alloc<Loop>(); + loop->body = child; + loop->out = stop; + loop->in = more; + return loop; + } } // general do-while loop auto ret = allocator.alloc<Loop>(); @@ -1327,6 +1376,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { body->list.push_back(condition); body->list.push_back(process(fbody)); body->list.push_back(process(finc)); + body->finalize(); ret->body = body; // loops do not automatically loop, add a branch back Block* block = blockify(ret->body); @@ -1340,6 +1390,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { // add an outer block for the init as well outer->list.push_back(process(finit)); outer->list.push_back(ret); + outer->finalize(); return outer; } else if (what == LABEL) { assert(parentLabel.isNull()); @@ -1356,10 +1407,10 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { auto ret = allocator.alloc<Block>(); ret->list.push_back(process(ast[1])); ret->list.push_back(process(ast[2])); - ret->type = ret->list[1]->type; + ret->finalize(); return ret; } else if (what == SWITCH) { - IString name; + IString name; // for breaking out of the entire switch if (!parentLabel.isNull()) { name = getBreakLabelName(parentLabel); parentLabel = IString(); @@ -1367,10 +1418,11 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { name = getNextId("switch"); } breakStack.push_back(name); - auto ret = allocator.alloc<Switch>(); - ret->name = name; - ret->value = process(ast[1]); - assert(ret->value->type == i32); + + 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 @@ -1390,18 +1442,23 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { } Binary* offsetor = allocator.alloc<Binary>(); offsetor->op = BinaryOp::Sub; - offsetor->left = ret->value; + offsetor->left = br->condition; offsetor->right = allocator.alloc<Const>()->set(Literal(min)); offsetor->type = i32; - ret->value = offsetor; + br->condition = offsetor; + + auto top = allocator.alloc<Block>(); + top->list.push_back(br); + top->finalize(); + for (unsigned i = 0; i < cases->size(); i++) { Ref curr = cases[i]; Ref condition = curr[0]; Ref body = curr[1]; - Switch::Case case_; - case_.body = processStatements(body, 0); + auto case_ = processStatements(body, 0); + Name name; if (condition->isNull()) { - case_.name = ret->default_ = getNextId("switch-default"); + name = br->default_ = getNextId("switch-default"); } else { assert(condition[0] == NUM || condition[0] == UNARY_PREFIX); int32_t index = getLiteral(condition).geti32(); @@ -1409,26 +1466,35 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { index -= min; assert(index >= 0); size_t index_s = index; - case_.name = getNextId("switch-case"); - if (ret->targets.size() <= index_s) { - ret->targets.resize(index_s+1); + name = getNextId("switch-case"); + if (br->targets.size() <= index_s) { + br->targets.resize(index_s+1); } - ret->targets[index_s] = case_.name; + br->targets[index_s] = name; } - ret->cases.push_back(case_); + auto next = allocator.alloc<Block>(); + top->name = name; + next->list.push_back(top); + next->list.push_back(case_); + next->finalize(); + top = next; } + // ensure a default - if (ret->default_.isNull()) { - Switch::Case defaultCase; - defaultCase.name = ret->default_ = getNextId("switch-default"); - defaultCase.body = allocator.alloc<Nop>(); // ok if others fall through to this - ret->cases.push_back(defaultCase); + if (br->default_.isNull()) { + br->default_ = getNextId("switch-default"); } - for (size_t i = 0; i < ret->targets.size(); i++) { - if (ret->targets[i].isNull()) ret->targets[i] = ret->default_; + for (size_t i = 0; i < br->targets.size(); i++) { + if (br->targets[i].isNull()) br->targets[i] = br->default_; } - // finalize + top->name = br->default_; + breakStack.pop_back(); + + // Create a topmost block for breaking out of the entire switch + auto ret = allocator.alloc<Block>(); + ret->name = name; + ret->list.push_back(top); return ret; } abort_on("confusing expression", ast); @@ -1461,6 +1527,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { for (unsigned i = from; i < ast->size(); i++) { block->list.push_back(process(ast[i])); } + block->finalize(); return block; }; // body @@ -1473,51 +1540,6 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { } void Asm2WasmBuilder::optimize() { - // Optimization passes. Note: no effort is made to free nodes that are no longer held on to. - - struct BlockBreakOptimizer : public WasmWalker<BlockBreakOptimizer> { - void visitBlock(Block *curr) { - // if the block ends in a break on this very block, then just put the value there - Break *last = curr->list[curr->list.size()-1]->dyn_cast<Break>(); - if (last && last->value && last->name == curr->name) { - curr->list[curr->list.size()-1] = last->value; - } - if (curr->list.size() > 1) return; // no hope to remove the block - // just one element; maybe we can return just the element - if (curr->name.isNull()) { - replaceCurrent(curr->list[0]); // cannot break into it - return; - } - // we might be broken to, but maybe there isn't a break (and we may have removed it, leading to this) - - struct BreakSeeker : public WasmWalker<BreakSeeker> { - IString target; // look for this one - size_t found; - - BreakSeeker(IString target) : target(target), found(false) {} - - void visitBreak(Break *curr) { - if (curr->name == target) found++; - } - }; - - // look for any breaks to this block - BreakSeeker breakSeeker(curr->name); - Expression *child = curr->list[0]; - breakSeeker.walk(child); - if (breakSeeker.found == 0) { - replaceCurrent(child); // no breaks to here, so eliminate the block - } - } - }; - - BlockBreakOptimizer blockBreakOptimizer; - for (auto pair : wasm.functionsMap) { - blockBreakOptimizer.startWalk(pair.second); - } - - // Standard passes - PassRunner passRunner(&allocator); passRunner.add("remove-unused-brs"); passRunner.add("remove-unused-names"); |