summaryrefslogtreecommitdiff
path: root/src/wasm2asm.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/wasm2asm.h')
-rw-r--r--src/wasm2asm.h993
1 files changed, 993 insertions, 0 deletions
diff --git a/src/wasm2asm.h b/src/wasm2asm.h
new file mode 100644
index 000000000..b256a63c5
--- /dev/null
+++ b/src/wasm2asm.h
@@ -0,0 +1,993 @@
+
+//
+// WebAssembly-to-asm.js translator. Uses the Emscripten optimizer
+// infrastructure.
+//
+
+#include "wasm.h"
+#include "emscripten-optimizer/optimizer.h"
+#include "mixed_arena.h"
+#include "asm_v_wasm.h"
+#include "shared-constants.h"
+
+namespace wasm {
+
+extern int debug;
+
+using namespace cashew;
+
+IString ASM_FUNC("asmFunc"),
+ ABORT_FUNC("abort"),
+ FUNCTION_TABLE("FUNCTION_TABLE"),
+ NO_RESULT("wasm2asm$noresult"), // no result at all
+ EXPRESSION_RESULT("wasm2asm$expresult"); // result in an expression, no temp var
+
+// Appends extra to block, flattening out if extra is a block as well
+void flattenAppend(Ref ast, Ref extra) {
+ int index;
+ if (ast[0] == BLOCK) index = 1;
+ else if (ast[0] == DEFUN) index = 3;
+ else abort();
+ if (extra[0] == BLOCK) {
+ for (int i = 0; i < extra[1]->size(); i++) {
+ ast[index]->push_back(extra[1][i]);
+ }
+ } else {
+ ast[index]->push_back(extra);
+ }
+}
+
+//
+// Wasm2AsmBuilder - converts a WebAssembly module into asm.js
+//
+// In general, asm.js => wasm is very straightforward, as can
+// be seen in asm2wasm.h. Just a single pass, plus a little
+// state bookkeeping (breakStack, etc.), and a few after-the
+// fact corrections for imports, etc. However, wasm => asm.js
+// is tricky because wasm has statements == expressions, or in
+// other words, things like `break` and `if` can show up
+// in places where asm.js can't handle them, like inside an
+// a loop's condition check.
+//
+// We therefore need the ability to lower an expression into
+// a block of statements, and we keep statementizing until we
+// reach a context in which we can emit those statments. This
+// requires that we create temp variables to store values
+// that would otherwise flow directly into their targets if
+// we were an expression (e.g. if a loop's condition check
+// is a bunch of statements, we execute those statements,
+// then use the computed value in the loop's condition;
+// we might also be able to avoid an assign to a temp var
+// at the end of those statements, and put just that
+// value in the loop's condition).
+//
+// It is possible to do this in a single pass, if we just
+// allocate temp vars freely. However, pathological cases
+// can easily show bad behavior here, with many unnecessary
+// temp vars. We could rely on optimization passes like
+// Emscripten's eliminate/registerize pair, but we want
+// wasm2asm to be fairly fast to run, as it might run on
+// the client.
+//
+// The approach taken here therefore performs 2 passes on
+// each function. First, it finds which expression will need to
+// be statementized. It also sees which labels can receive a break
+// with a value. Given that information, in the second pass we can
+// allocate // temp vars in an efficient manner, as we know when we
+// need them and when their use is finished. They are allocated
+// using an RAII class, so that they are automatically freed
+// when the scope ends. This means that a node cannot allocate
+// its own temp var; instead, the parent - which knows the
+// child will return a value in a temp var - allocates it,
+// and tells the child what temp var to emit to. The child
+// can then pass forward that temp var to its children,
+// optimizing away unnecessary forwarding.
+
+
+class Wasm2AsmBuilder {
+public:
+ Ref processWasm(Module* wasm);
+ Ref processFunction(Function* func);
+
+ // The first pass on an expression: scan it to see whether it will
+ // need to be statementized, and note spooky returns of values at
+ // a distance (aka break with a value).
+ void scanFunctionBody(Expression* curr);
+
+ // The second pass on an expression: process it fully, generating
+ // asm.js
+ // @param result Whether the context we are in receives a value,
+ // and its type, or if not, then we can drop our return,
+ // if we have one.
+ Ref processFunctionBody(Expression* curr, IString result);
+
+ // Get a temp var.
+ IString getTemp(WasmType type) {
+ IString ret;
+ if (frees[type].size() > 0) {
+ ret = frees[type].back();
+ frees[type].pop_back();
+ } else {
+ size_t index = temps[type]++;
+ ret = IString((std::string("wasm2asm_") + printWasmType(type) + "$" + std::to_string(index)).c_str(), false);
+ }
+ return ret;
+ }
+ // Free a temp var.
+ void freeTemp(WasmType type, IString temp) {
+ frees[type].push_back(temp);
+ }
+
+ static IString fromName(Name name) {
+ // TODO: more clever name fixing, including checking we do not collide
+ const char *str = name.str;
+ // check the various issues, and recurse so we check the others
+ if (strchr(str, '-')) {
+ char *mod = strdup(str); // XXX leak
+ str = mod;
+ while (*mod) {
+ if (*mod == '-') *mod = '_';
+ mod++;
+ }
+ return fromName(IString(str, false));
+ }
+ if (isdigit(str[0])) {
+ std::string prefixed = "$$";
+ prefixed += name.str;
+ return fromName(IString(prefixed.c_str(), false));
+ }
+ return name;
+ }
+
+ void setStatement(Expression* curr) {
+ willBeStatement.insert(curr);
+ }
+ bool isStatement(Expression* curr) {
+ return curr && willBeStatement.find(curr) != willBeStatement.end();
+ }
+
+ size_t getTableSize() {
+ return tableSize;
+ }
+
+private:
+ // How many temp vars we need
+ std::vector<int> temps; // type => num temps
+ // Which are currently free to use
+ std::vector<std::vector<IString>> frees; // type => list of free names
+
+ // Expressions that will be a statement.
+ std::set<Expression*> willBeStatement;
+
+ // All our function tables have the same size TODO: optimize?
+ size_t tableSize;
+
+ void addBasics(Ref ast);
+ void addImport(Ref ast, Import *import);
+ void addTables(Ref ast, Module *wasm);
+ void addExports(Ref ast, Module *wasm);
+};
+
+Ref Wasm2AsmBuilder::processWasm(Module* wasm) {
+ Ref ret = ValueBuilder::makeToplevel();
+ Ref asmFunc = ValueBuilder::makeFunction(ASM_FUNC);
+ ret[1]->push_back(asmFunc);
+ ValueBuilder::appendArgumentToFunction(asmFunc, GLOBAL);
+ ValueBuilder::appendArgumentToFunction(asmFunc, ENV);
+ ValueBuilder::appendArgumentToFunction(asmFunc, BUFFER);
+ asmFunc[3]->push_back(ValueBuilder::makeStatement(ValueBuilder::makeString(USE_ASM)));
+ // create heaps, etc
+ addBasics(asmFunc[3]);
+ for (auto import : wasm->imports) {
+ addImport(asmFunc[3], import);
+ }
+ // figure out the table size
+ tableSize = wasm->table.names.size();
+ size_t pow2ed = 1;
+ while (pow2ed < tableSize) {
+ pow2ed <<= 1;
+ }
+ tableSize = pow2ed;
+ // functions
+ for (auto func : wasm->functions) {
+ asmFunc[3]->push_back(processFunction(func));
+ }
+ addTables(asmFunc[3], wasm);
+ // memory XXX
+ addExports(asmFunc[3], wasm);
+ return ret;
+}
+
+void Wasm2AsmBuilder::addBasics(Ref ast) {
+ // heaps, var HEAP8 = new global.Int8Array(buffer); etc
+ auto addHeap = [&](IString name, IString view) {
+ Ref theVar = ValueBuilder::makeVar();
+ ast->push_back(theVar);
+ ValueBuilder::appendToVar(theVar,
+ name,
+ ValueBuilder::makeNew(
+ ValueBuilder::makeCall(
+ ValueBuilder::makeDot(
+ ValueBuilder::makeName(GLOBAL),
+ view
+ ),
+ ValueBuilder::makeName(BUFFER)
+ )
+ )
+ );
+ };
+ addHeap(HEAP8, INT8ARRAY);
+ addHeap(HEAP16, INT16ARRAY);
+ addHeap(HEAP32, INT32ARRAY);
+ addHeap(HEAPU8, UINT8ARRAY);
+ addHeap(HEAPU16, UINT16ARRAY);
+ addHeap(HEAPU32, UINT32ARRAY);
+ addHeap(HEAPF32, FLOAT32ARRAY);
+ addHeap(HEAPF64, FLOAT64ARRAY);
+ // core asm.js imports
+ auto addMath = [&](IString name, IString base) {
+ Ref theVar = ValueBuilder::makeVar();
+ ast->push_back(theVar);
+ ValueBuilder::appendToVar(theVar,
+ name,
+ ValueBuilder::makeDot(
+ ValueBuilder::makeDot(
+ ValueBuilder::makeName(GLOBAL),
+ MATH
+ ),
+ base
+ )
+ );
+ };
+ addMath(MATH_IMUL, IMUL);
+ addMath(MATH_FROUND, FROUND);
+ addMath(MATH_ABS, ABS);
+ addMath(MATH_CLZ32, CLZ32);
+}
+
+void Wasm2AsmBuilder::addImport(Ref ast, Import *import) {
+ Ref theVar = ValueBuilder::makeVar();
+ ast->push_back(theVar);
+ Ref module = ValueBuilder::makeName(ENV); // TODO: handle nested module imports
+ ValueBuilder::appendToVar(theVar,
+ fromName(import->name),
+ ValueBuilder::makeDot(
+ module,
+ fromName(import->base)
+ )
+ );
+}
+
+void Wasm2AsmBuilder::addTables(Ref ast, Module *wasm) {
+ std::map<std::string, std::vector<IString>> tables; // asm.js tables, sig => contents of table
+ for (size_t i = 0; i < wasm->table.names.size(); i++) {
+ Name name = wasm->table.names[i];
+ auto func = wasm->functionsMap[name];
+ std::string sig = getSig(func);
+ auto& table = tables[sig];
+ if (table.size() == 0) {
+ // fill it with the first of its type seen. we have to fill with something; and for asm2wasm output, the first is the null anyhow
+ table.resize(tableSize);
+ for (int j = 0; j < tableSize; j++) {
+ table[j] = fromName(name);
+ }
+ } else {
+ table[i] = fromName(name);
+ }
+ }
+ for (auto& pair : tables) {
+ auto& sig = pair.first;
+ auto& table = pair.second;
+ std::string stable = std::string("FUNCTION_TABLE_") + sig;
+ IString asmName = IString(stable.c_str(), false);
+ // add to asm module
+ Ref theVar = ValueBuilder::makeVar();
+ ast->push_back(theVar);
+ Ref theArray = ValueBuilder::makeArray();
+ ValueBuilder::appendToVar(theVar, asmName, theArray);
+ for (auto& name : table) {
+ ValueBuilder::appendToArray(theArray, ValueBuilder::makeName(name));
+ }
+ }
+}
+
+void Wasm2AsmBuilder::addExports(Ref ast, Module *wasm) {
+ Ref exports = ValueBuilder::makeObject();
+ for (auto export_ : wasm->exports) {
+ ValueBuilder::appendToObject(exports, fromName(export_->name), ValueBuilder::makeName(fromName(export_->value)));
+ }
+ ast->push_back(ValueBuilder::makeStatement(ValueBuilder::makeReturn(exports)));
+}
+
+Ref Wasm2AsmBuilder::processFunction(Function* func) {
+ if (debug) std::cerr << " processFunction " << func->name << '\n';
+ Ref ret = ValueBuilder::makeFunction(fromName(func->name));
+ frees.clear();
+ frees.resize(std::max(i32, std::max(f32, f64)) + 1);
+ temps.clear();
+ temps.resize(std::max(i32, std::max(f32, f64)) + 1);
+ temps[i32] = temps[f32] = temps[f64] = 0;
+ // arguments
+ for (auto& param : func->params) {
+ IString name = fromName(param.name);
+ ValueBuilder::appendArgumentToFunction(ret, name);
+ ret[3]->push_back(
+ ValueBuilder::makeStatement(
+ ValueBuilder::makeAssign(
+ ValueBuilder::makeName(name),
+ makeAsmCoercion(ValueBuilder::makeName(name), wasmToAsmType(param.type))
+ )
+ )
+ );
+ }
+ Ref theVar = ValueBuilder::makeVar();
+ size_t theVarIndex = ret[3]->size();
+ ret[3]->push_back(theVar);
+ // body
+ scanFunctionBody(func->body);
+ if (isStatement(func->body)) {
+ IString result = func->result != none ? getTemp(func->result) : NO_RESULT;
+ flattenAppend(ret, ValueBuilder::makeStatement(processFunctionBody(func->body, result)));
+ if (func->result != none) {
+ // do the actual return
+ ret[3]->push_back(ValueBuilder::makeStatement(ValueBuilder::makeReturn(makeAsmCoercion(ValueBuilder::makeName(result), wasmToAsmType(func->result)))));
+ freeTemp(func->result, result);
+ }
+ } else {
+ // whole thing is an expression, just do a return
+ if (func->result != none) {
+ ret[3]->push_back(ValueBuilder::makeStatement(ValueBuilder::makeReturn(makeAsmCoercion(processFunctionBody(func->body, EXPRESSION_RESULT), wasmToAsmType(func->result)))));
+ } else {
+ flattenAppend(ret, processFunctionBody(func->body, NO_RESULT));
+ }
+ }
+ // locals, including new temp locals
+ for (auto& local : func->locals) {
+ ValueBuilder::appendToVar(theVar, fromName(local.name), makeAsmCoercedZero(wasmToAsmType(local.type)));
+ }
+ for (auto f : frees[i32]) {
+ ValueBuilder::appendToVar(theVar, f, makeAsmCoercedZero(ASM_INT));
+ }
+ for (auto f : frees[f32]) {
+ ValueBuilder::appendToVar(theVar, f, makeAsmCoercedZero(ASM_FLOAT));
+ }
+ for (auto f : frees[f64]) {
+ ValueBuilder::appendToVar(theVar, f, makeAsmCoercedZero(ASM_DOUBLE));
+ }
+ if (theVar[1]->size() == 0) {
+ ret[3]->splice(theVarIndex, 1);
+ }
+ // checks
+ assert(frees[i32].size() == temps[i32]); // all temp vars should be free at the end
+ assert(frees[f32].size() == temps[f32]); // all temp vars should be free at the end
+ assert(frees[f64].size() == temps[f64]); // all temp vars should be free at the end
+ // cleanups
+ willBeStatement.clear();
+ return ret;
+}
+
+void Wasm2AsmBuilder::scanFunctionBody(Expression* curr) {
+ struct ExpressionScanner : public WasmWalker {
+ Wasm2AsmBuilder* parent;
+
+ ExpressionScanner(Wasm2AsmBuilder* parent) : parent(parent) {}
+
+ // Visitors
+
+ void visitBlock(Block *curr) override {
+ parent->setStatement(curr);
+ }
+ void visitIf(If *curr) override {
+ parent->setStatement(curr);
+ }
+ void visitLoop(Loop *curr) override {
+ parent->setStatement(curr);
+ }
+ void visitBreak(Break *curr) override {
+ parent->setStatement(curr);
+ }
+ void visitSwitch(Switch *curr) override {
+ parent->setStatement(curr);
+ }
+ void visitCall(Call *curr) override {
+ for (auto item : curr->operands) {
+ if (parent->isStatement(item)) {
+ parent->setStatement(curr);
+ break;
+ }
+ }
+ }
+ void visitCallImport(CallImport *curr) override {
+ visitCall(curr);
+ }
+ void visitCallIndirect(CallIndirect *curr) override {
+ if (parent->isStatement(curr->target)) {
+ parent->setStatement(curr);
+ return;
+ }
+ for (auto item : curr->operands) {
+ if (parent->isStatement(item)) {
+ parent->setStatement(curr);
+ break;
+ }
+ }
+ }
+ void visitSetLocal(SetLocal *curr) override {
+ if (parent->isStatement(curr->value)) {
+ parent->setStatement(curr);
+ }
+ }
+ void visitLoad(Load *curr) override {
+ if (parent->isStatement(curr->ptr)) {
+ parent->setStatement(curr);
+ }
+ }
+ void visitStore(Store *curr) override {
+ if (parent->isStatement(curr->ptr) || parent->isStatement(curr->value)) {
+ parent->setStatement(curr);
+ }
+ }
+ void visitUnary(Unary *curr) override {
+ if (parent->isStatement(curr->value)) {
+ parent->setStatement(curr);
+ }
+ }
+ void visitBinary(Binary *curr) override {
+ if (parent->isStatement(curr->left) || parent->isStatement(curr->right)) {
+ parent->setStatement(curr);
+ }
+ }
+ void visitSelect(Select *curr) override {
+ if (parent->isStatement(curr->condition) || parent->isStatement(curr->ifTrue) || parent->isStatement(curr->ifFalse)) {
+ parent->setStatement(curr);
+ }
+ }
+ void visitHost(Host *curr) override {
+ for (auto item : curr->operands) {
+ if (parent->isStatement(item)) {
+ parent->setStatement(curr);
+ break;
+ }
+ }
+ }
+ };
+ ExpressionScanner(this).walk(curr);
+}
+
+Ref Wasm2AsmBuilder::processFunctionBody(Expression* curr, IString result) {
+ struct ExpressionProcessor : public WasmVisitor<Ref> {
+ Wasm2AsmBuilder* parent;
+ IString result;
+ ExpressionProcessor(Wasm2AsmBuilder* parent) : parent(parent) {}
+
+ // A scoped temporary variable.
+ struct ScopedTemp {
+ Wasm2AsmBuilder* parent;
+ WasmType type;
+ IString temp;
+ bool needFree;
+ // @param possible if provided, this is a variable we can use as our temp. it has already been
+ // allocated in a higher scope, and we can just assign to it as our result is
+ // going there anyhow.
+ ScopedTemp(WasmType type, Wasm2AsmBuilder* parent, IString possible = NO_RESULT) : parent(parent), type(type) {
+ assert(possible != EXPRESSION_RESULT);
+ if (possible == NO_RESULT) {
+ temp = parent->getTemp(type);
+ needFree = true;
+ } else {
+ temp = possible;
+ needFree = false;
+ }
+ }
+ ~ScopedTemp() {
+ if (needFree) {
+ parent->freeTemp(type, temp);
+ }
+ }
+
+ IString getName() {
+ return temp;
+ }
+ Ref getAstName() {
+ return ValueBuilder::makeName(temp);
+ }
+ };
+
+ Ref visit(Expression* curr, IString nextResult) {
+ IString old = result;
+ result = nextResult;
+ Ref ret = WasmVisitor::visit(curr);
+ result = old; // keep it consistent for the rest of this frame, which may call visit on multiple children
+ return ret;
+ }
+
+ Ref visit(Expression* curr, ScopedTemp& temp) {
+ return visit(curr, temp.temp);
+ }
+
+ Ref visitForExpression(Expression* curr, WasmType type, IString& tempName) { // this result is for an asm expression slot, but it might be a statement
+ if (isStatement(curr)) {
+ ScopedTemp temp(type, parent);
+ tempName = temp.temp;
+ return visit(curr, temp);
+ } else {
+ return visit(curr, EXPRESSION_RESULT);
+ }
+ }
+
+ Ref visitAndAssign(Expression* curr, IString result) {
+ Ref ret = visit(curr, result);
+ // if it's not already a statement, then it's an expression, and we need to assign it
+ // (if it is a statement, it already assigns to the result var)
+ if (!isStatement(curr) && result != NO_RESULT) {
+ ret = ValueBuilder::makeStatement(ValueBuilder::makeAssign(ValueBuilder::makeName(result), ret));
+ }
+ return ret;
+ }
+
+ Ref visitAndAssign(Expression* curr, ScopedTemp& temp) {
+ return visitAndAssign(curr, temp.getName());
+ }
+
+ bool isStatement(Expression* curr) {
+ return parent->isStatement(curr);
+ }
+
+ // Expressions with control flow turn into a block, which we must
+ // then handle, even if we are an expression.
+ bool isBlock(Ref ast) {
+ return !!ast && ast[0] == BLOCK;
+ }
+
+ Ref blockify(Ref ast) {
+ if (isBlock(ast)) return ast;
+ Ref ret = ValueBuilder::makeBlock();
+ ret[1]->push_back(ValueBuilder::makeStatement(ast));
+ return ret;
+ }
+
+ // For spooky return-at-a-distance/break-with-result, this tells us
+ // what the result var is for a specific label.
+ std::map<Name, IString> breakResults;
+
+ // Breaks to the top of a loop should be emitted as continues, to that loop's main label
+ std::map<Name, Name> continueLabels;
+
+ IString fromName(Name name) {
+ return parent->fromName(name);
+ }
+
+ // Visitors
+
+ Ref visitBlock(Block *curr) override {
+ breakResults[curr->name] = result;
+ Ref ret = ValueBuilder::makeBlock();
+ size_t size = curr->list.size();
+ int noResults = result == NO_RESULT ? size : size-1;
+ for (size_t i = 0; i < noResults; i++) {
+ flattenAppend(ret, ValueBuilder::makeStatement(visit(curr->list[i], NO_RESULT)));
+ }
+ if (result != NO_RESULT) {
+ flattenAppend(ret, visitAndAssign(curr->list[size-1], result));
+ }
+ if (curr->name.is()) {
+ ret = ValueBuilder::makeLabel(fromName(curr->name), ret);
+ }
+ return ret;
+ }
+ Ref visitIf(If *curr) override {
+ IString temp;
+ Ref condition = visitForExpression(curr->condition, i32, temp);
+ Ref ifTrue = ValueBuilder::makeStatement(visitAndAssign(curr->ifTrue, result));
+ Ref ifFalse;
+ if (curr->ifFalse) {
+ ifFalse = ValueBuilder::makeStatement(visitAndAssign(curr->ifFalse, result));
+ }
+ if (temp.isNull()) {
+ return ValueBuilder::makeIf(condition, ifTrue, ifFalse); // simple if
+ }
+ condition = blockify(condition);
+ // just add an if to the block
+ condition[1]->push_back(ValueBuilder::makeIf(ValueBuilder::makeName(temp), ifTrue, ifFalse));
+ return condition;
+ }
+ Ref visitLoop(Loop *curr) override {
+ Name asmLabel = curr->out.is() ? curr->out : curr->in; // label using the outside, normal for breaks. if no outside, then inside
+ if (curr->in.is()) continueLabels[curr->in] = asmLabel;
+ Ref body = visit(curr->body, result);
+ Ref ret = ValueBuilder::makeDo(body, ValueBuilder::makeInt(0));
+ if (asmLabel.is()) {
+ ret = ValueBuilder::makeLabel(fromName(asmLabel), ret);
+ }
+ return ret;
+ }
+ Ref visitBreak(Break *curr) override {
+ if (curr->condition) {
+ // we need an equivalent to an if here, so use that code
+ Break fakeBreak = *curr;
+ fakeBreak.condition = nullptr;
+ If fakeIf;
+ fakeIf.condition = curr->condition;
+ fakeIf.ifTrue = &fakeBreak;
+ return visit(&fakeIf, result);
+ }
+ Ref theBreak;
+ auto iter = continueLabels.find(curr->name);
+ if (iter == continueLabels.end()) {
+ theBreak = ValueBuilder::makeBreak(fromName(curr->name));
+ } else {
+ theBreak = ValueBuilder::makeContinue(fromName(iter->second));
+ }
+ if (!curr->value) return theBreak;
+ // generate the value, including assigning to the result, and then do the break
+ Ref ret = visitAndAssign(curr->value, breakResults[curr->name]);
+ ret = blockify(ret);
+ ret[1]->push_back(theBreak);
+ return ret;
+ }
+ Ref visitSwitch(Switch *curr) override {
+ Ref ret = ValueBuilder::makeLabel(fromName(curr->name), ValueBuilder::makeBlock());
+ Ref value;
+ if (isStatement(curr->value)) {
+ ScopedTemp temp(i32, parent);
+ flattenAppend(ret[2], visit(curr->value, temp));
+ value = temp.getAstName();
+ } else {
+ value = visit(curr->value, EXPRESSION_RESULT);
+ }
+ Ref theSwitch = ValueBuilder::makeSwitch(value);
+ ret[2][1]->push_back(theSwitch);
+ for (auto& c : curr->cases) {
+ bool added = false;
+ for (size_t i = 0; i < curr->targets.size(); i++) {
+ if (curr->targets[i] == c.name) {
+ ValueBuilder::appendCaseToSwitch(theSwitch, ValueBuilder::makeNum(i));
+ added = true;
+ }
+ }
+ if (c.name == curr->default_) {
+ ValueBuilder::appendDefaultToSwitch(theSwitch);
+ added = true;
+ }
+ assert(added);
+ ValueBuilder::appendCodeToSwitch(theSwitch, blockify(visit(c.body, NO_RESULT)), false);
+ }
+ return ret;
+ }
+
+ Ref makeStatementizedCall(ExpressionList& operands, Ref ret, Ref theCall, IString result, WasmType type) {
+ std::vector<ScopedTemp*> temps; // TODO: utility class, with destructor?
+ for (auto& operand : operands) {
+ temps.push_back(new ScopedTemp(operand->type, parent));
+ IString temp = temps.back()->temp;
+ flattenAppend(ret, visitAndAssign(operand, temp));
+ theCall[2]->push_back(makeAsmCoercion(ValueBuilder::makeName(temp), wasmToAsmType(operand->type)));
+ }
+ theCall = makeAsmCoercion(theCall, wasmToAsmType(type));
+ if (result != NO_RESULT) {
+ theCall = ValueBuilder::makeStatement(ValueBuilder::makeAssign(ValueBuilder::makeName(result), theCall));
+ }
+ flattenAppend(ret, theCall);
+ for (auto temp : temps) {
+ delete temp;
+ }
+ return ret;
+ }
+
+ Ref visitCall(Call *curr) override {
+ Ref theCall = ValueBuilder::makeCall(fromName(curr->target));
+ if (!isStatement(curr)) {
+ // none of our operands is a statement; go right ahead and create a simple expression
+ for (auto operand : curr->operands) {
+ theCall[2]->push_back(makeAsmCoercion(visit(operand, EXPRESSION_RESULT), wasmToAsmType(operand->type)));
+ }
+ return makeAsmCoercion(theCall, wasmToAsmType(curr->type));
+ }
+ // we must statementize them all
+ return makeStatementizedCall(curr->operands, ValueBuilder::makeBlock(), theCall, result, curr->type);
+ }
+ Ref visitCallImport(CallImport *curr) override {
+ return visitCall(curr);
+ }
+ Ref visitCallIndirect(CallIndirect *curr) override {
+ std::string stable = std::string("FUNCTION_TABLE_") + getSig(curr->fullType);
+ IString table = IString(stable.c_str(), false);
+ auto makeTableCall = [&](Ref target) {
+ return ValueBuilder::makeCall(ValueBuilder::makeSub(
+ ValueBuilder::makeName(table),
+ ValueBuilder::makeBinary(target, AND, ValueBuilder::makeInt(parent->getTableSize()-1))
+ ));
+ };
+ if (!isStatement(curr)) {
+ // none of our operands is a statement; go right ahead and create a simple expression
+ Ref theCall = makeTableCall(visit(curr->target, EXPRESSION_RESULT));
+ for (auto operand : curr->operands) {
+ theCall[2]->push_back(makeAsmCoercion(visit(operand, EXPRESSION_RESULT), wasmToAsmType(operand->type)));
+ }
+ return makeAsmCoercion(theCall, wasmToAsmType(curr->type));
+ }
+ // we must statementize them all
+ Ref ret = ValueBuilder::makeBlock();
+ ScopedTemp temp(i32, parent);
+ flattenAppend(ret, visit(curr->target, temp));
+ Ref theCall = makeTableCall(temp.getAstName());
+ return makeStatementizedCall(curr->operands, ret, theCall, result, curr->type);
+ }
+ Ref visitGetLocal(GetLocal *curr) override {
+ return ValueBuilder::makeName(fromName(curr->name));
+ }
+ Ref visitSetLocal(SetLocal *curr) override {
+ if (!isStatement(curr)) {
+ return ValueBuilder::makeAssign(ValueBuilder::makeName(fromName(curr->name)), visit(curr->value, EXPRESSION_RESULT));
+ }
+ ScopedTemp temp(curr->type, parent, result); // if result was provided, our child can just assign there. otherwise, allocate a temp for it to assign to.
+ Ref ret = blockify(visit(curr->value, temp));
+ // the output was assigned to result, so we can just assign it to our target
+ ret[1]->push_back(ValueBuilder::makeStatement(ValueBuilder::makeAssign(ValueBuilder::makeName(fromName(curr->name)), temp.getAstName())));
+ return ret;
+ }
+ Ref visitLoad(Load *curr) override {
+ if (isStatement(curr)) {
+ ScopedTemp temp(i32, parent);
+ GetLocal fakeLocal;
+ fakeLocal.name = temp.getName();
+ Load fakeLoad = *curr;
+ fakeLoad.ptr = &fakeLocal;
+ Ref ret = blockify(visitAndAssign(curr->ptr, temp));
+ flattenAppend(ret, visitAndAssign(&fakeLoad, result));
+ return ret;
+ }
+ // normal load
+ assert(curr->bytes == curr->align); // TODO: unaligned
+ Ref ptr = visit(curr->ptr, EXPRESSION_RESULT);
+ Ref ret;
+ switch (curr->type) {
+ case i32: {
+ switch (curr->bytes) {
+ case 1: ret = ValueBuilder::makeSub(ValueBuilder::makeName(curr->signed_ ? HEAP8 : HEAPU8 ), ValueBuilder::makePtrShift(ptr, 0)); break;
+ case 2: ret = ValueBuilder::makeSub(ValueBuilder::makeName(curr->signed_ ? HEAP16 : HEAPU16), ValueBuilder::makePtrShift(ptr, 1)); break;
+ case 4: ret = ValueBuilder::makeSub(ValueBuilder::makeName(curr->signed_ ? HEAP32 : HEAPU32), ValueBuilder::makePtrShift(ptr, 2)); break;
+ default: abort();
+ }
+ break;
+ }
+ case f32: ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF32), ValueBuilder::makePtrShift(ptr, 2)); break;
+ case f64: ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF64), ValueBuilder::makePtrShift(ptr, 3)); break;
+ default: abort();
+ }
+ return makeAsmCoercion(ret, wasmToAsmType(curr->type));
+ }
+ Ref visitStore(Store *curr) override {
+ if (isStatement(curr)) {
+ ScopedTemp tempPtr(i32, parent);
+ ScopedTemp tempValue(curr->type, parent);
+ GetLocal fakeLocalPtr;
+ fakeLocalPtr.name = tempPtr.getName();
+ GetLocal fakeLocalValue;
+ fakeLocalValue.name = tempValue.getName();
+ Store fakeStore = *curr;
+ fakeStore.ptr = &fakeLocalPtr;
+ fakeStore.value = &fakeLocalValue;
+ Ref ret = blockify(visitAndAssign(curr->ptr, tempPtr));
+ flattenAppend(ret, visitAndAssign(curr->value, tempValue));
+ flattenAppend(ret, visitAndAssign(&fakeStore, result));
+ return ret;
+ }
+ // normal store
+ assert(curr->bytes == curr->align); // TODO: unaligned
+ Ref ptr = visit(curr->ptr, EXPRESSION_RESULT);
+ Ref value = visit(curr->value, EXPRESSION_RESULT);
+ Ref ret;
+ switch (curr->type) {
+ case i32: {
+ switch (curr->bytes) {
+ case 1: ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAP8), ValueBuilder::makePtrShift(ptr, 0)); break;
+ case 2: ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAP16), ValueBuilder::makePtrShift(ptr, 1)); break;
+ case 4: ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAP32), ValueBuilder::makePtrShift(ptr, 2)); break;
+ default: abort();
+ }
+ break;
+ }
+ case f32: ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF32), ValueBuilder::makePtrShift(ptr, 2)); break;
+ case f64: ret = ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF64), ValueBuilder::makePtrShift(ptr, 3)); break;
+ default: abort();
+ }
+ return ValueBuilder::makeAssign(ret, value);
+ }
+ Ref visitConst(Const *curr) override {
+ switch (curr->type) {
+ case i32: return ValueBuilder::makeInt(curr->value.i32);
+ case f32: {
+ Ref ret = ValueBuilder::makeCall(MATH_FROUND);
+ Const fake;
+ fake.value = double(curr->value.f32);
+ fake.type = f64;
+ ret[2]->push_back(visitConst(&fake));
+ return ret;
+ }
+ case f64: {
+ double d = curr->value.f64;
+ if (d == 0 && 1/d < 0) { // negative zero
+ return ValueBuilder::makeUnary(PLUS, ValueBuilder::makeUnary(MINUS, ValueBuilder::makeDouble(0)));
+ }
+ return ValueBuilder::makeUnary(PLUS, ValueBuilder::makeDouble(curr->value.f64));
+ }
+ default: abort();
+ }
+ }
+ Ref visitUnary(Unary *curr) override {
+ if (isStatement(curr)) {
+ ScopedTemp temp(curr->value->type, parent);
+ GetLocal fakeLocal;
+ fakeLocal.name = temp.getName();
+ Unary fakeUnary = *curr;
+ fakeUnary.value = &fakeLocal;
+ Ref ret = blockify(visitAndAssign(curr->value, temp));
+ flattenAppend(ret, visitAndAssign(&fakeUnary, result));
+ return ret;
+ }
+ // normal unary
+ Ref value = visit(curr->value, EXPRESSION_RESULT);
+ switch (curr->type) {
+ case i32: {
+ switch (curr->op) {
+ case Clz: return ValueBuilder::makeCall(MATH_CLZ32, value);
+ case Ctz: return ValueBuilder::makeCall(MATH_CTZ32, value);
+ case Popcnt: return ValueBuilder::makeCall(MATH_POPCNT32, value);
+ default: abort();
+ }
+ }
+ case f32:
+ case f64: {
+ Ref ret;
+ switch (curr->op) {
+ case Neg: ret = ValueBuilder::makeUnary(MINUS, value); break;
+ case Abs: ret = ValueBuilder::makeCall(MATH_ABS, value); break;
+ case Ceil: ret = ValueBuilder::makeCall(MATH_CEIL, value); break;
+ case Floor: ret = ValueBuilder::makeCall(MATH_FLOOR, value); break;
+ case Trunc: ret = ValueBuilder::makeCall(MATH_TRUNC, value); break;
+ case Nearest: ret = ValueBuilder::makeCall(MATH_NEAREST, value); break;
+ case Sqrt: ret = ValueBuilder::makeCall(MATH_SQRT, value); break;
+ case TruncSFloat32: ret = ValueBuilder::makePrefix(B_NOT, ValueBuilder::makePrefix(B_NOT, value)); break;
+ case PromoteFloat32:
+ case ConvertSInt32: ret = ValueBuilder::makePrefix(PLUS, ValueBuilder::makeBinary(value, OR, ValueBuilder::makeNum(0))); break;
+ case ConvertUInt32: ret = ValueBuilder::makePrefix(PLUS, ValueBuilder::makeBinary(value, TRSHIFT, ValueBuilder::makeNum(0))); break;
+ case DemoteFloat64: ret = value; break;
+ default: std::cerr << curr << '\n'; abort();
+ }
+ if (curr->type == f32) { // doubles need much less coercing
+ return makeAsmCoercion(ret, ASM_FLOAT);
+ }
+ return ret;
+ }
+ default: abort();
+ }
+ }
+ Ref visitBinary(Binary *curr) override {
+ if (isStatement(curr)) {
+ ScopedTemp tempLeft(curr->left->type, parent);
+ GetLocal fakeLocalLeft;
+ fakeLocalLeft.name = tempLeft.getName();
+ ScopedTemp tempRight(curr->right->type, parent);
+ GetLocal fakeLocalRight;
+ fakeLocalRight.name = tempRight.getName();
+ Binary fakeBinary = *curr;
+ fakeBinary.left = &fakeLocalLeft;
+ fakeBinary.right = &fakeLocalRight;
+ Ref ret = blockify(visitAndAssign(curr->left, tempLeft));
+ flattenAppend(ret, visitAndAssign(curr->right, tempRight));
+ flattenAppend(ret, visitAndAssign(&fakeBinary, result));
+ return ret;
+ }
+ // normal binary
+ Ref left = visit(curr->left, EXPRESSION_RESULT);
+ Ref right = visit(curr->right, EXPRESSION_RESULT);
+ Ref ret;
+ switch (curr->op) {
+ case Add: ret = ValueBuilder::makeBinary(left, PLUS, right); break;
+ case Sub: ret = ValueBuilder::makeBinary(left, MINUS, right); break;
+ case Mul: {
+ if (curr->type == i32) {
+ return ValueBuilder::makeCall(MATH_IMUL, left, right); // TODO: when one operand is a small int, emit a multiply
+ } else {
+ return ValueBuilder::makeBinary(left, MINUS, right); break;
+ }
+ }
+ case DivS: ret = ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), DIV, makeSigning(right, ASM_SIGNED)); break;
+ case DivU: ret = ValueBuilder::makeBinary(makeSigning(left, ASM_UNSIGNED), DIV, makeSigning(right, ASM_UNSIGNED)); break;
+ case RemS: ret = ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), MOD, makeSigning(right, ASM_SIGNED)); break;
+ case RemU: ret = ValueBuilder::makeBinary(makeSigning(left, ASM_UNSIGNED), MOD, makeSigning(right, ASM_UNSIGNED)); break;
+ case And: ret = ValueBuilder::makeBinary(left, AND, right); break;
+ case Or: ret = ValueBuilder::makeBinary(left, OR, right); break;
+ case Xor: ret = ValueBuilder::makeBinary(left, XOR, right); break;
+ case Shl: ret = ValueBuilder::makeBinary(left, LSHIFT, right); break;
+ case ShrU: ret = ValueBuilder::makeBinary(left, TRSHIFT, right); break;
+ case ShrS: ret = ValueBuilder::makeBinary(left, RSHIFT, right); break;
+ case Div: ret = ValueBuilder::makeBinary(left, DIV, right); break;
+ case Min: ret = ValueBuilder::makeCall(MATH_MIN, left, right); break;
+ case Max: ret = ValueBuilder::makeCall(MATH_MAX, left, right); break;
+ case Eq: {
+ if (curr->left->type == i32) {
+ return ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), EQ, makeSigning(right, ASM_SIGNED));
+ } else {
+ return ValueBuilder::makeBinary(left, EQ, right);
+ }
+ }
+ case Ne: {
+ if (curr->left->type == i32) {
+ return ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), NE, makeSigning(right, ASM_SIGNED));
+ } else {
+ return ValueBuilder::makeBinary(left, NE, right);
+ }
+ }
+ case LtS: return ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), LT, makeSigning(right, ASM_SIGNED));
+ case LtU: return ValueBuilder::makeBinary(makeSigning(left, ASM_UNSIGNED), LT, makeSigning(right, ASM_UNSIGNED));
+ case LeS: return ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), LE, makeSigning(right, ASM_SIGNED));
+ case LeU: return ValueBuilder::makeBinary(makeSigning(left, ASM_UNSIGNED), LE, makeSigning(right, ASM_UNSIGNED));
+ case GtS: return ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), GT, makeSigning(right, ASM_SIGNED));
+ case GtU: return ValueBuilder::makeBinary(makeSigning(left, ASM_UNSIGNED), GT, makeSigning(right, ASM_UNSIGNED));
+ case GeS: return ValueBuilder::makeBinary(makeSigning(left, ASM_SIGNED), GE, makeSigning(right, ASM_SIGNED));
+ case GeU: return ValueBuilder::makeBinary(makeSigning(left, ASM_UNSIGNED), GE, makeSigning(right, ASM_UNSIGNED));
+ case Lt: return ValueBuilder::makeBinary(left, LT, right);
+ case Le: return ValueBuilder::makeBinary(left, LE, right);
+ case Gt: return ValueBuilder::makeBinary(left, GT, right);
+ case Ge: return ValueBuilder::makeBinary(left, GE, right);
+ default: abort();
+ }
+ return makeAsmCoercion(ret, wasmToAsmType(curr->type));
+ }
+ Ref visitSelect(Select *curr) override {
+ if (isStatement(curr)) {
+ ScopedTemp tempCondition(i32, parent);
+ GetLocal fakeCondition;
+ fakeCondition.name = tempCondition.getName();
+ ScopedTemp tempIfTrue(curr->ifTrue->type, parent);
+ GetLocal fakeLocalIfTrue;
+ fakeLocalIfTrue.name = tempIfTrue.getName();
+ ScopedTemp tempIfFalse(curr->ifFalse->type, parent);
+ GetLocal fakeLocalIfFalse;
+ fakeLocalIfFalse.name = tempIfFalse.getName();
+ Select fakeSelect = *curr;
+ fakeSelect.condition = &fakeCondition;
+ fakeSelect.ifTrue = &fakeLocalIfTrue;
+ fakeSelect.ifFalse = &fakeLocalIfFalse;
+ Ref ret = blockify(visitAndAssign(curr->condition, tempCondition));
+ flattenAppend(ret, visitAndAssign(curr->ifTrue, tempIfTrue));
+ flattenAppend(ret, visitAndAssign(curr->ifFalse, tempIfFalse));
+ flattenAppend(ret, visitAndAssign(&fakeSelect, result));
+ return ret;
+ }
+ // normal select
+ Ref condition = visit(curr->condition, EXPRESSION_RESULT);
+ Ref ifTrue = visit(curr->ifTrue, EXPRESSION_RESULT);
+ Ref ifFalse = visit(curr->ifFalse, EXPRESSION_RESULT);
+ ScopedTemp tempCondition(i32, parent),
+ tempIfTrue(curr->type, parent),
+ tempIfFalse(curr->type, parent);
+ return
+ ValueBuilder::makeSeq(
+ ValueBuilder::makeAssign(tempCondition.getAstName(), condition),
+ ValueBuilder::makeSeq(
+ ValueBuilder::makeAssign(tempIfTrue.getAstName(), ifTrue),
+ ValueBuilder::makeSeq(
+ ValueBuilder::makeAssign(tempIfFalse.getAstName(), ifFalse),
+ ValueBuilder::makeConditional(tempCondition.getAstName(), tempIfTrue.getAstName(), tempIfFalse.getAstName())
+ )
+ )
+ );
+ }
+ Ref visitHost(Host *curr) override {
+ abort();
+ }
+ Ref visitNop(Nop *curr) override {
+ return ValueBuilder::makeToplevel();
+ }
+ Ref visitUnreachable(Unreachable *curr) override {
+ return ValueBuilder::makeCall(ABORT_FUNC);
+ }
+ };
+ return ExpressionProcessor(this).visit(curr, result);
+}
+
+} // namespace wasm
+