summaryrefslogtreecommitdiff
path: root/src/asm2wasm.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/asm2wasm.h')
-rw-r--r--src/asm2wasm.h797
1 files changed, 572 insertions, 225 deletions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h
index 9fa8d11dd..ca88fa883 100644
--- a/src/asm2wasm.h
+++ b/src/asm2wasm.h
@@ -7,34 +7,17 @@
#include "wasm.h"
#include "emscripten-optimizer/optimizer.h"
#include "mixed_arena.h"
+#include "shared-constants.h"
+#include "asm_v_wasm.h"
namespace wasm {
using namespace cashew;
-int debug = 0; // wasm::debug is set in main(), typically from an env var
+extern int debug; // wasm::debug is set in main(), typically from an env var
// Utilities
-IString GLOBAL("global"), NAN_("NaN"), INFINITY_("Infinity"),
- TOPMOST("topmost"),
- INT8ARRAY("Int8Array"),
- INT16ARRAY("Int16Array"),
- INT32ARRAY("Int32Array"),
- UINT8ARRAY("Uint8Array"),
- UINT16ARRAY("Uint16Array"),
- UINT32ARRAY("Uint32Array"),
- FLOAT32ARRAY("Float32Array"),
- FLOAT64ARRAY("Float64Array"),
- IMPOSSIBLE_CONTINUE("impossible-continue"),
- MATH("Math"),
- IMUL("imul"),
- CLZ32("clz32"),
- FROUND("fround"),
- ASM2WASM("asm2wasm"),
- F64_REM("f64-rem");
-
-
static void abort_on(std::string why) {
std::cerr << why << '\n';
abort();
@@ -75,13 +58,69 @@ struct AstStackHelper {
std::vector<Ref> AstStackHelper::astStack;
//
+// Asm2WasmPreProcessor - does some initial parsing/processing
+// of asm.js code.
+//
+
+struct Asm2WasmPreProcessor {
+ bool memoryGrowth = false;
+
+ char* process(char* input) {
+ // emcc --separate-asm modules can look like
+ //
+ // Module["asm"] = (function(global, env, buffer) {
+ // ..
+ // });
+ //
+ // we need to clean that up.
+ if (*input == 'M') {
+ size_t num = strlen(input);
+ while (*input != 'f') {
+ input++;
+ num--;
+ }
+ char *end = input + num - 1;
+ while (*end != '}') {
+ *end = 0;
+ end--;
+ }
+ }
+
+ // asm.js memory growth uses a quite elaborate pattern. Instead of parsing and
+ // matching it, we do a simpler detection on emscripten's asm.js output format
+ const char* START_FUNCS = "// EMSCRIPTEN_START_FUNCS";
+ char *marker = strstr(input, START_FUNCS);
+ if (marker) {
+ *marker = 0; // look for memory growth code just up to here
+ char *growthSign = strstr(input, "return true;"); // this can only show up in growth code, as normal asm.js lacks "true"
+ if (growthSign) {
+ memoryGrowth = true;
+ // clean out this function, we don't need it
+ char *growthFuncStart = strstr(input, "function ");
+ assert(strstr(growthFuncStart + 1, "function ") == 0); // should be only this one function in this area, so no confusion for us
+ char *growthFuncEnd = strchr(growthSign, '}');
+ assert(growthFuncEnd > growthFuncStart + 5);
+ growthFuncStart[0] = '/';
+ growthFuncStart[1] = '*';
+ growthFuncEnd--;
+ growthFuncEnd[0] = '*';
+ growthFuncEnd[1] = '/';
+ }
+ *marker = START_FUNCS[0];
+ }
+
+ return input;
+ }
+};
+
+//
// Asm2WasmBuilder - converts an asm.js module into WebAssembly
//
class Asm2WasmBuilder {
- Module& wasm;
+ AllocatingModule& wasm;
- MixedArena allocator;
+ MixedArena &allocator;
// globals
@@ -96,9 +135,34 @@ class Asm2WasmBuilder {
MappedGlobal(unsigned address, WasmType type, bool import, IString module, IString base) : address(address), type(type), import(import), module(module), base(base) {}
};
+ // function table
+ std::map<IString, int> functionTableStarts; // each asm function table gets a range in the one wasm table, starting at a location
+ 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;
+
public:
std::map<IString, MappedGlobal> mappedGlobals;
+ // the global mapping info is not present in the output wasm. We need to save it on the side
+ // if we intend to load and run this module's wasm.
+ void serializeMappedGlobals(const char *filename) {
+ FILE *f = fopen(filename, "w");
+ assert(f);
+ fprintf(f, "{\n");
+ bool first = true;
+ for (auto& pair : mappedGlobals) {
+ auto name = pair.first;
+ auto& global = pair.second;
+ if (first) first = false;
+ else fprintf(f, ",");
+ fprintf(f, "\"%s\": { \"address\": %d, \"type\": %d, \"import\": %d, \"module\": \"%s\", \"base\": \"%s\" }\n",
+ name.str, global.address, global.type, global.import, global.module.str, global.base.str);
+ }
+ fprintf(f, "}");
+ fclose(f);
+ }
+
private:
void allocateGlobal(IString name, WasmType type, bool import, IString module = IString(), IString base = IString()) {
assert(mappedGlobals.find(name) == mappedGlobals.end());
@@ -116,9 +180,14 @@ private:
};
std::map<IString, View> views; // name (e.g. HEAP8) => view info
- IString Math_imul; // imported name of Math.imul
- IString Math_clz32; // imported name of Math.imul
- IString Math_fround; // imported name of Math.fround
+
+ // Imported names of Math.*
+ IString Math_imul;
+ IString Math_clz32;
+ IString Math_fround;
+ IString Math_abs;
+ IString Math_floor;
+ IString Math_sqrt;
// function types. we fill in this information as we see
// uses, in the first pass
@@ -150,9 +219,9 @@ private:
if (previous.params.size() > i) {
if (previous.params[i] == none) {
previous.params[i] = type.params[i]; // use a more concrete type
- } else {
- previous.params.push_back(type.params[i]); // add a new param
}
+ } else {
+ previous.params.push_back(type.params[i]); // add a new param
}
}
if (previous.result == none) {
@@ -164,22 +233,13 @@ private:
}
}
- char getSigFromType(WasmType type) {
- switch (type) {
- case i32: return 'i';
- case f64: return 'd';
- case none: return 'v';
- default: abort();
- }
- }
-
FunctionType *getFunctionType(Ref parent, ExpressionList& operands) {
// generate signature
WasmType result = detectWasmType(parent, nullptr);
std::string str = "FUNCSIG$";
- str += getSigFromType(result);
+ str += getSig(result);
for (auto operand : operands) {
- str += getSigFromType(operand->type);
+ str += getSig(operand->type);
}
IString sig(str.c_str(), false);
if (wasm.functionTypesMap.find(sig) == wasm.functionTypesMap.end()) {
@@ -196,35 +256,12 @@ private:
}
public:
- Asm2WasmBuilder(Module& wasm) : wasm(wasm), nextGlobal(8), maxGlobal(1000) {} // XXX sync with emcc
+ Asm2WasmBuilder(AllocatingModule& wasm, bool memoryGrowth) : wasm(wasm), allocator(wasm.allocator), nextGlobal(8), maxGlobal(1000), memoryGrowth(memoryGrowth) {}
void processAsm(Ref ast);
void optimize();
private:
- WasmType asmToWasmType(AsmType asmType) {
- switch (asmType) {
- case ASM_INT: return WasmType::i32;
- case ASM_DOUBLE: return WasmType::f64;
- case ASM_FLOAT: return WasmType::f32;
- case ASM_NONE: return WasmType::none;
- default: {}
- }
- abort_on("confused asmType", asmType);
- return (WasmType)-1; // avoid warning
- }
- AsmType wasmToAsmType(WasmType type) {
- switch (type) {
- case WasmType::i32: return ASM_INT;
- case WasmType::f32: return ASM_FLOAT;
- case WasmType::f64: return ASM_DOUBLE;
- case WasmType::none: return ASM_NONE;
- default: {}
- }
- abort_on("confused wasmType", type);
- return (AsmType)-1; // avoid warning
- }
-
AsmType detectAsmType(Ref ast, AsmData *data) {
if (ast[0] == NAME) {
IString name = ast[1]->getIString();
@@ -240,31 +277,29 @@ private:
return view->second.type;
}
}
- return detectType(ast, data);
+ return detectType(ast, data, false, Math_fround);
}
WasmType detectWasmType(Ref ast, AsmData *data) {
return asmToWasmType(detectAsmType(ast, data));
}
- bool isUnsignedCoercion(Ref ast) { // TODO: use detectSign?
- if (ast[0] == BINARY && ast[1] == TRSHIFT) return true;
- return false;
+ bool isUnsignedCoercion(Ref ast) {
+ return detectSign(ast, Math_fround) == ASM_UNSIGNED;
}
- // an asm.js binary op can either be a binary or a relational in wasm
- bool parseAsmBinaryOp(IString op, Ref left, Ref right, BinaryOp &binary, RelationalOp &relational, AsmData *asmData) {
- if (op == PLUS) { binary = BinaryOp::Add; return true; }
- if (op == MINUS) { binary = BinaryOp::Sub; return true; }
- if (op == MUL) { binary = BinaryOp::Mul; return true; }
- if (op == AND) { binary = BinaryOp::And; return true; }
- if (op == OR) { binary = BinaryOp::Or; return true; }
- if (op == XOR) { binary = BinaryOp::Xor; return true; }
- if (op == LSHIFT) { binary = BinaryOp::Shl; return true; }
- if (op == RSHIFT) { binary = BinaryOp::ShrS; return true; }
- if (op == TRSHIFT) { binary = BinaryOp::ShrU; return true; }
- if (op == EQ) { relational = RelationalOp::Eq; return false; }
- if (op == NE) { relational = RelationalOp::Ne; return false; }
+ BinaryOp parseAsmBinaryOp(IString op, Ref left, Ref right, AsmData *asmData) {
+ if (op == PLUS) return BinaryOp::Add;
+ if (op == MINUS) return BinaryOp::Sub;
+ if (op == MUL) return BinaryOp::Mul;
+ if (op == AND) return BinaryOp::And;
+ if (op == OR) return BinaryOp::Or;
+ if (op == XOR) return BinaryOp::Xor;
+ if (op == LSHIFT) return BinaryOp::Shl;
+ if (op == RSHIFT) return BinaryOp::ShrS;
+ if (op == TRSHIFT) return BinaryOp::ShrU;
+ if (op == EQ) return BinaryOp::Eq;
+ if (op == NE) return BinaryOp::Ne;
WasmType leftType = detectWasmType(left, asmData);
#if 0
std::cout << "CHECK\n";
@@ -278,42 +313,42 @@ private:
bool isUnsigned = isUnsignedCoercion(left) || isUnsignedCoercion(right);
if (op == DIV) {
if (isInteger) {
- { binary = isUnsigned ? BinaryOp::DivU : BinaryOp::DivS; return true; }
+ return isUnsigned ? BinaryOp::DivU : BinaryOp::DivS;
}
- { binary = BinaryOp::Div; return true; }
+ return BinaryOp::Div;
}
if (op == MOD) {
if (isInteger) {
- { binary = isUnsigned ? BinaryOp::RemU : BinaryOp::RemS; return true; }
+ return isUnsigned ? BinaryOp::RemU : BinaryOp::RemS;
}
- { binary = BinaryOp::RemS; return true; } // XXX no floating-point remainder op, this must be handled by the caller
+ return BinaryOp::RemS; // XXX no floating-point remainder op, this must be handled by the caller
}
if (op == GE) {
if (isInteger) {
- { relational = isUnsigned ? RelationalOp::GeU : RelationalOp::GeS; return false; }
+ return isUnsigned ? BinaryOp::GeU : BinaryOp::GeS;
}
- { relational = RelationalOp::Ge; return false; }
+ return BinaryOp::Ge;
}
if (op == GT) {
if (isInteger) {
- { relational = isUnsigned ? RelationalOp::GtU : RelationalOp::GtS; return false; }
+ return isUnsigned ? BinaryOp::GtU : BinaryOp::GtS;
}
- { relational = RelationalOp::Gt; return false; }
+ return BinaryOp::Gt;
}
if (op == LE) {
if (isInteger) {
- { relational = isUnsigned ? RelationalOp::LeU : RelationalOp::LeS; return false; }
+ return isUnsigned ? BinaryOp::LeU : BinaryOp::LeS;
}
- { relational = RelationalOp::Le; return false; }
+ return BinaryOp::Le;
}
if (op == LT) {
if (isInteger) {
- { relational = isUnsigned ? RelationalOp::LtU : RelationalOp::LtS; return false; }
+ return isUnsigned ? BinaryOp::LtU : BinaryOp::LtS;
}
- { relational = RelationalOp::Lt; return false; }
+ return BinaryOp::Lt;
}
abort_on("bad wasm binary op", op);
- return false; // avoid warning
+ abort(); // avoid warning
}
unsigned bytesToShift(unsigned bytes) {
@@ -330,20 +365,83 @@ private:
std::map<unsigned, Ref> tempNums;
- Literal getLiteral(Ref ast) {
+ Literal checkLiteral(Ref ast) {
if (ast[0] == NUM) {
return Literal((int32_t)ast[1]->getInteger());
} else if (ast[0] == UNARY_PREFIX) {
+ if (ast[1] == PLUS && ast[2][0] == NUM) {
+ return Literal((double)ast[2][1]->getNumber());
+ }
if (ast[1] == MINUS && ast[2][0] == NUM) {
double num = -ast[2][1]->getNumber();
assert(isInteger32(num));
return Literal((int32_t)num);
}
+ if (ast[1] == PLUS && ast[2][0] == UNARY_PREFIX && ast[2][1] == MINUS && ast[2][2][0] == NUM) {
+ return Literal((double)-ast[2][2][1]->getNumber());
+ }
if (ast[1] == MINUS && ast[2][0] == UNARY_PREFIX && ast[2][1] == PLUS && ast[2][2][0] == NUM) {
return Literal((double)-ast[2][2][1]->getNumber());
}
}
- abort();
+ return Literal();
+ }
+
+ Literal getLiteral(Ref ast) {
+ Literal ret = checkLiteral(ast);
+ if (ret.type == none) abort();
+ return ret;
+ }
+
+ void fixCallType(Expression* call, WasmType type) {
+ if (call->is<Call>()) call->type = type;
+ if (call->is<CallImport>()) call->type = type;
+ else if (call->is<CallIndirect>()) call->type = type;
+ }
+
+ FunctionType* getBuiltinFunctionType(Name module, Name base, ExpressionList* operands = nullptr) {
+ if (module == GLOBAL_MATH) {
+ if (base == ABS) {
+ assert(operands && operands->size() == 1);
+ WasmType type = (*operands)[0]->type;
+ if (type == i32) {
+ static FunctionType* builtin = nullptr;
+ if (!builtin) {
+ builtin = new FunctionType();
+ builtin->params.push_back(i32);
+ builtin->result = i32;
+ }
+ return builtin;
+ }
+ if (type == f32) {
+ static FunctionType* builtin = nullptr;
+ if (!builtin) {
+ builtin = new FunctionType();
+ builtin->params.push_back(f32);
+ builtin->result = f32;
+ }
+ return builtin;
+ }
+ if (type == f64) {
+ static FunctionType* builtin = nullptr;
+ if (!builtin) {
+ builtin = new FunctionType();
+ builtin->params.push_back(f64);
+ builtin->result = f64;
+ }
+ return builtin;
+ }
+
+ }
+ }
+ return nullptr;
+ }
+
+ Block* blockify(Expression* expression) {
+ if (expression->is<Block>()) return expression->dyn_cast<Block>();
+ auto ret = allocator.alloc<Block>();
+ ret->list.push_back(expression);
+ return ret;
}
Function* processFunction(Ref ast);
@@ -376,6 +474,18 @@ void Asm2WasmBuilder::processAsm(Ref ast) {
assert(Math_fround.isNull());
Math_fround = name;
return;
+ } else if (imported[2] == ABS) {
+ assert(Math_abs.isNull());
+ Math_abs = name;
+ return;
+ } else if (imported[2] == FLOOR) {
+ assert(Math_floor.isNull());
+ Math_floor = name;
+ return;
+ } else if (imported[2] == SQRT) {
+ assert(Math_sqrt.isNull());
+ Math_sqrt = name;
+ return;
}
}
std::string fullName = module[1][1]->getCString();
@@ -402,7 +512,9 @@ void Asm2WasmBuilder::processAsm(Ref ast) {
}
};
- // first pass - do almost everything, but function imports
+ IString Int8Array, Int16Array, Int32Array, UInt8Array, UInt16Array, UInt32Array, Float32Array, Float64Array;
+
+ // first pass - do almost everything, but function imports and indirect calls
for (unsigned i = 1; i < body->size(); i++) {
Ref curr = body[i];
@@ -437,56 +549,96 @@ void Asm2WasmBuilder::processAsm(Ref ast) {
assert(value[1][0] == NAME && value[1][1] == Math_fround && value[2][0][0] == NUM && value[2][0][1]->getNumber() == 0);
allocateGlobal(name, WasmType::f32, false);
} else if (value[0] == DOT) {
+ // simple module.base import. can be a view, or a function.
+ if (value[1][0] == NAME) {
+ IString module = value[1][1]->getIString();
+ IString base = value[2]->getIString();
+ if (module == GLOBAL) {
+ if (base == INT8ARRAY) {
+ Int8Array = name;
+ } else if (base == INT16ARRAY) {
+ Int16Array = name;
+ } else if (base == INT32ARRAY) {
+ Int32Array = name;
+ } else if (base == UINT8ARRAY) {
+ UInt8Array = name;
+ } else if (base == UINT16ARRAY) {
+ UInt16Array = name;
+ } else if (base == UINT32ARRAY) {
+ UInt32Array = name;
+ } else if (base == FLOAT32ARRAY) {
+ Float32Array = name;
+ } else if (base == FLOAT64ARRAY) {
+ Float64Array = name;
+ }
+ }
+ }
// function import
addImport(name, value, WasmType::none);
} else if (value[0] == NEW) {
// ignore imports of typed arrays, but note the names of the arrays
value = value[1];
assert(value[0] == CALL);
- Ref constructor = value[1];
- assert(constructor[0] == DOT); // global.*Array
- IString heap = constructor[2]->getIString();
unsigned bytes;
bool integer, signed_;
AsmType asmType;
- if (heap == INT8ARRAY) {
- bytes = 1; integer = true; signed_ = true; asmType = ASM_INT;
- } else if (heap == INT16ARRAY) {
- bytes = 2; integer = true; signed_ = true; asmType = ASM_INT;
- } else if (heap == INT32ARRAY) {
- bytes = 4; integer = true; signed_ = true; asmType = ASM_INT;
- } else if (heap == UINT8ARRAY) {
- bytes = 1; integer = true; signed_ = false; asmType = ASM_INT;
- } else if (heap == UINT16ARRAY) {
- bytes = 2; integer = true; signed_ = false; asmType = ASM_INT;
- } else if (heap == UINT32ARRAY) {
- bytes = 4; integer = true; signed_ = false; asmType = ASM_INT;
- } else if (heap == FLOAT32ARRAY) {
- bytes = 4; integer = false; signed_ = true; asmType = ASM_DOUBLE;
- } else if (heap == FLOAT64ARRAY) {
- bytes = 8; integer = false; signed_ = true; asmType = ASM_DOUBLE;
+ Ref constructor = value[1];
+ if (constructor[0] == DOT) { // global.*Array
+ IString heap = constructor[2]->getIString();
+ if (heap == INT8ARRAY) {
+ bytes = 1; integer = true; signed_ = true; asmType = ASM_INT;
+ } else if (heap == INT16ARRAY) {
+ bytes = 2; integer = true; signed_ = true; asmType = ASM_INT;
+ } else if (heap == INT32ARRAY) {
+ bytes = 4; integer = true; signed_ = true; asmType = ASM_INT;
+ } else if (heap == UINT8ARRAY) {
+ bytes = 1; integer = true; signed_ = false; asmType = ASM_INT;
+ } else if (heap == UINT16ARRAY) {
+ bytes = 2; integer = true; signed_ = false; asmType = ASM_INT;
+ } else if (heap == UINT32ARRAY) {
+ bytes = 4; integer = true; signed_ = false; asmType = ASM_INT;
+ } else if (heap == FLOAT32ARRAY) {
+ bytes = 4; integer = false; signed_ = true; asmType = ASM_FLOAT;
+ } else if (heap == FLOAT64ARRAY) {
+ bytes = 8; integer = false; signed_ = true; asmType = ASM_DOUBLE;
+ } else {
+ abort_on("invalid view import", heap);
+ }
+ } else { // *ArrayView that was previously imported
+ assert(constructor[0] == NAME);
+ IString viewName = constructor[1]->getIString();
+ if (viewName == Int8Array) {
+ bytes = 1; integer = true; signed_ = true; asmType = ASM_INT;
+ } else if (viewName == Int16Array) {
+ bytes = 2; integer = true; signed_ = true; asmType = ASM_INT;
+ } else if (viewName == Int32Array) {
+ bytes = 4; integer = true; signed_ = true; asmType = ASM_INT;
+ } else if (viewName == UInt8Array) {
+ bytes = 1; integer = true; signed_ = false; asmType = ASM_INT;
+ } else if (viewName == UInt16Array) {
+ bytes = 2; integer = true; signed_ = false; asmType = ASM_INT;
+ } else if (viewName == UInt32Array) {
+ bytes = 4; integer = true; signed_ = false; asmType = ASM_INT;
+ } else if (viewName == Float32Array) {
+ bytes = 4; integer = false; signed_ = true; asmType = ASM_FLOAT;
+ } else if (viewName == Float64Array) {
+ bytes = 8; integer = false; signed_ = true; asmType = ASM_DOUBLE;
+ } else {
+ abort_on("invalid short view import", viewName);
+ }
}
assert(views.find(name) == views.end());
views.emplace(name, View(bytes, integer, signed_, asmType));
} else if (value[0] == ARRAY) {
- // function table. we "merge" them, so e.g. [foo, b1] , [b2, bar] => [foo, bar] , assuming b* are the aborting thunks
- // when minified, we can't tell from the name b\d+, but null thunks appear multiple times in a table; others never do
- // TODO: we can drop some b*s at the end of the table
+ // function table. we merge them into one big table, so e.g. [foo, b1] , [b2, bar] => [foo, b1, b2, bar]
+ // TODO: when not using aliasing function pointers, we could merge them by noticing that
+ // index 0 in each table is the null func, and each other index should only have one
+ // non-null func. However, that breaks down when function pointer casts are emulated.
+ functionTableStarts[name] = wasm.table.names.size(); // this table starts here
Ref contents = value[1];
- std::map<IString, unsigned> counts; // name -> how many times seen
for (unsigned k = 0; k < contents->size(); k++) {
IString curr = contents[k][1]->getIString();
- counts[curr]++;
- }
- for (unsigned k = 0; k < contents->size(); k++) {
- IString curr = contents[k][1]->getIString();
- if (wasm.table.names.size() <= k) {
- wasm.table.names.push_back(curr);
- } else {
- if (counts[curr] == 1) { // if just one appearance, not a null thunk
- wasm.table.names[k] = curr;
- }
- }
+ wasm.table.names.push_back(curr);
}
} else {
abort_on("invalid var element", pair);
@@ -512,7 +664,7 @@ void Asm2WasmBuilder::processAsm(Ref ast) {
}
}
- // second pass - function imports
+ // second pass. first, function imports
std::vector<IString> toErase;
@@ -520,6 +672,12 @@ void Asm2WasmBuilder::processAsm(Ref ast) {
IString name = pair.first;
Import& import = *pair.second;
if (importedFunctionTypes.find(name) != importedFunctionTypes.end()) {
+ // special math builtins
+ FunctionType* builtin = getBuiltinFunctionType(import.module, import.base);
+ if (builtin) {
+ import.type = *builtin;
+ continue;
+ }
import.type = importedFunctionTypes[name];
} else if (import.module != ASM2WASM) { // special-case the special module
// never actually used
@@ -530,6 +688,40 @@ void Asm2WasmBuilder::processAsm(Ref ast) {
for (auto curr : toErase) {
wasm.removeImport(curr);
}
+
+ // finalize indirect calls
+
+ for (auto& pair : callIndirects) {
+ CallIndirect* call = pair.first;
+ IString tableName = pair.second;
+ assert(functionTableStarts.find(tableName) != functionTableStarts.end());
+ auto sub = allocator.alloc<Binary>();
+ // note that the target is already masked, so we just offset it, we don't need to guard against overflow (which would be an error anyhow)
+ sub->op = Add;
+ sub->left = call->target;
+ sub->right = allocator.alloc<Const>()->set(Literal((int32_t)functionTableStarts[tableName]));
+ sub->type = WasmType::i32;
+ call->target = sub;
+ }
+
+ // apply memory growth, if relevant
+ if (memoryGrowth) {
+ // create and export a function that just calls memory growth
+ auto growWasmMemory = allocator.alloc<Function>();
+ growWasmMemory->name = GROW_WASM_MEMORY;
+ growWasmMemory->params.emplace_back(NEW_SIZE, i32); // the new size
+ auto get = allocator.alloc<GetLocal>();
+ get->name = NEW_SIZE;
+ auto grow = allocator.alloc<Host>();
+ grow->op = GrowMemory;
+ grow->operands.push_back(get);
+ growWasmMemory->body = grow;
+ wasm.addFunction(growWasmMemory);
+ auto export_ = allocator.alloc<Export>();
+ export_->name = export_->value = GROW_WASM_MEMORY;
+ wasm.addExport(export_);
+ }
+
}
Function* Asm2WasmBuilder::processFunction(Ref ast) {
@@ -563,7 +755,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
IStringSet functionVariables; // params or locals
- IString parentLabel; // set in LABEL, then read in WHILE/DO
+ IString parentLabel; // set in LABEL, then read in WHILE/DO/SWITCH
std::vector<IString> breakStack; // where a break will go
std::vector<IString> continueStack; // where a continue will go
@@ -575,7 +767,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]);
+ AsmType asmType = detectType(curr[3], nullptr, false, Math_fround);
function->params.emplace_back(name, asmToWasmType(asmType));
functionVariables.insert(name);
asmData.addParam(name, asmType);
@@ -586,7 +778,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);
+ AsmType asmType = detectType(pair[1], nullptr, true, Math_fround);
function->locals.emplace_back(name, asmToWasmType(asmType));
functionVariables.insert(name);
asmData.addVar(name, asmType);
@@ -594,6 +786,15 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
start++;
}
+ bool addedI32Temp = false;
+ auto ensureI32Temp = [&]() {
+ if (addedI32Temp) return;
+ addedI32Temp = true;
+ function->locals.emplace_back(I32_TEMP, i32);
+ functionVariables.insert(I32_TEMP);
+ asmData.addVar(I32_TEMP, ASM_INT);
+ };
+
bool seenReturn = false; // function->result is updated if we see a return
bool needTopmost = false; // we label the topmost b lock if we need one for a return
// processors
@@ -646,63 +847,63 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
ret->align = view.bytes;
ret->ptr = processUnshifted(target[2], view.bytes);
ret->value = process(ast[3]);
- ret->type = ret->value->type;
+ ret->type = asmToWasmType(view.type);
+ if (ret->type != ret->value->type) {
+ // in asm.js we have some implicit coercions that we must do explicitly here
+ if (ret->type == f32 && ret->value->type == f64) {
+ auto conv = allocator.alloc<Unary>();
+ conv->op = DemoteFloat64;
+ conv->value = ret->value;
+ conv->type = WasmType::f32;
+ ret->value = conv;
+ } else {
+ abort();
+ }
+ }
return ret;
}
abort_on("confusing assign", ast);
} else if (what == BINARY) {
- if (ast[1] == OR && ast[3][0] == NUM && ast[3][1]->getNumber() == 0) {
- auto ret = process(ast[2]); // just look through the ()|0 coercion
- ret->type = WasmType::i32; // we add it here for e.g. call coercions
+ if ((ast[1] == OR || ast[1] == TRSHIFT) && ast[3][0] == NUM && ast[3][1]->getNumber() == 0) {
+ auto ret = process(ast[2]); // just look through the ()|0 or ()>>>0 coercion
+ fixCallType(ret, i32);
return ret;
}
- BinaryOp binary;
- RelationalOp relational;
- bool isBinary = parseAsmBinaryOp(ast[1]->getIString(), ast[2], ast[3], binary, relational, &asmData);
- if (isBinary) {
- auto ret = allocator.alloc<Binary>();
- ret->op = binary;
- ret->left = process(ast[2]);
- ret->right = process(ast[3]);
- ret->type = ret->left->type;
- if (binary == BinaryOp::RemS && isWasmTypeFloat(ret->type)) {
- // WebAssembly does not have floating-point remainder, we have to emit a call to a special import of ours
- CallImport *call = allocator.alloc<CallImport>();
- call->target = F64_REM;
- call->operands.push_back(ret->left);
- call->operands.push_back(ret->right);
- call->type = f64;
- static bool addedImport = false;
- if (!addedImport) {
- addedImport = true;
- auto import = allocator.alloc<Import>(); // f64-rem = asm2wasm.f64-rem;
- import->name = F64_REM;
- import->module = ASM2WASM;
- import->base = F64_REM;
- import->type.name = F64_REM;
- import->type.result = f64;
- import->type.params.push_back(f64);
- import->type.params.push_back(f64);
- wasm.addImport(import);
- }
- return call;
+ BinaryOp binary = parseAsmBinaryOp(ast[1]->getIString(), ast[2], ast[3], &asmData);
+ auto ret = allocator.alloc<Binary>();
+ ret->op = binary;
+ ret->left = process(ast[2]);
+ ret->right = process(ast[3]);
+ ret->finalize();
+ if (binary == BinaryOp::RemS && isWasmTypeFloat(ret->type)) {
+ // WebAssembly does not have floating-point remainder, we have to emit a call to a special import of ours
+ CallImport *call = allocator.alloc<CallImport>();
+ call->target = F64_REM;
+ call->operands.push_back(ret->left);
+ call->operands.push_back(ret->right);
+ call->type = f64;
+ static bool addedImport = false;
+ if (!addedImport) {
+ addedImport = true;
+ auto import = allocator.alloc<Import>(); // f64-rem = asm2wasm.f64-rem;
+ import->name = F64_REM;
+ import->module = ASM2WASM;
+ import->base = F64_REM;
+ import->type.name = F64_REM;
+ import->type.result = f64;
+ import->type.params.push_back(f64);
+ import->type.params.push_back(f64);
+ wasm.addImport(import);
}
- return ret;
- } else {
- auto ret = allocator.alloc<Compare>();
- ret->op = relational;
- ret->left = process(ast[2]);
- ret->right = process(ast[3]);
- assert(ret->left->type == ret->right->type);
- ret->inputType = ret->left->type;
- return ret;
+ return call;
}
+ return ret;
} else if (what == NUM) {
auto ret = allocator.alloc<Const>();
double num = ast[1]->getNumber();
if (isInteger32(num)) {
ret->value.type = WasmType::i32;
- ret->value.i32 = num;
+ ret->value.i32 = toInteger32(num);
} else {
ret->value.type = WasmType::f64;
ret->value.f64 = num;
@@ -718,6 +919,23 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
ret->type = asmToWasmType(asmData.getType(name));
return ret;
}
+ if (name == DEBUGGER) {
+ CallImport *call = allocator.alloc<CallImport>();
+ call->target = DEBUGGER;
+ call->type = none;
+ static bool addedImport = false;
+ if (!addedImport) {
+ addedImport = true;
+ auto import = allocator.alloc<Import>(); // debugger = asm2wasm.debugger;
+ import->name = DEBUGGER;
+ import->module = ASM2WASM;
+ import->base = DEBUGGER;
+ import->type.name = DEBUGGER;
+ import->type.result = none;
+ wasm.addImport(import);
+ }
+ return call;
+ }
// global var, do a load from memory
assert(mappedGlobals.find(name) != mappedGlobals.end());
MappedGlobal global = mappedGlobals[name];
@@ -748,24 +966,26 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
return ret;
} else if (what == UNARY_PREFIX) {
if (ast[1] == PLUS) {
- if (ast[2][0] == NUM) {
- auto ret = allocator.alloc<Const>();
- ret->value.type = WasmType::f64;
- ret->value.f64 = ast[2][1]->getNumber();
- ret->type = ret->value.type;
- return ret;
+ Literal literal = checkLiteral(ast);
+ if (literal.type != none) {
+ return allocator.alloc<Const>()->set(literal);
}
- AsmType childType = detectAsmType(ast[2], &asmData);
- if (childType == ASM_INT) {
- auto ret = allocator.alloc<Convert>();
- ret->op = isUnsignedCoercion(ast[2]) ? ConvertUInt32 : ConvertSInt32;
- ret->value = process(ast[2]);
- ret->type = WasmType::f64;
- return ret;
+ auto ret = process(ast[2]); // we are a +() coercion
+ if (ret->type == i32) {
+ auto conv = allocator.alloc<Unary>();
+ conv->op = isUnsignedCoercion(ast[2]) ? ConvertUInt32 : ConvertSInt32;
+ conv->value = ret;
+ conv->type = WasmType::f64;
+ return conv;
+ }
+ if (ret->type == f32) {
+ auto conv = allocator.alloc<Unary>();
+ conv->op = PromoteFloat32;
+ conv->value = ret;
+ conv->type = WasmType::f64;
+ return conv;
}
- assert(childType == ASM_NONE || childType == ASM_DOUBLE); // e.g. a coercion on a call or for a return
- auto ret = process(ast[2]); // just look through the +() coercion
- ret->type = WasmType::f64; // we add it here for e.g. call coercions
+ fixCallType(ret, f64);
return ret;
} else if (ast[1] == MINUS) {
if (ast[2][0] == NUM || (ast[2][0] == UNARY_PREFIX && ast[2][1] == PLUS && ast[2][2][0] == NUM)) {
@@ -784,20 +1004,45 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
ret->type = WasmType::i32;
return ret;
}
- assert(asmType == ASM_DOUBLE);
auto ret = allocator.alloc<Unary>();
ret->op = Neg;
ret->value = process(ast[2]);
- ret->type = WasmType::f64;
+ if (asmType == ASM_DOUBLE) {
+ ret->type = WasmType::f64;
+ } else if (asmType == ASM_FLOAT) {
+ ret->type = WasmType::f32;
+ } else {
+ abort();
+ }
return ret;
} else if (ast[1] == B_NOT) {
// ~, might be ~~ as a coercion or just a not
if (ast[2][0] == UNARY_PREFIX && ast[2][1] == B_NOT) {
- auto ret = allocator.alloc<Convert>();
+#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.name = F64_TO_INT;
+ import->type.result = i32;
+ import->type.params.push_back(f64);
+ wasm.addImport(import);
+ }
+ return ret;
}
// no bitwise unary not, so do xor with -1
auto ret = allocator.alloc<Binary>();
@@ -808,12 +1053,12 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
return ret;
} else if (ast[1] == L_NOT) {
// no logical unary not, so do == 0
- auto ret = allocator.alloc<Compare>();
+ 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->inputType = ret->left->type;
+ ret->finalize();
return ret;
}
abort_on("bad unary", ast);
@@ -843,20 +1088,96 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
ret->type = WasmType::i32;
return ret;
}
+ if (name == Math_fround) {
+ assert(ast[2]->size() == 1);
+ Literal lit = checkLiteral(ast[2][0]);
+ if (lit.type == i32) {
+ return allocator.alloc<Const>()->set(Literal((float)lit.geti32()));
+ } else if (lit.type == f64) {
+ return allocator.alloc<Const>()->set(Literal((float)lit.getf64()));
+ }
+ auto ret = allocator.alloc<Unary>();
+ ret->value = process(ast[2][0]);
+ if (ret->value->type == f64) {
+ ret->op = DemoteFloat64;
+ } else if (ret->value->type == i32) {
+ ret->op = ConvertSInt32;
+ } else if (ret->value->type == f32) {
+ return ret->value;
+ } else if (ret->value->type == none) { // call, etc.
+ ret->value->type = f32;
+ return ret->value;
+ } else {
+ abort_on("confusing fround target", ast[2][0]);
+ }
+ ret->type = f32;
+ return ret;
+ }
+ if (name == Math_abs) {
+ // overloaded on type: i32, f32 or f64
+ Expression* value = process(ast[2][0]);
+ if (value->type == i32) {
+ // No wasm support, so use a temp local
+ ensureI32Temp();
+ auto set = allocator.alloc<SetLocal>();
+ set->name = I32_TEMP;
+ set->value = value;
+ set->type = i32;
+ auto get = [&]() {
+ auto ret = allocator.alloc<GetLocal>();
+ ret->name = I32_TEMP;
+ ret->type = i32;
+ return ret;
+ };
+ auto isNegative = allocator.alloc<Binary>();
+ isNegative->op = LtS;
+ isNegative->left = get();
+ isNegative->right = allocator.alloc<Const>()->set(0);
+ isNegative->finalize();
+ auto block = allocator.alloc<Block>();
+ block->list.push_back(set);
+ auto flip = allocator.alloc<Binary>();
+ flip->op = Sub;
+ flip->left = allocator.alloc<Const>()->set(0);
+ flip->right = get();
+ flip->type = i32;
+ auto select = allocator.alloc<Select>();
+ select->condition = isNegative;
+ select->ifTrue = flip;
+ select->ifFalse = get();
+ select->type = i32;
+ block->list.push_back(select);
+ block->type = i32;
+ return block;
+ } else if (value->type == f32 || value->type == f64) {
+ auto ret = allocator.alloc<Unary>();
+ ret->op = Abs;
+ ret->value = value;
+ ret->type = value->type;
+ return ret;
+ } else {
+ abort();
+ }
+ }
+ if (name == Math_floor || name == Math_sqrt) {
+ // overloaded on type: f32 or f64
+ Expression* value = process(ast[2][0]);
+ if (value->type == f32 || value->type == f64) {
+ auto ret = allocator.alloc<Unary>();
+ ret->op = name == Math_floor ? Floor : Sqrt;
+ ret->value = value;
+ ret->type = value->type;
+ return ret;
+ } else {
+ abort();
+ }
+ }
Call* ret;
if (wasm.importsMap.find(name) != wasm.importsMap.end()) {
Ref parent = astStackHelper.getParent();
WasmType type = !!parent ? detectWasmType(parent, &asmData) : none;
-#ifndef __EMSCRIPTEN__
- // no imports yet in reference interpreter, fake it
- if (type == none) return allocator.alloc<Nop>();
- if (type == i32) return allocator.alloc<Const>()->set(Literal((int32_t)0));
- if (type == f64) return allocator.alloc<Const>()->set(Literal((double)0.0));
- abort();
-#else
ret = allocator.alloc<CallImport>();
noteImportedFunctionCall(ast, type, &asmData);
-#endif
} else {
ret = allocator.alloc<Call>();
}
@@ -871,12 +1192,14 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
auto ret = allocator.alloc<CallIndirect>();
Ref target = ast[1];
assert(target[0] == SUB && target[1][0] == NAME && target[2][0] == BINARY && target[2][1] == AND && target[2][3][0] == NUM); // FUNCTION_TABLE[(expr) & mask]
- ret->target = process(target[2][2]);
+ ret->target = process(target[2]); // TODO: as an optimization, we could look through the mask
Ref args = ast[2];
for (unsigned i = 0; i < args->size(); i++) {
ret->operands.push_back(process(args[i]));
}
- ret->type = getFunctionType(astStackHelper.getParent(), ret->operands);
+ ret->fullType = getFunctionType(astStackHelper.getParent(), ret->operands);
+ ret->type = ret->fullType->result;
+ callIndirects[ret] = target[1][1]->getIString(); // we need to fix this up later, when we know how asm function tables are layed out inside the wasm table.
return ret;
} else if (what == RETURN) {
WasmType type = !!ast[1] ? detectWasmType(ast[1], &asmData) : none;
@@ -892,7 +1215,26 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
ret->value = !!ast[1] ? process(ast[1]) : nullptr;
return ret;
} else if (what == BLOCK) {
- return processStatements(ast[1], 0);
+ Name name;
+ if (parentLabel.is()) {
+ name = getBreakLabelName(parentLabel);
+ parentLabel = IString();
+ breakStack.push_back(name);
+ }
+ auto ret = processStatements(ast[1], 0);
+ if (name.is()) {
+ breakStack.pop_back();
+ Block* block = ret->dyn_cast<Block>();
+ if (block && block->name.isNull()) {
+ block->name = name;
+ } else {
+ block = allocator.alloc<Block>();
+ block->name = name;
+ block->list.push_back(ret);
+ ret = block;
+ }
+ }
+ return ret;
} else if (what == BREAK) {
auto ret = allocator.alloc<Break>();
assert(breakStack.size() > 0);
@@ -933,6 +1275,12 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
body->list.push_back(process(ast[2]));
ret->body = body;
}
+ // loops do not automatically loop, add a branch back
+ Block* block = blockify(ret->body);
+ auto continuer = allocator.alloc<Break>();
+ continuer->name = ret->in;
+ block->list.push_back(continuer);
+ ret->body = block;
continueStack.pop_back();
breakStack.pop_back();
return ret;
@@ -973,22 +1321,15 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
ret->body = process(ast[2]);
continueStack.pop_back();
breakStack.pop_back();
- Break *breakOut = allocator.alloc<Break>();
- breakOut->name = out;
- If *condition = allocator.alloc<If>();
- condition->condition = process(ast[1]);
- condition->ifTrue = allocator.alloc<Nop>();
- condition->ifFalse = breakOut;
- if (Block *block = ret->body->dyn_cast<Block>()) {
- block->list.push_back(condition);
- } else {
- auto newBody = allocator.alloc<Block>();
- newBody->list.push_back(ret->body);
- newBody->list.push_back(condition);
- ret->body = newBody;
- }
+ Break *continuer = allocator.alloc<Break>();
+ continuer->name = in;
+ continuer->condition = process(ast[1]);
+ Block *block = blockify(ret->body);
+ block->list.push_back(continuer);
+ ret->body = block;
return ret;
} else if (what == LABEL) {
+ assert(parentLabel.isNull());
parentLabel = ast[1]->getIString();
return process(ast[2]);
} else if (what == CONDITIONAL) {
@@ -1005,8 +1346,13 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
ret->type = ret->list[1]->type;
return ret;
} else if (what == SWITCH) {
- // XXX switch is still in flux in the spec repo, just emit a placeholder
- IString name = getNextId("switch");
+ IString name;
+ if (!parentLabel.isNull()) {
+ name = getBreakLabelName(parentLabel);
+ parentLabel = IString();
+ } else {
+ name = getNextId("switch");
+ }
breakStack.push_back(name);
auto ret = allocator.alloc<Switch>();
ret->name = name;
@@ -1118,6 +1464,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
}
// cleanups/checks
assert(breakStack.size() == 0 && continueStack.size() == 0);
+ assert(parentLabel.isNull());
return function;
}