diff options
Diffstat (limited to 'src/asm2wasm.h')
-rw-r--r-- | src/asm2wasm.h | 166 |
1 files changed, 115 insertions, 51 deletions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h index daf4d9af1..284fa0b7f 100644 --- a/src/asm2wasm.h +++ b/src/asm2wasm.h @@ -308,6 +308,13 @@ struct Asm2WasmPreProcessor { // class Asm2WasmBuilder { +public: + enum class TrapMode { + Allow, + Clamp, + JS + }; + Module& wasm; MixedArena &allocator; @@ -332,7 +339,8 @@ class Asm2WasmBuilder { Asm2WasmPreProcessor& preprocessor; bool debug; - bool imprecise; + + TrapMode trapMode; PassOptions passOptions; bool runOptimizationPasses; bool wasmOnly; @@ -437,13 +445,13 @@ private: } public: - Asm2WasmBuilder(Module& wasm, Asm2WasmPreProcessor& preprocessor, bool debug, bool imprecise, PassOptions passOptions, bool runOptimizationPasses, bool wasmOnly) + Asm2WasmBuilder(Module& wasm, Asm2WasmPreProcessor& preprocessor, bool debug, TrapMode trapMode, PassOptions passOptions, bool runOptimizationPasses, bool wasmOnly) : wasm(wasm), allocator(wasm.allocator), builder(wasm), preprocessor(preprocessor), debug(debug), - imprecise(imprecise), + trapMode(trapMode), passOptions(passOptions), runOptimizationPasses(runOptimizationPasses), wasmOnly(wasmOnly) {} @@ -614,9 +622,10 @@ private: return ret; } - Expression* makePotentiallyTrappingI32Binary(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 + // Some binary opts might trap, so emit them safely if necessary + Expression* makeTrappingI32Binary(BinaryOp op, Expression* left, Expression* right) { + if (trapMode == TrapMode::Allow) return builder.makeBinary(op, left, right); + // the wasm operation might trap if done over 0, so generate a safe call auto *call = allocator.alloc<Call>(); switch (op) { case BinaryOp::RemSInt32: call->target = I32S_REM; break; @@ -669,10 +678,10 @@ private: return call; } - // Some binary opts might trap, so emit them safely if we are precise - Expression* makePotentiallyTrappingI64Binary(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 + // Some binary opts might trap, so emit them safely if necessary + Expression* makeTrappingI64Binary(BinaryOp op, Expression* left, Expression* right) { + if (trapMode == TrapMode::Allow) return builder.makeBinary(op, left, right); + // 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; @@ -725,6 +734,93 @@ private: return call; } + // Some conversions might trap, so emit them safely if necessary + Expression* makeTrappingFloatToInt(Expression* value) { + if (trapMode == TrapMode::Allow) { + auto ret = allocator.alloc<Unary>(); + ret->value = value; + ret->op = ret->value->type == f64 ? TruncSFloat64ToInt32 : TruncSFloat32ToInt32; + ret->type = WasmType::i32; + return ret; + } + // WebAssembly traps on float-to-int overflows, but asm.js wouldn't, so we must do something + // First, normalize input to f64 + auto input = value; + if (input->type == f32) { + auto conv = allocator.alloc<Unary>(); + conv->op = PromoteFloat32; + conv->value = input; + conv->type = WasmType::f64; + input = conv; + } + // We can handle this in one of two ways: clamping, which is fast, or JS, which + // is precisely like JS but in order to do that we do a slow ffi + if (trapMode == TrapMode::JS) { + // WebAssembly traps on float-to-int overflows, but asm.js wouldn't, so we must emulate that + CallImport *ret = allocator.alloc<CallImport>(); + ret->target = F64_TO_INT; + ret->operands.push_back(input); + ret->type = i32; + static bool addedImport = false; + if (!addedImport) { + addedImport = true; + auto import = new Import; // f64-to-int = asm2wasm.f64-to-int; + import->name = F64_TO_INT; + import->module = ASM2WASM; + import->base = F64_TO_INT; + import->functionType = ensureFunctionType("id", &wasm)->name; + import->kind = ExternalKind::Function; + wasm.addImport(import); + } + return ret; + } + assert(trapMode == TrapMode::Clamp); + Call *ret = allocator.alloc<Call>(); + ret->target = F64_TO_INT; + ret->operands.push_back(input); + ret->type = i32; + static bool added = false; + if (!added) { + added = true; + auto func = new Function; + func->name = ret->target; + func->params.push_back(f64); + func->result = i32; + func->body = builder.makeUnary(TruncSFloat64ToInt32, + builder.makeGetLocal(0, f64) + ); + // too small XXX this is different than asm.js, which does frem. here we clamp, which is much simpler/faster, and similar to native builds + func->body = builder.makeIf( + builder.makeBinary(LeFloat64, + builder.makeGetLocal(0, f64), + builder.makeConst(Literal(double(std::numeric_limits<int32_t>::min()) - 1)) + ), + builder.makeConst(Literal(int32_t(std::numeric_limits<int32_t>::min()))), + func->body + ); + // too big XXX see above + func->body = builder.makeIf( + builder.makeBinary(GeFloat64, + builder.makeGetLocal(0, f64), + builder.makeConst(Literal(double(std::numeric_limits<int32_t>::max()) + 1)) + ), + builder.makeConst(Literal(int32_t(std::numeric_limits<int32_t>::min()))), // NB: min here as well. anything out of range => to the min + func->body + ); + // nan + func->body = builder.makeIf( + builder.makeBinary(NeFloat64, + builder.makeGetLocal(0, f64), + builder.makeGetLocal(0, f64) + ), + builder.makeConst(Literal(int32_t(std::numeric_limits<int32_t>::min()))), // NB: min here as well. anything invalid => to the min + func->body + ); + wasm.addFunction(func); + } + return ret; + } + Expression* truncateToInt32(Expression* value) { if (value->type == i64) return builder.makeUnary(UnaryOp::WrapInt64, value); // either i32, or a call_import whose type we don't know yet (but would be legalized to i32 anyhow) @@ -1579,10 +1675,10 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { wasm.addImport(import); } return call; - } else if (!imprecise && (ret->op == BinaryOp::RemSInt32 || ret->op == BinaryOp::RemUInt32 || - ret->op == BinaryOp::DivSInt32 || ret->op == BinaryOp::DivUInt32)) { - // we are precise, and the wasm operation might trap if done over 0, so generate a safe call - return makePotentiallyTrappingI32Binary(ret->op, ret->left, ret->right); + } else if (trapMode != TrapMode::Allow && + (ret->op == BinaryOp::RemSInt32 || ret->op == BinaryOp::RemUInt32 || + ret->op == BinaryOp::DivSInt32 || ret->op == BinaryOp::DivUInt32)) { + return makeTrappingI32Binary(ret->op, ret->left, ret->right); } return ret; } else if (what == SUB) { @@ -1654,39 +1750,7 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { } else if (ast[1] == B_NOT) { // ~, might be ~~ as a coercion or just a not if (ast[2]->isArray(UNARY_PREFIX) && ast[2][1] == B_NOT) { - if (imprecise) { - auto ret = allocator.alloc<Unary>(); - ret->value = process(ast[2][2]); - ret->op = ret->value->type == f64 ? TruncSFloat64ToInt32 : TruncSFloat32ToInt32; // imprecise, because this wasm thing might trap, while asm.js never would - ret->type = WasmType::i32; - return ret; - } else { - // WebAssembly traps on float-to-int overflows, but asm.js wouldn't, so we must emulate that - CallImport *ret = allocator.alloc<CallImport>(); - ret->target = F64_TO_INT; - auto input = process(ast[2][2]); - if (input->type == f32) { - auto conv = allocator.alloc<Unary>(); - conv->op = PromoteFloat32; - conv->value = input; - conv->type = WasmType::f64; - input = conv; - } - ret->operands.push_back(input); - ret->type = i32; - static bool addedImport = false; - if (!addedImport) { - addedImport = true; - auto import = new Import; // f64-to-int = asm2wasm.f64-to-int; - import->name = F64_TO_INT; - import->module = ASM2WASM; - import->base = F64_TO_INT; - import->functionType = ensureFunctionType("id", &wasm)->name; - import->kind = ExternalKind::Function; - wasm.addImport(import); - } - return ret; - } + return makeTrappingFloatToInt(process(ast[2][2])); } // no bitwise unary not, so do xor with -1 auto ret = allocator.alloc<Binary>(); @@ -1905,10 +1969,10 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { 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 makePotentiallyTrappingI64Binary(BinaryOp::DivUInt64, left, right); - if (name == I64_SDIV) return makePotentiallyTrappingI64Binary(BinaryOp::DivSInt64, left, right); - if (name == I64_UREM) return makePotentiallyTrappingI64Binary(BinaryOp::RemUInt64, left, right); - if (name == I64_SREM) return makePotentiallyTrappingI64Binary(BinaryOp::RemSInt64, left, right); + if (name == I64_UDIV) return makeTrappingI64Binary(BinaryOp::DivUInt64, left, right); + if (name == I64_SDIV) return makeTrappingI64Binary(BinaryOp::DivSInt64, left, right); + if (name == I64_UREM) return makeTrappingI64Binary(BinaryOp::RemUInt64, left, right); + if (name == I64_SREM) return makeTrappingI64Binary(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); |