summaryrefslogtreecommitdiff
path: root/src/asm2wasm.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/asm2wasm.h')
-rw-r--r--src/asm2wasm.h262
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");