-
Notifications
You must be signed in to change notification settings - Fork 850
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a linker that understands consts
The ConstAwareLinker turns invocations to look up object properties and variables that are truly constant into constants in the JVM. This makes constants massively faster -- when people use them.
- Loading branch information
Showing
5 changed files
with
177 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
100 changes: 100 additions & 0 deletions
100
rhino/src/main/java/org/mozilla/javascript/optimizer/ConstAwareLinker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package org.mozilla.javascript.optimizer; | ||
|
||
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; | ||
import jdk.dynalink.linker.GuardingDynamicLinker; | ||
import jdk.dynalink.linker.LinkRequest; | ||
import jdk.dynalink.linker.LinkerServices; | ||
import jdk.dynalink.linker.support.Guards; | ||
import org.mozilla.javascript.NativeWith; | ||
import org.mozilla.javascript.RhinoException; | ||
import org.mozilla.javascript.ScriptableObject; | ||
|
||
@SuppressWarnings("AndroidJdkLibsChecker") | ||
class ConstAwareLinker implements GuardingDynamicLinker { | ||
@Override | ||
public GuardedInvocation getGuardedInvocation(LinkRequest req, LinkerServices svc) | ||
throws Exception { | ||
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); | ||
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 (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. | ||
MethodHandle guard = Guards.asType(Guards.getIdentityGuard(target), mType); | ||
// Replace the actual method invocation with one that just returns a constant. | ||
// Works because we can drop all arguments here. | ||
MethodHandle mh = | ||
MethodHandles.dropArguments( | ||
MethodHandles.constant(Object.class, constValue), | ||
0, | ||
mType.parameterList()); | ||
if (DefaultLinker.DEBUG) { | ||
System.out.println(rootOp + " constant"); | ||
} | ||
return new GuardedInvocation(mh, guard); | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/** | ||
* Return the value of the specified property, but only if it's found in the root object that we | ||
* search, and only if it's a constant. Return null otherwise, which means that we can't handle | ||
* constants with a value of "null," which should not be a big loss. | ||
*/ | ||
private Object getConstValue(Object t, String name) { | ||
if (t instanceof NativeWith) { | ||
// Support constants referenced from inside functions | ||
return getConstValue(((NativeWith) t).getPrototype(), name); | ||
} | ||
if (!(t instanceof ScriptableObject)) { | ||
return null; | ||
} | ||
try { | ||
ScriptableObject target = (ScriptableObject) t; | ||
// Just look in the root of the object -- don't mess around with | ||
// nested scopes and things, to keep this simple and foolproof. | ||
if (target.has(name, target)) { | ||
int attributes = target.getAttributes(name); | ||
if ((attributes & ScriptableObject.READONLY) != 0 | ||
&& (attributes & ScriptableObject.PERMANENT) != 0 | ||
&& (attributes & ScriptableObject.UNINITIALIZED_CONST) == 0) { | ||
// If we get here then this object's value will not change for the | ||
// lifetime of the target object. | ||
return target.get(name, target); | ||
} | ||
} | ||
} catch (RhinoException re) { | ||
// Some implementations of ScriptableObject will fail on this operation with | ||
// an exception, so treat that as "not found". | ||
} | ||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
tests/src/test/java/org/mozilla/javascript/tests/ConstWhiteBoxTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package org.mozilla.javascript.tests; | ||
|
||
import org.mozilla.javascript.Context; | ||
import org.mozilla.javascript.drivers.LanguageVersion; | ||
import org.mozilla.javascript.drivers.RhinoTest; | ||
import org.mozilla.javascript.drivers.ScriptTestsBase; | ||
|
||
@RhinoTest("testsrc/jstests/const-white-box.js") | ||
@LanguageVersion(Context.VERSION_ES6) | ||
public class ConstWhiteBoxTest extends ScriptTestsBase {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
load("testsrc/assert.js"); | ||
|
||
// These tests deliberately exercise the optimizations in ConstAwareLinker. | ||
|
||
// Verify that things that aren't constant aren't constant | ||
var notConstant = 1; | ||
assertEquals(1, notConstant); | ||
notConstant = 2; | ||
assertEquals(2, notConstant); | ||
|
||
// Verify that things that are constant stay constant | ||
const constant = 1; | ||
assertEquals(1, constant); | ||
constant = 2; | ||
assertEquals(1, constant); | ||
|
||
// Verify that this works in a loop | ||
function checkConstantness() { | ||
constant++; | ||
assertEquals(1, constant); | ||
} | ||
const ITERATIONS = 10; | ||
for (let i = 0; i < ITERATIONS; i++) { | ||
checkConstantness(); | ||
} | ||
|
||
// Verify that we can set a local constant in a function | ||
function localConstantness() { | ||
const localConst = 1; | ||
assertEquals(1, localConst); | ||
localConst = 2; | ||
assertEquals(1, localConst); | ||
} | ||
for (let i = 0; i < ITERATIONS; i++) { | ||
localConstantness(); | ||
} | ||
|
||
// Set up an object with a const field and try it out | ||
const o = { | ||
notConst: 1, | ||
}; | ||
Object.defineProperty(o, "const", { | ||
value: 1, | ||
configurable: false, | ||
writable: false, | ||
}); | ||
assertEquals(1, o.notConst); | ||
assertEquals(1, o.const); | ||
o.notConst = 2; | ||
o.const = 2; | ||
assertEquals(2, o.notConst); | ||
assertEquals(1, o.const); | ||
|
||
// Verify that it works in a function | ||
function objectConstness() { | ||
o.const++; | ||
assertEquals(1, o.const); | ||
} | ||
for (let i = 0; i < ITERATIONS; i++) { | ||
objectConstness(); | ||
} | ||
|
||
'success'; |