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: and "NAME:BIND:". This - * translates them the first time a call site is seen to an object that can be easily consumed - * by the various types of linkers. + * Operation names in the bytecode are names like "PROP:GET: and "NAME:BIND:". (See + * the "Signatures" interface for a description of these.) This method translates them the first + * time a call site is seen to an object that can be easily consumed by the various types of + * linkers. */ private static Operation parseOperation(String name) throws NoSuchMethodException { String[] tokens = SEPARATOR.split(name, -1); @@ -169,20 +204,23 @@ private static Operation parseOperation(String name) throws NoSuchMethodExceptio } } - // Fall through to no match. This should only happen if the name in the bytecode - // does not match the pattern that this method understands. + // Fall through to no match. This will only happen if the name in the bytecode + // does not match the pattern that this method understands, which means that + // there is a mismatch between the bytecode and the runtime. throw new NoSuchMethodException(name); } // Given a list of name segments and a position, return the interned name at the - // specified position. + // specified position. This allows us, to pull a name like "foo" from an operation + // named, for example, "NAME:GET:foo". private static String getNameSegment(String[] segments, String name, int pos) { if (pos >= segments.length) { return ""; } - // Because segments of operation names, especially property names, are essentially - // wired in to the bootstrapping result, interning works and has a big impact on - // performance. + // The "slot maps" in ScriptableObject-based classes can shortcut when property names + // are "==", so interning strings improves performance in a measurable way, because + // the property names that we pull from the INDY operation descriptors are essentially + // constants. return segments[pos].intern(); } } diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/ConsStringLinker.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/ConsStringLinker.java new file mode 100644 index 0000000000..ddfe38ece5 --- /dev/null +++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/ConsStringLinker.java @@ -0,0 +1,91 @@ +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.ConsString; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; + +/** + * This linker optimizes: + * + *

+ */ +@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(); + } +}