summaryrefslogtreecommitdiff
path: root/src/asm2wasm.h
diff options
context:
space:
mode:
authorAlon Zakai <alonzakai@gmail.com>2017-03-07 13:56:35 -0800
committerGitHub <noreply@github.com>2017-03-07 13:56:35 -0800
commit71804e2bfd1ba49b7dd4ce82b6ad26ba13f1bca8 (patch)
tree7a91f560708b5017cf795850db94267f2cb58f62 /src/asm2wasm.h
parenta452f92aeb96e9617a20146503720cd5acb64f29 (diff)
downloadbinaryen-71804e2bfd1ba49b7dd4ce82b6ad26ba13f1bca8.tar.gz
binaryen-71804e2bfd1ba49b7dd4ce82b6ad26ba13f1bca8.tar.bz2
binaryen-71804e2bfd1ba49b7dd4ce82b6ad26ba13f1bca8.zip
Use 3 modes for potentially trapping ops in asm2wasm (#929)
* use 3 modes for potentially trapping ops in asm2wasm: allow (just emit a potentially trapping op), js (do exactly what js does, even if it takes a slow ffi to do it), and clamp (avoid the trap by clamping as necessary)
Diffstat (limited to 'src/asm2wasm.h')
-rw-r--r--src/asm2wasm.h166
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);