diff --git a/rhino/src/main/java/org/mozilla/javascript/BaseFunction.java b/rhino/src/main/java/org/mozilla/javascript/BaseFunction.java index 5229ac4d7e..13794f945b 100644 --- a/rhino/src/main/java/org/mozilla/javascript/BaseFunction.java +++ b/rhino/src/main/java/org/mozilla/javascript/BaseFunction.java @@ -509,7 +509,7 @@ protected boolean hasPrototypeProperty() { return prototypeProperty != null || this instanceof NativeFunction; } - protected Object getPrototypeProperty() { + public Object getPrototypeProperty() { Object result = prototypeProperty; if (result == null) { // only create default prototype on native JavaScript functions, diff --git a/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java b/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java index 1c3ac25ab7..3108512e32 100644 --- a/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java +++ b/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java @@ -3242,30 +3242,30 @@ public static Number remainder(Number val1, Number val2) { // 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(); + // Do 64-bit addition to account for overflow + long r = i1.longValue() + i2.longValue(); if ((r >= Integer.MIN_VALUE) && (r <= Integer.MAX_VALUE)) { - return Integer.valueOf((int) r); + return (int) r; } - return Double.valueOf((double) r); + return (double) r; } public static Number subtract(Integer i1, Integer i2) { // Account for overflow - long r = (long) i1.intValue() - (long) i2.intValue(); + long r = i1.longValue() - i2.longValue(); if ((r >= Integer.MIN_VALUE) && (r <= Integer.MAX_VALUE)) { - return Integer.valueOf((int) r); + return (int) r; } - return Double.valueOf((double) r); + return (double) r; } public static Number multiply(Integer i1, Integer i2) { - // Aunt for overflow - long r = (long) i1.intValue() * (long) i2.intValue(); + // Account for overflow + long r = i1.longValue() * i2.longValue(); if ((r >= Integer.MIN_VALUE) && (r <= Integer.MAX_VALUE)) { - return Integer.valueOf((int) r); + return (int) r; } - return Double.valueOf((double) r); + return (double) r; } @SuppressWarnings("AndroidJdkLibsChecker") diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/BaseFunctionLinker.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/BaseFunctionLinker.java new file mode 100644 index 0000000000..cf92e7dabf --- /dev/null +++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/BaseFunctionLinker.java @@ -0,0 +1,64 @@ +package org.mozilla.javascript.optimizer; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import jdk.dynalink.StandardNamespace; +import jdk.dynalink.StandardOperation; +import jdk.dynalink.linker.GuardedInvocation; +import jdk.dynalink.linker.LinkRequest; +import jdk.dynalink.linker.LinkerServices; +import jdk.dynalink.linker.TypeBasedGuardingDynamicLinker; +import jdk.dynalink.linker.support.Guards; +import org.mozilla.javascript.BaseFunction; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; + +/** + * This linker optimizes accesses to the "prototype" property of any standard Rhino function so that + * it calls the native function rather than going through' a property name match. + */ +@SuppressWarnings("AndroidJdkLibsChecker") +class BaseFunctionLinker implements TypeBasedGuardingDynamicLinker { + @Override + public boolean canLinkType(Class> type) { + return BaseFunction.class.isAssignableFrom(type); + } + + @Override + public GuardedInvocation getGuardedInvocation(LinkRequest req, LinkerServices svc) + throws Exception { + if (req.isCallSiteUnstable()) { + return null; + } + + ParsedOperation op = new ParsedOperation(req.getCallSiteDescriptor().getOperation()); + MethodHandle mh = null; + MethodHandle guard = null; + + if (op.isNamespace(StandardNamespace.PROPERTY)) { + if (op.isOperation(StandardOperation.GET, RhinoOperation.GETNOWARN) + && "prototype".equals(op.getName())) { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodType mType = req.getCallSiteDescriptor().getMethodType(); + mh = lookup.findStatic(BaseFunctionLinker.class, "getPrototype", mType); + guard = Guards.getInstanceOfGuard(BaseFunction.class); + } + } + + if (mh != null) { + assert guard != null; + if (DefaultLinker.DEBUG) { + System.out.println(op + " base function operation"); + } + return new GuardedInvocation(mh, guard); + } + + return null; + } + + @SuppressWarnings("unused") + private static Object getPrototype(Object o, Context cx, Scriptable scope) { + return ((BaseFunction) o).getPrototypeProperty(); + } +} diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/BooleanLinker.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/BooleanLinker.java new file mode 100644 index 0000000000..cbd7ed96e9 --- /dev/null +++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/BooleanLinker.java @@ -0,0 +1,74 @@ +package org.mozilla.javascript.optimizer; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Objects; +import jdk.dynalink.linker.GuardedInvocation; +import jdk.dynalink.linker.LinkRequest; +import jdk.dynalink.linker.LinkerServices; +import jdk.dynalink.linker.TypeBasedGuardingDynamicLinker; +import jdk.dynalink.linker.support.Guards; + +/** + * This linker short-circuits invocations of "==", "===", and "toBoolean" operations when the + * argument is already a boolean. + */ +@SuppressWarnings("AndroidJdkLibsChecker") +class BooleanLinker implements TypeBasedGuardingDynamicLinker { + @Override + public boolean canLinkType(Class> type) { + return Boolean.class.equals(type); + } + + @Override + public GuardedInvocation getGuardedInvocation(LinkRequest req, LinkerServices svc) + throws Exception { + if (req.isCallSiteUnstable()) { + return null; + } + + ParsedOperation op = new ParsedOperation(req.getCallSiteDescriptor().getOperation()); + MethodHandle mh = null; + MethodHandle guard = null; + + if (op.isNamespace(RhinoNamespace.MATH)) { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodType mType = req.getCallSiteDescriptor().getMethodType(); + + if (op.isOperation(RhinoOperation.EQ, RhinoOperation.SHALLOWEQ) + && req.getArguments()[1] instanceof Boolean) { + mh = lookup.findStatic(BooleanLinker.class, "eq", mType); + guard = lookup.findStatic(BooleanLinker.class, "testEq", mType); + } else if (op.isOperation(RhinoOperation.TOBOOLEAN)) { + mh = lookup.findStatic(BooleanLinker.class, "toBoolean", mType); + guard = Guards.getInstanceOfGuard(Boolean.class); + } + } + + if (mh != null) { + assert guard != null; + if (DefaultLinker.DEBUG) { + System.out.println(op + " boolean operation"); + } + return new GuardedInvocation(mh, guard); + } + + return null; + } + + @SuppressWarnings("unused") + private static boolean testEq(Object lval, Object rval) { + return lval instanceof Boolean && rval instanceof Boolean; + } + + @SuppressWarnings("unused") + private static boolean eq(Object lval, Object rval) { + return Objects.equals(lval, rval); + } + + @SuppressWarnings("unused") + private static boolean toBoolean(Object raw) { + return ((Boolean) raw); + } +} diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/Bootstrapper.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/Bootstrapper.java index 4d0fdfc7b5..b8d9e44cd4 100644 --- a/rhino/src/main/java/org/mozilla/javascript/optimizer/Bootstrapper.java +++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/Bootstrapper.java @@ -3,6 +3,7 @@ import java.lang.invoke.CallSite; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.util.Arrays; import java.util.regex.Pattern; import jdk.dynalink.CallSiteDescriptor; import jdk.dynalink.DynamicLinker; @@ -10,18 +11,29 @@ import jdk.dynalink.Operation; import jdk.dynalink.StandardNamespace; import jdk.dynalink.StandardOperation; +import jdk.dynalink.linker.support.CompositeTypeBasedGuardingDynamicLinker; import jdk.dynalink.support.ChainedCallSite; import org.mozilla.classfile.ByteCode; import org.mozilla.classfile.ClassFileWriter; /** * The Bootstrapper contains the method that is called by invokedynamic instructions in the bytecode - * to map a call site to a method. We should never go down this entire code path on Android. + * to map a call site to a method. The "bootstrap" method here is called the first time the runtime + * encounters a particular "invokedynamic" call site, and it is responsible for setting up method + * handles that may be used to invoke code. To learn more about this entire sequence, read up on the + * "jdk.dynalink" package. + * + *
We will never go down this entire code path on Android because we do not support bytecode
+ * generation there.
*/
@SuppressWarnings("AndroidJdkLibsChecker")
public class Bootstrapper {
private static final Pattern SEPARATOR = Pattern.compile(":");
+ /**
+ * This is the method handle that's wired in to the bytecode for every dynamic call site in the
+ * bytecode.
+ */
public static final ClassFileWriter.MHandle BOOTSTRAP_HANDLE =
new ClassFileWriter.MHandle(
ByteCode.MH_INVOKESTATIC,
@@ -33,27 +45,50 @@ public class Bootstrapper {
private static final DynamicLinker linker;
static {
- // Set up the linkers
+ // Set up the linkers that will map each call site to a method handle.
DynamicLinkerFactory factory = new DynamicLinkerFactory();
- // The const-aware-linker will only bind a few operations, and everything
- // else will fall back to the default linker, which will always bind.
- factory.setPrioritizedLinkers(new ConstAwareLinker(), new DefaultLinker());
+ // Set up a linker that will delegate to other linkers based on the class
+ // of the first argument to each dynamic invocation. (That's why the method
+ // signatures in "Signatures" sometimes have different orders than their
+ // counterparts in "ScriptRuntime".)
+ // The linker caches the results so that it can efficiently only delegate to
+ // compatible linkers. It will still go in order, so we put the linkers
+ // likely to have the biggest impact on performance at the top of the list.
+ CompositeTypeBasedGuardingDynamicLinker typeLinker =
+ new CompositeTypeBasedGuardingDynamicLinker(
+ Arrays.asList(
+ new ConstAwareLinker(),
+ new BooleanLinker(),
+ new IntegerLinker(),
+ new DoubleLinker(),
+ new StringLinker(),
+ new ConsStringLinker(),
+ new NativeArrayLinker(),
+ new BaseFunctionLinker()));
+ // Add the default linker, which can link anything no matter what.
+ factory.setPrioritizedLinkers(typeLinker, new DefaultLinker());
linker = factory.createLinker();
}
- /** This is the method called by every call site in the bytecode to map it to a function. */
+ /**
+ * This is the method called by every call site in the bytecode to map it to a method handle.
+ */
@SuppressWarnings("unused")
public static CallSite bootstrap(MethodHandles.Lookup lookup, String name, MethodType mType)
throws NoSuchMethodException {
Operation op = parseOperation(name);
- // ChainedCallSite lets a call site have a few options for complex situations
+ // ChainedCallSite lets a call site have a few options for complex situations.
+ // It caches up to eight invocations, so that we can quickly select the best
+ // implementation in situations where the same call site is invoked in different
+ // contexts.
return linker.link(new ChainedCallSite(new CallSiteDescriptor(lookup, op, mType)));
}
/**
- * Operation names in the bytecode are names like "PROP:GET:
+ *
+ */
+@SuppressWarnings("AndroidJdkLibsChecker")
+class ConsStringLinker implements TypeBasedGuardingDynamicLinker {
+ @Override
+ public boolean canLinkType(Class> type) {
+ return ConsString.class.equals(type);
+ }
+
+ @Override
+ public GuardedInvocation getGuardedInvocation(LinkRequest req, LinkerServices svc)
+ throws Exception {
+ if (req.isCallSiteUnstable()) {
+ return null;
+ }
+ Object arg2 = null;
+ if (req.getArguments().length > 1) {
+ arg2 = req.getArguments()[1];
+ }
+
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ ParsedOperation op = new ParsedOperation(req.getCallSiteDescriptor().getOperation());
+ MethodType mType = req.getCallSiteDescriptor().getMethodType();
+ MethodHandle mh = null;
+ MethodHandle guard = null;
+
+ if (op.isNamespace(RhinoNamespace.MATH)) {
+ if (op.isOperation(RhinoOperation.ADD)) {
+ MethodType guardType = mType.changeReturnType(Boolean.TYPE);
+ if (arg2 instanceof CharSequence) {
+ mh = lookup.findStatic(ConsStringLinker.class, "add", mType);
+ guard = lookup.findStatic(ConsStringLinker.class, "testAdd", guardType);
+ }
+ }
+ } else if (op.isNamespace(StandardNamespace.PROPERTY)) {
+ if (op.isOperation(StandardOperation.GET, RhinoOperation.GETNOWARN)
+ && "length".equals(op.getName())) {
+ mh = lookup.findStatic(ConsStringLinker.class, "getLength", mType);
+ guard = Guards.getInstanceOfGuard(ConsString.class);
+ }
+ }
+
+ if (mh != null) {
+ assert guard != null;
+ if (DefaultLinker.DEBUG) {
+ System.out.println(op + " ConsString operation");
+ }
+ return new GuardedInvocation(mh, guard);
+ }
+
+ return null;
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean testAdd(Object lval, Object rval, Context cx) {
+ return lval instanceof ConsString && rval instanceof CharSequence;
+ }
+
+ @SuppressWarnings("unused")
+ private static Object add(Object lval, Object rval, Context cx) {
+ return new ConsString((ConsString) lval, ((CharSequence) rval).toString());
+ }
+
+ @SuppressWarnings("unused")
+ private static Object getLength(Object o, Context cx, Scriptable scope) {
+ return ((ConsString) o).length();
+ }
+}
diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/ConstAwareLinker.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/ConstAwareLinker.java
index d7edf22b89..a7ef281b58 100644
--- a/rhino/src/main/java/org/mozilla/javascript/optimizer/ConstAwareLinker.java
+++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/ConstAwareLinker.java
@@ -6,23 +6,35 @@
import jdk.dynalink.StandardNamespace;
import jdk.dynalink.StandardOperation;
import jdk.dynalink.linker.GuardedInvocation;
-import jdk.dynalink.linker.GuardingDynamicLinker;
import jdk.dynalink.linker.LinkRequest;
import jdk.dynalink.linker.LinkerServices;
+import jdk.dynalink.linker.TypeBasedGuardingDynamicLinker;
import jdk.dynalink.linker.support.Guards;
import org.mozilla.javascript.NativeWith;
import org.mozilla.javascript.RhinoException;
import org.mozilla.javascript.ScriptableObject;
+/**
+ * This linker optimizes accesses to constants, either as object properties or in the current scope.
+ * These constants must be truly constants, which means neither "writable" nor "configurable," which
+ * pretty much means that they were declared with the "const" keyword. In those cases, it will
+ * replace the entire property lookup with code that directly returns the constant value, which is
+ * much faster.
+ */
@SuppressWarnings("AndroidJdkLibsChecker")
-class ConstAwareLinker implements GuardingDynamicLinker {
+class ConstAwareLinker implements TypeBasedGuardingDynamicLinker {
+ @Override
+ public boolean canLinkType(Class> type) {
+ return ScriptableObject.class.isAssignableFrom(type)
+ || NativeWith.class.isAssignableFrom(type);
+ }
+
@Override
public GuardedInvocation getGuardedInvocation(LinkRequest req, LinkerServices svc) {
if (req.isCallSiteUnstable()) {
return null;
}
- MethodType mType = req.getCallSiteDescriptor().getMethodType();
ParsedOperation op = new ParsedOperation(req.getCallSiteDescriptor().getOperation());
Object target = req.getReceiver();
@@ -31,6 +43,7 @@ public GuardedInvocation getGuardedInvocation(LinkRequest req, LinkerServices sv
&& op.isOperation(StandardOperation.GET, RhinoOperation.GETNOWARN))) {
Object constValue = getConstValue(target, op.getName());
if (constValue != null) {
+ MethodType mType = req.getCallSiteDescriptor().getMethodType();
// The guard returns boolean and compares the first argument to the
// target here. This works because the target is always our first argument.
MethodHandle guard = Guards.asType(Guards.getIdentityGuard(target), mType);
@@ -61,9 +74,7 @@ private Object getConstValue(Object t, String name) {
// Support constants referenced from inside functions
return getConstValue(((NativeWith) t).getPrototype(), name);
}
- if (!(t instanceof ScriptableObject)) {
- return null;
- }
+ assert t instanceof ScriptableObject;
try {
ScriptableObject target = (ScriptableObject) t;
// Just look in the root of the object -- don't mess around with
diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/DoubleLinker.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/DoubleLinker.java
new file mode 100644
index 0000000000..f9f038dc5c
--- /dev/null
+++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/DoubleLinker.java
@@ -0,0 +1,221 @@
+package org.mozilla.javascript.optimizer;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import jdk.dynalink.linker.GuardedInvocation;
+import jdk.dynalink.linker.LinkRequest;
+import jdk.dynalink.linker.LinkerServices;
+import jdk.dynalink.linker.TypeBasedGuardingDynamicLinker;
+import jdk.dynalink.linker.support.Guards;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.ScriptRuntime;
+
+/**
+ * This linker optimizes a suite of math operations when the LHS is a Double object, and the RHS (if
+ * any) is either a Double or an Integer. It avoids a gigantic set of "if...then" statements in
+ * ScriptRuntime for the generic case.
+ */
+@SuppressWarnings("AndroidJdkLibsChecker")
+class DoubleLinker implements TypeBasedGuardingDynamicLinker {
+ @Override
+ public boolean canLinkType(Class> type) {
+ return Double.class.equals(type);
+ }
+
+ @Override
+ public GuardedInvocation getGuardedInvocation(LinkRequest req, LinkerServices svc)
+ throws Exception {
+ if (req.isCallSiteUnstable()) {
+ return null;
+ }
+
+ ParsedOperation op = new ParsedOperation(req.getCallSiteDescriptor().getOperation());
+ MethodHandle mh = null;
+ MethodHandle guard = null;
+
+ if (op.isNamespace(RhinoNamespace.MATH)) {
+ Object arg2 = null;
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ MethodType mType = req.getCallSiteDescriptor().getMethodType();
+ if (req.getArguments().length > 1) {
+ arg2 = req.getArguments()[1];
+ }
+ if (op.isOperation(RhinoOperation.ADD) && arg2 instanceof Double) {
+ mh = lookup.findStatic(DoubleLinker.class, "add", mType);
+ MethodType guardType = mType.changeReturnType(Boolean.TYPE);
+ guard = lookup.findStatic(DoubleLinker.class, "testAdd", guardType);
+ } else if (op.isOperation(RhinoOperation.ADD) && arg2 instanceof Integer) {
+ mh = lookup.findStatic(DoubleLinker.class, "addInt", mType);
+ MethodType guardType = mType.changeReturnType(Boolean.TYPE);
+ guard = lookup.findStatic(DoubleLinker.class, "testAddInt", guardType);
+ } else if (op.isOperation(RhinoOperation.EQ, RhinoOperation.SHALLOWEQ)
+ && arg2 instanceof Double) {
+ mh = lookup.findStatic(DoubleLinker.class, "eq", mType);
+ guard = lookup.findStatic(DoubleLinker.class, "testTwo", mType);
+ } else if (op.isOperation(RhinoOperation.EQ, RhinoOperation.SHALLOWEQ)
+ && arg2 instanceof Integer) {
+ mh = lookup.findStatic(DoubleLinker.class, "eqInt", mType);
+ guard = lookup.findStatic(DoubleLinker.class, "testTwoInt", mType);
+ } else if (op.isOperation(RhinoOperation.COMPARE_LT) && arg2 instanceof Double) {
+ mh = lookup.findStatic(DoubleLinker.class, "compareLT", mType);
+ guard = lookup.findStatic(DoubleLinker.class, "testTwo", mType);
+ } else if (op.isOperation(RhinoOperation.COMPARE_GT) && arg2 instanceof Double) {
+ mh = lookup.findStatic(DoubleLinker.class, "compareGT", mType);
+ guard = lookup.findStatic(DoubleLinker.class, "testTwo", mType);
+ } else if (op.isOperation(RhinoOperation.COMPARE_LE) && arg2 instanceof Double) {
+ mh = lookup.findStatic(DoubleLinker.class, "compareLE", mType);
+ guard = lookup.findStatic(DoubleLinker.class, "testTwo", mType);
+ } else if (op.isOperation(RhinoOperation.COMPARE_GE) && arg2 instanceof Double) {
+ mh = lookup.findStatic(DoubleLinker.class, "compareGE", mType);
+ guard = lookup.findStatic(DoubleLinker.class, "testTwo", mType);
+ } else if (op.isOperation(RhinoOperation.COMPARE_LT) && arg2 instanceof Integer) {
+ mh = lookup.findStatic(DoubleLinker.class, "compareLTInt", mType);
+ guard = lookup.findStatic(DoubleLinker.class, "testTwoInt", mType);
+ } else if (op.isOperation(RhinoOperation.COMPARE_GT) && arg2 instanceof Integer) {
+ mh = lookup.findStatic(DoubleLinker.class, "compareGTInt", mType);
+ guard = lookup.findStatic(DoubleLinker.class, "testTwoInt", mType);
+ } else if (op.isOperation(RhinoOperation.COMPARE_LE) && arg2 instanceof Integer) {
+ mh = lookup.findStatic(DoubleLinker.class, "compareLEInt", mType);
+ guard = lookup.findStatic(DoubleLinker.class, "testTwoInt", mType);
+ } else if (op.isOperation(RhinoOperation.COMPARE_GE) && arg2 instanceof Integer) {
+ mh = lookup.findStatic(DoubleLinker.class, "compareGEInt", mType);
+ guard = lookup.findStatic(DoubleLinker.class, "testTwoInt", mType);
+ } else if (op.isOperation(RhinoOperation.TOBOOLEAN)) {
+ mh = lookup.findStatic(DoubleLinker.class, "toBoolean", mType);
+ guard = Guards.getInstanceOfGuard(Double.class);
+ } else if (op.isOperation(RhinoOperation.TONUMBER)) {
+ mh = lookup.findStatic(DoubleLinker.class, "toNumber", mType);
+ guard = Guards.getInstanceOfGuard(Double.class);
+ } else if (op.isOperation(RhinoOperation.TONUMERIC)) {
+ mh = lookup.findStatic(DoubleLinker.class, "toNumeric", mType);
+ guard = Guards.getInstanceOfGuard(Double.class);
+ } else if (op.isOperation(RhinoOperation.TOINT32)) {
+ mh = lookup.findStatic(DoubleLinker.class, "toInt32", mType);
+ guard = Guards.getInstanceOfGuard(Double.class);
+ } else if (op.isOperation(RhinoOperation.TOUINT32)) {
+ mh = lookup.findStatic(DoubleLinker.class, "toUint32", mType);
+ guard = Guards.getInstanceOfGuard(Double.class);
+ }
+ }
+
+ if (mh != null) {
+ assert guard != null;
+ if (DefaultLinker.DEBUG) {
+ System.out.println(op + " double operation");
+ }
+ return new GuardedInvocation(mh, guard);
+ }
+
+ return null;
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean testAdd(Object lval, Object rval, Context cx) {
+ return lval instanceof Double && rval instanceof Double;
+ }
+
+ @SuppressWarnings("unused")
+ private static Object add(Object lval, Object rval, Context cx) {
+ return ((Double) lval) + ((Double) rval);
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean testAddInt(Object lval, Object rval, Context cx) {
+ return lval instanceof Double && rval instanceof Integer;
+ }
+
+ @SuppressWarnings("unused")
+ private static Object addInt(Object lval, Object rval, Context cx) {
+ return (Double) lval + (Integer) rval;
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean testTwo(Object lval, Object rval) {
+ return lval instanceof Double && rval instanceof Double;
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean testTwoInt(Object lval, Object rval) {
+ return lval instanceof Double && rval instanceof Integer;
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean eq(Object lval, Object rval) {
+ return ((Double) lval).doubleValue() == ((Double) rval).doubleValue();
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean eqInt(Object lval, Object rval) {
+ return ((Double) lval) == ((Integer) rval).doubleValue();
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean compareLT(Object lval, Object rval) {
+ return ((Double) lval) < ((Double) rval);
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean compareGT(Object lval, Object rval) {
+ return ((Double) lval) > ((Double) rval);
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean compareLE(Object lval, Object rval) {
+ return ((Double) lval) <= ((Double) rval);
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean compareGE(Object lval, Object rval) {
+ return ((Double) lval) >= ((Double) rval);
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean compareLTInt(Object lval, Object rval) {
+ return ((Double) lval) < ((Integer) rval).doubleValue();
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean compareGTInt(Object lval, Object rval) {
+ return ((Double) lval) > ((Integer) rval).doubleValue();
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean compareLEInt(Object lval, Object rval) {
+ return ((Double) lval) <= ((Integer) rval).doubleValue();
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean compareGEInt(Object lval, Object rval) {
+ return ((Double) lval) >= ((Integer) rval).doubleValue();
+ }
+
+ @SuppressWarnings("unused")
+ private static double toNumber(Object raw) {
+ return (Double) raw;
+ }
+
+ @SuppressWarnings("unused")
+ private static Number toNumeric(Object raw) {
+ return (Double) raw;
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean toBoolean(Object raw) {
+ double v = (Double) raw;
+ if (Double.isNaN(v)) {
+ return false;
+ }
+ return v != 0.0;
+ }
+
+ @SuppressWarnings("unused")
+ private static int toInt32(Object raw) {
+ return ScriptRuntime.toInt32(((Double) raw).doubleValue());
+ }
+
+ @SuppressWarnings("unused")
+ private static long toUint32(Object raw) {
+ return ScriptRuntime.toUint32(((Double) raw).doubleValue());
+ }
+}
diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/IntegerLinker.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/IntegerLinker.java
new file mode 100644
index 0000000000..c0f56e94aa
--- /dev/null
+++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/IntegerLinker.java
@@ -0,0 +1,220 @@
+package org.mozilla.javascript.optimizer;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.Objects;
+import jdk.dynalink.linker.GuardedInvocation;
+import jdk.dynalink.linker.LinkRequest;
+import jdk.dynalink.linker.LinkerServices;
+import jdk.dynalink.linker.TypeBasedGuardingDynamicLinker;
+import jdk.dynalink.linker.support.Guards;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.ScriptRuntime;
+
+/**
+ * This linker optimizes a suite of math operations when the LHS is an Integer object, and the RHS
+ * (if any) is either an Integer or a Double. It avoids a gigantic set of "if...then" statements in
+ * ScriptRuntime for the generic case. When operating on Integers (and there is no overflow) it and
+ * ScriptRuntime contrive to return Integer results, which can result in faster operations later.
+ */
+@SuppressWarnings("AndroidJdkLibsChecker")
+class IntegerLinker implements TypeBasedGuardingDynamicLinker {
+ @Override
+ public boolean canLinkType(Class> type) {
+ return Integer.class.equals(type);
+ }
+
+ @Override
+ public GuardedInvocation getGuardedInvocation(LinkRequest req, LinkerServices svc)
+ throws Exception {
+ if (req.isCallSiteUnstable()) {
+ return null;
+ }
+
+ ParsedOperation op = new ParsedOperation(req.getCallSiteDescriptor().getOperation());
+ MethodHandle mh = null;
+ MethodHandle guard = null;
+
+ if (op.isNamespace(RhinoNamespace.MATH)) {
+ Object arg2 = null;
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ MethodType mType = req.getCallSiteDescriptor().getMethodType();
+
+ if (req.getArguments().length > 1) {
+ arg2 = req.getArguments()[1];
+ }
+ if (op.isOperation(RhinoOperation.ADD) && arg2 instanceof Integer) {
+ mh = lookup.findStatic(IntegerLinker.class, "add", mType);
+ MethodType guardType = mType.changeReturnType(Boolean.TYPE);
+ guard = lookup.findStatic(IntegerLinker.class, "testAdd", guardType);
+ } else if (op.isOperation(RhinoOperation.ADD) && arg2 instanceof Double) {
+ mh = lookup.findStatic(IntegerLinker.class, "addDouble", mType);
+ MethodType guardType = mType.changeReturnType(Boolean.TYPE);
+ guard = lookup.findStatic(IntegerLinker.class, "testAddDouble", guardType);
+ } else if (op.isOperation(RhinoOperation.EQ, RhinoOperation.SHALLOWEQ)
+ && arg2 instanceof Integer) {
+ mh = lookup.findStatic(IntegerLinker.class, "eq", mType);
+ guard = lookup.findStatic(IntegerLinker.class, "testTwo", mType);
+ } else if (op.isOperation(RhinoOperation.EQ, RhinoOperation.SHALLOWEQ)
+ && arg2 instanceof Double) {
+ mh = lookup.findStatic(IntegerLinker.class, "eqDouble", mType);
+ guard = lookup.findStatic(IntegerLinker.class, "testTwoDouble", mType);
+ } else if (op.isOperation(RhinoOperation.COMPARE_LT) && arg2 instanceof Integer) {
+ mh = lookup.findStatic(IntegerLinker.class, "compareLT", mType);
+ guard = lookup.findStatic(IntegerLinker.class, "testTwo", mType);
+ } else if (op.isOperation(RhinoOperation.COMPARE_GT) && arg2 instanceof Integer) {
+ mh = lookup.findStatic(IntegerLinker.class, "compareGT", mType);
+ guard = lookup.findStatic(IntegerLinker.class, "testTwo", mType);
+ } else if (op.isOperation(RhinoOperation.COMPARE_LE) && arg2 instanceof Integer) {
+ mh = lookup.findStatic(IntegerLinker.class, "compareLE", mType);
+ guard = lookup.findStatic(IntegerLinker.class, "testTwo", mType);
+ } else if (op.isOperation(RhinoOperation.COMPARE_GE) && arg2 instanceof Integer) {
+ mh = lookup.findStatic(IntegerLinker.class, "compareGE", mType);
+ guard = lookup.findStatic(IntegerLinker.class, "testTwo", mType);
+ } else if (op.isOperation(RhinoOperation.COMPARE_LT) && arg2 instanceof Double) {
+ mh = lookup.findStatic(IntegerLinker.class, "compareLTDouble", mType);
+ guard = lookup.findStatic(IntegerLinker.class, "testTwoDouble", mType);
+ } else if (op.isOperation(RhinoOperation.COMPARE_GT) && arg2 instanceof Double) {
+ mh = lookup.findStatic(IntegerLinker.class, "compareGTDouble", mType);
+ guard = lookup.findStatic(IntegerLinker.class, "testTwoDouble", mType);
+ } else if (op.isOperation(RhinoOperation.COMPARE_LE) && arg2 instanceof Double) {
+ mh = lookup.findStatic(IntegerLinker.class, "compareLEDouble", mType);
+ guard = lookup.findStatic(IntegerLinker.class, "testTwoDouble", mType);
+ } else if (op.isOperation(RhinoOperation.COMPARE_GE) && arg2 instanceof Double) {
+ mh = lookup.findStatic(IntegerLinker.class, "compareGEDouble", mType);
+ guard = lookup.findStatic(IntegerLinker.class, "testTwoDouble", mType);
+ } else if (op.isOperation(RhinoOperation.TOBOOLEAN)) {
+ mh = lookup.findStatic(IntegerLinker.class, "toBoolean", mType);
+ guard = Guards.getInstanceOfGuard(Integer.class);
+ } else if (op.isOperation(RhinoOperation.TONUMBER)) {
+ mh = lookup.findStatic(IntegerLinker.class, "toNumber", mType);
+ guard = Guards.getInstanceOfGuard(Integer.class);
+ } else if (op.isOperation(RhinoOperation.TONUMERIC)) {
+ mh = lookup.findStatic(IntegerLinker.class, "toNumeric", mType);
+ guard = Guards.getInstanceOfGuard(Integer.class);
+ } else if (op.isOperation(RhinoOperation.TOINT32)) {
+ mh = lookup.findStatic(IntegerLinker.class, "toInt32", mType);
+ guard = Guards.getInstanceOfGuard(Integer.class);
+ } else if (op.isOperation(RhinoOperation.TOUINT32)) {
+ mh = lookup.findStatic(IntegerLinker.class, "toUint32", mType);
+ guard = Guards.getInstanceOfGuard(Integer.class);
+ }
+ }
+
+ if (mh != null) {
+ assert guard != null;
+ if (DefaultLinker.DEBUG) {
+ System.out.println(op + " integer operation");
+ }
+ return new GuardedInvocation(mh, guard);
+ }
+
+ return null;
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean testAdd(Object lval, Object rval, Context cx) {
+ return lval instanceof Integer && rval instanceof Integer;
+ }
+
+ @SuppressWarnings("unused")
+ private static Object add(Object lval, Object rval, Context cx) {
+ return ScriptRuntime.add((Integer) lval, (Integer) rval);
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean testAddDouble(Object lval, Object rval, Context cx) {
+ return lval instanceof Integer && rval instanceof Double;
+ }
+
+ @SuppressWarnings("unused")
+ private static Object addDouble(Object lval, Object rval, Context cx) {
+ return ((Integer) lval).doubleValue() + (Double) rval;
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean testTwo(Object lval, Object rval) {
+ return lval instanceof Integer && rval instanceof Integer;
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean testTwoDouble(Object lval, Object rval) {
+ return lval instanceof Integer && rval instanceof Double;
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean eq(Object lval, Object rval) {
+ return Objects.equals(lval, rval);
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean eqDouble(Object lval, Object rval) {
+ return ((Integer) lval).doubleValue() == (Double) rval;
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean compareLT(Object lval, Object rval) {
+ return ((Integer) lval) < ((Integer) rval);
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean compareGT(Object lval, Object rval) {
+ return ((Integer) lval) > ((Integer) rval);
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean compareLE(Object lval, Object rval) {
+ return ((Integer) lval) <= ((Integer) rval);
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean compareGE(Object lval, Object rval) {
+ return ((Integer) lval) >= ((Integer) rval);
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean compareLTDouble(Object lval, Object rval) {
+ return ((Integer) lval).doubleValue() < ((Double) rval);
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean compareGTDouble(Object lval, Object rval) {
+ return ((Integer) lval).doubleValue() > ((Double) rval);
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean compareLEDouble(Object lval, Object rval) {
+ return ((Integer) lval).doubleValue() <= ((Double) rval);
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean compareGEDouble(Object lval, Object rval) {
+ return ((Integer) lval).doubleValue() >= ((Double) rval);
+ }
+
+ @SuppressWarnings("unused")
+ private static double toNumber(Object raw) {
+ return ((Integer) raw).doubleValue();
+ }
+
+ @SuppressWarnings("unused")
+ private static Number toNumeric(Object raw) {
+ return (Number) raw;
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean toBoolean(Object raw) {
+ return ((Integer) raw) != 0;
+ }
+
+ @SuppressWarnings("unused")
+ private static int toInt32(Object raw) {
+ return ((Integer) raw);
+ }
+
+ @SuppressWarnings("unused")
+ private static long toUint32(Object raw) {
+ return Integer.toUnsignedLong((Integer) raw);
+ }
+}
diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/NativeArrayLinker.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/NativeArrayLinker.java
new file mode 100644
index 0000000000..985caf3ae0
--- /dev/null
+++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/NativeArrayLinker.java
@@ -0,0 +1,69 @@
+package org.mozilla.javascript.optimizer;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import jdk.dynalink.StandardNamespace;
+import jdk.dynalink.StandardOperation;
+import jdk.dynalink.linker.GuardedInvocation;
+import jdk.dynalink.linker.LinkRequest;
+import jdk.dynalink.linker.LinkerServices;
+import jdk.dynalink.linker.TypeBasedGuardingDynamicLinker;
+import jdk.dynalink.linker.support.Guards;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.NativeArray;
+import org.mozilla.javascript.Scriptable;
+
+/**
+ * This linker optimizes accesses to the "length" property of native arrays by delegating directly
+ * to the native code. It helps in the common case that code is iterating over the length of an
+ * array.
+ */
+@SuppressWarnings("AndroidJdkLibsChecker")
+class NativeArrayLinker implements TypeBasedGuardingDynamicLinker {
+ @Override
+ public boolean canLinkType(Class> type) {
+ return NativeArray.class.isAssignableFrom(type);
+ }
+
+ @Override
+ public GuardedInvocation getGuardedInvocation(LinkRequest req, LinkerServices svc)
+ throws Exception {
+ if (req.isCallSiteUnstable()) {
+ return null;
+ }
+
+ ParsedOperation op = new ParsedOperation(req.getCallSiteDescriptor().getOperation());
+ MethodHandle mh = null;
+ MethodHandle guard = null;
+
+ if (op.isNamespace(StandardNamespace.PROPERTY)) {
+ if (op.isOperation(StandardOperation.GET, RhinoOperation.GETNOWARN)
+ && "length".equals(op.getName())) {
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ MethodType mType = req.getCallSiteDescriptor().getMethodType();
+ mh = lookup.findStatic(NativeArrayLinker.class, "getLength", mType);
+ guard = Guards.getInstanceOfGuard(NativeArray.class);
+ }
+ }
+
+ if (mh != null) {
+ assert guard != null;
+ if (DefaultLinker.DEBUG) {
+ System.out.println(op + " native array operation");
+ }
+ return new GuardedInvocation(mh, guard);
+ }
+
+ return null;
+ }
+
+ @SuppressWarnings("unused")
+ private static Object getLength(Object o, Context cx, Scriptable scope) {
+ long length = ((NativeArray) o).getLength();
+ if (length < Integer.MAX_VALUE) {
+ return (int) length;
+ }
+ return (double) length;
+ }
+}
diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/StringLinker.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/StringLinker.java
new file mode 100644
index 0000000000..d367f76f76
--- /dev/null
+++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/StringLinker.java
@@ -0,0 +1,110 @@
+package org.mozilla.javascript.optimizer;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.Objects;
+import jdk.dynalink.StandardNamespace;
+import jdk.dynalink.StandardOperation;
+import jdk.dynalink.linker.GuardedInvocation;
+import jdk.dynalink.linker.LinkRequest;
+import jdk.dynalink.linker.LinkerServices;
+import jdk.dynalink.linker.TypeBasedGuardingDynamicLinker;
+import jdk.dynalink.linker.support.Guards;
+import org.mozilla.javascript.ConsString;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Scriptable;
+
+/**
+ * This linker optimizes a few string operations:
+ *
+ *
+ *
+ *
+ * As in other custom linkers, this avoids the large cascading "if...then" statements in the
+ * ScriptRuntime classes.
+ */
+@SuppressWarnings("AndroidJdkLibsChecker")
+class StringLinker implements TypeBasedGuardingDynamicLinker {
+ @Override
+ public boolean canLinkType(Class> type) {
+ return String.class.equals(type);
+ }
+
+ @Override
+ public GuardedInvocation getGuardedInvocation(LinkRequest req, LinkerServices svc)
+ throws Exception {
+ if (req.isCallSiteUnstable()) {
+ return null;
+ }
+ Object arg2 = null;
+ if (req.getArguments().length > 1) {
+ arg2 = req.getArguments()[1];
+ }
+
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ ParsedOperation op = new ParsedOperation(req.getCallSiteDescriptor().getOperation());
+ MethodType mType = req.getCallSiteDescriptor().getMethodType();
+ MethodHandle mh = null;
+ MethodHandle guard = null;
+
+ if (op.isNamespace(RhinoNamespace.MATH)) {
+ if (op.isOperation(RhinoOperation.ADD)) {
+ MethodType guardType = mType.changeReturnType(Boolean.TYPE);
+ if (arg2 instanceof CharSequence) {
+ mh = lookup.findStatic(StringLinker.class, "add", mType);
+ guard = lookup.findStatic(StringLinker.class, "testAdd", guardType);
+ }
+ } else if (op.isOperation(RhinoOperation.EQ, RhinoOperation.SHALLOWEQ)
+ && (arg2 instanceof String)) {
+ mh = lookup.findStatic(StringLinker.class, "eq", mType);
+ guard = lookup.findStatic(StringLinker.class, "testEq", mType);
+ }
+ } else if (op.isNamespace(StandardNamespace.PROPERTY)) {
+ if (op.isOperation(StandardOperation.GET, RhinoOperation.GETNOWARN)
+ && "length".equals(op.getName())) {
+ mh = lookup.findStatic(StringLinker.class, "getLength", mType);
+ guard = Guards.getInstanceOfGuard(String.class);
+ }
+ }
+
+ if (mh != null) {
+ assert guard != null;
+ if (DefaultLinker.DEBUG) {
+ System.out.println(op + " string operation");
+ }
+ return new GuardedInvocation(mh, guard);
+ }
+
+ return null;
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean testAdd(Object lval, Object rval, Context cx) {
+ return lval instanceof String && rval instanceof CharSequence;
+ }
+
+ @SuppressWarnings("unused")
+ private static Object add(Object lval, Object rval, Context cx) {
+ return new ConsString((String) lval, ((CharSequence) rval).toString());
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean testEq(Object lVal, Object rval) {
+ return lVal instanceof String && rval instanceof String;
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean eq(Object lVal, Object rval) {
+ return Objects.equals(lVal, rval);
+ }
+
+ @SuppressWarnings("unused")
+ private static Object getLength(Object o, Context cx, Scriptable scope) {
+ return ((String) o).length();
+ }
+}