| /* |
| * 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 "asmjs/shared-constants.h" |
| #include "ir/trapping.h" |
| #include "mixed_arena.h" |
| #include "pass.h" |
| #include "support/name.h" |
| #include "wasm-builder.h" |
| #include "wasm-type.h" |
| #include "wasm.h" |
| |
| namespace wasm { |
| |
| Name I64S_REM("i64s-rem"); |
| Name I64U_REM("i64u-rem"); |
| Name I64S_DIV("i64s-div"); |
| Name I64U_DIV("i64u-div"); |
| |
| static Expression* ensureDouble(Expression* expr, MixedArena& allocator) { |
| if (expr->type == Type::f32) { |
| auto conv = allocator.alloc<Unary>(); |
| conv->op = PromoteFloat32; |
| conv->value = expr; |
| conv->type = Type::f64; |
| return conv; |
| } |
| assert(expr->type == Type::f64); |
| return expr; |
| } |
| |
| 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 == Type::i64; |
| Builder builder(wasm); |
| Expression* result = builder.makeBinary( |
| op, builder.makeLocalGet(0, type), builder.makeLocalGet(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.makeLocalGet(0, type), builder.makeConst(minLit)), |
| builder.makeBinary( |
| eqOp, builder.makeLocalGet(1, type), builder.makeConst(negLit))), |
| builder.makeConst(zeroLit), |
| result); |
| } |
| auto funcSig = Signature({type, type}, type); |
| auto func = Builder::makeFunction(getBinaryFuncName(curr), funcSig, {}); |
| func->body = |
| builder.makeIf(builder.makeUnary(eqZOp, builder.makeLocalGet(1, type)), |
| builder.makeConst(zeroLit), |
| result); |
| // TODO: use unique_ptr properly and do not release ownership. |
| return func.release(); |
| } |
| |
| 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 == 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("unexpected op"); |
| } |
| |
| auto func = |
| Builder::makeFunction(getUnaryFuncName(curr), Signature(type, retType), {}); |
| func->body = builder.makeUnary(truncOp, builder.makeLocalGet(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.makeLocalGet(0, type), |
| builder.makeConst(fMin)), |
| builder.makeConst(iMin), |
| func->body); |
| // too big XXX see above |
| func->body = builder.makeIf( |
| builder.makeBinary( |
| geOp, builder.makeLocalGet(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.makeLocalGet(0, type), builder.makeLocalGet(0, type)), |
| // NB: min here as well. anything invalid => to the min |
| builder.makeConst(iMin), |
| func->body); |
| // TODO: use unique_ptr properly and do not release ownership. |
| return func.release(); |
| } |
| |
| 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; |
| } |
| |
| // f64-to-int = asm2wasm.f64-to-int; |
| auto import = new Function; |
| import->name = F64_TO_INT; |
| import->module = ASM2WASM; |
| import->base = F64_TO_INT; |
| import->type = Signature(Type::f64, Type::i32); |
| 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 != 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.makeCall(F64_TO_INT, {f64Value}, Type::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); } |
| |
| std::unique_ptr<Pass> create() override { |
| return std::make_unique<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 = |
| std::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 |