From c588025bb92c1f36a001783513fc68f96341bdc5 Mon Sep 17 00:00:00 2001 From: Greg Brail Date: Thu, 24 Oct 2024 22:59:45 -0700 Subject: [PATCH] Add INDY ops for math operations This will let us optimize them later, since most math operations are long chains of "instanceof" checks. --- .../javascript/optimizer/BodyCodegen.java | 72 +++---- .../javascript/optimizer/Bootstrapper.java | 35 +++- .../optimizer/ConstAwareLinker.java | 27 +-- .../javascript/optimizer/DefaultLinker.java | 181 +++++++++++------- .../javascript/optimizer/ParsedOperation.java | 56 ++++++ .../javascript/optimizer/RhinoNamespace.java | 1 + .../javascript/optimizer/RhinoOperation.java | 12 ++ .../javascript/optimizer/Signatures.java | 47 ++++- 8 files changed, 301 insertions(+), 130 deletions(-) create mode 100644 rhino/src/main/java/org/mozilla/javascript/optimizer/ParsedOperation.java diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/BodyCodegen.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/BodyCodegen.java index 8957bc58d5..f4432f9c97 100644 --- a/rhino/src/main/java/org/mozilla/javascript/optimizer/BodyCodegen.java +++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/BodyCodegen.java @@ -1195,7 +1195,7 @@ private void generateExpression(Node node, Node parent) { { generateExpression(child, node); cfw.add(ByteCode.DUP); - addScriptRuntimeInvoke("toBoolean", "(Ljava/lang/Object;)Z"); + addDynamicInvoke("MATH:TOBOOLEAN", Signatures.MATH_TO_BOOLEAN); int falseTarget = cfw.acquireLabel(); if (type == Token.AND) cfw.add(ByteCode.IFEQ, falseTarget); else cfw.add(ByteCode.IFNE, falseTarget); @@ -1210,7 +1210,7 @@ private void generateExpression(Node node, Node parent) { Node ifThen = child.getNext(); Node ifElse = ifThen.getNext(); generateExpression(child, node); - addScriptRuntimeInvoke("toBoolean", "(Ljava/lang/Object;)Z"); + addDynamicInvoke("MATH:TOBOOLEAN", Signatures.MATH_TO_BOOLEAN); int elseTarget = cfw.acquireLabel(); cfw.add(ByteCode.IFEQ, elseTarget); int stack = cfw.getStackTop(); @@ -1245,12 +1245,7 @@ private void generateExpression(Node node, Node parent) { break; default: cfw.addALoad(contextLocal); - addScriptRuntimeInvoke( - "add", - "(Ljava/lang/Object;" - + "Ljava/lang/Object;" - + "Lorg/mozilla/javascript/Context;" - + ")Ljava/lang/Object;"); + addDynamicInvoke("MATH:ADD", Signatures.MATH_ADD); } } break; @@ -1928,7 +1923,7 @@ private void generateIfJump(Node node, Node parent, int trueLabel, int falseLabe default: // Generate generic code for non-optimized jump generateExpression(node, parent); - addScriptRuntimeInvoke("toBoolean", "(Ljava/lang/Object;)Z"); + addDynamicInvoke("MATH:TOBOOLEAN", Signatures.MATH_TO_BOOLEAN); cfw.add(ByteCode.IFNE, trueLabel); cfw.add(ByteCode.GOTO, falseLabel); } @@ -2433,18 +2428,11 @@ private void visitStandardCall(Node node, Node child) { // there are no checks for it String name = child.getString(); if (isOptionalChainingCall) { // name?.() - // eval name and this and push name on stack - cfw.addPush(name); - cfw.addALoad(contextLocal); + // eval name and this and put name in dynamic signature just like + // with "GETWITHTHIS". cfw.addALoad(variableObjectLocal); - addScriptRuntimeInvoke( - "getNameFunctionAndThisOptional", - "" - + "(" - + "Ljava/lang/String;" - + "Lorg/mozilla/javascript/Context;" - + "Lorg/mozilla/javascript/Scriptable;" - + ")Lorg/mozilla/javascript/Callable;"); + cfw.addALoad(contextLocal); + addDynamicInvoke("NAME:GETWITHTHISOPTIONAL:" + name, Signatures.NAME_GET_THIS); // jump to afterLabel is name is not null and not undefined afterLabel = cfw.acquireLabel(); @@ -3373,8 +3361,7 @@ private void visitSwitch(Jump switchNode, Node child) { Node test = caseNode.getFirstChild(); generateExpression(test, caseNode); cfw.addALoad(selector); - addScriptRuntimeInvoke( - "shallowEq", "(Ljava/lang/Object;" + "Ljava/lang/Object;" + ")Z"); + addDynamicInvoke("MATH:SHALLOWEQ", Signatures.MATH_SHALLOW_EQ); addGoto(caseNode.target, ByteCode.IFNE); } releaseWordLocal(selector); @@ -3719,9 +3706,9 @@ private void visitBitOp(Node node, int type, Node child) { // that we can return a 32-bit unsigned value, and call // toUint32 instead of toInt32. if (type == Token.URSH) { - addScriptRuntimeInvoke("toUint32", "(Ljava/lang/Object;)J"); + addDynamicInvoke("MATH:TOUINT32", Signatures.MATH_TO_UINT32); generateExpression(child.getNext(), node); - addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)I"); + addDynamicInvoke("MATH:TOINT32", Signatures.MATH_TO_INT32); // Looks like we need to explicitly mask the shift to 5 bits - // LUSHR takes 6 bits. cfw.addPush(31); @@ -3916,8 +3903,25 @@ private void visitIfJumpRelOp(Node node, Node child, int trueGOTO, int falseGOTO generateExpression(rChild, node); } - cfw.addPush(type); - addScriptRuntimeInvoke("compare", "(Ljava/lang/Object;" + "Ljava/lang/Object;" + "I)Z"); + String compareOp; + switch (type) { + case Token.GT: + compareOp = "MATH:COMPAREGT"; + break; + case Token.LT: + compareOp = "MATH:COMPARELT"; + break; + case Token.GE: + compareOp = "MATH:COMPAREGE"; + break; + case Token.LE: + compareOp = "MATH:COMPARELE"; + break; + default: + throw Kit.codeBug(); + } + + addDynamicInvoke(compareOp, Signatures.MATH_COMPARE); cfw.add(ByteCode.IFNE, trueGOTO); cfw.add(ByteCode.GOTO, falseGOTO); } @@ -3986,25 +3990,25 @@ private void visitIfJumpEqOp(Node node, Node child, int trueGOTO, int falseGOTO) int testCode; switch (type) { case Token.EQ: - name = "eq"; + name = "MATH:EQ"; testCode = ByteCode.IFNE; break; case Token.NE: - name = "eq"; + name = "MATH:EQ"; testCode = ByteCode.IFEQ; break; case Token.SHEQ: - name = "shallowEq"; + name = "MATH:SHALLOWEQ"; testCode = ByteCode.IFNE; break; case Token.SHNE: - name = "shallowEq"; + name = "MATH:SHALLOWEQ"; testCode = ByteCode.IFEQ; break; default: throw Codegen.badTree(); } - addScriptRuntimeInvoke(name, "(Ljava/lang/Object;" + "Ljava/lang/Object;" + ")Z"); + addDynamicInvoke(name, Signatures.MATH_EQ); cfw.add(testCode, trueGOTO); cfw.add(ByteCode.GOTO, falseGOTO); } @@ -4269,7 +4273,7 @@ private void visitDotQuery(Node node, Node child) { cfw.add(ByteCode.POP); generateExpression(child.getNext(), node); - addScriptRuntimeInvoke("toBoolean", "(Ljava/lang/Object;)Z"); + addDynamicInvoke("MATH:TOBOOLEAN", Signatures.MATH_TO_BOOLEAN); cfw.addALoad(variableObjectLocal); addScriptRuntimeInvoke( "updateDotQuery", @@ -4326,11 +4330,11 @@ private void addGoto(Node target, int jumpcode) { } private void addObjectToDouble() { - addScriptRuntimeInvoke("toNumber", "(Ljava/lang/Object;)D"); + addDynamicInvoke("MATH:TONUMBER", Signatures.MATH_TO_NUMBER); } private void addObjectToNumeric() { - addScriptRuntimeInvoke("toNumeric", "(Ljava/lang/Object;)Ljava/lang/Number;"); + addDynamicInvoke("MATH:TONUMERIC", Signatures.MATH_TO_NUMERIC); } private void addNewObjectArray(int size) { 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 d81a0180ad..104c8bc94f 100644 --- a/rhino/src/main/java/org/mozilla/javascript/optimizer/Bootstrapper.java +++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/Bootstrapper.java @@ -33,8 +33,11 @@ public class Bootstrapper { private static final DynamicLinker linker; static { - // Set up the linkers that will be invoked whenever a call site needs to be resolved. + // Set up the linkers DynamicLinkerFactory factory = new DynamicLinkerFactory(); + // Check the list of type-based linkers first, and fall back to the + // default linker, which will always work by falling back to + // generic ScriptRuntime methods. factory.setPrioritizedLinkers(new ConstAwareLinker(), new DefaultLinker()); linker = factory.createLinker(); } @@ -44,7 +47,7 @@ public class Bootstrapper { public static CallSite bootstrap(MethodHandles.Lookup lookup, String name, MethodType mType) throws NoSuchMethodException, IllegalAccessException { Operation op = parseOperation(name); - // For now, use a very simple call site. + // ChainedCallSite lets a call site have a few options for complex situations return linker.link(new ChainedCallSite(new CallSiteDescriptor(lookup, op, mType))); } @@ -137,6 +140,34 @@ private static Operation parseOperation(String name) throws NoSuchMethodExceptio .withNamespace(RhinoNamespace.NAME) .named(getNameSegment(tokens, name, 2)); } + + } else if ("MATH".equals(namespaceName)) { + switch (opName) { + case "ADD": + return RhinoOperation.ADD.withNamespace(RhinoNamespace.MATH); + case "TOBOOLEAN": + return RhinoOperation.TOBOOLEAN.withNamespace(RhinoNamespace.MATH); + case "TOINT32": + return RhinoOperation.TOINT32.withNamespace(RhinoNamespace.MATH); + case "TOUINT32": + return RhinoOperation.TOUINT32.withNamespace(RhinoNamespace.MATH); + case "EQ": + return RhinoOperation.EQ.withNamespace(RhinoNamespace.MATH); + case "SHALLOWEQ": + return RhinoOperation.SHALLOWEQ.withNamespace(RhinoNamespace.MATH); + case "TONUMBER": + return RhinoOperation.TONUMBER.withNamespace(RhinoNamespace.MATH); + case "TONUMERIC": + return RhinoOperation.TONUMERIC.withNamespace(RhinoNamespace.MATH); + case "COMPAREGT": + return RhinoOperation.COMPARE_GT.withNamespace(RhinoNamespace.MATH); + case "COMPARELT": + return RhinoOperation.COMPARE_LT.withNamespace(RhinoNamespace.MATH); + case "COMPAREGE": + return RhinoOperation.COMPARE_GE.withNamespace(RhinoNamespace.MATH); + case "COMPARELE": + return RhinoOperation.COMPARE_LE.withNamespace(RhinoNamespace.MATH); + } } // Fall through to no match. This should only happen if the name in the bytecode 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 87908eaa70..d7edf22b89 100644 --- a/rhino/src/main/java/org/mozilla/javascript/optimizer/ConstAwareLinker.java +++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/ConstAwareLinker.java @@ -3,9 +3,6 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -import jdk.dynalink.NamedOperation; -import jdk.dynalink.NamespaceOperation; -import jdk.dynalink.Operation; import jdk.dynalink.StandardNamespace; import jdk.dynalink.StandardOperation; import jdk.dynalink.linker.GuardedInvocation; @@ -20,29 +17,19 @@ @SuppressWarnings("AndroidJdkLibsChecker") class ConstAwareLinker implements GuardingDynamicLinker { @Override - public GuardedInvocation getGuardedInvocation(LinkRequest req, LinkerServices svc) - throws Exception { + public GuardedInvocation getGuardedInvocation(LinkRequest req, LinkerServices svc) { if (req.isCallSiteUnstable()) { - if (DefaultLinker.DEBUG) { - System.out.println( - req.getCallSiteDescriptor().getOperation() + ": unstable call site"); - } return null; } - MethodHandles.Lookup lookup = MethodHandles.lookup(); - Operation rootOp = req.getCallSiteDescriptor().getOperation(); MethodType mType = req.getCallSiteDescriptor().getMethodType(); - String name = DefaultLinker.getName(rootOp); - Operation op = NamedOperation.getBaseOperation(rootOp); + ParsedOperation op = new ParsedOperation(req.getCallSiteDescriptor().getOperation()); Object target = req.getReceiver(); - if (NamespaceOperation.contains(op, StandardOperation.GET, RhinoNamespace.NAME) - || NamespaceOperation.contains( - op, StandardOperation.GET, StandardNamespace.PROPERTY) - || NamespaceOperation.contains( - op, RhinoOperation.GETNOWARN, StandardNamespace.PROPERTY)) { - Object constValue = getConstValue(target, name); + if ((op.isNamespace(RhinoNamespace.NAME) && op.isOperation(StandardOperation.GET)) + || (op.isNamespace(StandardNamespace.PROPERTY) + && op.isOperation(StandardOperation.GET, RhinoOperation.GETNOWARN))) { + Object constValue = getConstValue(target, op.getName()); if (constValue != null) { // The guard returns boolean and compares the first argument to the // target here. This works because the target is always our first argument. @@ -55,7 +42,7 @@ public GuardedInvocation getGuardedInvocation(LinkRequest req, LinkerServices sv 0, mType.parameterList()); if (DefaultLinker.DEBUG) { - System.out.println(rootOp + " constant"); + System.out.println(op + ": constant"); } return new GuardedInvocation(mh, guard); } diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/DefaultLinker.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/DefaultLinker.java index 1b3d48e5bd..7b49bbcbf0 100644 --- a/rhino/src/main/java/org/mozilla/javascript/optimizer/DefaultLinker.java +++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/DefaultLinker.java @@ -3,9 +3,6 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -import jdk.dynalink.NamedOperation; -import jdk.dynalink.NamespaceOperation; -import jdk.dynalink.Operation; import jdk.dynalink.StandardNamespace; import jdk.dynalink.StandardOperation; import jdk.dynalink.linker.GuardedInvocation; @@ -16,6 +13,7 @@ import org.mozilla.javascript.Context; import org.mozilla.javascript.ScriptRuntime; import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.Token; /** * This linker is the last one in the chain, and as such it must be able to link every type of @@ -38,70 +36,69 @@ class DefaultLinker implements GuardingDynamicLinker { public GuardedInvocation getGuardedInvocation(LinkRequest req, LinkerServices svc) throws NoSuchMethodException, IllegalAccessException { MethodHandles.Lookup lookup = MethodHandles.lookup(); - Operation rootOp = req.getCallSiteDescriptor().getOperation(); MethodType mType = req.getCallSiteDescriptor().getMethodType(); + ParsedOperation op = new ParsedOperation(req.getCallSiteDescriptor().getOperation()); - // So far, every operation in our application is a NamedOperation, but if - // we add non-named operations in the future, this will still work because - // "getName" will return an empty string and "Op" will return "rootOp". - String name = getName(rootOp); - Operation op = NamedOperation.getBaseOperation(rootOp); - - // In our application, every operation has a namespace. - if (!(op instanceof NamespaceOperation)) { - throw new UnsupportedOperationException(op.toString()); - } - NamespaceOperation nsOp = (NamespaceOperation) op; - op = NamespaceOperation.getBaseOperation(op); - - GuardedInvocation invocation = getInvocation(lookup, mType, rootOp, nsOp, op, name); + GuardedInvocation invocation = getInvocation(lookup, mType, op); if (DEBUG) { - System.out.println(rootOp + ": default link"); + String t1 = + req.getReceiver() == null + ? "null" + : req.getReceiver().getClass().getSimpleName(); + String t2 = ""; + if (req.getArguments().length > 1 && req.getArguments()[1] != null) { + t2 = req.getArguments()[1].getClass().getSimpleName(); + } + System.out.println(op + "(" + t1 + ", " + t2 + "): default link"); } return invocation; } private GuardedInvocation getInvocation( - MethodHandles.Lookup lookup, - MethodType mType, - Operation rootOp, - NamespaceOperation nsOp, - Operation op, - String name) + MethodHandles.Lookup lookup, MethodType mType, ParsedOperation op) throws NoSuchMethodException, IllegalAccessException { - if (nsOp.contains(StandardNamespace.PROPERTY)) { - return getPropertyInvocation(lookup, mType, rootOp, op, name); - } else if (nsOp.contains(RhinoNamespace.NAME)) { - return getNameInvocation(lookup, mType, rootOp, op, name); + if (op.isNamespace(StandardNamespace.PROPERTY)) { + return getPropertyInvocation(lookup, mType, op); + } else if (op.isNamespace(RhinoNamespace.NAME)) { + return getNameInvocation(lookup, mType, op); + } else if (op.isNamespace(RhinoNamespace.MATH)) { + return getMathInvocation(lookup, mType, op); } - throw new UnsupportedOperationException(rootOp.toString()); + throw new UnsupportedOperationException(op.toString()); } private GuardedInvocation getPropertyInvocation( - MethodHandles.Lookup lookup, - MethodType mType, - Operation rootOp, - Operation op, - String name) + MethodHandles.Lookup lookup, MethodType mType, ParsedOperation op) throws NoSuchMethodException, IllegalAccessException { - MethodType tt; MethodHandle mh = null; // The name of the property to look up is now not on the Java stack, // but was passed as part of the operation name in the bytecode. // Put the property name back in the right place in the parameter list // so that we can invoke the operation normally. - if (StandardOperation.GET.equals(op)) { - mh = bindStringParameter(lookup, mType, ScriptRuntime.class, "getObjectProp", 1, name); - } else if (RhinoOperation.GETNOWARN.equals(op)) { + if (op.isOperation(StandardOperation.GET)) { + mh = + bindStringParameter( + lookup, mType, ScriptRuntime.class, "getObjectProp", 1, op.getName()); + } else if (op.isOperation(RhinoOperation.GETNOWARN)) { mh = bindStringParameter( - lookup, mType, ScriptRuntime.class, "getObjectPropNoWarn", 1, name); - } else if (RhinoOperation.GETWITHTHIS.equals(op)) { + lookup, + mType, + ScriptRuntime.class, + "getObjectPropNoWarn", + 1, + op.getName()); + } else if (op.isOperation(RhinoOperation.GETWITHTHIS)) { mh = bindStringParameter( - lookup, mType, ScriptRuntime.class, "getPropFunctionAndThis", 1, name); - } else if (RhinoOperation.GETWITHTHISOPTIONAL.equals(op)) { + lookup, + mType, + ScriptRuntime.class, + "getPropFunctionAndThis", + 1, + op.getName()); + } else if (op.isOperation(RhinoOperation.GETWITHTHISOPTIONAL)) { mh = bindStringParameter( lookup, @@ -109,16 +106,18 @@ private GuardedInvocation getPropertyInvocation( ScriptRuntime.class, "getPropFunctionAndThisOptional", 1, - name); - } else if (StandardOperation.SET.equals(op)) { - mh = bindStringParameter(lookup, mType, ScriptRuntime.class, "setObjectProp", 1, name); - } else if (RhinoOperation.GETELEMENT.equals(op)) { + op.getName()); + } else if (op.isOperation(StandardOperation.SET)) { + mh = + bindStringParameter( + lookup, mType, ScriptRuntime.class, "setObjectProp", 1, op.getName()); + } else if (op.isOperation(RhinoOperation.GETELEMENT)) { mh = lookup.findStatic(ScriptRuntime.class, "getObjectElem", mType); - } else if (RhinoOperation.GETINDEX.equals(op)) { + } else if (op.isOperation(RhinoOperation.GETINDEX)) { mh = lookup.findStatic(ScriptRuntime.class, "getObjectIndex", mType); - } else if (RhinoOperation.SETELEMENT.equals(op)) { + } else if (op.isOperation(RhinoOperation.SETELEMENT)) { mh = lookup.findStatic(ScriptRuntime.class, "setObjectElem", mType); - } else if (RhinoOperation.SETINDEX.equals(op)) { + } else if (op.isOperation(RhinoOperation.SETINDEX)) { mh = lookup.findStatic(ScriptRuntime.class, "setObjectIndex", mType); } @@ -127,71 +126,107 @@ private GuardedInvocation getPropertyInvocation( } // We will only get here if a new operation was introduced // without appropriate changes. This particular linker must never return null. - throw new UnsupportedOperationException(rootOp.toString()); + throw new UnsupportedOperationException(op.toString()); } private GuardedInvocation getNameInvocation( - MethodHandles.Lookup lookup, - MethodType mType, - Operation rootOp, - Operation op, - String name) + MethodHandles.Lookup lookup, MethodType mType, ParsedOperation op) throws NoSuchMethodException, IllegalAccessException { MethodType tt; MethodHandle mh = null; + String name = op.getName(); // Like above for properties, the name to handle is not on the Java stack, // but is something that we parsed from the name of the invokedynamic operation. - if (RhinoOperation.BIND.equals(op)) { + if (op.isOperation(RhinoOperation.BIND)) { tt = MethodType.methodType( Scriptable.class, Context.class, Scriptable.class, String.class); mh = lookup.findStatic(ScriptRuntime.class, "bind", tt); mh = MethodHandles.insertArguments(mh, 2, name); mh = MethodHandles.permuteArguments(mh, mType, 1, 0); - } else if (StandardOperation.GET.equals(op)) { + } else if (op.isOperation(StandardOperation.GET)) { tt = MethodType.methodType(Object.class, Context.class, Scriptable.class, String.class); mh = lookup.findStatic(ScriptRuntime.class, "name", tt); mh = MethodHandles.insertArguments(mh, 2, name); mh = MethodHandles.permuteArguments(mh, mType, 1, 0); - } else if (RhinoOperation.GETWITHTHIS.equals(op)) { + } else if (op.isOperation(RhinoOperation.GETWITHTHIS)) { tt = MethodType.methodType( Callable.class, String.class, Context.class, Scriptable.class); mh = lookup.findStatic(ScriptRuntime.class, "getNameFunctionAndThis", tt); mh = MethodHandles.insertArguments(mh, 0, name); mh = MethodHandles.permuteArguments(mh, mType, 1, 0); - } else if (RhinoOperation.GETWITHTHISOPTIONAL.equals(op)) { + } else if (op.isOperation(RhinoOperation.GETWITHTHISOPTIONAL)) { tt = MethodType.methodType( Callable.class, String.class, Context.class, Scriptable.class); mh = lookup.findStatic(ScriptRuntime.class, "getNameFunctionAndThisOptional", tt); mh = MethodHandles.insertArguments(mh, 0, name); mh = MethodHandles.permuteArguments(mh, mType, 1, 0); - } else if (StandardOperation.SET.equals(op)) { + } else if (op.isOperation(StandardOperation.SET)) { mh = bindStringParameter(lookup, mType, ScriptRuntime.class, "setName", 4, name); - } else if (RhinoOperation.SETSTRICT.equals(op)) { + } else if (op.isOperation(RhinoOperation.SETSTRICT)) { mh = bindStringParameter(lookup, mType, ScriptRuntime.class, "strictSetName", 4, name); - } else if (RhinoOperation.SETCONST.equals(op)) { + } else if (op.isOperation(RhinoOperation.SETCONST)) { mh = bindStringParameter(lookup, mType, ScriptRuntime.class, "setConst", 3, name); } if (mh != null) { return new GuardedInvocation(mh); } - throw new UnsupportedOperationException(rootOp.toString()); + throw new UnsupportedOperationException(op.toString()); } - /** If the operation is a named operation, then return the name, */ - static String getName(Operation op) { - Object nameObj = NamedOperation.getName(op); - if (nameObj instanceof String) { - return (String) nameObj; - } else if (nameObj != null) { - throw new UnsupportedOperationException(op.toString()); - } else { - return ""; + private GuardedInvocation getMathInvocation( + MethodHandles.Lookup lookup, MethodType mType, ParsedOperation op) + throws NoSuchMethodException, IllegalAccessException { + MethodHandle mh = null; + + if (op.isOperation(RhinoOperation.ADD)) { + mh = lookup.findStatic(ScriptRuntime.class, "add", mType); + } else if (op.isOperation(RhinoOperation.TONUMBER)) { + mh = lookup.findStatic(ScriptRuntime.class, "toNumber", mType); + } else if (op.isOperation(RhinoOperation.TONUMERIC)) { + mh = lookup.findStatic(ScriptRuntime.class, "toNumeric", mType); + } else if (op.isOperation(RhinoOperation.TOBOOLEAN)) { + mh = lookup.findStatic(ScriptRuntime.class, "toBoolean", mType); + } else if (op.isOperation(RhinoOperation.TOINT32)) { + mh = lookup.findStatic(ScriptRuntime.class, "toInt32", mType); + } else if (op.isOperation(RhinoOperation.TOUINT32)) { + mh = lookup.findStatic(ScriptRuntime.class, "toUint32", mType); + } else if (op.isOperation(RhinoOperation.EQ)) { + mh = lookup.findStatic(ScriptRuntime.class, "eq", mType); + } else if (op.isOperation(RhinoOperation.SHALLOWEQ)) { + mh = lookup.findStatic(ScriptRuntime.class, "shallowEq", mType); + } else if (op.isOperation(RhinoOperation.COMPARE_GT)) { + mh = makeCompare(lookup, Token.GT); + } else if (op.isOperation(RhinoOperation.COMPARE_LT)) { + mh = makeCompare(lookup, Token.LT); + } else if (op.isOperation(RhinoOperation.COMPARE_GE)) { + mh = makeCompare(lookup, Token.GE); + } else if (op.isOperation(RhinoOperation.COMPARE_LE)) { + mh = makeCompare(lookup, Token.LE); } + + if (mh != null) { + return new GuardedInvocation(mh); + } + throw new UnsupportedOperationException(op.toString()); + } + + /** + * The "compare" operation in ScriptRuntime uses an integer flag to determine how to compare, + * but in our bytecode we've split this into four separate instructions for future optimization + * -- here, construct a method handle back to doing the things the old way as a fallback. + */ + private MethodHandle makeCompare(MethodHandles.Lookup lookup, int op) + throws NoSuchMethodException, IllegalAccessException { + MethodType tt = + MethodType.methodType(Boolean.TYPE, Object.class, Object.class, Integer.TYPE); + MethodHandle mh = lookup.findStatic(ScriptRuntime.class, "compare", tt); + mh = MethodHandles.insertArguments(mh, 2, op); + return mh; } /** diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/ParsedOperation.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/ParsedOperation.java new file mode 100644 index 0000000000..2538c5c5ee --- /dev/null +++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/ParsedOperation.java @@ -0,0 +1,56 @@ +package org.mozilla.javascript.optimizer; + +import jdk.dynalink.NamedOperation; +import jdk.dynalink.Namespace; +import jdk.dynalink.NamespaceOperation; +import jdk.dynalink.Operation; + +@SuppressWarnings("AndroidJdkLibsChecker") +class ParsedOperation { + private final Operation root; + private final Namespace namespace; + private final String name; + private final Operation operation; + + ParsedOperation(Operation rootOp) { + this.root = rootOp; + // Many, but not all, operations in our system are named operations + Object nameObj = NamedOperation.getName(rootOp); + if (nameObj instanceof String) { + this.name = (String) nameObj; + } else if (nameObj != null) { + throw new UnsupportedOperationException(rootOp.toString()); + } else { + this.name = ""; + } + + // All operations in our system are namespace operations with one namespace + Operation op = NamedOperation.getBaseOperation(rootOp); + assert op instanceof NamespaceOperation; + NamespaceOperation nsOp = (NamespaceOperation) op; + assert nsOp.getNamespaceCount() == 1; + this.namespace = nsOp.getNamespace(0); + this.operation = nsOp.getBaseOperation(); + } + + boolean isNamespace(Namespace ns) { + return ns.equals(namespace); + } + + boolean isOperation(Operation op) { + return op.equals(operation); + } + + boolean isOperation(Operation op1, Operation op2) { + return op1.equals(operation) || op2.equals(operation); + } + + String getName() { + return name; + } + + @Override + public String toString() { + return root.toString(); + } +} diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/RhinoNamespace.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/RhinoNamespace.java index 12ccd43549..627313fd25 100644 --- a/rhino/src/main/java/org/mozilla/javascript/optimizer/RhinoNamespace.java +++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/RhinoNamespace.java @@ -9,4 +9,5 @@ @SuppressWarnings("AndroidJdkLibsChecker") public enum RhinoNamespace implements Namespace { NAME, + MATH, } diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/RhinoOperation.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/RhinoOperation.java index f60ead750b..f55aed4e24 100644 --- a/rhino/src/main/java/org/mozilla/javascript/optimizer/RhinoOperation.java +++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/RhinoOperation.java @@ -18,4 +18,16 @@ public enum RhinoOperation implements Operation { SETCONST, SETELEMENT, SETINDEX, + ADD, + EQ, + SHALLOWEQ, + COMPARE_GT, + COMPARE_LT, + COMPARE_GE, + COMPARE_LE, + TOBOOLEAN, + TOINT32, + TOUINT32, + TONUMBER, + TONUMERIC, } diff --git a/rhino/src/main/java/org/mozilla/javascript/optimizer/Signatures.java b/rhino/src/main/java/org/mozilla/javascript/optimizer/Signatures.java index 93ac321aa4..ffa1310759 100644 --- a/rhino/src/main/java/org/mozilla/javascript/optimizer/Signatures.java +++ b/rhino/src/main/java/org/mozilla/javascript/optimizer/Signatures.java @@ -104,7 +104,9 @@ interface Signatures { /** * NAME:GETWITHTHIS:{name}: Looks up a name in the scope like NAME:GET, and sets "this" in the * "last stored scriptable." Falls back to ScriptRuntime.getNameFunctionAndThis. Also, this - * version of the signature makes the scope the first argument, as described above. + * version of the signature makes the scope the first argument, as described above. Also, + * NAME:GETWITHTHISOPTIONAL:{name} has different semantics for an optional function call, but it + * uses this same signature. */ String NAME_GET_THIS = "(Lorg/mozilla/javascript/Scriptable;" @@ -150,4 +152,47 @@ interface Signatures { + "Ljava/lang/Object;" + "Lorg/mozilla/javascript/Context;" + ")Ljava/lang/Object;"; + + /** + * MATH:ADD: Add the first two arguments on the stack, which could be numbers, strings, or + * really just about anything. + */ + String MATH_ADD = + "(Ljava/lang/Object;" + + "Ljava/lang/Object;" + + "Lorg/mozilla/javascript/Context;" + + ")Ljava/lang/Object;"; + + /** MATH:TOBOOLEAN: Make the object into a primitive boolean. */ + String MATH_TO_BOOLEAN = "(Ljava/lang/Object;)Z"; + + /** MATH:EQ: Are the two arguments equal? */ + String MATH_EQ = "(Ljava/lang/Object;Ljava/lang/Object;)Z"; + + /** MATH:SHALLOWEQ: Like EQ but not. */ + String MATH_SHALLOW_EQ = MATH_EQ; + + /** + * A list of four possible compare operations, that all share the same signature: + * + * + */ + String MATH_COMPARE = "(Ljava/lang/Object;Ljava/lang/Object;)Z"; + + /** MATH:TONUMBER: Convert the object to a Java "double". */ + String MATH_TO_NUMBER = "(Ljava/lang/Object;)D"; + + /** MATH:TONUMERIC: Convert the object to a Java "Number". */ + String MATH_TO_NUMERIC = "(Ljava/lang/Object;)Ljava/lang/Number;"; + + /** MATH:TOINT32: Convert the object to a Java "int". */ + String MATH_TO_INT32 = "(Ljava/lang/Object;)I"; + + /** MATH:TOUINT32: Convert the object to a Java "long" that represents an unsigned integer. */ + String MATH_TO_UINT32 = "(Ljava/lang/Object;)J"; }