blob: 68d6aad4bf780e0df7e8739e84d0d479c65279b1 [file] [log] [blame] [edit]
/*
* 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 "ir/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;
Type 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) {
Type type = curr->value->type;
Type 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
Type 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);
super::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