| /* | |
| * 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 "pass.h" | |
| #include "support/mixed_arena.h" | |
| #include "support/name.h" | |
| #include "wasm-builder.h" | |
| #include "wasm-type.h" | |
| #include "wasm.h" | |
| namespace wasm { | |
| static const Name I64S_REM("i64s-rem"); | |
| static const Name I64U_REM("i64u-rem"); | |
| static const Name I64S_DIV("i64s-div"); | |
| static const Name I64U_DIV("i64u-div"); | |
| static const Name F64_TO_INT64("f64-to-int64"); | |
| static const Name F64_TO_UINT64("f64-to-uint64"); | |
| static const Name F32_TO_INT64("f32-to-int64"); | |
| static const Name F32_TO_UINT64("f32-to-uint64"); | |
| 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), Type(funcSig, NonNullable, Exact), {}); | |
| 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), | |
| Type(Signature(type, retType), NonNullable, Exact), | |
| {}); | |
| 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 = Type(Signature(Type::f64, Type::i32), NonNullable, Inexact); | |
| 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 |