| #include "pass.h" | |
| #include "wasm-builder.h" | |
| #include "wasm.h" | |
| #include <limits> | |
| #include <memory> | |
| // Note: This pass is no longer used either in emscripten, or internally by any | |
| // other part of binaryen. Unless there are other uses that show up we may | |
| // consider this pass for removal. | |
| // By default LLVM emits nontrapping float-to-int instructions to implement its | |
| // fptoui/fptosi conversion instructions. This pass replaces these instructions | |
| // with code sequences which also implement LLVM's fptoui/fptosi, but which are | |
| // not semantically equivalent in wasm. This is because out-of-range inputs to | |
| // these instructions produce poison values. So we need only ensure that there | |
| // is no trap, but need not ensure any particular result. The transformation | |
| // in this pass is the same as the one used by LLVM to lower fptoui/fptosi | |
| // to wasm trapping instructions. | |
| // For example, if a conversion is guarded by a range check in the source, LLVM | |
| // can move the conversion before the check (and instead guard the use of the | |
| // result, which may be poison). This is valid in LLVM and for the nontrapping | |
| // wasm fptoint instructions but not for the trapping conversions. The | |
| // transformation in this pass is valid only if the nontrapping conversions | |
| // in the wasm were generated from LLVM and implement LLVM's conversion | |
| // semantics. | |
| namespace wasm { | |
| struct LLVMNonTrappingFPToIntLoweringImpl | |
| : public WalkerPass<PostWalker<LLVMNonTrappingFPToIntLoweringImpl>> { | |
| bool isFunctionParallel() override { return true; } | |
| std::unique_ptr<Pass> create() override { | |
| return std::make_unique<LLVMNonTrappingFPToIntLoweringImpl>(); | |
| } | |
| UnaryOp getReplacementOp(UnaryOp op) { | |
| switch (op) { | |
| case TruncSatSFloat32ToInt32: | |
| return TruncSFloat32ToInt32; | |
| case TruncSatUFloat32ToInt32: | |
| return TruncUFloat32ToInt32; | |
| case TruncSatSFloat64ToInt32: | |
| return TruncSFloat64ToInt32; | |
| case TruncSatUFloat64ToInt32: | |
| return TruncUFloat64ToInt32; | |
| case TruncSatSFloat32ToInt64: | |
| return TruncSFloat32ToInt64; | |
| case TruncSatUFloat32ToInt64: | |
| return TruncUFloat32ToInt64; | |
| case TruncSatSFloat64ToInt64: | |
| return TruncSFloat64ToInt64; | |
| case TruncSatUFloat64ToInt64: | |
| return TruncUFloat64ToInt64; | |
| default: | |
| WASM_UNREACHABLE("Unexpected opcode"); | |
| } | |
| } | |
| template<typename From, typename To> void replaceSigned(Unary* curr) { | |
| BinaryOp ltOp; | |
| UnaryOp absOp; | |
| switch (curr->op) { | |
| case TruncSatSFloat32ToInt32: | |
| case TruncSatSFloat32ToInt64: | |
| ltOp = LtFloat32; | |
| absOp = AbsFloat32; | |
| break; | |
| case TruncSatSFloat64ToInt32: | |
| case TruncSatSFloat64ToInt64: | |
| ltOp = LtFloat64; | |
| absOp = AbsFloat64; | |
| break; | |
| default: | |
| WASM_UNREACHABLE("Unexpected opcode"); | |
| } | |
| Builder builder(*getModule()); | |
| Index v = Builder::addVar(getFunction(), curr->value->type); | |
| // if fabs(operand) < INT_MAX then use the trapping operation, else return | |
| // INT_MIN. The alternate value is correct for the case where the input is | |
| // INT_MIN itself; otherwise it's UB so any value will do. | |
| replaceCurrent(builder.makeIf( | |
| builder.makeBinary( | |
| ltOp, | |
| builder.makeUnary( | |
| absOp, builder.makeLocalTee(v, curr->value, curr->value->type)), | |
| builder.makeConst(static_cast<From>(std::numeric_limits<To>::max()))), | |
| builder.makeUnary(getReplacementOp(curr->op), | |
| builder.makeLocalGet(v, curr->value->type)), | |
| builder.makeConst(std::numeric_limits<To>::min()))); | |
| } | |
| template<typename From, typename To> void replaceUnsigned(Unary* curr) { | |
| BinaryOp ltOp, geOp; | |
| switch (curr->op) { | |
| case TruncSatUFloat32ToInt32: | |
| case TruncSatUFloat32ToInt64: | |
| ltOp = LtFloat32; | |
| geOp = GeFloat32; | |
| break; | |
| case TruncSatUFloat64ToInt32: | |
| case TruncSatUFloat64ToInt64: | |
| ltOp = LtFloat64; | |
| geOp = GeFloat64; | |
| break; | |
| default: | |
| WASM_UNREACHABLE("Unexpected opcode"); | |
| } | |
| Builder builder(*getModule()); | |
| Index v = Builder::addVar(getFunction(), curr->value->type); | |
| // if op < INT_MAX and op >= 0 then use the trapping operation, else return | |
| // 0 | |
| replaceCurrent(builder.makeIf( | |
| builder.makeBinary( | |
| AndInt32, | |
| builder.makeBinary( | |
| ltOp, | |
| builder.makeLocalTee(v, curr->value, curr->value->type), | |
| builder.makeConst(static_cast<From>(std::numeric_limits<To>::max()))), | |
| builder.makeBinary(geOp, | |
| builder.makeLocalGet(v, curr->value->type), | |
| builder.makeConst(static_cast<From>(0.0)))), | |
| builder.makeUnary(getReplacementOp(curr->op), | |
| builder.makeLocalGet(v, curr->value->type)), | |
| builder.makeConst(static_cast<To>(0)))); | |
| } | |
| void visitUnary(Unary* curr) { | |
| switch (curr->op) { | |
| case TruncSatSFloat32ToInt32: | |
| replaceSigned<float, int32_t>(curr); | |
| break; | |
| case TruncSatSFloat64ToInt32: | |
| replaceSigned<double, int32_t>(curr); | |
| break; | |
| case TruncSatSFloat32ToInt64: | |
| replaceSigned<float, int64_t>(curr); | |
| break; | |
| case TruncSatSFloat64ToInt64: | |
| replaceSigned<double, int64_t>(curr); | |
| break; | |
| case TruncSatUFloat32ToInt32: | |
| replaceUnsigned<float, uint32_t>(curr); | |
| break; | |
| case TruncSatUFloat64ToInt32: | |
| replaceUnsigned<double, uint32_t>(curr); | |
| break; | |
| case TruncSatUFloat32ToInt64: | |
| replaceUnsigned<float, uint64_t>(curr); | |
| break; | |
| case TruncSatUFloat64ToInt64: | |
| replaceUnsigned<double, uint64_t>(curr); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| void doWalkFunction(Function* func) { Super::doWalkFunction(func); } | |
| }; | |
| struct LLVMNonTrappingFPToIntLowering : public Pass { | |
| void run(Module* module) override { | |
| if (!module->features.hasTruncSat()) { | |
| return; | |
| } | |
| PassRunner runner(module); | |
| // Run the Impl pass as an inner pass in parallel. This pass updates the | |
| // module features, so it can't be parallel. | |
| runner.add(std::make_unique<LLVMNonTrappingFPToIntLoweringImpl>()); | |
| runner.setIsNested(true); | |
| runner.run(); | |
| module->features.disable(FeatureSet::TruncSat); | |
| } | |
| }; | |
| Pass* createLLVMNonTrappingFPToIntLoweringPass() { | |
| return new LLVMNonTrappingFPToIntLowering(); | |
| } | |
| } // namespace wasm |