diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/asmjs/shared-constants.cpp | 4 | ||||
-rw-r--r-- | src/asmjs/shared-constants.h | 4 | ||||
-rw-r--r-- | src/emscripten-optimizer/simple_ast.h | 15 | ||||
-rw-r--r-- | src/passes/I64ToI32Lowering.cpp | 252 | ||||
-rw-r--r-- | src/tools/wasm2asm.cpp | 3 | ||||
-rw-r--r-- | src/wasm2asm.h | 252 |
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; |