8037534: Use scope types to determine optimistic types
Reviewed-by: hannesw, lagergren
--- a/nashorn/src/jdk/nashorn/internal/codegen/Attr.java Mon Mar 24 18:40:11 2014 +0100
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Attr.java Mon Mar 24 18:41:06 2014 +0100
@@ -173,6 +173,13 @@
}
@Override
+ public boolean enterAccessNode(AccessNode accessNode) {
+ tagNeverOptimistic(accessNode.getBase());
+ tagNeverOptimistic(accessNode.getProperty());
+ return true;
+ };
+
+ @Override
public Node leaveAccessNode(final AccessNode accessNode) {
return end(ensureSymbolTypeOverride(accessNode, Type.OBJECT));
}
@@ -355,6 +362,7 @@
for (final Expression arg : callNode.getArgs()) {
tagOptimistic(arg);
}
+ tagNeverOptimistic(callNode.getFunction());
return true;
}
@@ -669,7 +677,7 @@
if (!identNode.isInitializedHere()) {
symbol.increaseUseCount();
}
- addLocalUse(identNode.getName());
+ addLocalUse(name);
IdentNode node = (IdentNode)identNode.setSymbol(lc, symbol);
if (isTaggedOptimistic(identNode) && symbol.isScope()) {
node = ensureSymbolTypeOverride(node, symbol.getSymbolType());
@@ -794,6 +802,12 @@
}
@Override
+ public boolean enterIndexNode(IndexNode indexNode) {
+ tagNeverOptimistic(indexNode.getBase());
+ return true;
+ }
+
+ @Override
public Node leaveIndexNode(final IndexNode indexNode) {
// return end(ensureSymbolOptimistic(Type.OBJECT, indexNode));
return end(ensureSymbolTypeOverride(indexNode, Type.OBJECT));
--- a/nashorn/src/jdk/nashorn/internal/codegen/ClassEmitter.java Mon Mar 24 18:40:11 2014 +0100
+++ b/nashorn/src/jdk/nashorn/internal/codegen/ClassEmitter.java Mon Mar 24 18:41:06 2014 +0100
@@ -49,14 +49,13 @@
import static jdk.nashorn.internal.codegen.CompilerConstants.STRICT_MODE;
import static jdk.nashorn.internal.codegen.CompilerConstants.className;
import static jdk.nashorn.internal.codegen.CompilerConstants.methodDescriptor;
-import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
import static jdk.nashorn.internal.codegen.CompilerConstants.typeDescriptor;
+import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.security.AccessController;
import java.security.PrivilegedAction;
-import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;
@@ -285,7 +284,7 @@
}
/**
- * Constructs a primitive specific method for getting the ith entry from the constants table and cast.
+ * Constructs a primitive specific method for getting the ith entry from the constants table as an array.
* @param clazz Array class.
*/
private void defineGetArrayMethod(final Class<?> clazz) {
@@ -299,9 +298,8 @@
.load(Type.INT, 0)
.arrayload()
.checkcast(clazz)
- .dup()
- .arraylength()
- .invoke(staticCallNoLookup(Arrays.class, "copyOf", clazz, clazz, int.class))
+ .invoke(virtualCallNoLookup(clazz, "clone", Object.class))
+ .checkcast(clazz)
._return();
getArrayMethod.end();
}
--- a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java Mon Mar 24 18:40:11 2014 +0100
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java Mon Mar 24 18:41:06 2014 +0100
@@ -178,9 +178,9 @@
private static final Type SCRIPTFUNCTION_IMPL_TYPE = Type.typeFor(ScriptFunction.class);
private static final Call INIT_REWRITE_EXCEPTION = CompilerConstants.specialCallNoLookup(RewriteException.class,
- "<init>", void.class, UnwarrantedOptimismException.class, Object[].class);
+ "<init>", void.class, UnwarrantedOptimismException.class, Object[].class, String[].class, ScriptObject.class);
private static final Call INIT_REWRITE_EXCEPTION_REST_OF = CompilerConstants.specialCallNoLookup(RewriteException.class,
- "<init>", void.class, UnwarrantedOptimismException.class, Object[].class, int[].class);
+ "<init>", void.class, UnwarrantedOptimismException.class, Object[].class, String[].class, ScriptObject.class, int[].class);
private static final Call ENSURE_INT = CompilerConstants.staticCallNoLookup(OptimisticReturnFilters.class,
"ensureInt", int.class, Object.class, int.class);
@@ -1535,7 +1535,7 @@
try {
final boolean markOptimistic;
if (emittedMethods.add(functionNode.getName())) {
- markOptimistic = generateUnwarrantedOptimismExceptionHandlers();
+ markOptimistic = generateUnwarrantedOptimismExceptionHandlers(functionNode);
generateContinuationHandler();
method.end(); // wrap up this method
unit = lc.popCompileUnit(functionNode.getCompileUnit());
@@ -4133,7 +4133,7 @@
* entry to its immediately preceding one for longest matching prefix.
* @return true if there is at least one exception handler
*/
- private boolean generateUnwarrantedOptimismExceptionHandlers() {
+ private boolean generateUnwarrantedOptimismExceptionHandlers(final FunctionNode fn) {
if(!useOptimisticTypes()) {
return false;
}
@@ -4274,8 +4274,14 @@
method.dup(2);
method.dup(2);
method.pop();
+ loadConstant(getByteCodeSymbolNames(fn));
+ if (fn.compilerConstant(SCOPE).hasSlot()) {
+ method.loadCompilerConstant(SCOPE);
+ } else {
+ method.loadNull();
+ }
final CompilationEnvironment env = compiler.getCompilationEnvironment();
- if(env.isCompileRestOf()) {
+ if (env.isCompileRestOf()) {
loadConstant(env.getContinuationEntryPoints());
method.invoke(INIT_REWRITE_EXCEPTION_REST_OF);
} else {
@@ -4287,6 +4293,26 @@
return true;
}
+ private static String[] getByteCodeSymbolNames(final FunctionNode fn) {
+ // Only names of local variables on the function level are captured. This information is used to reduce
+ // deoptimizations, so as much as we can capture will help. We rely on the fact that function wide variables are
+ // all live all the time, so the array passed to rewrite exception contains one element for every slotted symbol
+ // here.
+ final List<String> names = new ArrayList<>();
+ for (final Symbol symbol: fn.getBody().getSymbols()) {
+ if (symbol.hasSlot()) {
+ if (symbol.isScope()) {
+ // slot + scope can only be true for parameters
+ assert symbol.isParam();
+ names.add(null);
+ } else {
+ names.add(symbol.getName());
+ }
+ }
+ }
+ return names.toArray(new String[names.size()]);
+ }
+
private static String commonPrefix(final String s1, final String s2) {
final int l1 = s1.length();
final int l = Math.min(l1, s2.length());
--- a/nashorn/src/jdk/nashorn/internal/codegen/CompilationEnvironment.java Mon Mar 24 18:40:11 2014 +0100
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CompilationEnvironment.java Mon Mar 24 18:41:06 2014 +0100
@@ -35,9 +35,17 @@
import java.util.Map;
import java.util.Set;
import jdk.nashorn.internal.codegen.types.Type;
+import jdk.nashorn.internal.ir.AccessNode;
+import jdk.nashorn.internal.ir.Expression;
import jdk.nashorn.internal.ir.FunctionNode;
+import jdk.nashorn.internal.ir.IdentNode;
+import jdk.nashorn.internal.ir.IndexNode;
import jdk.nashorn.internal.ir.Optimistic;
+import jdk.nashorn.internal.objects.NativeArray;
+import jdk.nashorn.internal.runtime.FindProperty;
+import jdk.nashorn.internal.runtime.Property;
import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
+import jdk.nashorn.internal.runtime.ScriptObject;
/**
* Class for managing metadata during a compilation, e.g. which phases
@@ -52,6 +60,10 @@
private RecompilableScriptFunctionData compiledFunction;
+ // Runtime scope in effect at the time of the compilation. Used to evaluate types of expressions and prevent overly
+ // optimistic assumptions (which will lead to unnecessary deoptimizing recompilations).
+ private final ScriptObject runtimeScope;
+
private boolean strict;
private final boolean onDemand;
@@ -169,14 +181,18 @@
public CompilationEnvironment(
final CompilationPhases phases,
final boolean strict) {
- this(phases, null, null, null, null, strict, false);
+ this(phases, null, null, null, null, null, strict, false);
}
/**
* Constructor for compilation environment of the rest-of method
* @param phases compilation phases
* @param strict strict mode
- * @param compiledFunction recompiled function
+ * @param compiledFunction the function being compiled
+ * @param runtimeScope the runtime scope in effect at the time of compilation. It can be used to evaluate types of
+ * scoped variables to guide the optimistic compilation, should the call to this method trigger code compilation.
+ * Can be null if current runtime scope is not known, but that might cause compilation of code that will need more
+ * subsequent deoptimization passes.
* @param paramTypeMap known parameter types if any exist
* @param invalidatedProgramPoints map of invalidated program points to their type
* @param continuationEntryPoint program points used as the continuation entry points in the current rest-of sequence
@@ -186,11 +202,12 @@
final CompilationPhases phases,
final boolean strict,
final RecompilableScriptFunctionData compiledFunction,
+ final ScriptObject runtimeScope,
final ParamTypeMap paramTypeMap,
final Map<Integer, Type> invalidatedProgramPoints,
final int[] continuationEntryPoint,
final boolean onDemand) {
- this(phases, paramTypeMap, invalidatedProgramPoints, compiledFunction, continuationEntryPoint, strict, onDemand);
+ this(phases, paramTypeMap, invalidatedProgramPoints, compiledFunction, runtimeScope, continuationEntryPoint, strict, onDemand);
}
/**
@@ -198,6 +215,10 @@
* @param phases compilation phases
* @param strict strict mode
* @param compiledFunction recompiled function
+ * @param runtimeScope the runtime scope in effect at the time of compilation. It can be used to evaluate types of
+ * scoped variables to guide the optimistic compilation, should the call to this method trigger code compilation.
+ * Can be null if current runtime scope is not known, but that might cause compilation of code that will need more
+ * subsequent deoptimization passes.
* @param paramTypeMap known parameter types
* @param invalidatedProgramPoints map of invalidated program points to their type
* @param onDemand is this an on demand compilation
@@ -206,10 +227,11 @@
final CompilationPhases phases,
final boolean strict,
final RecompilableScriptFunctionData compiledFunction,
+ final ScriptObject runtimeScope,
final ParamTypeMap paramTypeMap,
final Map<Integer, Type> invalidatedProgramPoints,
final boolean onDemand) {
- this(phases, paramTypeMap, invalidatedProgramPoints, compiledFunction, null, strict, onDemand);
+ this(phases, paramTypeMap, invalidatedProgramPoints, compiledFunction, runtimeScope, null, strict, onDemand);
}
private CompilationEnvironment(
@@ -217,17 +239,16 @@
final ParamTypeMap paramTypes,
final Map<Integer, Type> invalidatedProgramPoints,
final RecompilableScriptFunctionData compiledFunction,
+ final ScriptObject runtimeScope,
final int[] continuationEntryPoints,
final boolean strict,
final boolean onDemand) {
this.phases = phases;
this.paramTypes = paramTypes;
this.continuationEntryPoints = continuationEntryPoints;
- this.invalidatedProgramPoints =
- invalidatedProgramPoints == null ?
- Collections.unmodifiableMap(new HashMap<Integer, Type>()) :
- invalidatedProgramPoints;
+ this.invalidatedProgramPoints = invalidatedProgramPoints == null ? new HashMap<Integer, Type>() : invalidatedProgramPoints;
this.compiledFunction = compiledFunction;
+ this.runtimeScope = runtimeScope;
this.strict = strict;
this.optimistic = phases.contains(CompilationPhase.PROGRAM_POINT_PHASE);
this.onDemand = onDemand;
@@ -352,13 +373,94 @@
*/
Type getOptimisticType(final Optimistic node) {
assert useOptimisticTypes();
- final Type invalidType = invalidatedProgramPoints.get(node.getProgramPoint());
- if (invalidType != null) {
- return invalidType;//.nextWider();
+ final int programPoint = node.getProgramPoint();
+ final Type validType = invalidatedProgramPoints.get(programPoint);
+ if (validType != null) {
+ return validType;
+ }
+ final Type mostOptimisticType = node.getMostOptimisticType();
+ final Type evaluatedType = getEvaluatedType(node);
+ if(evaluatedType != null) {
+ if(evaluatedType.widerThan(mostOptimisticType)) {
+ final Type newValidType = evaluatedType.isObject() || evaluatedType.isBoolean() ? Type.OBJECT : evaluatedType;
+ // Update invalidatedProgramPoints so we don't re-evaluate the expression next time. This is a heuristic
+ // as we're doing a tradeoff. Re-evaluating expressions on each recompile takes time, but it might
+ // notice a widening in the type of the expression and thus prevent an unnecessary deoptimization later.
+ // We'll presume though that the types of expressions are mostly stable, so if we evaluated it in one
+ // compilation, we'll keep to that and risk a low-probability deoptimization if its type gets widened
+ // in the future.
+ invalidatedProgramPoints.put(node.getProgramPoint(), newValidType);
+ }
+ return evaluatedType;
}
return node.getMostOptimisticType();
}
+
+ private Type getEvaluatedType(final Optimistic expr) {
+ if(expr instanceof IdentNode) {
+ return runtimeScope == null ? null : getPropertyType(runtimeScope, ((IdentNode)expr).getName());
+ } else if(expr instanceof AccessNode) {
+ final AccessNode accessNode = (AccessNode)expr;
+ final Object base = evaluateSafely(accessNode.getBase());
+ if(!(base instanceof ScriptObject)) {
+ return null;
+ }
+ return getPropertyType((ScriptObject)base, accessNode.getProperty().getName());
+ } else if(expr instanceof IndexNode) {
+ final IndexNode indexNode = (IndexNode)expr;
+ final Object base = evaluateSafely(indexNode.getBase());
+ if(!(base instanceof NativeArray)) {
+ // We only know how to deal with NativeArray. TODO: maybe manage buffers too
+ return null;
+ }
+ // NOTE: optimistic array getters throw UnwarrantedOptimismException based on the type of their underlying
+ // array storage, not based on values of individual elements. Thus, a LongArrayData will throw UOE for every
+ // optimistic int linkage attempt, even if the long value being returned in the first invocation would be
+ // representable as int. That way, we can presume that the array's optimistic type is the most optimistic
+ // type for which an element getter has a chance of executing successfully.
+ return ((NativeArray)base).getArray().getOptimisticType();
+ }
+ return null;
+ }
+
+ private static Type getPropertyType(final ScriptObject sobj, final String name) {
+ final FindProperty find = sobj.findProperty(name, true);
+ if(find == null) {
+ return null;
+ }
+ final Class<?> clazz = find.getProperty().getCurrentType();
+ return clazz == null ? null : Type.typeFor(clazz);
+ }
+
+ private Object evaluateSafely(Expression expr) {
+ if(expr instanceof IdentNode) {
+ return runtimeScope == null ? null : evaluatePropertySafely(runtimeScope, ((IdentNode)expr).getName());
+ } else if(expr instanceof AccessNode) {
+ final AccessNode accessNode = (AccessNode)expr;
+ final Object base = evaluateSafely(accessNode.getBase());
+ if(!(base instanceof ScriptObject)) {
+ return null;
+ }
+ return evaluatePropertySafely((ScriptObject)base, accessNode.getProperty().getName());
+ }
+ return null;
+ }
+
+ private static Object evaluatePropertySafely(final ScriptObject sobj, final String name) {
+ final FindProperty find = sobj.findProperty(name, true);
+ if(find == null) {
+ return null;
+ }
+ final Property property = find.getProperty();
+ final ScriptObject owner = find.getOwner();
+ if(property.hasGetterFunction(owner)) {
+ // Possible side effects; can't evaluate safely
+ return null;
+ }
+ return property.getObjectValue(owner, owner);
+ }
+
/**
* Should this compilation use optimistic types in general.
* If this is false we will only set non-object types to things that can
--- a/nashorn/src/jdk/nashorn/internal/codegen/ConstantData.java Mon Mar 24 18:40:11 2014 +0100
+++ b/nashorn/src/jdk/nashorn/internal/codegen/ConstantData.java Mon Mar 24 18:41:06 2014 +0100
@@ -63,7 +63,7 @@
private int calcHashCode() {
final Class<?> cls = array.getClass();
- if (cls == Object[].class) {
+ if (!cls.getComponentType().isPrimitive()) {
return Arrays.hashCode((Object[])array);
} else if (cls == double[].class) {
return Arrays.hashCode((double[])array);
@@ -91,7 +91,7 @@
final Class<?> cls = array.getClass();
if (cls == otherArray.getClass()) {
- if (cls == Object[].class) {
+ if (!cls.getComponentType().isPrimitive()) {
return Arrays.equals((Object[])array, (Object[])otherArray);
} else if (cls == double[].class) {
return Arrays.equals((double[])array, (double[])otherArray);
--- a/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunction.java Mon Mar 24 18:40:11 2014 +0100
+++ b/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunction.java Mon Mar 24 18:41:06 2014 +0100
@@ -620,7 +620,7 @@
return null;
}
SwitchPoint.invalidateAll(new SwitchPoint[] { optimisticAssumptions });
- return data.compile(callSiteType, invalidatedProgramPoints, "Deoptimizing recompilation");
+ return data.compile(callSiteType, invalidatedProgramPoints, e.getRuntimeScope(), "Deoptimizing recompilation");
}
MethodHandle compileRestOfMethod(final MethodType callSiteType, final RewriteException e) {
@@ -634,7 +634,7 @@
System.arraycopy(prevEntryPoints, 0, entryPoints, 1, l);
}
entryPoints[0] = e.getProgramPoint();
- return data.compileRestOfMethod(callSiteType, invalidatedProgramPoints, entryPoints);
+ return data.compileRestOfMethod(callSiteType, invalidatedProgramPoints, entryPoints, e.getRuntimeScope());
}
}
--- a/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Mon Mar 24 18:40:11 2014 +0100
+++ b/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Mon Mar 24 18:41:06 2014 +0100
@@ -320,7 +320,7 @@
return sb.toString();
}
- MethodHandle compileRestOfMethod(final MethodType fnCallSiteType, final Map<Integer, Type> invalidatedProgramPoints, final int[] continuationEntryPoints) {
+ MethodHandle compileRestOfMethod(final MethodType fnCallSiteType, final Map<Integer, Type> invalidatedProgramPoints, final int[] continuationEntryPoints, final ScriptObject runtimeScope) {
LOG.info("Rest-of compilation of '", functionName, "' signature: ", fnCallSiteType, " ", stringifyInvalidations(invalidatedProgramPoints));
final String scriptName = RECOMPILATION_PREFIX + RECOMPILE_ID.incrementAndGet() + "$restOf";
@@ -331,6 +331,7 @@
CompilationPhases.EAGER.makeOptimistic(),
isStrict(),
this,
+ runtimeScope,
isVariableArity() ? null : new ParamTypeMap(functionNodeId, explicitParams(fnCallSiteType)),
invalidatedProgramPoints,
continuationEntryPoints,
@@ -345,11 +346,11 @@
return lookupWithExplicitType(fn, MethodType.methodType(fn.getReturnType().getTypeClass(), RewriteException.class));
}
- private FunctionNode compileTypeSpecialization(final MethodType actualCallSiteType) {
- return compile(actualCallSiteType, null, "Type specialized compilation");
+ private FunctionNode compileTypeSpecialization(final MethodType actualCallSiteType, final ScriptObject runtimeScope) {
+ return compile(actualCallSiteType, null, runtimeScope, "Type specialized compilation");
}
- FunctionNode compile(final MethodType actualCallSiteType, final Map<Integer, Type> invalidatedProgramPoints, final String reason) {
+ FunctionNode compile(final MethodType actualCallSiteType, final Map<Integer, Type> invalidatedProgramPoints, final ScriptObject runtimeScope, final String reason) {
final String scriptName = RECOMPILATION_PREFIX + RECOMPILE_ID.incrementAndGet();
final MethodType fnCallSiteType = actualCallSiteType == null ? null : actualCallSiteType.changeParameterType(0, ScriptFunction.class);
LOG.info(reason, " of '", functionName, "' signature: ", fnCallSiteType, " ", stringifyInvalidations(invalidatedProgramPoints));
@@ -361,6 +362,7 @@
phases.makeOptimistic(ScriptEnvironment.globalOptimistic()),
isStrict(),
this,
+ runtimeScope,
fnCallSiteType == null || isVariableArity() ?
null :
new ParamTypeMap(
@@ -506,11 +508,11 @@
}
@Override
- CompiledFunction getBest(final MethodType callSiteType) {
+ CompiledFunction getBest(final MethodType callSiteType, final ScriptObject runtimeScope) {
synchronized(code) {
- final CompiledFunction existingBest = super.getBest(callSiteType);
+ final CompiledFunction existingBest = super.getBest(callSiteType, runtimeScope);
// TODO: what if callSiteType is vararg?
- return existingBest != null ? existingBest : addCode(compileTypeSpecialization(callSiteType), callSiteType);
+ return existingBest != null ? existingBest : addCode(compileTypeSpecialization(callSiteType, runtimeScope), callSiteType);
}
}
--- a/nashorn/src/jdk/nashorn/internal/runtime/RewriteException.java Mon Mar 24 18:40:11 2014 +0100
+++ b/nashorn/src/jdk/nashorn/internal/runtime/RewriteException.java Mon Mar 24 18:41:06 2014 +0100
@@ -39,6 +39,7 @@
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.lookup.MethodHandleFactory;
import jdk.nashorn.internal.lookup.MethodHandleFunctionality;
+import jdk.nashorn.internal.objects.Global;
/**
* Used to signal to the linker to relink the callee
@@ -47,6 +48,9 @@
public class RewriteException extends Exception {
private static final MethodHandleFunctionality MH = MethodHandleFactory.getFunctionality();
+ // Runtime scope in effect at the time of the compilation. Used to evaluate types of expressions and prevent overly
+ // optimistic assumptions (which will lead to unnecessary deoptimizing recompilations).
+ private ScriptObject runtimeScope;
//contents of bytecode slots
private Object[] byteCodeSlots;
private final int[] previousContinuationEntryPoints;
@@ -84,8 +88,8 @@
* @param e the {@link UnwarrantedOptimismException} that triggered this exception.
* @param byteCodeSlots contents of local variable slots at the time of rewrite at the program point
*/
- public RewriteException(final UnwarrantedOptimismException e, final Object[] byteCodeSlots) {
- this(e, byteCodeSlots, null);
+ public RewriteException(final UnwarrantedOptimismException e, final Object[] byteCodeSlots, final String[] byteCodeSymbolNames, final ScriptObject runtimeScope) {
+ this(e, byteCodeSlots, byteCodeSymbolNames, runtimeScope, null);
}
/**
@@ -95,12 +99,28 @@
* @param previousContinuationEntryPoints an array of continuation entry points that were already executed during
* one logical invocation of the function (a rest-of triggering a rest-of triggering a...)
*/
- public RewriteException(final UnwarrantedOptimismException e, final Object[] byteCodeSlots, final int[] previousContinuationEntryPoints) {
+ public RewriteException(final UnwarrantedOptimismException e, final Object[] byteCodeSlots, final String[] byteCodeSymbolNames, final ScriptObject runtimeScope, final int[] previousContinuationEntryPoints) {
super("", e, false, Context.DEBUG);
this.byteCodeSlots = byteCodeSlots;
+ this.runtimeScope = mergeSlotsWithScope(byteCodeSlots, byteCodeSymbolNames, runtimeScope);
this.previousContinuationEntryPoints = previousContinuationEntryPoints;
}
+ private static ScriptObject mergeSlotsWithScope(final Object[] byteCodeSlots, final String[] byteCodeSymbolNames,
+ final ScriptObject runtimeScope) {
+ final ScriptObject locals = Global.newEmptyInstance();
+ final int l = Math.min(byteCodeSlots.length, byteCodeSymbolNames.length);
+ for(int i = 0; i < l; ++i) {
+ final String name = byteCodeSymbolNames[i];
+ final Object value = byteCodeSlots[i];
+ if(name != null) {
+ locals.set(name, value, true);
+ }
+ }
+ locals.setProto(runtimeScope);
+ return locals;
+ }
+
/**
* Array populator used for saving the local variable state into the array contained in the
* RewriteException
@@ -127,6 +147,7 @@
public Object getReturnValueDestructive() {
assert byteCodeSlots != null;
byteCodeSlots = null;
+ runtimeScope = null;
return getUOE().getReturnValueDestructive();
}
@@ -165,6 +186,14 @@
return previousContinuationEntryPoints;
}
+ /**
+ * Returns the runtime scope that was in effect when the exception was thrown.
+ * @return the runtime scope.
+ */
+ public ScriptObject getRuntimeScope() {
+ return runtimeScope;
+ }
+
private static String stringify(final Object returnValue) {
if (returnValue == null) {
return "null";
--- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunction.java Mon Mar 24 18:40:11 2014 +0100
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunction.java Mon Mar 24 18:41:06 2014 +0100
@@ -331,7 +331,7 @@
* assumptions.
*/
private GuardedInvocation getBestInvoker(final MethodType callSiteType, final int callerProgramPoint) {
- return data.getBestInvoker(callSiteType, callerProgramPoint);
+ return data.getBestInvoker(callSiteType, callerProgramPoint, scope);
}
/**
@@ -342,7 +342,7 @@
* @return bound invoke handle
*/
public final MethodHandle getBoundInvokeHandle(final Object self) {
- return MH.bindTo(bindToCalleeIfNeeded(data.getGenericInvoker()), self);
+ return MH.bindTo(bindToCalleeIfNeeded(data.getGenericInvoker(scope)), self);
}
/**
@@ -471,7 +471,7 @@
protected GuardedInvocation findNewMethod(final CallSiteDescriptor desc) {
final MethodType type = desc.getMethodType();
assert desc.getMethodType().returnType() == Object.class && !NashornCallSiteDescriptor.isOptimistic(desc);
- final GuardedInvocation bestCtorInv = data.getBestConstructor(type);
+ final GuardedInvocation bestCtorInv = data.getBestConstructor(type, scope);
//TODO - ClassCastException
return new GuardedInvocation(pairArguments(bestCtorInv.getInvocation(), type), getFunctionGuard(this), bestCtorInv.getSwitchPoint());
}
@@ -697,7 +697,7 @@
* These don't want a callee parameter, so bind that. Name binding is optional.
*/
MethodHandle getCallMethodHandle(final MethodType type, final String bindName) {
- return pairArguments(bindToNameIfNeeded(bindToCalleeIfNeeded(data.getGenericInvoker()), bindName), type);
+ return pairArguments(bindToNameIfNeeded(bindToCalleeIfNeeded(data.getGenericInvoker(scope)), bindName), type);
}
private static MethodHandle bindToNameIfNeeded(final MethodHandle methodHandle, final String bindName) {
--- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java Mon Mar 24 18:40:11 2014 +0100
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java Mon Mar 24 18:41:06 2014 +0100
@@ -38,7 +38,6 @@
import java.util.Map;
import java.util.WeakHashMap;
import jdk.internal.dynalink.linker.GuardedInvocation;
-import jdk.nashorn.internal.runtime.linker.JavaAdapterFactory;
import jdk.nashorn.internal.runtime.linker.LinkerCallSite;
/**
@@ -203,18 +202,18 @@
* @return guarded invocation with method handle to best invoker and potentially a switch point guarding optimistic
* assumptions.
*/
- final GuardedInvocation getBestInvoker(final MethodType callSiteType, final int callerProgramPoint) {
- final CompiledFunction cf = getBest(callSiteType);
+ final GuardedInvocation getBestInvoker(final MethodType callSiteType, final int callerProgramPoint, final ScriptObject runtimeScope) {
+ final CompiledFunction cf = getBest(callSiteType, runtimeScope);
assert cf != null;
return new GuardedInvocation(cf.createInvoker(callSiteType.returnType(), callerProgramPoint), cf.getOptimisticAssumptionsSwitchPoint());
}
- final GuardedInvocation getBestConstructor(final MethodType callSiteType) {
+ final GuardedInvocation getBestConstructor(final MethodType callSiteType, final ScriptObject runtimeScope) {
if (!isConstructor()) {
throw typeError("not.a.constructor", toSource());
}
// Constructor call sites don't have a "this", but getBest is meant to operate on "callee, this, ..." style
- final CompiledFunction cf = getBest(callSiteType.insertParameterTypes(1, Object.class));
+ final CompiledFunction cf = getBest(callSiteType.insertParameterTypes(1, Object.class), runtimeScope);
return new GuardedInvocation(cf.getConstructor(), cf.getOptimisticAssumptionsSwitchPoint());
}
@@ -232,12 +231,12 @@
* is generated, get the most generic of all versions of this function and adapt it
* to Objects.
*
- * TODO this is only public because {@link JavaAdapterFactory} can't supply us with
- * a MethodType that we can use for lookup due to boostrapping problems. Can be fixed
- *
+ * @param runtimeScope the runtime scope. It can be used to evaluate types of scoped variables to guide the
+ * optimistic compilation, should the call to this method trigger code compilation. Can be null if current runtime
+ * scope is not known, but that might cause compilation of code that will need more deoptimization passes.
* @return generic invoker of this script function
*/
- public final MethodHandle getGenericInvoker() {
+ final MethodHandle getGenericInvoker(final ScriptObject runtimeScope) {
MethodHandle invoker;
final Reference<MethodHandle> ref = GENERIC_INVOKERS.get(this);
if(ref != null) {
@@ -246,16 +245,16 @@
return invoker;
}
}
- invoker = createGenericInvoker();
+ invoker = createGenericInvoker(runtimeScope);
GENERIC_INVOKERS.put(this, new WeakReference<>(invoker));
return invoker;
}
- private MethodHandle createGenericInvoker() {
- return makeGenericMethod(getGeneric().createComposableInvoker());
+ private MethodHandle createGenericInvoker(final ScriptObject runtimeScope) {
+ return makeGenericMethod(getGeneric(runtimeScope).createComposableInvoker());
}
- final MethodHandle getGenericConstructor() {
+ final MethodHandle getGenericConstructor(final ScriptObject runtimeScope) {
MethodHandle constructor;
final Reference<MethodHandle> ref = GENERIC_CONSTRUCTORS.get(this);
if(ref != null) {
@@ -264,29 +263,32 @@
return constructor;
}
}
- constructor = createGenericConstructor();
+ constructor = createGenericConstructor(runtimeScope);
GENERIC_CONSTRUCTORS.put(this, new WeakReference<>(constructor));
return constructor;
}
- private MethodHandle createGenericConstructor() {
- return makeGenericMethod(getGeneric().createComposableConstructor());
+ private MethodHandle createGenericConstructor(final ScriptObject runtimeScope) {
+ return makeGenericMethod(getGeneric(runtimeScope).createComposableConstructor());
}
/**
* Returns the best function for the specified call site type.
* @param callSiteType The call site type. Call site types are expected to have the form
* {@code (callee, this[, args...])}.
+ * @param runtimeScope the runtime scope. It can be used to evaluate types of scoped variables to guide the
+ * optimistic compilation, should the call to this method trigger code compilation. Can be null if current runtime
+ * scope is not known, but that might cause compilation of code that will need more deoptimization passes.
* @return the best function for the specified call site type.
*/
- CompiledFunction getBest(final MethodType callSiteType) {
+ CompiledFunction getBest(final MethodType callSiteType, final ScriptObject runtimeScope) {
return code.best(callSiteType, isRecompilable());
}
abstract boolean isRecompilable();
- CompiledFunction getGeneric() {
- return getBest(getGenericType());
+ CompiledFunction getGeneric(final ScriptObject runtimeScope) {
+ return getBest(getGenericType(), runtimeScope);
}
@@ -326,7 +328,8 @@
final int boundFlags = flags & ~NEEDS_CALLEE & ~USES_THIS;
final CompiledFunctions boundList = new CompiledFunctions(fn.getName());
- final CompiledFunction bindTarget = new CompiledFunction(getGenericInvoker(), getGenericConstructor());
+ final ScriptObject runtimeScope = fn.getScope();
+ final CompiledFunction bindTarget = new CompiledFunction(getGenericInvoker(runtimeScope), getGenericConstructor(runtimeScope));
boundList.add(bind(bindTarget, fn, self, allArgs));
return new FinalScriptFunctionData(name, Math.max(0, getArity() - length), boundList, boundFlags);
@@ -518,7 +521,7 @@
* @throws Throwable if there is an exception/error with the invocation or thrown from it
*/
Object invoke(final ScriptFunction fn, final Object self, final Object... arguments) throws Throwable {
- final MethodHandle mh = getGenericInvoker();
+ final MethodHandle mh = getGenericInvoker(fn.getScope());
final Object selfObj = convertThisObject(self);
final Object[] args = arguments == null ? ScriptRuntime.EMPTY_ARRAY : arguments;
@@ -572,7 +575,7 @@
}
Object construct(final ScriptFunction fn, final Object... arguments) throws Throwable {
- final MethodHandle mh = getGenericConstructor();
+ final MethodHandle mh = getGenericConstructor(fn.getScope());
final Object[] args = arguments == null ? ScriptRuntime.EMPTY_ARRAY : arguments;
if (isVarArg(mh)) {
--- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java Mon Mar 24 18:40:11 2014 +0100
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java Mon Mar 24 18:41:06 2014 +0100
@@ -3024,7 +3024,7 @@
assert sobj != null : "no parent global object in scope";
}
//this will unbox any Number object to its primitive type in case the
- //property supporst primitive types, so it doesn't matter that it comes
+ //property supports primitive types, so it doesn't matter that it comes
//in as an Object.
sobj.addSpillProperty(key, 0, value, true);
}
--- a/nashorn/test/script/currently-failing/OptimisticRecompilationTest.java Mon Mar 24 18:40:11 2014 +0100
+++ b/nashorn/test/script/currently-failing/OptimisticRecompilationTest.java Mon Mar 24 18:41:06 2014 +0100
@@ -24,18 +24,19 @@
*/
package jdk.nashorn.internal.runtime;
+import static org.testng.Assert.fail;
+
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import static org.testng.Assert.fail;
-import org.testng.annotations.Test;
-import org.testng.annotations.AfterTest;
-import org.testng.annotations.BeforeTest;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
/**
* @test
@@ -102,56 +103,54 @@
@Test
public void divisionByZeroTest() {
- //Check that two Deoptimizing recompilations and RewriteExceptions happened
- runTest("function f() {var x = { a: 2, b:1 }; x.a = Number.POSITIVE_INFINITY;"
- + " x.b = 0; print(x.a/x.b);} f()",
- getRecompilationPattern("double", "Infinity"), 2);
+ //Check that one Deoptimizing recompilation and RewriteExceptions happened
+ runTest("function f() {var x1 = { a: 2, b:1 }; x1.a = Number.POSITIVE_INFINITY;"
+ + " x1.b = 0; print(x1.a/x1.b);} f()",
+ getRecompilationPattern("double", "Infinity"), 1);
}
@Test
public void divisionWithRemainderTest() {
//Check that one Deoptimizing recompilation and RewriteException happened
- runTest("function f() {var x = { a: 7, b:2 }; print(x.a/x.b);} f()",
+ runTest("function f() {var x2 = { a: 7, b:2 }; print(x2.a/x2.b);} f()",
getRecompilationPattern("double", "3.5"), 1);
}
@Test
public void infinityMultiplicationTest() {
- //Check that three Deoptimizing recompilations and RewriteExceptions happened
- runTest("function f() {var x = { a: Number.POSITIVE_INFINITY, "
- + "b: Number.POSITIVE_INFINITY}; print(x.a*x.b);} f()",
- getRecompilationPattern("double", "Infinity"), 3);
+ //Check that one deoptimizing recompilation and RewriteExceptions happened
+ runTest("function f() {var x3 = { a: Number.POSITIVE_INFINITY, "
+ + "b: Number.POSITIVE_INFINITY}; print(x3.a*x3.b);} f()",
+ getRecompilationPattern("double", "Infinity"), 1);
}
@Test
public void maxValueMultiplicationTest() {
- runTest("function f() {var x = { a: Number.MAX_VALUE, b: Number.MAX_VALUE};"
- + " print(x.a*x.b);} f()",
- getRecompilationPattern("double", "1.7976931348623157E308"), 3);
+ runTest("function f() {var x4 = { a: Number.MAX_VALUE, b: Number.MAX_VALUE};"
+ + " print(x4.a*x4.b);} f()",
+ getRecompilationPattern("double", "1.7976931348623157E308"), 1);
}
@Test
public void divisionByInfinityTest() {
- //Check that two Deoptimizing recompilations and RewriteExceptions happened
- runTest("function f() {var x = { a: -1, b: Number.POSITIVE_INFINITY};"
- + " print(x.a/x.b);} f()",
- getRecompilationPattern("double", "Infinity"), 2);
+ //Check that one Deoptimizing recompilation and RewriteExceptions happened
+ runTest("function f() {var x5 = { a: -1, b: Number.POSITIVE_INFINITY};"
+ + " print(x5.a/x5.b);} f()",
+ getRecompilationPattern("double", "Infinity"), 1);
}
@Test
public void divisionByStringTest() {
- //Check that three Deoptimizing recompilations and RewriteExceptions happened
- String str1 = getRecompilationPattern("double", "Infinity");
- String str2 = getRecompilationPattern("object", "Hello");
- runTest("function f() {var x = { a: Number.POSITIVE_INFINITY, b: 'Hello'};"
- + " print(x.a/x.b);} f()", String.format("(?s)%s.*%1$s.*%s", str1, str2), 1);
+ //Check that one deoptimizing recompilations and RewriteExceptions happened
+ runTest("function f() {var x6 = { a: Number.POSITIVE_INFINITY, b: 'Hello'};"
+ + " print(x6.a/x6.b);} f()", getRecompilationPattern("double", "Infinity"), 1);
}
@Test
public void nestedFunctionTest() {
//Check that one Deoptimizing recompilations and RewriteExceptions happened
- runTest("var a=3,b,c; function f() {var x = 2, y =1; function g(){ "
- + "var y = x; var z = a; z = x*y; print(a*b); } g() } f()",
+ runTest("var a=3,b,c; function f() {var x7 = 2, y =1; function g(){ "
+ + "var y = x7; var z = a; z = x7*y; print(a*b); } g() } f()",
getRecompilationPattern("object", "undefined"), 1);
}
@@ -165,7 +164,7 @@
@Test
public void functionTest() {
//Check that one Deoptimizing recompilations and RewriteExceptions happened
- runTest("function f(a,b,c) { d = (a + b) * c; print(d);} f()",
+ runTest("function f(a,b,c) { h = (a + b) * c; print(h);} f()",
getRecompilationPattern("double", "NaN"), 1);
}
}