8037534: Use scope types to determine optimistic types
Reviewed-by: hannesw, lagergren
This commit is contained in:
parent
a7394f09e5
commit
0cbec476dd
@ -172,6 +172,13 @@ final class Attr extends NodeOperatorVisitor<OptimisticLexicalContext> {
|
||||
return end(node);
|
||||
}
|
||||
|
||||
@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 @@ final class Attr extends NodeOperatorVisitor<OptimisticLexicalContext> {
|
||||
for (final Expression arg : callNode.getArgs()) {
|
||||
tagOptimistic(arg);
|
||||
}
|
||||
tagNeverOptimistic(callNode.getFunction());
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -669,7 +677,7 @@ final class Attr extends NodeOperatorVisitor<OptimisticLexicalContext> {
|
||||
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());
|
||||
@ -793,6 +801,12 @@ final class Attr extends NodeOperatorVisitor<OptimisticLexicalContext> {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterIndexNode(IndexNode indexNode) {
|
||||
tagNeverOptimistic(indexNode.getBase());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveIndexNode(final IndexNode indexNode) {
|
||||
// return end(ensureSymbolOptimistic(Type.OBJECT, indexNode));
|
||||
|
@ -49,14 +49,13 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE;
|
||||
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 @@ public class ClassEmitter implements Emitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 @@ public class ClassEmitter implements Emitter {
|
||||
.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();
|
||||
}
|
||||
|
@ -178,9 +178,9 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||
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 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||
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 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||
* 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 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||
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 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||
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());
|
||||
|
@ -35,9 +35,17 @@ import java.util.List;
|
||||
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 @@ public final class CompilationEnvironment {
|
||||
|
||||
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 final class CompilationEnvironment {
|
||||
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 @@ public final class CompilationEnvironment {
|
||||
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 @@ public final class CompilationEnvironment {
|
||||
* @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 @@ public final class CompilationEnvironment {
|
||||
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 @@ public final class CompilationEnvironment {
|
||||
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 @@ public final class CompilationEnvironment {
|
||||
*/
|
||||
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
|
||||
|
@ -63,7 +63,7 @@ class ConstantData {
|
||||
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 @@ class ConstantData {
|
||||
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);
|
||||
|
@ -620,7 +620,7 @@ final class CompiledFunction {
|
||||
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 @@ final class CompiledFunction {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -320,7 +320,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData {
|
||||
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 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData {
|
||||
CompilationPhases.EAGER.makeOptimistic(),
|
||||
isStrict(),
|
||||
this,
|
||||
runtimeScope,
|
||||
isVariableArity() ? null : new ParamTypeMap(functionNodeId, explicitParams(fnCallSiteType)),
|
||||
invalidatedProgramPoints,
|
||||
continuationEntryPoints,
|
||||
@ -345,11 +346,11 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData {
|
||||
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 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData {
|
||||
phases.makeOptimistic(ScriptEnvironment.globalOptimistic()),
|
||||
isStrict(),
|
||||
this,
|
||||
runtimeScope,
|
||||
fnCallSiteType == null || isVariableArity() ?
|
||||
null :
|
||||
new ParamTypeMap(
|
||||
@ -506,11 +508,11 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData {
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,7 @@ import jdk.nashorn.internal.codegen.CompilerConstants.Call;
|
||||
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 @@ import jdk.nashorn.internal.lookup.MethodHandleFunctionality;
|
||||
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 @@ public class RewriteException extends Exception {
|
||||
* @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 @@ public class RewriteException extends Exception {
|
||||
* @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 class RewriteException extends Exception {
|
||||
public Object getReturnValueDestructive() {
|
||||
assert byteCodeSlots != null;
|
||||
byteCodeSlots = null;
|
||||
runtimeScope = null;
|
||||
return getUOE().getReturnValueDestructive();
|
||||
}
|
||||
|
||||
@ -165,6 +186,14 @@ public class RewriteException extends Exception {
|
||||
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";
|
||||
|
@ -331,7 +331,7 @@ public abstract class ScriptFunction extends ScriptObject {
|
||||
* 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 @@ public abstract class ScriptFunction extends ScriptObject {
|
||||
* @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 @@ public abstract class ScriptFunction extends ScriptObject {
|
||||
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 @@ public abstract class ScriptFunction extends ScriptObject {
|
||||
* 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) {
|
||||
|
@ -38,7 +38,6 @@ import java.util.Collections;
|
||||
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 @@ public abstract class ScriptFunctionData {
|
||||
* @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 @@ public abstract class ScriptFunctionData {
|
||||
* 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 @@ public abstract class ScriptFunctionData {
|
||||
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 @@ public abstract class ScriptFunctionData {
|
||||
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 @@ public abstract class ScriptFunctionData {
|
||||
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 @@ public abstract class ScriptFunctionData {
|
||||
* @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 @@ public abstract class ScriptFunctionData {
|
||||
}
|
||||
|
||||
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)) {
|
||||
|
@ -3024,7 +3024,7 @@ public abstract class ScriptObject implements PropertyAccess {
|
||||
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);
|
||||
}
|
||||
|
@ -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 @@ public class OptimisticRecompilationTest {
|
||||
|
||||
@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 @@ public class OptimisticRecompilationTest {
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user