summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/asmjs/shared-constants.cpp4
-rw-r--r--src/asmjs/shared-constants.h4
-rw-r--r--src/emscripten-optimizer/simple_ast.h15
-rw-r--r--src/passes/I64ToI32Lowering.cpp252
-rw-r--r--src/tools/wasm2asm.cpp3
-rw-r--r--src/wasm2asm.h252
6 files changed, 472 insertions, 58 deletions
diff --git a/src/asmjs/shared-constants.cpp b/src/asmjs/shared-constants.cpp
index c6efe64f9..bf956f173 100644
--- a/src/asmjs/shared-constants.cpp
+++ b/src/asmjs/shared-constants.cpp
@@ -89,5 +89,7 @@ cashew::IString GLOBAL("global"),
WASM_ROTR32("__wasm_rotr_i32"),
WASM_ROTR64("__wasm_rotr_i64"),
WASM_GROW_MEMORY("__wasm_grow_memory"),
- WASM_CURRENT_MEMORY("__wasm_current_memory");
+ WASM_CURRENT_MEMORY("__wasm_current_memory"),
+ WASM_FETCH_HIGH_BITS("__wasm_fetch_high_bits"),
+ INT64_TO_32_HIGH_BITS("i64toi32_i32$HIGH_BITS");
}
diff --git a/src/asmjs/shared-constants.h b/src/asmjs/shared-constants.h
index 28142fecc..973b5eb49 100644
--- a/src/asmjs/shared-constants.h
+++ b/src/asmjs/shared-constants.h
@@ -92,7 +92,9 @@ extern cashew::IString GLOBAL,
WASM_ROTR32,
WASM_ROTR64,
WASM_GROW_MEMORY,
- WASM_CURRENT_MEMORY;
+ WASM_CURRENT_MEMORY,
+ WASM_FETCH_HIGH_BITS,
+ INT64_TO_32_HIGH_BITS;
}
#endif // wasm_asmjs_shared_constants_h
diff --git a/src/emscripten-optimizer/simple_ast.h b/src/emscripten-optimizer/simple_ast.h
index 62bf975f0..64aab7708 100644
--- a/src/emscripten-optimizer/simple_ast.h
+++ b/src/emscripten-optimizer/simple_ast.h
@@ -836,6 +836,19 @@ struct JSPrinter {
}
static char* numToString(double d, bool finalize=true) {
+ if (std::isnan(d)) {
+ if (std::signbit(d)) {
+ return (char*) "-NaN";
+ } else {
+ return (char*) "NaN";
+ }
+ } else if (!std::isfinite(d)) {
+ if (std::signbit(d)) {
+ return (char*) "-Infinity";
+ } else {
+ return (char*) "Infinity";
+ }
+ }
bool neg = d < 0;
if (neg) d = -d;
// try to emit the fewest necessary characters
@@ -1046,6 +1059,8 @@ struct JSPrinter {
ensure(1); // we temporarily append a 0
char *curr = buffer + last; // ensure might invalidate
buffer[used] = 0;
+ if (strstr(curr, "Infinity")) return;
+ if (strstr(curr, "NaN")) return;
if (strchr(curr, '.')) return; // already a decimal point, all good
char *e = strchr(curr, 'e');
if (!e) {
diff --git a/src/passes/I64ToI32Lowering.cpp b/src/passes/I64ToI32Lowering.cpp
index e6da9a1b8..2f6fdf122 100644
--- a/src/passes/I64ToI32Lowering.cpp
+++ b/src/passes/I64ToI32Lowering.cpp
@@ -40,10 +40,10 @@ static Name makeHighName(Name n) {
struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
struct TempVar {
- TempVar(Index idx, I64ToI32Lowering& pass) :
- idx(idx), pass(pass), moved(false) {}
+ TempVar(Index idx, Type ty, I64ToI32Lowering& pass) :
+ idx(idx), pass(pass), moved(false), ty(ty) {}
- TempVar(TempVar&& other) : idx(other), pass(other.pass), moved(false) {
+ TempVar(TempVar&& other) : idx(other), pass(other.pass), moved(false), ty(other.ty) {
assert(!other.moved);
other.moved = true;
}
@@ -78,18 +78,17 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
private:
void freeIdx() {
- assert(std::find(pass.freeTemps.begin(), pass.freeTemps.end(), idx) ==
- pass.freeTemps.end());
- pass.freeTemps.push_back(idx);
+ auto &freeList = pass.freeTemps[(int) ty];
+ assert(std::find(freeList.begin(), freeList.end(), idx) == freeList.end());
+ freeList.push_back(idx);
}
Index idx;
I64ToI32Lowering& pass;
bool moved; // since C++ will still destruct moved-from values
+ Type ty;
};
- static Name highBitsGlobal;
-
// false since function types need to be lowered
// TODO: allow module-level transformations in parallel passes
bool isFunctionParallel() override { return false; }
@@ -114,7 +113,7 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
// to return the high 32 bits.
auto* highBits = new Global();
highBits->type = i32;
- highBits->name = highBitsGlobal;
+ highBits->name = INT64_TO_32_HIGH_BITS;
highBits->init = builder->makeConst(Literal(int32_t(0)));
highBits->mutable_ = true;
module->addGlobal(highBits);
@@ -159,7 +158,7 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
BinaryOp secondOp = leftShift ? ShrUInt32 : ShlInt32;
Block* equalRotateBlock = builder->blockify(
builder->makeSetGlobal(
- highBitsGlobal,
+ INT64_TO_32_HIGH_BITS,
builder->makeGetLocal(lowBits, i32)
),
builder->makeGetLocal(highBits, i32)
@@ -182,7 +181,7 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
)
),
builder->makeSetGlobal(
- highBitsGlobal,
+ INT64_TO_32_HIGH_BITS,
builder->makeBinary(
OrInt32,
builder->makeBinary(
@@ -221,7 +220,7 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
)
),
builder->makeSetGlobal(
- highBitsGlobal,
+ INT64_TO_32_HIGH_BITS,
builder->makeBinary(
OrInt32,
builder->makeBinary(
@@ -328,18 +327,17 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
func->body
);
SetGlobal* setHigh = builder->makeSetGlobal(
- highBitsGlobal,
+ INT64_TO_32_HIGH_BITS,
builder->makeGetLocal(highBits, i32)
);
GetLocal* getLow = builder->makeGetLocal(lowBits, i32);
func->body = builder->blockify(setLow, setHigh, getLow);
}
}
- assert(freeTemps.size() == nextTemp - func->getNumLocals());
int idx = 0;
for (size_t i = func->getNumLocals(); i < nextTemp; i++) {
Name tmpName("i64toi32_i32$" + std::to_string(idx++));
- builder->addVar(func, tmpName, i32);
+ builder->addVar(func, tmpName, tempTypes[i]);
}
}
@@ -488,7 +486,7 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
);
SetLocal* setHigh = builder->makeSetLocal(
highBits,
- builder->makeGetGlobal(highBitsGlobal, i32)
+ builder->makeGetGlobal(INT64_TO_32_HIGH_BITS, i32)
);
GetLocal* getLow = builder->makeGetLocal(lowBits, i32);
Block* result = builder->blockify(doCall, setHigh, getLow);
@@ -733,15 +731,14 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
void lowerReinterpretFloat64(Unary* curr) {
// Assume that the wasm file assumes the address 0 is invalid and roundtrip
// our f64 through memory at address 0
- Expression* zero = builder->makeConst(Literal(int32_t(0)));
TempVar highBits = getTemp();
Block *result = builder->blockify(
- builder->makeStore(8, 0, 8, zero, curr->value, f64),
+ builder->makeStore(8, 0, 8, builder->makeConst(Literal(int32_t(0))), curr->value, f64),
builder->makeSetLocal(
highBits,
- builder->makeLoad(4, true, 4, 4, zero, i32)
+ builder->makeLoad(4, true, 4, 4, builder->makeConst(Literal(int32_t(0))), i32)
),
- builder->makeLoad(4, true, 0, 4, zero, i32)
+ builder->makeLoad(4, true, 0, 4, builder->makeConst(Literal(int32_t(0))), i32)
);
setOutParam(result, std::move(highBits));
replaceCurrent(result);
@@ -751,13 +748,192 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
// Assume that the wasm file assumes the address 0 is invalid and roundtrip
// our i64 through memory at address 0
TempVar highBits = fetchOutParam(curr->value);
- TempVar lowBits = getTemp();
- Expression* zero = builder->makeConst(Literal(int32_t(0)));
Block *result = builder->blockify(
- builder->makeStore(4, 0, 4, zero, curr->value, i32),
- builder->makeStore(4, 4, 4, zero, builder->makeGetLocal(highBits, i32), i32),
- builder->makeLoad(8, true, 0, 8, zero, f64)
+ builder->makeStore(4, 0, 4, builder->makeConst(Literal(int32_t(0))), curr->value, i32),
+ builder->makeStore(4, 4, 4, builder->makeConst(Literal(int32_t(0))), builder->makeGetLocal(highBits, i32), i32),
+ builder->makeLoad(8, true, 0, 8, builder->makeConst(Literal(int32_t(0))), f64)
+ );
+ replaceCurrent(result);
+ }
+
+ void lowerTruncFloatToInt(Unary *curr) {
+ // hiBits = if abs(f) >= 1.0 {
+ // if f > 0.0 {
+ // (unsigned) min(
+ // floor(f / (float) U32_MAX),
+ // (float) U32_MAX - 1,
+ // )
+ // } else {
+ // (unsigned) ceil((f - (float) (unsigned) f) / ((float) U32_MAX))
+ // }
+ // } else {
+ // 0
+ // }
+ //
+ // loBits = (unsigned) f;
+
+ Literal litZero, litOne, u32Max;
+ UnaryOp trunc, convert, abs, floor, ceil;
+ Type localType;
+ BinaryOp ge, gt, min, div, sub;
+ switch (curr->op) {
+ case TruncSFloat32ToInt64:
+ case TruncUFloat32ToInt64: {
+ litZero = Literal((float) 0);
+ litOne = Literal((float) 1);
+ u32Max = Literal(((float) UINT_MAX) + 1);
+ trunc = TruncUFloat32ToInt32;
+ convert = ConvertUInt32ToFloat32;
+ localType = f32;
+ abs = AbsFloat32;
+ ge = GeFloat32;
+ gt = GtFloat32;
+ min = MinFloat32;
+ floor = FloorFloat32;
+ ceil = CeilFloat32;
+ div = DivFloat32;
+ sub = SubFloat32;
+ break;
+ }
+ case TruncSFloat64ToInt64:
+ case TruncUFloat64ToInt64: {
+ litZero = Literal((double) 0);
+ litOne = Literal((double) 1);
+ u32Max = Literal(((double) UINT_MAX) + 1);
+ trunc = TruncUFloat64ToInt32;
+ convert = ConvertUInt32ToFloat64;
+ localType = f64;
+ abs = AbsFloat64;
+ ge = GeFloat64;
+ gt = GtFloat64;
+ min = MinFloat64;
+ floor = FloorFloat64;
+ ceil = CeilFloat64;
+ div = DivFloat64;
+ sub = SubFloat64;
+ break;
+ }
+ default: abort();
+ }
+
+ TempVar f = getTemp(localType);
+ TempVar highBits = getTemp();
+
+ Expression *gtZeroBranch = builder->makeBinary(
+ min,
+ builder->makeUnary(
+ floor,
+ builder->makeBinary(
+ div,
+ builder->makeGetLocal(f, localType),
+ builder->makeConst(u32Max)
+ )
+ ),
+ builder->makeBinary(sub, builder->makeConst(u32Max), builder->makeConst(litOne))
+ );
+ Expression *ltZeroBranch = builder->makeUnary(
+ ceil,
+ builder->makeBinary(
+ div,
+ builder->makeBinary(
+ sub,
+ builder->makeGetLocal(f, localType),
+ builder->makeUnary(convert,
+ builder->makeUnary(trunc, builder->makeGetLocal(f, localType))
+ )
+ ),
+ builder->makeConst(u32Max)
+ )
+ );
+
+ If *highBitsCalc = builder->makeIf(
+ builder->makeBinary(
+ gt,
+ builder-> makeGetLocal(f, localType),
+ builder->makeConst(litZero)
+ ),
+ builder->makeUnary(trunc, gtZeroBranch),
+ builder->makeUnary(trunc, ltZeroBranch)
+ );
+ If *highBitsVal = builder->makeIf(
+ builder->makeBinary(
+ ge,
+ builder->makeUnary(abs, builder->makeGetLocal(f, localType)),
+ builder->makeConst(litOne)
+ ),
+ highBitsCalc,
+ builder->makeConst(Literal(int32_t(0)))
+ );
+ Block *result = builder->blockify(
+ builder->makeSetLocal(f, curr->value),
+ builder->makeSetLocal(highBits, highBitsVal),
+ builder->makeUnary(trunc, builder->makeGetLocal(f, localType))
+ );
+ setOutParam(result, std::move(highBits));
+ replaceCurrent(result);
+ }
+
+ void lowerConvertIntToFloat(Unary *curr) {
+ // Here the same strategy as `emcc` is taken which takes the two halves of
+ // the 64-bit integer and creates a mathematical expression using float
+ // arithmetic to reassemble the final floating point value.
+ //
+ // For example for i64 -> f32 we generate:
+ //
+ // ((double) (unsigned) lowBits) + ((double) U32_MAX) * ((double) (int) highBits)
+ //
+ // Mostly just shuffling things around here with coercions and whatnot!
+ // Note though that all arithmetic is done with f64 to have as much
+ // precision as we can.
+ TempVar highBits = fetchOutParam(curr->value);
+ TempVar lowBits = getTemp();
+ TempVar highResult = getTemp();
+
+ UnaryOp convertHigh;
+ switch (curr->op) {
+ case ConvertSInt64ToFloat32:
+ case ConvertSInt64ToFloat64:
+ convertHigh = ConvertSInt32ToFloat64;
+ break;
+ case ConvertUInt64ToFloat32:
+ case ConvertUInt64ToFloat64:
+ convertHigh = ConvertUInt32ToFloat64;
+ break;
+ default: abort();
+ }
+
+ Expression *result = builder->blockify(
+ builder->makeSetLocal(lowBits, curr->value),
+ builder->makeSetLocal(
+ highResult,
+ builder->makeConst(Literal(int32_t(0)))
+ ),
+ builder->makeBinary(
+ AddFloat64,
+ builder->makeUnary(
+ ConvertUInt32ToFloat64,
+ builder->makeGetLocal(lowBits, i32)
+ ),
+ builder->makeBinary(
+ MulFloat64,
+ builder->makeConst(Literal((double)UINT_MAX + 1)),
+ builder->makeUnary(
+ convertHigh,
+ builder->makeGetLocal(highBits, i32)
+ )
+ )
+ )
);
+
+ switch (curr->op) {
+ case ConvertSInt64ToFloat32:
+ case ConvertUInt64ToFloat32: {
+ result = builder->makeUnary(DemoteFloat64, result);
+ break;
+ }
+ default: break;
+ }
+
replaceCurrent(result);
}
@@ -889,11 +1065,11 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
case TruncSFloat32ToInt64:
case TruncUFloat32ToInt64:
case TruncSFloat64ToInt64:
- case TruncUFloat64ToInt64:
+ case TruncUFloat64ToInt64: lowerTruncFloatToInt(curr); break;
case ConvertSInt64ToFloat32:
case ConvertSInt64ToFloat64:
case ConvertUInt64ToFloat32:
- case ConvertUInt64ToFloat64:
+ case ConvertUInt64ToFloat64: lowerConvertIntToFloat(curr); break;
default:
std::cerr << "Unhandled unary operator: " << curr->op << std::endl;
abort();
@@ -1386,7 +1562,7 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
),
builder->makeSetLocal(
rightHigh,
- builder->makeGetGlobal(highBitsGlobal, i32)
+ builder->makeGetGlobal(INT64_TO_32_HIGH_BITS, i32)
),
builder->makeGetLocal(lowResult, i32)
);
@@ -1680,7 +1856,7 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> {
TempVar highBits = fetchOutParam(curr->value);
SetLocal* setLow = builder->makeSetLocal(lowBits, curr->value);
SetGlobal* setHigh = builder->makeSetGlobal(
- highBitsGlobal,
+ INT64_TO_32_HIGH_BITS,
builder->makeGetLocal(highBits, i32)
);
curr->value = builder->makeGetLocal(lowBits, i32);
@@ -1693,20 +1869,24 @@ private:
std::unordered_map<Index, Index> indexMap;
std::unordered_map<Expression*, TempVar> highBitVars;
std::unordered_map<Name, TempVar> labelHighBitVars;
- std::vector<Index> freeTemps;
+ std::unordered_map<int, std::vector<Index>> freeTemps;
+ std::unordered_map<Index, Type> tempTypes;
Index nextTemp;
bool needRotl64 = false;
bool needRotr64 = false;
- TempVar getTemp() {
+ TempVar getTemp(Type ty = i32) {
Index ret;
- if (freeTemps.size() > 0) {
- ret = freeTemps.back();
- freeTemps.pop_back();
+ auto &freeList = freeTemps[(int) ty];
+ if (freeList.size() > 0) {
+ ret = freeList.back();
+ freeList.pop_back();
} else {
ret = nextTemp++;
+ tempTypes[ret] = ty;
}
- return TempVar(ret, *this);
+ assert(tempTypes[ret] == ty);
+ return TempVar(ret, ty, *this);
}
bool hasOutParam(Expression* e) {
@@ -1726,8 +1906,6 @@ private:
}
};
-Name I64ToI32Lowering::highBitsGlobal("i64toi32_i32$HIGH_BITS");
-
Pass *createI64ToI32LoweringPass() {
return new I64ToI32Lowering();
}
diff --git a/src/tools/wasm2asm.cpp b/src/tools/wasm2asm.cpp
index cc53dc834..2251a332c 100644
--- a/src/tools/wasm2asm.cpp
+++ b/src/tools/wasm2asm.cpp
@@ -39,7 +39,8 @@ int main(int argc, const char *argv[]) {
})
.add("--allow-asserts", "", "Allow compilation of .wast testing asserts",
Options::Arguments::Zero,
- [](Options* o, const std::string& argument) {
+ [&](Options* o, const std::string& argument) {
+ builderFlags.allowAsserts = true;
o->extra["asserts"] = "1";
})
.add("--pedantic", "", "Emulate WebAssembly trapping behavior",
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;