summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/asm2wasm.h357
-rw-r--r--src/asm_v_wasm.h3
-rw-r--r--src/asmjs/asm_v_wasm.cpp12
-rw-r--r--src/asmjs/shared-constants.cpp6
-rw-r--r--src/asmjs/shared-constants.h6
-rw-r--r--src/ast/trapping.h100
-rw-r--r--src/passes/CMakeLists.txt1
-rw-r--r--src/passes/TrapMode.cpp318
-rw-r--r--src/passes/pass.cpp2
-rw-r--r--src/passes/passes.h2
-rw-r--r--src/shared-constants.h1
-rw-r--r--src/tools/asm2wasm.cpp11
-rw-r--r--src/tools/s2wasm.cpp27
13 files changed, 546 insertions, 300 deletions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h
index 044517d12..7d4ce5fbe 100644
--- a/src/asm2wasm.h
+++ b/src/asm2wasm.h
@@ -33,6 +33,7 @@
#include "parsing.h"
#include "ast_utils.h"
#include "ast/branch-utils.h"
+#include "ast/trapping.h"
#include "wasm-builder.h"
#include "wasm-emscripten.h"
#include "wasm-module-building.h"
@@ -88,10 +89,6 @@ Name I32_CTTZ("i32_cttz"),
I64_CTTZ("i64_cttz"),
I64_CTLZ("i64_ctlz"),
I64_CTPOP("i64_ctpop"),
- I64S_REM("i64s-rem"),
- I64U_REM("i64u-rem"),
- I64S_DIV("i64s-div"),
- I64U_DIV("i64u-div"),
F32_COPYSIGN("f32_copysign"),
F64_COPYSIGN("f64_copysign"),
LOAD1("load1"),
@@ -356,12 +353,6 @@ struct AdjustDebugInfo : public WalkerPass<PostWalker<AdjustDebugInfo, Visitor<A
class Asm2WasmBuilder {
public:
- enum class TrapMode {
- Allow,
- Clamp,
- JS
- };
-
Module& wasm;
MixedArena &allocator;
@@ -388,6 +379,7 @@ public:
bool debug;
TrapMode trapMode;
+ TrappingFunctionContainer trappingFunctions;
PassOptions passOptions;
bool legalizeJavaScriptFFI;
bool runOptimizationPasses;
@@ -522,6 +514,7 @@ public:
preprocessor(preprocessor),
debug(debug),
trapMode(trapMode),
+ trappingFunctions(trapMode, wasm, /* immediate = */ true),
passOptions(passOptions),
legalizeJavaScriptFFI(legalizeJavaScriptFFI),
runOptimizationPasses(runOptimizationPasses),
@@ -701,277 +694,8 @@ private:
return ret;
}
- // converts an f32 to an f64 if necessary
Expression* ensureDouble(Expression* expr) {
- if (expr->type == f32) {
- auto conv = allocator.alloc<Unary>();
- conv->op = PromoteFloat32;
- conv->value = expr;
- conv->type = WasmType::f64;
- return conv;
- }
- assert(expr->type == f64);
- return expr;
- }
-
- // 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;
- case BinaryOp::RemUInt32: call->target = I32U_REM; break;
- case BinaryOp::DivSInt32: call->target = I32S_DIV; break;
- case BinaryOp::DivUInt32: call->target = I32U_DIV; break;
- default: WASM_UNREACHABLE();
- }
- call->operands.push_back(left);
- call->operands.push_back(right);
- call->type = i32;
- static std::set<Name> addedFunctions;
- if (addedFunctions.count(call->target) == 0) {
- Expression* result = builder.makeBinary(op,
- builder.makeGetLocal(0, i32),
- builder.makeGetLocal(1, i32)
- );
- if (op == DivSInt32) {
- // guard against signed division overflow
- result = builder.makeIf(
- builder.makeBinary(AndInt32,
- builder.makeBinary(EqInt32,
- builder.makeGetLocal(0, i32),
- builder.makeConst(Literal(std::numeric_limits<int32_t>::min()))
- ),
- builder.makeBinary(EqInt32,
- builder.makeGetLocal(1, i32),
- builder.makeConst(Literal(int32_t(-1)))
- )
- ),
- builder.makeConst(Literal(int32_t(0))),
- result
- );
- }
- addedFunctions.insert(call->target);
- auto func = new Function;
- func->name = call->target;
- func->params.push_back(i32);
- func->params.push_back(i32);
- func->result = i32;
- func->body = builder.makeIf(
- builder.makeUnary(EqZInt32,
- builder.makeGetLocal(1, i32)
- ),
- builder.makeConst(Literal(int32_t(0))),
- result
- );
- wasm.addFunction(func);
- }
- return 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;
- case BinaryOp::RemUInt64: call->target = I64U_REM; break;
- case BinaryOp::DivSInt64: call->target = I64S_DIV; break;
- case BinaryOp::DivUInt64: call->target = I64U_DIV; break;
- default: WASM_UNREACHABLE();
- }
- call->operands.push_back(left);
- call->operands.push_back(right);
- call->type = i64;
- static std::set<Name> addedFunctions;
- if (addedFunctions.count(call->target) == 0) {
- Expression* result = builder.makeBinary(op,
- builder.makeGetLocal(0, i64),
- builder.makeGetLocal(1, i64)
- );
- if (op == DivSInt64) {
- // guard against signed division overflow
- result = builder.makeIf(
- builder.makeBinary(AndInt32,
- builder.makeBinary(EqInt64,
- builder.makeGetLocal(0, i64),
- builder.makeConst(Literal(std::numeric_limits<int64_t>::min()))
- ),
- builder.makeBinary(EqInt64,
- builder.makeGetLocal(1, i64),
- builder.makeConst(Literal(int64_t(-1)))
- )
- ),
- builder.makeConst(Literal(int64_t(0))),
- result
- );
- }
- addedFunctions.insert(call->target);
- auto func = new Function;
- func->name = call->target;
- func->params.push_back(i64);
- func->params.push_back(i64);
- func->result = i64;
- func->body = builder.makeIf(
- builder.makeUnary(EqZInt64,
- builder.makeGetLocal(1, i64)
- ),
- builder.makeConst(Literal(int64_t(0))),
- result
- );
- wasm.addFunction(func);
- }
- return call;
- }
-
- // Some conversions might trap, so emit them safely if necessary
- Expression* makeTrappingFloatToInt32(bool signed_, Expression* value) {
- if (trapMode == TrapMode::Allow) {
- auto ret = allocator.alloc<Unary>();
- ret->value = value;
- bool isF64 = ret->value->type == f64;
- if (signed_) {
- ret->op = isF64 ? TruncSFloat64ToInt32 : TruncSFloat32ToInt32;
- } else {
- ret->op = isF64 ? TruncUFloat64ToInt32 : TruncUFloat32ToInt32;
- }
- 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 = ensureDouble(value);
- // 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* makeTrappingFloatToInt64(bool signed_, Expression* value) {
- if (trapMode == TrapMode::Allow) {
- auto ret = allocator.alloc<Unary>();
- ret->value = value;
- bool isF64 = ret->value->type == f64;
- if (signed_) {
- ret->op = isF64 ? TruncSFloat64ToInt64 : TruncSFloat32ToInt64;
- } else {
- ret->op = isF64 ? TruncUFloat64ToInt64 : TruncUFloat32ToInt64;
- }
- ret->type = WasmType::i64;
- 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 = ensureDouble(value);
- // There is no "JS" way to handle this, as no i64s in JS, so always clamp if we don't allow traps
- Call *ret = allocator.alloc<Call>();
- ret->target = F64_TO_INT64;
- ret->operands.push_back(input);
- ret->type = i64;
- static bool added = false;
- if (!added) {
- added = true;
- auto func = new Function;
- func->name = ret->target;
- func->params.push_back(f64);
- func->result = i64;
- func->body = builder.makeUnary(TruncSFloat64ToInt64,
- builder.makeGetLocal(0, f64)
- );
- // too small
- func->body = builder.makeIf(
- builder.makeBinary(LeFloat64,
- builder.makeGetLocal(0, f64),
- builder.makeConst(Literal(double(std::numeric_limits<int64_t>::min()) - 1))
- ),
- builder.makeConst(Literal(int64_t(std::numeric_limits<int64_t>::min()))),
- func->body
- );
- // too big
- func->body = builder.makeIf(
- builder.makeBinary(GeFloat64,
- builder.makeGetLocal(0, f64),
- builder.makeConst(Literal(double(std::numeric_limits<int64_t>::max()) + 1))
- ),
- builder.makeConst(Literal(int64_t(std::numeric_limits<int64_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(int64_t(std::numeric_limits<int64_t>::min()))), // NB: min here as well. anything invalid => to the min
- func->body
- );
- wasm.addFunction(func);
- }
- return ret;
+ return wasm::ensureDouble(expr, allocator);
}
Expression* truncateToInt32(Expression* value) {
@@ -1433,15 +1157,23 @@ void Asm2WasmBuilder::processAsm(Ref ast) {
}
}
}
- auto importResult = getModule()->getFunctionType(getModule()->getImport(curr->target)->functionType)->result;
+ Module* wasm = getModule();
+ auto importResult = wasm->getFunctionType(wasm->getImport(curr->target)->functionType)->result;
if (curr->type != importResult) {
auto old = curr->type;
curr->type = importResult;
if (importResult == f64) {
// we use a JS f64 value which is the most general, and convert to it
switch (old) {
- case i32: replaceCurrent(parent->makeTrappingFloatToInt32(true /* signed, asm.js ffi */, curr)); break;
- case f32: replaceCurrent(parent->builder.makeUnary(DemoteFloat64, curr)); break;
+ case i32: {
+ Unary* trunc = parent->builder.makeUnary(TruncSFloat64ToInt32, curr);
+ replaceCurrent(makeTrappingUnary(trunc, parent->trappingFunctions));
+ break;
+ }
+ case f32: {
+ replaceCurrent(parent->builder.makeUnary(DemoteFloat64, curr));
+ break;
+ }
case none: {
// this function returns a value, but we are not using it, so it must be dropped.
// autodrop will do that for us.
@@ -1896,12 +1628,8 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
wasm.addImport(import);
}
return call;
- } 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;
+ return makeTrappingBinary(ret, trappingFunctions);
} else if (what == SUB) {
Ref target = ast[1];
assert(target->isString());
@@ -1969,7 +1697,20 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
// ~, might be ~~ as a coercion or just a not
if (ast[2]->isArray(UNARY_PREFIX) && ast[2][1] == B_NOT) {
// if we have an unsigned coercion on us, it is an unsigned op
- return makeTrappingFloatToInt32(!isParentUnsignedCoercion(astStackHelper.getParent()), process(ast[2][2]));
+ Expression* expr = process(ast[2][2]);
+ bool isSigned = !isParentUnsignedCoercion(astStackHelper.getParent());
+ bool isF64 = expr->type == f64;
+ UnaryOp op;
+ if (isSigned && isF64) {
+ op = UnaryOp::TruncSFloat64ToInt32;
+ } else if (isSigned && !isF64) {
+ op = UnaryOp::TruncSFloat32ToInt32;
+ } else if (!isSigned && isF64) {
+ op = UnaryOp::TruncUFloat64ToInt32;
+ } else { // !isSigned && !isF64
+ op = UnaryOp::TruncUFloat32ToInt32;
+ }
+ return makeTrappingUnary(builder.makeUnary(op, expr), trappingFunctions);
}
// no bitwise unary not, so do xor with -1
auto ret = allocator.alloc<Binary>();
@@ -2216,8 +1957,22 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
if (name == I64_S2D) return builder.makeUnary(UnaryOp::ConvertSInt64ToFloat64, value);
if (name == I64_U2F) return builder.makeUnary(UnaryOp::ConvertUInt64ToFloat32, value);
if (name == I64_U2D) return builder.makeUnary(UnaryOp::ConvertUInt64ToFloat64, value);
- if (name == I64_F2S || name == I64_D2S) return makeTrappingFloatToInt64(true /* signed */, value);
- if (name == I64_F2U || name == I64_D2U) return makeTrappingFloatToInt64(false /* unsigned */, value);
+ if (name == I64_F2S) {
+ Unary* conv = builder.makeUnary(UnaryOp::TruncSFloat32ToInt64, value);
+ return makeTrappingUnary(conv, trappingFunctions);
+ }
+ if (name == I64_D2S) {
+ Unary* conv = builder.makeUnary(UnaryOp::TruncSFloat64ToInt64, value);
+ return makeTrappingUnary(conv, trappingFunctions);
+ }
+ if (name == I64_F2U) {
+ Unary* conv = builder.makeUnary(UnaryOp::TruncUFloat32ToInt64, value);
+ return makeTrappingUnary(conv, trappingFunctions);
+ }
+ if (name == I64_D2U) {
+ Unary* conv = builder.makeUnary(UnaryOp::TruncUFloat64ToInt64, value);
+ return makeTrappingUnary(conv, trappingFunctions);
+ }
if (name == I64_BC2D) return builder.makeUnary(UnaryOp::ReinterpretInt64, value);
if (name == I64_BC2I) return builder.makeUnary(UnaryOp::ReinterpretFloat64, value);
if (name == I64_CTTZ) return builder.makeUnary(UnaryOp::CtzInt64, value);
@@ -2231,10 +1986,22 @@ 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 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_UDIV) {
+ Binary* div = builder.makeBinary(BinaryOp::DivUInt64, left, right);
+ return makeTrappingBinary(div, trappingFunctions);
+ }
+ if (name == I64_SDIV) {
+ Binary* div = builder.makeBinary(BinaryOp::DivSInt64, left, right);
+ return makeTrappingBinary(div, trappingFunctions);
+ }
+ if (name == I64_UREM) {
+ Binary* rem = builder.makeBinary(BinaryOp::RemUInt64, left, right);
+ return makeTrappingBinary(rem, trappingFunctions);
+ }
+ if (name == I64_SREM) {
+ Binary* rem = builder.makeBinary(BinaryOp::RemSInt64, left, right);
+ return makeTrappingBinary(rem, trappingFunctions);
+ }
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/asm_v_wasm.h b/src/asm_v_wasm.h
index 53881861c..d42a1082a 100644
--- a/src/asm_v_wasm.h
+++ b/src/asm_v_wasm.h
@@ -70,6 +70,9 @@ FunctionType* sigToFunctionType(std::string sig);
FunctionType* ensureFunctionType(std::string sig, Module* wasm);
+// converts an f32 to an f64 if necessary
+Expression* ensureDouble(Expression* expr, MixedArena& allocator);
+
} // namespace wasm
#endif // wasm_asm_v_wasm_h
diff --git a/src/asmjs/asm_v_wasm.cpp b/src/asmjs/asm_v_wasm.cpp
index ae7d320ca..bfb04a9fd 100644
--- a/src/asmjs/asm_v_wasm.cpp
+++ b/src/asmjs/asm_v_wasm.cpp
@@ -109,4 +109,16 @@ FunctionType* ensureFunctionType(std::string sig, Module* wasm) {
return type;
}
+Expression* ensureDouble(Expression* expr, MixedArena& allocator) {
+ if (expr->type == f32) {
+ auto conv = allocator.alloc<Unary>();
+ conv->op = PromoteFloat32;
+ conv->value = expr;
+ conv->type = WasmType::f64;
+ return conv;
+ }
+ assert(expr->type == f64);
+ return expr;
+}
+
} // namespace wasm
diff --git a/src/asmjs/shared-constants.cpp b/src/asmjs/shared-constants.cpp
index 43c3d1065..f62be6168 100644
--- a/src/asmjs/shared-constants.cpp
+++ b/src/asmjs/shared-constants.cpp
@@ -42,7 +42,13 @@ cashew::IString GLOBAL("global"),
ASM2WASM("asm2wasm"),
F64_REM("f64-rem"),
F64_TO_INT("f64-to-int"),
+ F64_TO_UINT("f64-to-uint"),
F64_TO_INT64("f64-to-int64"),
+ F64_TO_UINT64("f64-to-uint64"),
+ F32_TO_INT("f32-to-int"),
+ F32_TO_UINT("f32-to-uint"),
+ F32_TO_INT64("f32-to-int64"),
+ F32_TO_UINT64("f32-to-uint64"),
I32S_DIV("i32s-div"),
I32U_DIV("i32u-div"),
I32S_REM("i32s-rem"),
diff --git a/src/asmjs/shared-constants.h b/src/asmjs/shared-constants.h
index ce5cd95a6..7e4b27c85 100644
--- a/src/asmjs/shared-constants.h
+++ b/src/asmjs/shared-constants.h
@@ -45,7 +45,13 @@ extern cashew::IString GLOBAL,
ASM2WASM,
F64_REM,
F64_TO_INT,
+ F64_TO_UINT,
F64_TO_INT64,
+ F64_TO_UINT64,
+ F32_TO_INT,
+ F32_TO_UINT,
+ F32_TO_INT64,
+ F32_TO_UINT64,
I32S_DIV,
I32U_DIV,
I32S_REM,
diff --git a/src/ast/trapping.h b/src/ast/trapping.h
new file mode 100644
index 000000000..80cc14da9
--- /dev/null
+++ b/src/ast/trapping.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2017 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef wasm_ast_trapping_h
+#define wasm_ast_trapping_h
+
+#include "pass.h"
+
+namespace wasm {
+
+enum class TrapMode {
+ Allow,
+ Clamp,
+ JS
+};
+
+inline void addTrapModePass(PassRunner& runner, TrapMode trapMode) {
+ if (trapMode == TrapMode::Clamp) {
+ runner.add("trap-mode-clamp");
+ } else if (trapMode == TrapMode::JS) {
+ runner.add("trap-mode-js");
+ }
+}
+
+class TrappingFunctionContainer {
+public:
+ TrappingFunctionContainer(TrapMode mode, Module &wasm, bool immediate = false)
+ : mode(mode),
+ wasm(wasm),
+ immediate(immediate) { }
+
+ bool hasFunction(Name name) {
+ return functions.find(name) != functions.end();
+ }
+ bool hasImport(Name name) {
+ return imports.find(name) != imports.end();
+ }
+
+ void addFunction(Function* function) {
+ functions[function->name] = function;
+ if (immediate) {
+ wasm.addFunction(function);
+ }
+ }
+ void addImport(Import* import) {
+ imports[import->name] = import;
+ if (immediate) {
+ wasm.addImport(import);
+ }
+ }
+
+ void addToModule() {
+ if (!immediate) {
+ for (auto &pair : functions) {
+ wasm.addFunction(pair.second);
+ }
+ for (auto &pair : imports) {
+ wasm.addImport(pair.second);
+ }
+ }
+ functions.clear();
+ imports.clear();
+ }
+
+ TrapMode getMode() {
+ return mode;
+ }
+
+ Module& getModule() {
+ return wasm;
+ }
+
+private:
+ std::map<Name, Function*> functions;
+ std::map<Name, Import*> imports;
+
+ TrapMode mode;
+ Module& wasm;
+ bool immediate;
+};
+
+Expression* makeTrappingBinary(Binary* curr, TrappingFunctionContainer &trappingFunctions);
+Expression* makeTrappingUnary(Unary* curr, TrappingFunctionContainer &trappingFunctions);
+
+} // wasm
+
+#endif // wasm_ast_trapping_h
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt
index 6870259c4..3cb00b796 100644
--- a/src/passes/CMakeLists.txt
+++ b/src/passes/CMakeLists.txt
@@ -34,6 +34,7 @@ SET(passes_SOURCES
RemoveUnusedModuleElements.cpp
ReorderLocals.cpp
ReorderFunctions.cpp
+ TrapMode.cpp
SafeHeap.cpp
SimplifyLocals.cpp
SSAify.cpp
diff --git a/src/passes/TrapMode.cpp b/src/passes/TrapMode.cpp
new file mode 100644
index 000000000..e648f66a5
--- /dev/null
+++ b/src/passes/TrapMode.cpp
@@ -0,0 +1,318 @@
+/*
+ * Copyright 2017 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Pass that supports potentially-trapping wasm operations.
+// For example, integer division traps when dividing by zero, so this pass
+// generates a check and replaces the result with zero in that case.
+//
+
+#include "asm_v_wasm.h"
+#include "asmjs/shared-constants.h"
+#include "ast/trapping.h"
+#include "mixed_arena.h"
+#include "pass.h"
+#include "wasm.h"
+#include "wasm-builder.h"
+#include "wasm-printing.h"
+#include "wasm-type.h"
+#include "support/name.h"
+
+namespace wasm {
+
+Name I64S_REM("i64s-rem"),
+ I64U_REM("i64u-rem"),
+ I64S_DIV("i64s-div"),
+ I64U_DIV("i64u-div");
+
+Name getBinaryFuncName(Binary* curr) {
+ switch (curr->op) {
+ case RemSInt32: return I32S_REM;
+ case RemUInt32: return I32U_REM;
+ case DivSInt32: return I32S_DIV;
+ case DivUInt32: return I32U_DIV;
+ case RemSInt64: return I64S_REM;
+ case RemUInt64: return I64U_REM;
+ case DivSInt64: return I64S_DIV;
+ case DivUInt64: return I64U_DIV;
+ default: return Name();
+ }
+}
+
+Name getUnaryFuncName(Unary* curr) {
+ switch (curr->op) {
+ case TruncSFloat32ToInt32: return F32_TO_INT;
+ case TruncUFloat32ToInt32: return F32_TO_UINT;
+ case TruncSFloat32ToInt64: return F32_TO_INT64;
+ case TruncUFloat32ToInt64: return F32_TO_UINT64;
+ case TruncSFloat64ToInt32: return F64_TO_INT;
+ case TruncUFloat64ToInt32: return F64_TO_UINT;
+ case TruncSFloat64ToInt64: return F64_TO_INT64;
+ case TruncUFloat64ToInt64: return F64_TO_UINT64;
+ default: return Name();
+ }
+}
+
+bool isTruncOpSigned(UnaryOp op) {
+ switch (op) {
+ case TruncUFloat32ToInt32:
+ case TruncUFloat32ToInt64:
+ case TruncUFloat64ToInt32:
+ case TruncUFloat64ToInt64: return false;
+ default: return true;
+ }
+}
+
+Function* generateBinaryFunc(Module& wasm, Binary *curr) {
+ BinaryOp op = curr->op;
+ WasmType type = curr->type;
+ bool isI64 = type == i64;
+ Builder builder(wasm);
+ Expression* result = builder.makeBinary(op,
+ builder.makeGetLocal(0, type),
+ builder.makeGetLocal(1, type)
+ );
+ BinaryOp divSIntOp = isI64 ? DivSInt64 : DivSInt32;
+ UnaryOp eqZOp = isI64 ? EqZInt64 : EqZInt32;
+ Literal minLit = isI64 ? Literal(std::numeric_limits<int64_t>::min())
+ : Literal(std::numeric_limits<int32_t>::min());
+ Literal zeroLit = isI64 ? Literal(int64_t(0)) : Literal(int32_t(0));
+ if (op == divSIntOp) {
+ // guard against signed division overflow
+ BinaryOp eqOp = isI64 ? EqInt64 : EqInt32;
+ Literal negLit = isI64 ? Literal(int64_t(-1)) : Literal(int32_t(-1));
+ result = builder.makeIf(
+ builder.makeBinary(AndInt32,
+ builder.makeBinary(eqOp,
+ builder.makeGetLocal(0, type),
+ builder.makeConst(minLit)
+ ),
+ builder.makeBinary(eqOp,
+ builder.makeGetLocal(1, type),
+ builder.makeConst(negLit)
+ )
+ ),
+ builder.makeConst(zeroLit),
+ result
+ );
+ }
+ auto func = new Function;
+ func->name = getBinaryFuncName(curr);
+ func->params.push_back(type);
+ func->params.push_back(type);
+ func->result = type;
+ func->body = builder.makeIf(
+ builder.makeUnary(eqZOp,
+ builder.makeGetLocal(1, type)
+ ),
+ builder.makeConst(zeroLit),
+ result
+ );
+ return func;
+}
+
+template <typename IntType, typename FloatType>
+void makeClampLimitLiterals(Literal& iMin, Literal& fMin, Literal& fMax) {
+ IntType minVal = std::numeric_limits<IntType>::min();
+ IntType maxVal = std::numeric_limits<IntType>::max();
+ iMin = Literal(minVal);
+ fMin = Literal(FloatType(minVal) - 1);
+ fMax = Literal(FloatType(maxVal) + 1);
+}
+
+Function* generateUnaryFunc(Module& wasm, Unary *curr) {
+ WasmType type = curr->value->type;
+ WasmType retType = curr->type;
+ UnaryOp truncOp = curr->op;
+ bool isF64 = type == f64;
+
+ Builder builder(wasm);
+
+ BinaryOp leOp = isF64 ? LeFloat64 : LeFloat32;
+ BinaryOp geOp = isF64 ? GeFloat64 : GeFloat32;
+ BinaryOp neOp = isF64 ? NeFloat64 : NeFloat32;
+
+ Literal iMin, fMin, fMax;
+ switch (truncOp) {
+ case TruncSFloat32ToInt32: makeClampLimitLiterals< int32_t, float>(iMin, fMin, fMax); break;
+ case TruncUFloat32ToInt32: makeClampLimitLiterals<uint32_t, float>(iMin, fMin, fMax); break;
+ case TruncSFloat32ToInt64: makeClampLimitLiterals< int64_t, float>(iMin, fMin, fMax); break;
+ case TruncUFloat32ToInt64: makeClampLimitLiterals<uint64_t, float>(iMin, fMin, fMax); break;
+ case TruncSFloat64ToInt32: makeClampLimitLiterals< int32_t, double>(iMin, fMin, fMax); break;
+ case TruncUFloat64ToInt32: makeClampLimitLiterals<uint32_t, double>(iMin, fMin, fMax); break;
+ case TruncSFloat64ToInt64: makeClampLimitLiterals< int64_t, double>(iMin, fMin, fMax); break;
+ case TruncUFloat64ToInt64: makeClampLimitLiterals<uint64_t, double>(iMin, fMin, fMax); break;
+ default: WASM_UNREACHABLE();
+ }
+
+ auto func = new Function;
+ func->name = getUnaryFuncName(curr);
+ func->params.push_back(type);
+ func->result = retType;
+ func->body = builder.makeUnary(truncOp,
+ builder.makeGetLocal(0, type)
+ );
+ // 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(leOp,
+ builder.makeGetLocal(0, type),
+ builder.makeConst(fMin)
+ ),
+ builder.makeConst(iMin),
+ func->body
+ );
+ // too big XXX see above
+ func->body = builder.makeIf(
+ builder.makeBinary(geOp,
+ builder.makeGetLocal(0, type),
+ builder.makeConst(fMax)
+ ),
+ // NB: min here as well. anything out of range => to the min
+ builder.makeConst(iMin),
+ func->body
+ );
+ // nan
+ func->body = builder.makeIf(
+ builder.makeBinary(neOp,
+ builder.makeGetLocal(0, type),
+ builder.makeGetLocal(0, type)
+ ),
+ // NB: min here as well. anything invalid => to the min
+ builder.makeConst(iMin),
+ func->body
+ );
+ return func;
+}
+
+void ensureBinaryFunc(Binary* curr, Module& wasm,
+ TrappingFunctionContainer &trappingFunctions) {
+ Name name = getBinaryFuncName(curr);
+ if (trappingFunctions.hasFunction(name)) {
+ return;
+ }
+ trappingFunctions.addFunction(generateBinaryFunc(wasm, curr));
+}
+
+void ensureUnaryFunc(Unary *curr, Module& wasm,
+ TrappingFunctionContainer &trappingFunctions) {
+ Name name = getUnaryFuncName(curr);
+ if (trappingFunctions.hasFunction(name)) {
+ return;
+ }
+ trappingFunctions.addFunction(generateUnaryFunc(wasm, curr));
+}
+
+void ensureF64ToI64JSImport(TrappingFunctionContainer &trappingFunctions) {
+ if (trappingFunctions.hasImport(F64_TO_INT)) {
+ return;
+ }
+
+ Module& wasm = trappingFunctions.getModule();
+ 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;
+ trappingFunctions.addImport(import);
+}
+
+Expression* makeTrappingBinary(Binary* curr, TrappingFunctionContainer &trappingFunctions) {
+ Name name = getBinaryFuncName(curr);
+ if (!name.is() || trappingFunctions.getMode() == TrapMode::Allow) {
+ return curr;
+ }
+
+ // the wasm operation might trap if done over 0, so generate a safe call
+ WasmType type = curr->type;
+ Module& wasm = trappingFunctions.getModule();
+ Builder builder(wasm);
+ ensureBinaryFunc(curr, wasm, trappingFunctions);
+ return builder.makeCall(name, {curr->left, curr->right}, type);
+}
+
+Expression* makeTrappingUnary(Unary* curr, TrappingFunctionContainer &trappingFunctions) {
+ Name name = getUnaryFuncName(curr);
+ TrapMode mode = trappingFunctions.getMode();
+ if (!name.is() || mode == TrapMode::Allow) {
+ return curr;
+ }
+
+ Module& wasm = trappingFunctions.getModule();
+ Builder builder(wasm);
+ // WebAssembly traps on float-to-int overflows, but asm.js wouldn't, so we must do something
+ // 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 i64, there is no "JS" way to handle this, as no i64s in JS, so always clamp if we don't allow traps
+ // asm.js doesn't have unsigned f64-to-int, so just use the signed one.
+ if (curr->type != i64 && mode == TrapMode::JS) {
+ // WebAssembly traps on float-to-int overflows, but asm.js wouldn't, so we must emulate that
+ ensureF64ToI64JSImport(trappingFunctions);
+ Expression* f64Value = ensureDouble(curr->value, wasm.allocator);
+ return builder.makeCallImport(F64_TO_INT, {f64Value}, i32);
+ }
+
+ ensureUnaryFunc(curr, wasm, trappingFunctions);
+ return builder.makeCall(name, {curr->value}, curr->type);
+}
+
+struct TrapModePass : public WalkerPass<PostWalker<TrapModePass>> {
+public:
+
+ // Needs to be non-parallel so that visitModule gets called after visiting
+ // each node in the module, so we can add the functions that we created.
+ bool isFunctionParallel() override { return false; }
+
+ TrapModePass(TrapMode mode) : mode(mode) {
+ assert(mode != TrapMode::Allow);
+ }
+
+ Pass* create() override { return new TrapModePass(mode); }
+
+ void visitUnary(Unary* curr) {
+ replaceCurrent(makeTrappingUnary(curr, *trappingFunctions));
+ }
+
+ void visitBinary(Binary* curr) {
+ replaceCurrent(makeTrappingBinary(curr, *trappingFunctions));
+ }
+
+ void visitModule(Module* curr) {
+ trappingFunctions->addToModule();
+ }
+
+ void doWalkModule(Module* module) {
+ trappingFunctions = make_unique<TrappingFunctionContainer>(mode, *module);
+ WalkerPass<PostWalker<TrapModePass>>::doWalkModule(module);
+ }
+
+private:
+ TrapMode mode;
+ // Need to defer adding generated functions because adding functions while
+ // iterating over existing functions causes problems.
+ std::unique_ptr<TrappingFunctionContainer> trappingFunctions;
+};
+
+Pass *createTrapModeClamp() {
+ return new TrapModePass(TrapMode::Clamp);
+}
+
+Pass *createTrapModeJS() {
+ return new TrapModePass(TrapMode::JS);
+}
+
+} // namespace wasm
diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp
index a208a03dd..6cfe77a01 100644
--- a/src/passes/pass.cpp
+++ b/src/passes/pass.cpp
@@ -109,6 +109,8 @@ void PassRegistry::registerPasses() {
registerPass("simplify-locals-nostructure", "miscellaneous locals-related optimizations", createSimplifyLocalsNoStructurePass);
registerPass("simplify-locals-notee-nostructure", "miscellaneous locals-related optimizations", createSimplifyLocalsNoTeeNoStructurePass);
registerPass("ssa", "ssa-ify variables so that they have a single assignment", createSSAifyPass);
+ registerPass("trap-mode-clamp", "replace trapping operations with clamping semantics", createTrapModeClamp);
+ registerPass("trap-mode-js", "replace trapping operations with js semantics", createTrapModeJS);
registerPass("untee", "removes tee_locals, replacing them with sets and gets", createUnteePass);
registerPass("vacuum", "removes obviously unneeded code", createVacuumPass);
// registerPass("lower-i64", "lowers i64 into pairs of i32s", createLowerInt64Pass);
diff --git a/src/passes/passes.h b/src/passes/passes.h
index a02216083..58a9e2b27 100644
--- a/src/passes/passes.h
+++ b/src/passes/passes.h
@@ -67,6 +67,8 @@ Pass *createSimplifyLocalsNoTeePass();
Pass *createSimplifyLocalsNoStructurePass();
Pass *createSimplifyLocalsNoTeeNoStructurePass();
Pass *createSSAifyPass();
+Pass *createTrapModeClamp();
+Pass *createTrapModeJS();
Pass *createUnteePass();
Pass *createVacuumPass();
diff --git a/src/shared-constants.h b/src/shared-constants.h
index 31b5b7c18..e8f98edb7 100644
--- a/src/shared-constants.h
+++ b/src/shared-constants.h
@@ -15,6 +15,7 @@
*/
#ifndef wasm_shared_constants_h
+#define wasm_shared_constants_h
#include "wasm.h"
diff --git a/src/tools/asm2wasm.cpp b/src/tools/asm2wasm.cpp
index 3fa97d981..cbc3c486b 100644
--- a/src/tools/asm2wasm.cpp
+++ b/src/tools/asm2wasm.cpp
@@ -18,6 +18,7 @@
// asm2wasm console tool
//
+#include "ast/trapping.h"
#include "support/colors.h"
#include "support/command-line.h"
#include "support/file.h"
@@ -34,7 +35,7 @@ using namespace wasm;
int main(int argc, const char *argv[]) {
bool legalizeJavaScriptFFI = true;
- Asm2WasmBuilder::TrapMode trapMode = Asm2WasmBuilder::TrapMode::JS;
+ TrapMode trapMode = TrapMode::JS;
bool wasmOnly = false;
std::string sourceMapFilename;
std::string sourceMapUrl;
@@ -79,19 +80,19 @@ int main(int argc, const char *argv[]) {
})
.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;
+ trapMode = 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;
+ trapMode = 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;
+ trapMode = 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;
+ trapMode = 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 &) {
diff --git a/src/tools/s2wasm.cpp b/src/tools/s2wasm.cpp
index a35783479..559188d1f 100644
--- a/src/tools/s2wasm.cpp
+++ b/src/tools/s2wasm.cpp
@@ -18,6 +18,7 @@
// wasm2asm console tool
//
+#include "ast/trapping.h"
#include "support/colors.h"
#include "support/command-line.h"
#include "support/file.h"
@@ -37,6 +38,7 @@ int main(int argc, const char *argv[]) {
bool importMemory = false;
std::string startFunction;
std::vector<std::string> archiveLibraries;
+ TrapMode trapMode = TrapMode::Allow;
Options options("s2wasm", "Link .s file into .wast");
options.extra["validate"] = "wasm";
options
@@ -81,6 +83,24 @@ int main(int argc, const char *argv[]) {
[&allowMemoryGrowth](Options *, const std::string &) {
allowMemoryGrowth = true;
})
+ .add("--emit-potential-traps", "",
+ "Emit instructions that might trap, like div/rem of 0",
+ Options::Arguments::Zero,
+ [&trapMode](Options *o, const std::string &) {
+ trapMode = TrapMode::Allow;
+ })
+ .add("--emit-clamped-potential-traps", "",
+ "Clamp instructions that might trap, like float => int",
+ Options::Arguments::Zero,
+ [&trapMode](Options *o, const std::string &) {
+ trapMode = TrapMode::Clamp;
+ })
+ .add("--emit-jsified-potential-traps", "",
+ "Avoid instructions that might trap, handling them exactly like JS would",
+ Options::Arguments::Zero,
+ [&trapMode](Options *o, const std::string &) {
+ trapMode = TrapMode::JS;
+ })
.add("--emscripten-glue", "-e", "Generate emscripten glue",
Options::Arguments::Zero,
[&generateEmscriptenGlue](Options *, const std::string &) {
@@ -144,6 +164,13 @@ int main(int argc, const char *argv[]) {
S2WasmBuilder mainbuilder(input.c_str(), options.debug);
linker.linkObject(mainbuilder);
+ if (trapMode != TrapMode::Allow) {
+ Module* wasm = &(linker.getOutput().wasm);
+ PassRunner runner(wasm);
+ addTrapModePass(runner, trapMode);
+ runner.run();
+ }
+
for (const auto& m : archiveLibraries) {
auto archiveFile(read_file<std::vector<char>>(m, Flags::Binary, debugFlag));
bool error;