diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/PrimitiveBinaryOperationFIF.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/PrimitiveBinaryOperationFIF.java index 598d3601c85..8e7fd5637f4 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/PrimitiveBinaryOperationFIF.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/factories/PrimitiveBinaryOperationFIF.java @@ -19,10 +19,7 @@ package org.jetbrains.kotlin.js.translate.intrinsic.functions.factories; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableMap; -import com.google.dart.compiler.backend.js.ast.JsBinaryOperation; -import com.google.dart.compiler.backend.js.ast.JsBinaryOperator; -import com.google.dart.compiler.backend.js.ast.JsExpression; -import com.google.dart.compiler.backend.js.ast.JsInvocation; +import com.google.dart.compiler.backend.js.ast.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; @@ -64,13 +61,24 @@ public enum PrimitiveBinaryOperationFIF implements FunctionIntrinsicFactory { } private static final BinaryOperationIntrinsicBase INT_MULTIPLICATION_INTRINSIC = new BinaryOperationIntrinsicBase() { + // IEEE 754 mantissa is 52 bits long. Assuming one argument may be up to 2^31, the second argument should be + // not greater than 2^21 in order their product don't exceed 2^52. We preserve two extra bits to be more safe. + private static final int SAFE_THRESHOLD = 2 << 19; + @NotNull @Override - public JsExpression doApply( - @NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context - ) { + public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) { + if (isSafeConstant(left) || isSafeConstant(right)) { + return JsAstUtils.toInt32(JsAstUtils.mul(left, right)); + } return new JsInvocation(Namer.imul(), left, right); } + + private boolean isSafeConstant(@NotNull JsExpression expression) { + if (!(expression instanceof JsNumberLiteral.JsIntLiteral)) return false; + int value = ((JsNumberLiteral.JsIntLiteral) expression).value; + return Math.abs(value) < SAFE_THRESHOLD; + } }; @NotNull @@ -94,7 +102,7 @@ public enum PrimitiveBinaryOperationFIF implements FunctionIntrinsicFactory { @NotNull private static final NamePredicate BINARY_OPERATIONS = new NamePredicate(OperatorNameConventions.BINARY_OPERATION_NAMES); - private static final DescriptorPredicate INT_BINARY_OPERATIONS = pattern("Int.plus|minus|div(Int)"); + private static final DescriptorPredicate INT_BINARY_OPERATIONS = pattern("Int.plus|minus(Int)"); private static final DescriptorPredicate SIMPLE_INT_MULTIPLICATION = pattern("Byte|Short.times(Byte|Short)"); private static final DescriptorPredicate INT_DIVISION = pattern("Byte|Short|Int.div(Byte|Short|Int)"); private static final DescriptorPredicate PRIMITIVE_NUMBERS_BINARY_OPERATIONS = @@ -155,11 +163,14 @@ public enum PrimitiveBinaryOperationFIF implements FunctionIntrinsicFactory { if (INT_WITH_BIT_OPERATIONS.apply(descriptor)) { JsBinaryOperator op = BINARY_BITWISE_OPERATIONS.get(descriptor.getName().asString()); if (op != null) { - return new PrimitiveBinaryOperationFunctionIntrinsic(op); + return new OptimizedIntBinaryOperationInstrinsic(op); } } JsBinaryOperator operator = getOperator(descriptor); - if (INT_BINARY_OPERATIONS.apply(descriptor) || SIMPLE_INT_MULTIPLICATION.apply(descriptor) || INT_DIVISION.apply(descriptor)) { + if (INT_BINARY_OPERATIONS.apply(descriptor)) { + return new AdditiveIntBinaryOperationInstrinsic(operator); + } + if (SIMPLE_INT_MULTIPLICATION.apply(descriptor) || INT_DIVISION.apply(descriptor)) { return new IntBinaryOperationFunctionIntrinsic(operator); } BinaryOperationIntrinsicBase result = new PrimitiveBinaryOperationFunctionIntrinsic(operator); @@ -217,6 +228,48 @@ public enum PrimitiveBinaryOperationFIF implements FunctionIntrinsicFactory { } } + private static class OptimizedIntBinaryOperationInstrinsic extends PrimitiveBinaryOperationFunctionIntrinsic { + public OptimizedIntBinaryOperationInstrinsic(@NotNull JsBinaryOperator operator) { + super(operator); + } + + @NotNull + @Override + public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) { + left = unwrapAdditive(left); + right = unwrapAdditive(right); + return super.doApply(left, right, context); + } + + @NotNull + private static JsExpression unwrapAdditive(@NotNull JsExpression expression) { + JsExpression toIntArgument = JsAstUtils.extractToInt32Argument(expression); + if (toIntArgument == null) return expression; + + if (!(toIntArgument instanceof JsBinaryOperation)) return expression; + JsBinaryOperator operator = ((JsBinaryOperation) toIntArgument).getOperator(); + switch (operator) { + case ADD: + case SUB: + return toIntArgument; + default: + return expression; + } + } + } + + private static class AdditiveIntBinaryOperationInstrinsic extends OptimizedIntBinaryOperationInstrinsic { + public AdditiveIntBinaryOperationInstrinsic(@NotNull JsBinaryOperator operator) { + super(operator); + } + + @NotNull + @Override + public JsExpression doApply(@NotNull JsExpression left, @NotNull JsExpression right, @NotNull TranslationContext context) { + return JsAstUtils.toInt32(super.doApply(left, right, context)); + } + } + private static class CharAndIntBinaryOperationFunctionIntrinsic extends BinaryOperationIntrinsicBase { @NotNull diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsAstUtils.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsAstUtils.java index 5049e9ba060..363e59e251e 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsAstUtils.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsAstUtils.java @@ -135,6 +135,18 @@ public final class JsAstUtils { return new JsBinaryOperation(JsBinaryOperator.BIT_OR, expression, JsNumberLiteral.ZERO); } + @Nullable + public static JsExpression extractToInt32Argument(@NotNull JsExpression expression) { + if (!(expression instanceof JsBinaryOperation)) return null; + + JsBinaryOperation binary = (JsBinaryOperation) expression; + if (binary.getOperator() != JsBinaryOperator.BIT_OR) return null; + + if (!(binary.getArg2() instanceof JsNumberLiteral.JsIntLiteral)) return null; + JsNumberLiteral.JsIntLiteral arg2 = (JsNumberLiteral.JsIntLiteral) binary.getArg2(); + return arg2.value == 0 ? binary.getArg1() : null; + } + @NotNull public static JsExpression charToInt(@NotNull JsExpression expression) { return invokeMethod(expression, "charCodeAt", JsNumberLiteral.ZERO); diff --git a/js/js.translator/testData/box/number/intOverflow.kt b/js/js.translator/testData/box/number/intOverflow.kt index 041d9de23c9..ba76aad6290 100644 --- a/js/js.translator/testData/box/number/intOverflow.kt +++ b/js/js.translator/testData/box/number/intOverflow.kt @@ -4,6 +4,8 @@ fun bigValue() = 0x7FFFFFFC fun mediumValue() = 0x12345 +fun four() = 4 + fun box(): String { var v = bigValue() + 1 if (v != 0x7FFFFFFD) return "fail1: $v" @@ -11,11 +13,17 @@ fun box(): String { v = bigValue() + 8 if (v != -0x7FFFFFFC) return "fail2: $v" + v = bigValue() + four() + 4 + if (v != -0x7FFFFFFC) return "fail3: $v" + + v = (bigValue() + four() - 4) shr 1 + if (v != 0x3FFFFFFE) return "fail3: $v" + v = mediumValue() * 0x23456 - if (v != -2112496338) return "fail3: $v" + if (v != -2112496338) return "fail5: $v" v = bigValue() * bigValue() - if (v != 16) return "fail4: $v" + if (v != 16) return "fail6: $v" return "OK" } \ No newline at end of file