summaryrefslogtreecommitdiff
path: root/src/wasm2asm.h
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2018-05-16 18:40:45 -0500
committerAlon Zakai <alonzakai@gmail.com>2018-05-16 16:40:45 -0700
commit3c5a2de669542caebd7ef001db6678b70375f0b5 (patch)
treebbe25d21142627cc04041d7ecbea9d4d15f3a680 /src/wasm2asm.h
parentd4aa0d30234ac9e553d743bd881a767d96554a4a (diff)
downloadbinaryen-3c5a2de669542caebd7ef001db6678b70375f0b5.tar.gz
binaryen-3c5a2de669542caebd7ef001db6678b70375f0b5.tar.bz2
binaryen-3c5a2de669542caebd7ef001db6678b70375f0b5.zip
wasm2asm: Implement float<->int conversions (#1550)
This commit lifts the same conversion strategy that `emcc` takes to convert between floats point numbers and integers, and it should implement all the various matrices of i32/u32/i64/u64 to f32/f64 Some refactoring was performed in the i64->i32 pass to allow for temporary variables to get allocated which have types other than i32, but otherwise this contains a pretty direct translation of `emcc`'s operations to `wasm2asm`.
Diffstat (limited to 'src/wasm2asm.h')
-rw-r--r--src/wasm2asm.h252
1 files changed, 234 insertions, 18 deletions
diff --git a/src/wasm2asm.h b/src/wasm2asm.h
index 9e3abaa25..8dd569d82 100644
--- a/src/wasm2asm.h
+++ b/src/wasm2asm.h
@@ -114,6 +114,7 @@ public:
struct Flags {
bool debug = false;
bool pedantic = false;
+ bool allowAsserts = false;
};
Wasm2AsmBuilder(Flags f) : flags(f) {}
@@ -213,6 +214,9 @@ private:
Ref makeAssertReturnFunc(SExpressionWasmBuilder& sexpBuilder,
Builder& wasmBuilder,
Element& e, Name testFuncName);
+ Ref makeAssertReturnNanFunc(SExpressionWasmBuilder& sexpBuilder,
+ Builder& wasmBuilder,
+ Element& e, Name testFuncName);
Ref makeAssertTrapFunc(SExpressionWasmBuilder& sexpBuilder,
Builder& wasmBuilder,
Element& e, Name testFuncName);
@@ -416,13 +420,35 @@ Ref Wasm2AsmBuilder::processWasm(Module* wasm) {
}
tableSize = pow2ed;
// globals
+ bool generateFetchHighBits = false;
for (auto& global : wasm->globals) {
addGlobal(asmFunc[3], global.get());
+ if (flags.allowAsserts && global->name == INT64_TO_32_HIGH_BITS) {
+ generateFetchHighBits = true;
+ }
}
// functions
for (auto& func : wasm->functions) {
asmFunc[3]->push_back(processFunction(func.get()));
}
+ if (generateFetchHighBits) {
+ Builder builder(allocator);
+ std::vector<Type> params;
+ std::vector<Type> vars;
+ asmFunc[3]->push_back(processFunction(builder.makeFunction(
+ WASM_FETCH_HIGH_BITS,
+ std::move(params),
+ i32,
+ std::move(vars),
+ builder.makeGetGlobal(INT64_TO_32_HIGH_BITS, i32)
+ )));
+ auto e = new Export();
+ e->name = WASM_FETCH_HIGH_BITS;
+ e->value = WASM_FETCH_HIGH_BITS;
+ e->kind = ExternalKind::Function;
+ wasm->addExport(e);
+ }
+
addTables(asmFunc[3], wasm);
// memory XXX
addExports(asmFunc[3], wasm);
@@ -1320,6 +1346,18 @@ Ref Wasm2AsmBuilder::processFunctionBody(Function* func, IString result) {
Ref visitConst(Const* curr) {
switch (curr->type) {
case i32: return ValueBuilder::makeInt(curr->value.geti32());
+ // An i64 argument translates to two actual arguments to asm.js
+ // functions, so we do a bit of a hack here to get our one `Ref` to look
+ // like two function arguments.
+ case i64: {
+ unsigned lo = (unsigned) curr->value.geti64();
+ unsigned hi = (unsigned) (curr->value.geti64() >> 32);
+ std::ostringstream out;
+ out << lo << "," << hi;
+ std::string os = out.str();
+ IString name(os.c_str(), false);
+ return ValueBuilder::makeName(name);
+ }
case f32: {
Ref ret = ValueBuilder::makeCall(MATH_FROUND);
Const fake(allocator);
@@ -1393,6 +1431,31 @@ Ref Wasm2AsmBuilder::processFunctionBody(Function* func, IString result) {
ValueBuilder::makeSub(ValueBuilder::makeName(HEAP32), zero)
);
}
+ // generate (~~expr), what Emscripten does
+ case TruncSFloat32ToInt32:
+ case TruncSFloat64ToInt32:
+ return ValueBuilder::makeUnary(
+ B_NOT,
+ ValueBuilder::makeUnary(
+ B_NOT,
+ visit(curr->value, EXPRESSION_RESULT)
+ ));
+
+ // generate (~~expr >>> 0), what Emscripten does
+ case TruncUFloat32ToInt32:
+ case TruncUFloat64ToInt32:
+ return ValueBuilder::makeBinary(
+ ValueBuilder::makeUnary(
+ B_NOT,
+ ValueBuilder::makeUnary(
+ B_NOT,
+ visit(curr->value, EXPRESSION_RESULT)
+ )
+ ),
+ TRSHIFT,
+ ValueBuilder::makeNum(0)
+ );
+
default: {
std::cerr << "Unhandled unary i32 operator: " << curr
<< std::endl;
@@ -1470,6 +1533,37 @@ Ref Wasm2AsmBuilder::processFunctionBody(Function* func, IString result) {
ValueBuilder::makeSub(ValueBuilder::makeName(HEAPF32), zero)
);
}
+ // Coerce the integer to a float as emscripten does
+ case ConvertSInt32ToFloat32:
+ return makeAsmCoercion(
+ makeAsmCoercion(visit(curr->value, EXPRESSION_RESULT), ASM_INT),
+ ASM_FLOAT
+ );
+ case ConvertSInt32ToFloat64:
+ return makeAsmCoercion(
+ makeAsmCoercion(visit(curr->value, EXPRESSION_RESULT), ASM_INT),
+ ASM_DOUBLE
+ );
+
+ // Generate (expr >>> 0), followed by a coercion
+ case ConvertUInt32ToFloat32:
+ return makeAsmCoercion(
+ ValueBuilder::makeBinary(
+ visit(curr->value, EXPRESSION_RESULT),
+ TRSHIFT,
+ ValueBuilder::makeInt(0)
+ ),
+ ASM_FLOAT
+ );
+ case ConvertUInt32ToFloat64:
+ return makeAsmCoercion(
+ ValueBuilder::makeBinary(
+ visit(curr->value, EXPRESSION_RESULT),
+ TRSHIFT,
+ ValueBuilder::makeInt(0)
+ ),
+ ASM_DOUBLE
+ );
// TODO: more complex unary conversions
default:
std::cerr << "Unhandled unary float operator: " << curr
@@ -1756,7 +1850,36 @@ Ref Wasm2AsmBuilder::processFunctionBody(Function* func, IString result) {
return ExpressionProcessor(this, func).visit(func->body, result);
}
-static Ref makeInstantiation() {
+static void makeInstantiation(Ref ret) {
+ // var __array_buffer = new ArrayBuffer(..)
+ Ref mem = ValueBuilder::makeNew(
+ ValueBuilder::makeCall(ARRAY_BUFFER, ValueBuilder::makeInt(0x10000)));
+ Ref arrayBuffer = ValueBuilder::makeVar();
+ Name buffer("__array_buffer");
+ ValueBuilder::appendToVar(arrayBuffer, buffer, mem);
+ flattenAppend(ret, arrayBuffer);
+
+ // var HEAP32 = new Int32Array(__array_buffer);
+ Ref heap32Array = ValueBuilder::makeNew(
+ ValueBuilder::makeCall(INT32ARRAY, ValueBuilder::makeName(buffer)));
+ Ref heap32 = ValueBuilder::makeVar();
+ ValueBuilder::appendToVar(heap32, HEAP32, heap32Array);
+ flattenAppend(ret, heap32);
+
+ // var HEAPF32 = new Float32Array(__array_buffer);
+ Ref heapf32Array = ValueBuilder::makeNew(
+ ValueBuilder::makeCall(FLOAT32ARRAY, ValueBuilder::makeName(buffer)));
+ Ref heapf32 = ValueBuilder::makeVar();
+ ValueBuilder::appendToVar(heapf32, HEAPF32, heapf32Array);
+ flattenAppend(ret, heapf32);
+
+ // var HEAPF64 = new Float64Array(__array_buffer);
+ Ref heapf64Array = ValueBuilder::makeNew(
+ ValueBuilder::makeCall(FLOAT64ARRAY, ValueBuilder::makeName(buffer)));
+ Ref heapf64 = ValueBuilder::makeVar();
+ ValueBuilder::appendToVar(heapf64, HEAPF64, heapf64Array);
+ flattenAppend(ret, heapf64);
+
Ref lib = ValueBuilder::makeObject();
auto insertItem = [&](IString item) {
ValueBuilder::appendToObject(lib, item, ValueBuilder::makeName(item));
@@ -1771,12 +1894,56 @@ static Ref makeInstantiation() {
insertItem(FLOAT32ARRAY);
insertItem(FLOAT64ARRAY);
Ref env = ValueBuilder::makeObject();
- Ref mem = ValueBuilder::makeNew(
- ValueBuilder::makeCall(ARRAY_BUFFER, ValueBuilder::makeInt(0x10000)));
- Ref call = ValueBuilder::makeCall(IString(ASM_FUNC), lib, env, mem);
- Ref ret = ValueBuilder::makeVar();
- ValueBuilder::appendToVar(ret, ASM_MODULE, call);
- return ret;
+ Ref call = ValueBuilder::makeCall(IString(ASM_FUNC), lib, env,
+ ValueBuilder::makeName(buffer));
+ Ref module = ValueBuilder::makeVar();
+ ValueBuilder::appendToVar(module, ASM_MODULE, call);
+ flattenAppend(ret, module);
+
+ // When equating floating point values in spec tests we want to use bitwise
+ // equality like wasm does. Unfortunately though NaN makes this tricky. JS
+ // implementations like Spidermonkey and JSC will canonicalize NaN loads from
+ // `Float32Array`, but V8 will not. This means that NaN representations are
+ // kind of all over the place and difficult to bitwise equate.
+ //
+ // To work around this problem we just use a small shim which considers all
+ // NaN representations equivalent and otherwise tests for bitwise equality.
+ flattenAppend(ret, ValueBuilder::makeName(R"(
+ function f32Equal(a, b) {
+ var i = new Int32Array(1);
+ var f = new Float32Array(i.buffer);
+ f[0] = a;
+ var ai = f[0];
+ f[0] = b;
+ var bi = f[0];
+
+ return (isNaN(a) && isNaN(b)) || a == b;
+ }
+ )"));
+ flattenAppend(ret, ValueBuilder::makeName(R"(
+ function f64Equal(a, b) {
+ var i = new Int32Array(2);
+ var f = new Float64Array(i.buffer);
+ f[0] = a;
+ var ai1 = f[0];
+ var ai2 = f[1];
+ f[0] = b;
+ var bi1 = f[0];
+ var bi2 = f[1];
+
+ return (isNaN(a) && isNaN(b)) || (ai1 == bi1 && ai2 == bi2);
+ }
+ )"));
+
+ // 64-bit numbers get a different ABI w/ wasm2asm, and in general you can't
+ // actually export them from wasm at the boundary. We hack around this though
+ // to get the spec tests working.
+ flattenAppend(ret, ValueBuilder::makeName(R"(
+ function i64Equal(actual_lo, expected_lo, expected_hi) {
+ return actual_lo == (expected_lo | 0) &&
+ asmModule.__wasm_fetch_high_bits() == (expected_hi | 0);
+ }
+ )"));
}
static void prefixCalls(Ref asmjs) {
@@ -1787,11 +1954,25 @@ static void prefixCalls(Ref asmjs) {
}
if (arr.size() > 0 && arr[0]->isString() && arr[0]->getIString() == CALL) {
assert(arr.size() >= 2);
- Ref prefixed = ValueBuilder::makeDot(ValueBuilder::makeName(ASM_MODULE),
- arr[1]->getIString());
- arr[1]->setArray(prefixed->getArray());
+ if (arr[1]->getIString() == "f32Equal" ||
+ arr[1]->getIString() == "f64Equal" ||
+ arr[1]->getIString() == "i64Equal") {
+ // ...
+ } else if (arr[1]->getIString() == "Math_fround") {
+ arr[1]->setString("Math.fround");
+ } else {
+ Name name = arr[1]->getIString() == "isNaN" ? "Math" : ASM_MODULE;
+ Ref prefixed = ValueBuilder::makeDot(ValueBuilder::makeName(name),
+ arr[1]->getIString());
+ arr[1]->setArray(prefixed->getArray());
+ }
}
}
+
+ if (asmjs->isAssign()) {
+ prefixCalls(asmjs->asAssign()->target());
+ prefixCalls(asmjs->asAssign()->value());
+ }
}
Ref Wasm2AsmBuilder::makeAssertReturnFunc(SExpressionWasmBuilder& sexpBuilder,
@@ -1812,18 +1993,29 @@ Ref Wasm2AsmBuilder::makeAssertReturnFunc(SExpressionWasmBuilder& sexpBuilder,
Expression* expected = sexpBuilder.parseExpression(e[2]);
Type resType = expected->type;
actual->type = resType;
- BinaryOp eqOp;
switch (resType) {
- case i32: eqOp = EqInt32; break;
- case i64: eqOp = EqInt64; break;
- case f32: eqOp = EqFloat32; break;
- case f64: eqOp = EqFloat64; break;
+ case i32:
+ body = wasmBuilder.makeBinary(EqInt32, actual, expected);
+ break;
+
+ case i64:
+ body = wasmBuilder.makeCall("i64Equal", {actual, expected}, i32);
+ break;
+
+ case f32: {
+ body = wasmBuilder.makeCall("f32Equal", {actual, expected}, i32);
+ break;
+ }
+ case f64: {
+ body = wasmBuilder.makeCall("f64Equal", {actual, expected}, i32);
+ break;
+ }
+
default: {
std::cerr << "Unhandled type in assert: " << resType << std::endl;
abort();
}
}
- body = wasmBuilder.makeBinary(eqOp, actual, expected);
} else {
assert(false && "Unexpected number of parameters in assert_return");
}
@@ -1841,6 +2033,25 @@ Ref Wasm2AsmBuilder::makeAssertReturnFunc(SExpressionWasmBuilder& sexpBuilder,
return jsFunc;
}
+Ref Wasm2AsmBuilder::makeAssertReturnNanFunc(SExpressionWasmBuilder& sexpBuilder,
+ Builder& wasmBuilder,
+ Element& e, Name testFuncName) {
+ Expression* actual = sexpBuilder.parseExpression(e[1]);
+ Expression* body = wasmBuilder.makeCallImport("isNaN", {actual}, i32);
+ std::unique_ptr<Function> testFunc(
+ wasmBuilder.makeFunction(
+ testFuncName,
+ std::vector<NameType>{},
+ body->type,
+ std::vector<NameType>{},
+ body
+ )
+ );
+ Ref jsFunc = processFunction(testFunc.get());
+ prefixCalls(jsFunc);
+ return jsFunc;
+}
+
Ref Wasm2AsmBuilder::makeAssertTrapFunc(SExpressionWasmBuilder& sexpBuilder,
Builder& wasmBuilder,
Element& e, Name testFuncName) {
@@ -1855,6 +2066,7 @@ Ref Wasm2AsmBuilder::makeAssertTrapFunc(SExpressionWasmBuilder& sexpBuilder,
);
IString expectedErr = e[2]->str();
Ref innerFunc = processFunction(exprFunc.get());
+ prefixCalls(innerFunc);
Ref outerFunc = ValueBuilder::makeFunction(testFuncName);
outerFunc[3]->push_back(innerFunc);
Ref tryBlock = ValueBuilder::makeBlock();
@@ -2042,6 +2254,7 @@ void Wasm2AsmBuilder::addMemoryGrowthFuncs(Ref ast) {
bool Wasm2AsmBuilder::isAssertHandled(Element& e) {
return e.isList() && e.size() >= 2 && e[0]->isStr()
&& (e[0]->str() == Name("assert_return") ||
+ e[0]->str() == Name("assert_return_nan") ||
(flags.pedantic && e[0]->str() == Name("assert_trap")))
&& e[1]->isList() && e[1]->size() >= 2 && (*e[1])[0]->isStr()
&& (*e[1])[0]->str() == Name("invoke");
@@ -2051,7 +2264,7 @@ Ref Wasm2AsmBuilder::processAsserts(Element& root,
SExpressionWasmBuilder& sexpBuilder) {
Builder wasmBuilder(sexpBuilder.getAllocator());
Ref ret = ValueBuilder::makeBlock();
- flattenAppend(ret, makeInstantiation());
+ makeInstantiation(ret);
for (size_t i = 1; i < root.size(); ++i) {
Element& e = *root[i];
if (!isAssertHandled(e)) {
@@ -2060,6 +2273,7 @@ Ref Wasm2AsmBuilder::processAsserts(Element& root,
}
Name testFuncName(IString(("check" + std::to_string(i)).c_str(), false));
bool isReturn = (e[0]->str() == Name("assert_return"));
+ bool isReturnNan = (e[0]->str() == Name("assert_return_nan"));
Element& testOp = *e[1];
// Replace "invoke" with "call"
testOp[0]->setString(IString("call"), false, false);
@@ -2068,7 +2282,9 @@ Ref Wasm2AsmBuilder::processAsserts(Element& root,
Ref testFunc = isReturn ?
makeAssertReturnFunc(sexpBuilder, wasmBuilder, e, testFuncName) :
- makeAssertTrapFunc(sexpBuilder, wasmBuilder, e, testFuncName);
+ (isReturnNan ?
+ makeAssertReturnNanFunc(sexpBuilder, wasmBuilder, e, testFuncName) :
+ makeAssertTrapFunc(sexpBuilder, wasmBuilder, e, testFuncName));
flattenAppend(ret, testFunc);
std::stringstream failFuncName;