Aggressively try to use Integer objects

When doing math, try to do integer arithmetic when the arguments passed
are Integer objects, rather than always falling back to floating-point
math. This speeds up some benchmarks that rely heavily on integer
arithmetic.
This commit is contained in:
Greg Brail 2024-08-13 12:01:12 -07:00
Родитель cbb0e2f170
Коммит 8b7b580cb3
7 изменённых файлов: 94 добавлений и 22 удалений

Просмотреть файл

@ -120,7 +120,8 @@ public class ArrayLikeAbstractOperations {
break;
case FIND_INDEX:
case FIND_LAST_INDEX:
if (ScriptRuntime.toBoolean(result)) return ScriptRuntime.wrapNumber(i);
if (ScriptRuntime.toBoolean(result))
return ScriptRuntime.wrapNumber((double) i);
break;
}
}

Просмотреть файл

@ -458,7 +458,7 @@ public class ScriptRuntime {
// Preserve backward-compatibility with historical value of this.
public static final double negativeZero = Double.longBitsToDouble(0x8000000000000000L);
public static final Double zeroObj = Double.valueOf(0.0);
public static final Integer zeroObj = Integer.valueOf(0);
public static final Double negativeZeroObj = Double.valueOf(-0.0);
static double stringPrefixToNumber(String s, int start, int radix) {
@ -2991,6 +2991,9 @@ public class ScriptRuntime {
|| (val1 instanceof BigInteger && val2 instanceof Number)) {
throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt");
}
if (val1 instanceof Integer && val2 instanceof Integer) {
return add((Integer) val1, (Integer) val2);
}
if (val1 instanceof Number && val2 instanceof Number) {
return wrapNumber(((Number) val1).doubleValue() + ((Number) val2).doubleValue());
}
@ -3064,6 +3067,8 @@ public class ScriptRuntime {
return ((BigInteger) val1).subtract((BigInteger) val2);
} else if (val1 instanceof BigInteger || val2 instanceof BigInteger) {
throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt");
} else if (val1 instanceof Integer && val2 instanceof Integer) {
return subtract((Integer) val1, (Integer) val2);
} else {
return val1.doubleValue() - val2.doubleValue();
}
@ -3074,6 +3079,8 @@ public class ScriptRuntime {
return ((BigInteger) val1).multiply((BigInteger) val2);
} else if (val1 instanceof BigInteger || val2 instanceof BigInteger) {
throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt");
} else if (val1 instanceof Integer && val2 instanceof Integer) {
return multiply((Integer) val1, (Integer) val2);
} else {
return val1.doubleValue() * val2.doubleValue();
}
@ -3088,6 +3095,8 @@ public class ScriptRuntime {
} else if (val1 instanceof BigInteger || val2 instanceof BigInteger) {
throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt");
} else {
// Do not try to optimize for the integer case because JS doesn't
// have an integer type.
return val1.doubleValue() / val2.doubleValue();
}
}
@ -3101,10 +3110,41 @@ public class ScriptRuntime {
} else if (val1 instanceof BigInteger || val2 instanceof BigInteger) {
throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt");
} else {
// Do not try an integer-specific optimization because we need to get
// both +0 and -0 right.
return val1.doubleValue() % val2.doubleValue();
}
}
// Integer-optimized methods.
public static Object add(Integer i1, Integer i2) {
// Try to add integers for efficiency, but account for overflow
long r = (long) i1.intValue() + (long) i2.intValue();
if ((r >= Integer.MIN_VALUE) && (r <= Integer.MAX_VALUE)) {
return Integer.valueOf((int) r);
}
return Double.valueOf((double) r);
}
public static Number subtract(Integer i1, Integer i2) {
// Account for overflow
long r = (long) i1.intValue() - (long) i2.intValue();
if ((r >= Integer.MIN_VALUE) && (r <= Integer.MAX_VALUE)) {
return Integer.valueOf((int) r);
}
return Double.valueOf((double) r);
}
public static Number multiply(Integer i1, Integer i2) {
// Aunt for overflow
long r = (long) i1.intValue() * (long) i2.intValue();
if ((r >= Integer.MIN_VALUE) && (r <= Integer.MAX_VALUE)) {
return Integer.valueOf((int) r);
}
return Double.valueOf((double) r);
}
public static Number exponentiate(Number val1, Number val2) {
if (val1 instanceof BigInteger && val2 instanceof BigInteger) {
if (((BigInteger) val2).signum() == -1) {
@ -3130,6 +3170,8 @@ public class ScriptRuntime {
return ((BigInteger) val1).and((BigInteger) val2);
} else if (val1 instanceof BigInteger || val2 instanceof BigInteger) {
throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt");
} else if (val1 instanceof Integer && val2 instanceof Integer) {
return Integer.valueOf(((Integer) val1).intValue() & ((Integer) val2).intValue());
} else {
int result = toInt32(val1.doubleValue()) & toInt32(val2.doubleValue());
return Double.valueOf(result);
@ -3141,6 +3183,8 @@ public class ScriptRuntime {
return ((BigInteger) val1).or((BigInteger) val2);
} else if (val1 instanceof BigInteger || val2 instanceof BigInteger) {
throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt");
} else if (val1 instanceof Integer && val2 instanceof Integer) {
return Integer.valueOf(((Integer) val1).intValue() | ((Integer) val2).intValue());
} else {
int result = toInt32(val1.doubleValue()) | toInt32(val2.doubleValue());
return Double.valueOf(result);
@ -3152,6 +3196,8 @@ public class ScriptRuntime {
return ((BigInteger) val1).xor((BigInteger) val2);
} else if (val1 instanceof BigInteger || val2 instanceof BigInteger) {
throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt");
} else if (val1 instanceof Integer && val2 instanceof Integer) {
return Integer.valueOf(((Integer) val1).intValue() ^ ((Integer) val2).intValue());
} else {
int result = toInt32(val1.doubleValue()) ^ toInt32(val2.doubleValue());
return Double.valueOf(result);
@ -3169,6 +3215,8 @@ public class ScriptRuntime {
}
} else if (val1 instanceof BigInteger || val2 instanceof BigInteger) {
throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt");
} else if (val1 instanceof Integer && val2 instanceof Integer) {
return Integer.valueOf(((Integer) val1).intValue() << ((Integer) val2).intValue());
} else {
int result = toInt32(val1.doubleValue()) << toInt32(val2.doubleValue());
return Double.valueOf(result);
@ -3186,6 +3234,8 @@ public class ScriptRuntime {
}
} else if (val1 instanceof BigInteger || val2 instanceof BigInteger) {
throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt");
} else if (val1 instanceof Integer && val2 instanceof Integer) {
return Integer.valueOf(((Integer) val1).intValue() >> ((Integer) val2).intValue());
} else {
int result = toInt32(val1.doubleValue()) >> toInt32(val2.doubleValue());
return Double.valueOf(result);
@ -3195,6 +3245,8 @@ public class ScriptRuntime {
public static Number bitwiseNOT(Number val) {
if (val instanceof BigInteger) {
return ((BigInteger) val).not();
} else if (val instanceof Integer) {
return Integer.valueOf(~((Integer) val).intValue());
} else {
int result = ~toInt32(val.doubleValue());
return Double.valueOf(result);
@ -3292,6 +3344,12 @@ public class ScriptRuntime {
} else {
result = ((BigInteger) number).subtract(BigInteger.ONE);
}
} else if (number instanceof Integer) {
if ((incrDecrMask & Node.DECR_FLAG) == 0) {
result = ((Integer) number).intValue() + 1;
} else {
result = ((Integer) number).intValue() - 1;
}
} else {
if ((incrDecrMask & Node.DECR_FLAG) == 0) {
result = number.doubleValue() + 1.0;
@ -3332,6 +3390,12 @@ public class ScriptRuntime {
} else {
result = ((BigInteger) number).subtract(BigInteger.ONE);
}
} else if (number instanceof Integer) {
if ((incrDecrMask & Node.DECR_FLAG) == 0) {
result = ((Integer) number).intValue() + 1;
} else {
result = ((Integer) number).intValue() - 1;
}
} else {
if ((incrDecrMask & Node.DECR_FLAG) == 0) {
result = number.doubleValue() + 1.0;
@ -3371,6 +3435,12 @@ public class ScriptRuntime {
} else {
result = ((BigInteger) number).subtract(BigInteger.ONE);
}
} else if (number instanceof Integer) {
if ((incrDecrMask & Node.DECR_FLAG) == 0) {
result = ((Integer) number).intValue() + 1;
} else {
result = ((Integer) number).intValue() - 1;
}
} else {
if ((incrDecrMask & Node.DECR_FLAG) == 0) {
result = number.doubleValue() + 1.0;
@ -3390,6 +3460,17 @@ public class ScriptRuntime {
if (val instanceof BigInteger) {
return ((BigInteger) val).negate();
}
if (val instanceof Integer) {
int iv = (Integer) val;
if (iv == 0) {
return negativeZeroObj;
}
if (iv > Integer.MIN_VALUE && iv < Integer.MAX_VALUE) {
// Account for twos-complement representation by not trying
// to negate values at the extremes
return Integer.valueOf(-((Integer) val).intValue());
}
}
return -val.doubleValue();
}

Просмотреть файл

@ -1143,7 +1143,7 @@ public class Codegen implements Evaluator {
ByteCode.GETSTATIC,
"org/mozilla/javascript/ScriptRuntime",
"zeroObj",
"Ljava/lang/Double;");
"Ljava/lang/Integer;");
} else {
cfw.addPush(num);
addDoubleWrap(cfw);
@ -1154,7 +1154,7 @@ public class Codegen implements Evaluator {
ByteCode.GETSTATIC,
"org/mozilla/javascript/optimizer/OptRuntime",
"oneObj",
"Ljava/lang/Double;");
"Ljava/lang/Integer;");
return;
} else if (num == -1.0) {
@ -1162,7 +1162,7 @@ public class Codegen implements Evaluator {
ByteCode.GETSTATIC,
"org/mozilla/javascript/optimizer/OptRuntime",
"minusOneObj",
"Ljava/lang/Double;");
"Ljava/lang/Integer;");
} else if (Double.isNaN(num)) {
cfw.add(

Просмотреть файл

@ -21,8 +21,8 @@ import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Undefined;
public final class OptRuntime extends ScriptRuntime {
public static final Double oneObj = Double.valueOf(1.0);
public static final Double minusOneObj = Double.valueOf(-1.0);
public static final Integer oneObj = Integer.valueOf(1);
public static final Integer minusOneObj = Integer.valueOf(-1);
/** Implement ....() call shrinking optimizer code. */
public static Object call0(Callable fun, Scriptable thisObj, Context cx, Scriptable scope) {
@ -145,16 +145,7 @@ public final class OptRuntime extends ScriptRuntime {
}
public static Double wrapDouble(double num) {
if (num == 0.0) {
if (1 / num > 0) {
// +0.0
return zeroObj;
}
} else if (num == 1.0) {
return oneObj;
} else if (num == -1.0) {
return minusOneObj;
} else if (Double.isNaN(num)) {
if (Double.isNaN(num)) {
return NaNobj;
}
return Double.valueOf(num);

Просмотреть файл

@ -56,9 +56,9 @@ public class Bug687669Test {
public void eval() {
// test EmptyStatement node doesn't infer with return values (in
// contrast to wrapping EmptyExpression into an ExpressionStatement)
assertEquals(1d, eval("1;;;;"));
assertEquals(1, eval("1;;;;"));
assertEquals(Undefined.instance, eval("(function(){1;;;;})()"));
assertEquals(1d, eval("(function(){return 1;;;;})()"));
assertEquals(1, eval("(function(){return 1;;;;})()"));
}
@Test

Просмотреть файл

@ -48,7 +48,7 @@ public class DefineFunctionPropertiesTest {
public void simpleFunction() {
try (Context cx = Context.enter()) {
Object result = cx.evaluateString(global, "f(7) + 1", "test source", 1, null);
assertEquals(15.0, result);
assertEquals(15, result);
}
}

Просмотреть файл

@ -440,8 +440,7 @@ public class NativeConsoleTest {
assertPrintCalls(
"console.assert(false, 'Fail', 1)",
Collections.singletonList(
new PrinterCall(
Level.ERROR, new Object[] {"Assertion failed: Fail", 1.0})));
new PrinterCall(Level.ERROR, new Object[] {"Assertion failed: Fail", 1})));
assertPrintMsg("console.assert(false, 'Fail', 1)", "Assertion failed: Fail 1");
assertPrintCalls(