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