summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/asm2wasm.h166
-rw-r--r--src/tools/asm2wasm.cpp22
2 files changed, 132 insertions, 56 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);
diff --git a/src/tools/asm2wasm.cpp b/src/tools/asm2wasm.cpp
index 01602c8b7..0679ad3f5 100644
--- a/src/tools/asm2wasm.cpp
+++ b/src/tools/asm2wasm.cpp
@@ -33,7 +33,7 @@ using namespace wasm;
int main(int argc, const char *argv[]) {
PassOptions passOptions;
bool runOptimizationPasses = false;
- bool imprecise = false;
+ Asm2WasmBuilder::TrapMode trapMode = Asm2WasmBuilder::TrapMode::JS;
bool wasmOnly = false;
bool debugInfo = false;
std::string symbolMap;
@@ -76,9 +76,21 @@ int main(int argc, const char *argv[]) {
[](Options *o, const std::string &) {
std::cerr << "--no-opts is deprecated (use -O0, etc.)\n";
})
- .add("--imprecise", "-i", "Imprecise optimizations", Options::Arguments::Zero,
- [&imprecise](Options *o, const std::string &) {
- imprecise = true;
+ .add("--emit-potential-traps", "-i", "Emit instructions that might trap, like div/rem of 0", Options::Arguments::Zero,
+ [&trapMode](Options *o, const std::string &) {
+ trapMode = Asm2WasmBuilder::TrapMode::Allow;
+ })
+ .add("--emit-clamped-potential-traps", "-i", "Clamp instructions that might trap, like float => int", Options::Arguments::Zero,
+ [&trapMode](Options *o, const std::string &) {
+ trapMode = Asm2WasmBuilder::TrapMode::Clamp;
+ })
+ .add("--emit-jsified-potential-traps", "-i", "Avoid instructions that might trap, handling them exactly like JS would", Options::Arguments::Zero,
+ [&trapMode](Options *o, const std::string &) {
+ trapMode = Asm2WasmBuilder::TrapMode::JS;
+ })
+ .add("--imprecise", "-i", "Imprecise optimizations (old name for --emit-potential-traps)", Options::Arguments::Zero,
+ [&trapMode](Options *o, const std::string &) {
+ trapMode = Asm2WasmBuilder::TrapMode::Allow;
})
.add("--wasm-only", "-w", "Input is in WebAssembly-only format, and not actually valid asm.js", Options::Arguments::Zero,
[&wasmOnly](Options *o, const std::string &) {
@@ -128,7 +140,7 @@ int main(int argc, const char *argv[]) {
if (options.debug) std::cerr << "wasming..." << std::endl;
Module wasm;
wasm.memory.initial = wasm.memory.max = totalMemory / Memory::kPageSize;
- Asm2WasmBuilder asm2wasm(wasm, pre, options.debug, imprecise, passOptions, runOptimizationPasses, wasmOnly);
+ Asm2WasmBuilder asm2wasm(wasm, pre, options.debug, trapMode, passOptions, runOptimizationPasses, wasmOnly);
asm2wasm.processAsm(asmjs);
// import mem init file, if provided