JS: optimize integer overflow emulation when possible
This commit is contained in:
+63
-10
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
+10
-2
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user