# HG changeset patch # User lagergren # Date 1400506182 -7200 # Node ID 31aed7d9c02a207adaa775f8293eb670aafd8995 # Parent eb9658fa012020b88f9b1ad4b965bf17b6bb4c65 8034206: Make parts of code pipeline reusable in order to facilitate faster warmup and faster lazy compilation. Reviewed-by: hannesw, attila diff -r eb9658fa0120 -r 31aed7d9c02a nashorn/bin/runopt.sh --- a/nashorn/bin/runopt.sh Thu May 15 15:28:51 2014 +0200 +++ b/nashorn/bin/runopt.sh Mon May 19 15:29:42 2014 +0200 @@ -1,13 +1,43 @@ #!/bin/sh +########################################################################################### +# This is a helper script to evaluate nashorn with optimistic types +# it produces a flight recording for every run, and uses the best +# known flags for performance for the current configration +########################################################################################### + +# Flags to instrument lambdaform computation, caching, interpretation and compilation +# Default compile threshold for lambdaforms is 30 #FLAGS="-Djava.lang.invoke.MethodHandle.COMPILE_THRESHOLD=3 -Djava.lang.invoke.MethodHandle.DUMP_CLASS_FILES=true -Djava.lang.invoke.MethodHandle.TRACE_METHOD_LINKAGE=true -Djava.lang.invoke.MethodHandle.TRACE_INTERPRETER=true" + + +# Flags to run trusted tests from the Nashorn test suite #FLAGS="-Djava.security.manager -Djava.security.policy=../build/nashorn.policy -Dnashorn.debug" -FILENAME="./optimistic_$(date|sed "s/ /_/g"|sed "s/:/_/g").jfr" +# Unique timestamped file name for JFR recordings. For JFR, we also have to +# crank up the stack cutoff depth to 1024, because of ridiculously long lambda form +# stack traces. +# +# It is also recommended that you go into $JAVA_HOME/jre/lib/jfr/default.jfc and +# set the "method-sampling-interval" Normal and Maximum sample time as low as you +# can go (10 ms on most platforms). The default is normally higher. The increased +# sampling overhead is usually negligible for Nashorn runs, but the data is better +JFR_FILENAME="./nashorn_$(date|sed "s/ /_/g"|sed "s/:/_/g").jfr" + + +# Directory where to look for nashorn.jar in a dist folder. The default is "..", assuming +# that we run the script from the make dir DIR=.. NASHORN_JAR=$DIR/dist/nashorn.jar + +# The built Nashorn jar is placed first in the bootclasspath to override the JDK +# nashorn.jar in $JAVA_HOME/jre/lib/ext. Thus, we also need -esa, as assertions in +# nashorn count as system assertions in this configuration + +# Type profiling default level is 111, 222 adds some compile time, but is faster + $JAVA_HOME/bin/java \ $FLAGS \ -ea \ @@ -16,17 +46,33 @@ -Xms2G -Xmx2G \ -XX:+UnlockCommercialFeatures \ -XX:+FlightRecorder \ --XX:FlightRecorderOptions=defaultrecording=true,disk=true,dumponexit=true,dumponexitpath=$FILENAME,stackdepth=1024 \ +-XX:FlightRecorderOptions=defaultrecording=true,disk=true,dumponexit=true,dumponexitpath=$JFR_FILENAME,stackdepth=1024 \ -XX:TypeProfileLevel=222 \ --XX:+UnlockExperimentalVMOptions \ --XX:+UseTypeSpeculation \ --XX:+UseMathExactIntrinsics \ --XX:+UnlockDiagnosticVMOptions \ -cp $CLASSPATH:../build/test/classes/ \ jdk.nashorn.tools.Shell ${@} -#-Djava.security.manager= -Djava.security.policy=$DIR/build/nashorn.policy \ +# Below are flags that may come in handy, but aren't used for default runs + + +# Type specialization and math intrinsic replacement should be enabled by default in 8u20 and nine, +# keeping this flag around for experimental reasons. Replace + with - to switch it off +#-XX:+UseTypeSpeculation \ + + +# Same with math intrinsics. They should be enabled by default in 8u20 and 9 +#-XX:+UseMathExactIntrinsics \ + + +# Add -Dnashorn.time to time the compilation phases. +#-Dnashorn.time \ + + +# Add ShowHiddenFrames to get lambda form internals on the stack traces #-XX:+ShowHiddenFrames \ -#-XX:+PrintOptoAssembly \ -#-XX:-TieredCompilation \ -#-XX:CICompilerCount=1 \ + + +# Add print optoassembly to get an asm dump. This requires 1) a debug build, not product, +# That tired compilation is switched off, for C2 only output and that the number of +# compiler threads is set to 1 for determinsm. +#-XX:+PrintOptoAssembly -XX:-TieredCompilation -XX:CICompilerCount=1 \ + diff -r eb9658fa0120 -r 31aed7d9c02a nashorn/src/jdk/nashorn/internal/codegen/ApplySpecialization.java --- a/nashorn/src/jdk/nashorn/internal/codegen/ApplySpecialization.java Thu May 15 15:28:51 2014 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/ApplySpecialization.java Mon May 19 15:29:42 2014 +0200 @@ -35,6 +35,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; + import jdk.nashorn.internal.ir.AccessNode; import jdk.nashorn.internal.ir.CallNode; import jdk.nashorn.internal.ir.Expression; @@ -45,8 +46,6 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.objects.Global; import jdk.nashorn.internal.runtime.Context; -import jdk.nashorn.internal.runtime.Debug; -import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData; import jdk.nashorn.internal.runtime.logging.DebugLogger; import jdk.nashorn.internal.runtime.logging.Loggable; import jdk.nashorn.internal.runtime.logging.Logger; @@ -82,39 +81,31 @@ */ @Logger(name="apply2call") -public final class ApplySpecialization implements Loggable { +public final class ApplySpecialization extends NodeVisitor implements Loggable { private static final boolean USE_APPLY2CALL = Options.getBooleanProperty("nashorn.apply2call", true); - private final RecompilableScriptFunctionData data; - - private FunctionNode functionNode; - - private final MethodType actualCallSiteType; - private final DebugLogger log; - private static final String ARGUMENTS = ARGUMENTS_VAR.symbolName(); + private final Compiler compiler; + + private final Set changed = new HashSet<>(); - private boolean changed; + private final Deque> explodedArguments = new ArrayDeque<>(); - private boolean finished; + private static final String ARGUMENTS = ARGUMENTS_VAR.symbolName(); /** * Apply specialization optimization. Try to explode arguments and call * applies as calls if they just pass on the "arguments" array and * "arguments" doesn't escape. * - * @param context context - * @param data recompilable script function data, which contains e.g. needs callee information - * @param functionNode functionNode - * @param actualCallSiteType actual call site type that we use (not Object[] varargs) + * @param compiler compiler */ - public ApplySpecialization(final Context context, final RecompilableScriptFunctionData data, final FunctionNode functionNode, final MethodType actualCallSiteType) { - this.data = data; - this.functionNode = functionNode; - this.actualCallSiteType = actualCallSiteType; - this.log = initLogger(context); + public ApplySpecialization(final Compiler compiler) { + super(new LexicalContext()); + this.compiler = compiler; + this.log = initLogger(compiler.getContext()); } @Override @@ -128,14 +119,6 @@ } /** - * Return the function node, possibly after transformation - * @return function node - */ - public FunctionNode getFunctionNode() { - return functionNode; - } - - /** * Arguments may only be used as args to the apply. Everything else is disqualified * We cannot control arguments if they escape from the method and go into an unknown * scope, thus we are conservative and treat any access to arguments outside the @@ -143,12 +126,12 @@ * * @return true if arguments escape */ - private boolean argumentsEscape() { + private boolean argumentsEscape(final FunctionNode functionNode) { final Deque> stack = new ArrayDeque<>(); //ensure that arguments is only passed as arg to apply try { - functionNode = (FunctionNode)functionNode.accept(new NodeVisitor(new LexicalContext()) { + functionNode.accept(new NodeVisitor(new LexicalContext()) { private boolean isCurrentArg(final Expression expr) { return !stack.isEmpty() && stack.peek().contains(expr); //args to current apply call } @@ -202,53 +185,60 @@ return false; } - private boolean finish() { - finished = true; - return changed; + @Override + public boolean enterCallNode(final CallNode callNode) { + return !explodedArguments.isEmpty(); } - /** - * Try to do the apply to call transformation - * @return true if successful, false otherwise - */ - public boolean transform() { - if (!USE_APPLY2CALL) { + @Override + public Node leaveCallNode(final CallNode callNode) { + //apply needs to be a global symbol or we don't allow it + + final List newParams = explodedArguments.peek(); + if (isApply(callNode)) { + final List newArgs = new ArrayList<>(); + for (final Expression arg : callNode.getArgs()) { + if (arg instanceof IdentNode && ARGUMENTS.equals(((IdentNode)arg).getName())) { + newArgs.addAll(newParams); + } else { + newArgs.add(arg); + } + } + + changed.add(lc.getCurrentFunction().getId()); + + final CallNode newCallNode = callNode.setArgs(newArgs).setIsApplyToCall(); + + log.fine("Transformed ", + callNode, + " from apply to call => ", + newCallNode, + " in ", + DebugLogger.quote(lc.getCurrentFunction().getName())); + + return newCallNode; + } + + return callNode; + } + + private boolean pushExplodedArgs(final FunctionNode functionNode) { + int start = 0; + + final MethodType actualCallSiteType = compiler.getCallSiteType(functionNode); + if (actualCallSiteType == null) { return false; } - - if (finished) { - throw new AssertionError("Can't apply transform twice"); - } - - assert actualCallSiteType.parameterType(actualCallSiteType.parameterCount() - 1) != Object[].class : "error vararg callsite passed to apply2call " + functionNode.getName() + " " + actualCallSiteType; - changed = false; - - if (!Global.instance().isSpecialNameValid("apply")) { - log.fine("Apply transform disabled: apply/call overridden"); - assert !Global.instance().isSpecialNameValid("call") : "call and apply should have the same SwitchPoint"; - return finish(); - } - - //eval can do anything to escape arguments so that is not ok - if (functionNode.hasEval()) { - return finish(); - } - - if (argumentsEscape()) { - return finish(); - } - - int start = 0; - - if (data.needsCallee()) { + final TypeMap ptm = compiler.getTypeMap(); + if (ptm.needsCallee()) { start++; } start++; //we always uses this - final List params = functionNode.getParameters(); + final List params = functionNode.getParameters(); final List newParams = new ArrayList<>(); final long to = Math.max(params.size(), actualCallSiteType.parameterCount() - start); for (int i = 0; i < to; i++) { @@ -259,45 +249,66 @@ } } - // expand arguments - // this function has to be guarded with call and apply being builtins - functionNode = (FunctionNode)functionNode.accept(new NodeVisitor(new LexicalContext()) { - @Override - public Node leaveCallNode(final CallNode callNode) { - //apply needs to be a global symbol or we don't allow it + explodedArguments.push(newParams); + return true; + } + + @Override + public boolean enterFunctionNode(final FunctionNode functionNode) { + if (!USE_APPLY2CALL) { + return false; + } - if (isApply(callNode)) { - final List newArgs = new ArrayList<>(); - for (final Expression arg : callNode.getArgs()) { - if (arg instanceof IdentNode && ARGUMENTS.equals(((IdentNode)arg).getName())) { - newArgs.addAll(newParams); - } else { - newArgs.add(arg); - } - } - - changed = true; + if (!Global.instance().isSpecialNameValid("apply")) { + log.fine("Apply transform disabled: apply/call overridden"); + assert !Global.instance().isSpecialNameValid("call") : "call and apply should have the same SwitchPoint"; + return false; + } - final CallNode newCallNode = callNode.setArgs(newArgs).setIsApplyToCall(); - log.fine("Transformed " + callNode + " from apply to call => " + newCallNode + " in '" + functionNode.getName() + "'"); - return newCallNode; - } + if (!compiler.isOnDemandCompilation()) { + return false; + } - return callNode; - } - }); + if (functionNode.hasEval()) { + return false; + } - if (changed) { - functionNode = functionNode.clearFlag(null, FunctionNode.USES_ARGUMENTS). - setFlag(null, FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION). - setParameters(null, newParams); + if (argumentsEscape(functionNode)) { + return false; } - if (log.isEnabled()) { - log.info(Debug.id(data) + " Successfully specialized apply to call in '" + functionNode.getName() + "' id=" + functionNode.getId() + " signature=" + actualCallSiteType + " needsCallee=" + data.needsCallee() + " " + functionNode.getSource().getURL()); + return pushExplodedArgs(functionNode); + } + + /** + * Try to do the apply to call transformation + * @return true if successful, false otherwise + */ + @Override + public Node leaveFunctionNode(final FunctionNode functionNode0) { + FunctionNode newFunctionNode = functionNode0; + final String functionName = newFunctionNode.getName(); + + if (changed.contains(newFunctionNode.getId())) { + newFunctionNode = newFunctionNode.clearFlag(lc, FunctionNode.USES_ARGUMENTS). + setFlag(lc, FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION). + setParameters(lc, explodedArguments.peek()); + + if (log.isEnabled()) { + log.info("Successfully specialized apply to call in '", + functionName, + " params=", + explodedArguments.peek(), + "' id=", + newFunctionNode.getId(), + " source=", + newFunctionNode.getSource().getURL()); + } } - return finish(); + explodedArguments.pop(); + + return newFunctionNode; } private static boolean isApply(final CallNode callNode) { diff -r eb9658fa0120 -r 31aed7d9c02a nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java --- a/nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java Thu May 15 15:28:51 2014 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/AssignSymbols.java Mon May 19 15:29:42 2014 +0200 @@ -152,12 +152,12 @@ private final Deque> thisProperties = new ArrayDeque<>(); private final Map globalSymbols = new HashMap<>(); //reuse the same global symbol - private final CompilationEnvironment env; + private final Compiler compiler; - public AssignSymbols(final CompilationEnvironment env) { + public AssignSymbols(final Compiler compiler) { super(new LexicalContext()); - this.env = env; - this.log = initLogger(env.getContext()); + this.compiler = compiler; + this.log = initLogger(compiler.getContext()); this.debug = log.isEnabled(); } @@ -216,7 +216,7 @@ }); } - private IdentNode compilerConstantIdentifier(CompilerConstants cc) { + private IdentNode compilerConstantIdentifier(final CompilerConstants cc) { return (IdentNode)createImplicitIdentifier(cc.symbolName()).setSymbol(lc.getCurrentFunction().compilerConstant(cc)); } @@ -382,8 +382,8 @@ symbol.setFlags(flags); } - if((isVar || isParam) && env.useOptimisticTypes() && env.isOnDemandCompilation()) { - env.declareLocalSymbol(name); + if((isVar || isParam) && compiler.useOptimisticTypes() && compiler.isOnDemandCompilation()) { + compiler.declareLocalSymbol(name); } return symbol; @@ -751,7 +751,7 @@ } @Override - public Node leaveFunctionNode(FunctionNode functionNode) { + public Node leaveFunctionNode(final FunctionNode functionNode) { return markProgramBlock( removeUnusedSlots( @@ -842,8 +842,8 @@ return runtimeNode; } - private FunctionNode markProgramBlock(FunctionNode functionNode) { - if (env.isOnDemandCompilation() || !functionNode.isProgram()) { + private FunctionNode markProgramBlock(final FunctionNode functionNode) { + if (compiler.isOnDemandCompilation() || !functionNode.isProgram()) { return functionNode; } diff -r eb9658fa0120 -r 31aed7d9c02a nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java --- a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java Thu May 15 15:28:51 2014 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java Mon May 19 15:29:42 2014 +0200 @@ -253,16 +253,20 @@ // Number of live locals on entry to (and thus also break from) labeled blocks. private final IntDeque labeledBlockBreakLiveLocals = new IntDeque(); + //is this a rest of compilation + private final int[] continuationEntryPoints; + /** * Constructor. * * @param compiler */ - CodeGenerator(final Compiler compiler) { + CodeGenerator(final Compiler compiler, final int[] continuationEntryPoints) { super(new CodeGeneratorLexicalContext()); - this.compiler = compiler; - this.callSiteFlags = compiler.getEnv()._callsite_flags; - this.log = initLogger(compiler.getCompilationEnvironment().getContext()); + this.compiler = compiler; + this.continuationEntryPoints = continuationEntryPoints; + this.callSiteFlags = compiler.getEnv()._callsite_flags; + this.log = initLogger(compiler.getContext()); } @Override @@ -324,8 +328,36 @@ return method; } + private boolean isRestOf() { + return continuationEntryPoints != null; + } + private boolean isOptimisticOrRestOf() { - return useOptimisticTypes() || compiler.getCompilationEnvironment().isCompileRestOf(); + return useOptimisticTypes() || isRestOf(); + } + + private boolean isCurrentContinuationEntryPoint(final int programPoint) { + return isRestOf() && getCurrentContinuationEntryPoint() == programPoint; + } + + private int[] getContinuationEntryPoints() { + return isRestOf() ? continuationEntryPoints : null; + } + + private int getCurrentContinuationEntryPoint() { + return isRestOf() ? continuationEntryPoints[0] : INVALID_PROGRAM_POINT; + } + + private boolean isContinuationEntryPoint(final int programPoint) { + if (isRestOf()) { + assert continuationEntryPoints != null; + for (final int cep : continuationEntryPoints) { + if (cep == programPoint) { + return true; + } + } + } + return false; } /** @@ -406,6 +438,7 @@ } void getProto() { + //empty } @Override @@ -443,7 +476,7 @@ //information. final FunctionNode fn = lc.getCurrentFunction(); final int fnId = fn.getId(); - final int externalDepth = compiler.getCompilationEnvironment().getScriptFunctionData(fnId).getExternalSymbolDepth(symbol.getName()); + final int externalDepth = compiler.getScriptFunctionData(fnId).getExternalSymbolDepth(symbol.getName()); //count the number of scopes from this place to the start of the function @@ -589,11 +622,11 @@ return maybeNew(objectToNumber(narrowest), objectToNumber(widest)); } - private static Type booleanToInt(Type t) { + private static Type booleanToInt(final Type t) { return t == Type.BOOLEAN ? Type.INT : t; } - private static Type objectToNumber(Type t) { + private static Type objectToNumber(final Type t) { return t.isObject() ? Type.NUMBER : t; } @@ -805,7 +838,7 @@ } @Override - public boolean enterSUB(UnaryNode unaryNode) { + public boolean enterSUB(final UnaryNode unaryNode) { loadSUB(unaryNode, resultBounds); return false; } @@ -877,19 +910,19 @@ } @Override - public boolean enterNOT(UnaryNode unaryNode) { + public boolean enterNOT(final UnaryNode unaryNode) { loadNOT(unaryNode); return false; } @Override - public boolean enterADD(UnaryNode unaryNode) { + public boolean enterADD(final UnaryNode unaryNode) { loadADD(unaryNode, resultBounds); return false; } @Override - public boolean enterBIT_NOT(UnaryNode unaryNode) { + public boolean enterBIT_NOT(final UnaryNode unaryNode) { loadBIT_NOT(unaryNode); return false; } @@ -913,7 +946,7 @@ } @Override - public boolean enterVOID(UnaryNode unaryNode) { + public boolean enterVOID(final UnaryNode unaryNode) { loadVOID(unaryNode, resultBounds); return false; } @@ -1041,7 +1074,7 @@ } private boolean useOptimisticTypes() { - return !lc.inSplitNode() && compiler.getCompilationEnvironment().useOptimisticTypes(); + return !lc.inSplitNode() && compiler.useOptimisticTypes(); } @Override @@ -1438,7 +1471,7 @@ * @param flags the flags that need optimism stripped from them. * @return flags without optimism */ - static int nonOptimisticFlags(int flags) { + static int nonOptimisticFlags(final int flags) { return flags & ~(CALLSITE_OPTIMISTIC | -1 << CALLSITE_PROGRAM_POINT_SHIFT); } @@ -1626,12 +1659,14 @@ } else if (symbol.isParam() && (varsInScope || hasArguments || symbol.isScope())) { assert symbol.isScope() : "scope for " + symbol + " should have been set in AssignSymbols already " + function.getName() + " varsInScope="+varsInScope+" hasArguments="+hasArguments+" symbol.isScope()=" + symbol.isScope(); assert !(hasArguments && symbol.hasSlot()) : "slot for " + symbol + " should have been removed in Lower already " + function.getName(); - final Type paramType; + + final Type paramType; final Symbol paramSymbol; - if(hasArguments) { + + if (hasArguments) { assert !symbol.hasSlot() : "slot for " + symbol + " should have been removed in Lower already "; paramSymbol = null; - paramType = null; + paramType = null; } else { paramSymbol = symbol; // NOTE: We're relying on the fact here that Block.symbols is a LinkedHashMap, hence it will @@ -1647,11 +1682,15 @@ } } } + tuples.add(new MapTuple(symbol.getName(), symbol, paramType, paramSymbol) { //this symbol will be put fielded, we can't initialize it as undefined with a known type @Override public Class getValueType() { - return OBJECT_FIELDS_ONLY || value == null || paramType.isBoolean() ? Object.class : paramType.getTypeClass(); + if (OBJECT_FIELDS_ONLY || value == null || paramType == null) { + return Object.class; + } + return paramType.isBoolean() ? Object.class : paramType.getTypeClass(); } }); } @@ -1718,13 +1757,13 @@ } } - private void initializeInternalFunctionParameter(CompilerConstants cc, final FunctionNode fn, final Label functionStart, final int slot) { + private void initializeInternalFunctionParameter(final CompilerConstants cc, final FunctionNode fn, final Label functionStart, final int slot) { final Symbol symbol = initializeInternalFunctionOrSplitParameter(cc, fn, functionStart, slot); // Internal function params (:callee, this, and :varargs) are never expanded to multiple slots assert symbol.getFirstSlot() == slot; } - private Symbol initializeInternalFunctionOrSplitParameter(CompilerConstants cc, final FunctionNode fn, final Label functionStart, final int slot) { + private Symbol initializeInternalFunctionOrSplitParameter(final CompilerConstants cc, final FunctionNode fn, final Label functionStart, final int slot) { final Symbol symbol = fn.getBody().getExistingSymbol(cc.symbolName()); final Type type = Type.typeFor(cc.type()); method.initializeMethodParameter(symbol, type, functionStart); @@ -1739,7 +1778,7 @@ * and we need to spread them into their new locations. * @param function the function for which parameter-spreading code needs to be emitted */ - private void expandParameterSlots(FunctionNode function) { + private void expandParameterSlots(final FunctionNode function) { final List parameters = function.getParameters(); // Calculate the total number of incoming parameter slots int currentIncomingSlot = function.needsCallee() ? 2 : 1; @@ -1787,7 +1826,7 @@ * doing an on-demand ("just-in-time") compilation, then we aren't generating code for inner functions. */ private boolean compileOutermostOnly() { - return RecompilableScriptFunctionData.LAZY_COMPILATION || compiler.getCompilationEnvironment().isOnDemandCompilation(); + return RecompilableScriptFunctionData.LAZY_COMPILATION || compiler.isOnDemandCompilation(); } @Override @@ -1818,10 +1857,8 @@ unit = lc.pushCompileUnit(functionNode.getCompileUnit()); assert lc.hasCompileUnits(); - final CompilationEnvironment compEnv = compiler.getCompilationEnvironment(); - final boolean isRestOf = compEnv.isCompileRestOf(); final ClassEmitter classEmitter = unit.getClassEmitter(); - pushMethodEmitter(isRestOf ? classEmitter.restOfMethod(functionNode) : classEmitter.method(functionNode)); + pushMethodEmitter(isRestOf() ? classEmitter.restOfMethod(functionNode) : classEmitter.method(functionNode)); method.setPreventUndefinedLoad(); if(useOptimisticTypes()) { lc.pushUnwarrantedOptimismHandlers(); @@ -1832,7 +1869,7 @@ method.begin(); - if (isRestOf) { + if (isRestOf()) { final ContinuationInfo ci = new ContinuationInfo(); fnIdToContinuationInfo.put(fnId, ci); method.gotoLoopStart(ci.getHandlerLabel()); @@ -1868,8 +1905,8 @@ markOptimistic = false; } - FunctionNode newFunctionNode = functionNode.setState(lc, CompilationState.EMITTED); - if(markOptimistic) { + FunctionNode newFunctionNode = functionNode.setState(lc, CompilationState.BYTECODE_GENERATED); + if (markOptimistic) { newFunctionNode = newFunctionNode.setFlag(lc, FunctionNode.IS_DEOPTIMIZABLE); } @@ -1972,6 +2009,7 @@ unit = lc.pushCompileUnit(arrayUnit.getCompileUnit()); final String className = unit.getUnitClassName(); + assert unit != null; final String name = currentFunction.uniqueName(SPLIT_PREFIX.symbolName()); final String signature = methodDescriptor(type, ScriptFunction.class, Object.class, ScriptObject.class, type); @@ -2213,7 +2251,7 @@ public Boolean get() { value.accept(new NodeVisitor(new LexicalContext()) { @Override - public boolean enterFunctionNode(FunctionNode functionNode) { + public boolean enterFunctionNode(final FunctionNode functionNode) { return false; } @@ -2240,11 +2278,10 @@ final List> tuples = new ArrayList<>(); final List gettersSetters = new ArrayList<>(); + final int ccp = getCurrentContinuationEntryPoint(); + Expression protoNode = null; - boolean restOfProperty = false; - final CompilationEnvironment env = compiler.getCompilationEnvironment(); - final int ccp = env.getCurrentContinuationEntryPoint(); for (final PropertyNode propertyNode : elements) { final Expression value = propertyNode.getValue(); @@ -2366,15 +2403,17 @@ final Symbol rhsSymbol = rhs instanceof IdentNode ? ((IdentNode)rhs).getSymbol() : null; // One must be a "undefined" identifier, otherwise we can't get here assert lhsSymbol != null || rhsSymbol != null; + final Symbol undefinedSymbol; - if(isUndefinedSymbol(lhsSymbol)) { + if (isUndefinedSymbol(lhsSymbol)) { undefinedSymbol = lhsSymbol; } else { assert isUndefinedSymbol(rhsSymbol); undefinedSymbol = rhsSymbol; } - if (!undefinedSymbol.isScope()) { + assert undefinedSymbol != null; //remove warning + if (undefinedSymbol == null || !undefinedSymbol.isScope()) { return false; //disallow undefined as local var or parameter } @@ -2391,15 +2430,14 @@ return false; } - final CompilationEnvironment env = compiler.getCompilationEnvironment(); // TODO: why? - if (env.isCompileRestOf()) { + if (isRestOf()) { return false; } //make sure that undefined has not been overridden or scoped as a local var //between us and global - if (!env.isGlobalSymbol(lc.getCurrentFunction(), "undefined")) { + if (!compiler.isGlobalSymbol(lc.getCurrentFunction(), "undefined")) { return false; } @@ -2532,8 +2570,7 @@ * @return true if the expression or any of its subexpressions was deoptimized in the current recompilation chain. */ private boolean isDeoptimizedExpression(final Expression rootExpr) { - final CompilationEnvironment env = compiler.getCompilationEnvironment(); - if(!env.isCompileRestOf()) { + if(!isRestOf()) { return false; } return new Supplier() { @@ -2542,14 +2579,14 @@ public Boolean get() { rootExpr.accept(new NodeVisitor(new LexicalContext()) { @Override - public boolean enterFunctionNode(FunctionNode functionNode) { + public boolean enterFunctionNode(final FunctionNode functionNode) { return false; } @Override public boolean enterDefault(final Node node) { if(!contains && node instanceof Optimistic) { final int pp = ((Optimistic)node).getProgramPoint(); - contains = isValid(pp) && env.isContinuationEntryPoint(pp); + contains = isValid(pp) && isContinuationEntryPoint(pp); } return !contains; } @@ -2806,12 +2843,11 @@ return false; } - final CaseNode defaultCase = switchNode.getDefaultCase(); - final Label breakLabel = switchNode.getBreakLabel(); - final int liveLocalsOnBreak = method.getUsedSlotsWithLiveTemporaries(); - - final boolean hasDefault = defaultCase != null; - if(hasDefault && cases.size() == 1) { + final CaseNode defaultCase = switchNode.getDefaultCase(); + final Label breakLabel = switchNode.getBreakLabel(); + final int liveLocalsOnBreak = method.getUsedSlotsWithLiveTemporaries(); + + if (defaultCase != null && cases.size() == 1) { // default case only assert cases.get(0) == defaultCase; loadAndDiscard(expression); @@ -2822,7 +2858,7 @@ // NOTE: it can still change in the tableswitch/lookupswitch case if there's no default case // but we need to add a synthetic default case for local variable conversions - Label defaultLabel = hasDefault? defaultCase.getEntry() : breakLabel; + Label defaultLabel = defaultCase != null ? defaultCase.getEntry() : breakLabel; final boolean hasSkipConversion = LocalVariableConversion.hasLiveConversion(switchNode); if (switchNode.isInteger()) { @@ -2922,7 +2958,8 @@ method.ifne(caseNode.getEntry()); } } - if(hasDefault) { + + if (defaultCase != null) { method._goto(defaultLabel); } else { method.beforeJoinPoint(switchNode); @@ -3244,9 +3281,10 @@ method.label(repeatLabel); final int liveLocalsOnContinue = method.getUsedSlotsWithLiveTemporaries(); - final Block body = loopNode.getBody(); - final Label breakLabel = loopNode.getBreakLabel(); + final Block body = loopNode.getBody(); + final Label breakLabel = loopNode.getBreakLabel(); final boolean testHasLiveConversion = test != null && LocalVariableConversion.hasLiveConversion(test); + if(Expression.isAlwaysTrue(test)) { if(test != null) { loadAndDiscard(test); @@ -3254,12 +3292,14 @@ method.beforeJoinPoint(test); } } - } else if(testHasLiveConversion) { - emitBranch(test.getExpression(), body.getEntryLabel(), true); - method.beforeJoinPoint(test); - method._goto(breakLabel); - } else { - emitBranch(test.getExpression(), breakLabel, false); + } else if (test != null) { + if (testHasLiveConversion) { + emitBranch(test.getExpression(), body.getEntryLabel(), true); + method.beforeJoinPoint(test); + method._goto(breakLabel); + } else { + emitBranch(test.getExpression(), breakLabel, false); + } } body.accept(this); @@ -3376,7 +3416,7 @@ method._try(tryLabel, endLabel, catchLabel); } - boolean reachable = method.isReachable(); + final boolean reachable = method.isReachable(); if(reachable) { popScope(); if(bodyCanThrow) { @@ -3955,13 +3995,13 @@ } @Override - public boolean enterLabelNode(LabelNode labelNode) { + public boolean enterLabelNode(final LabelNode labelNode) { labeledBlockBreakLiveLocals.push(lc.getUsedSlotCount()); return true; } @Override - protected boolean enterDefault(Node node) { + protected boolean enterDefault(final Node node) { throw new AssertionError("Code generator entered node of type " + node.getClass().getName()); } @@ -3973,7 +4013,7 @@ final Label falseLabel = new Label("ternary_false"); final Label exitLabel = new Label("ternary_exit"); - Type outNarrowest = Type.narrowest(resultBounds.widest, Type.generic(Type.widestReturnType(trueExpr.getType(), falseExpr.getType()))); + final Type outNarrowest = Type.narrowest(resultBounds.widest, Type.generic(Type.widestReturnType(trueExpr.getType(), falseExpr.getType()))); final TypeBounds outBounds = resultBounds.notNarrowerThan(outNarrowest); emitBranch(test, falseLabel, false); @@ -4255,9 +4295,8 @@ assert lc.peek() == functionNode; final int fnId = functionNode.getId(); - final CompilationEnvironment env = compiler.getCompilationEnvironment(); - - final RecompilableScriptFunctionData data = env.getScriptFunctionData(fnId); + + final RecompilableScriptFunctionData data = compiler.getScriptFunctionData(fnId); assert data != null : functionNode.getName() + " has no data"; @@ -4276,7 +4315,7 @@ createFunction.end(); } - if (addInitializer && !initializedFunctionIds.contains(fnId) && !env.isOnDemandCompilation()) { + if (addInitializer && !initializedFunctionIds.contains(fnId) && !compiler.isOnDemandCompilation()) { functionNode.getCompileUnit().addFunctionInitializer(data, functionNode); initializedFunctionIds.add(fnId); } @@ -4359,11 +4398,10 @@ } MethodEmitter emit(final int ignoredArgCount) { - final CompilationEnvironment env = compiler.getCompilationEnvironment(); - final int programPoint = optimistic.getProgramPoint(); - final boolean optimisticOrContinuation = isOptimistic || env.isContinuationEntryPoint(programPoint); - final boolean currentContinuationEntryPoint = env.isCurrentContinuationEntryPoint(programPoint); - final int stackSizeOnEntry = method.getStackSize() - ignoredArgCount; + final int programPoint = optimistic.getProgramPoint(); + final boolean optimisticOrContinuation = isOptimistic || isContinuationEntryPoint(programPoint); + final boolean currentContinuationEntryPoint = isCurrentContinuationEntryPoint(programPoint); + final int stackSizeOnEntry = method.getStackSize() - ignoredArgCount; // First store the values on the stack opportunistically into local variables. Doing it before loadStack() // allows us to not have to pop/load any arguments that are pushed onto it by loadStack() in the second @@ -4387,7 +4425,8 @@ final Label afterConsumeStack = isOptimistic || currentContinuationEntryPoint ? new Label("after_consume_stack") : null; if(isOptimistic) { beginTry = new Label("try_optimistic"); - catchLabel = new Label(afterConsumeStack.toString() + "_handler"); + final String catchLabelName = (afterConsumeStack == null ? "" : afterConsumeStack.toString()) + "_handler"; + catchLabel = new Label(catchLabelName); method.label(beginTry); } else { beginTry = catchLabel = null; @@ -4414,6 +4453,7 @@ } if(currentContinuationEntryPoint) { final ContinuationInfo ci = getContinuationInfo(); + assert ci != null : "no continuation info found for " + lc.getCurrentFunction(); assert !ci.hasTargetLabel(); // No duplicate program points ci.setTargetLabel(afterConsumeStack); ci.getHandlerLabel().markAsOptimisticContinuationHandlerFor(afterConsumeStack); @@ -4907,9 +4947,8 @@ method.dup(2); method.pop(); loadConstant(getByteCodeSymbolNames(fn)); - final CompilationEnvironment env = compiler.getCompilationEnvironment(); - if (env.isCompileRestOf()) { - loadConstant(env.getContinuationEntryPoints()); + if (isRestOf()) { + loadConstant(getContinuationEntryPoints()); method.invoke(INIT_REWRITE_EXCEPTION_REST_OF); } else { method.invoke(INIT_REWRITE_EXCEPTION); @@ -5079,7 +5118,7 @@ } private void generateContinuationHandler() { - if (!compiler.getCompilationEnvironment().isCompileRestOf()) { + if (!isRestOf()) { return; } diff -r eb9658fa0120 -r 31aed7d9c02a nashorn/src/jdk/nashorn/internal/codegen/CompilationEnvironment.java --- a/nashorn/src/jdk/nashorn/internal/codegen/CompilationEnvironment.java Thu May 15 15:28:51 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,529 +0,0 @@ -/* - * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.nashorn.internal.codegen; - -import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -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.Context; -import jdk.nashorn.internal.runtime.FindProperty; -import jdk.nashorn.internal.runtime.JSType; -import jdk.nashorn.internal.runtime.Property; -import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData; -import jdk.nashorn.internal.runtime.ScriptObject; -import jdk.nashorn.internal.runtime.ScriptRuntime; - -/** - * Class for managing metadata during a compilation, e.g. which phases - * should be run, should we use optimistic types, is this a lazy compilation - * and various parameter types known to the runtime system - */ -public final class CompilationEnvironment { - private final CompilationPhases phases; - private final boolean optimistic; - - private final Context context; - - private final ParamTypeMap paramTypes; - - 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; - - /** - * If this is a recompilation, this is how we pass in the invalidations, e.g. programPoint=17, Type == int means - * that using whatever was at program point 17 as an int failed. - */ - private final Map invalidatedProgramPoints; - - /** - * Contains the program point that should be used as the continuation entry point, as well as all previous - * continuation entry points executed as part of a single logical invocation of the function. In practical terms, if - * we execute a rest-of method from the program point 17, but then we hit deoptimization again during it at program - * point 42, and execute a rest-of method from the program point 42, and then we hit deoptimization again at program - * point 57 and are compiling a rest-of method for it, the values in the array will be [57, 42, 17]. This is only - * set when compiling a rest-of method. If this method is a rest-of for a non-rest-of method, the array will have - * one element. If it is a rest-of for a rest-of, the array will have two elements, and so on. - */ - private final int[] continuationEntryPoints; - - /** - * Compilation phases that a compilation goes through - */ - public static final class CompilationPhases implements Iterable { - - /** - * Standard (non-lazy) compilation, that basically will take an entire script - * and JIT it at once. This can lead to long startup time and fewer type - * specializations - */ - final static CompilationPhase[] SEQUENCE_EAGER_ARRAY = new CompilationPhase[] { - CompilationPhase.CONSTANT_FOLDING_PHASE, - CompilationPhase.LOWERING_PHASE, - CompilationPhase.SPLITTING_PHASE, - CompilationPhase.SYMBOL_ASSIGNMENT_PHASE, - CompilationPhase.SCOPE_DEPTH_COMPUTATION_PHASE, - CompilationPhase.OPTIMISTIC_TYPE_ASSIGNMENT_PHASE, - CompilationPhase.LOCAL_VARIABLE_TYPE_CALCULATION_PHASE, - CompilationPhase.BYTECODE_GENERATION_PHASE - }; - - private final static List SEQUENCE_EAGER; - static { - final LinkedList eager = new LinkedList<>(); - for (final CompilationPhase phase : SEQUENCE_EAGER_ARRAY) { - eager.add(phase); - } - SEQUENCE_EAGER = Collections.unmodifiableList(eager); - } - - /** Singleton that describes a standard eager compilation */ - public static CompilationPhases EAGER = new CompilationPhases(SEQUENCE_EAGER); - - private final List phases; - - private CompilationPhases(final List phases) { - this.phases = phases; - } - - @SuppressWarnings("unused") - private CompilationPhases addFirst(final CompilationPhase phase) { - if (phases.contains(phase)) { - return this; - } - final LinkedList list = new LinkedList<>(phases); - list.addFirst(phase); - return new CompilationPhases(Collections.unmodifiableList(list)); - } - - private CompilationPhases addAfter(final CompilationPhase phase, final CompilationPhase newPhase) { - final LinkedList list = new LinkedList<>(); - for (final CompilationPhase p : phases) { - list.add(p); - if (p == phase) { - list.add(newPhase); - } - } - return new CompilationPhases(Collections.unmodifiableList(list)); - } - - /** - * Turn a CompilationPhases into an optimistic one. NOP if already optimistic - * @param isOptimistic should this be optimistic - * @return new CompilationPhases that is optimistic or same if already optimistic - */ - public CompilationPhases makeOptimistic(final boolean isOptimistic) { - return isOptimistic ? addAfter(CompilationPhase.LOWERING_PHASE, CompilationPhase.PROGRAM_POINT_PHASE) : this; - } - - /** - * Turn a CompilationPhases into an optimistic one. NOP if already optimistic - * @return new CompilationPhases that is optimistic or same if already optimistic - */ - public CompilationPhases makeOptimistic() { - return makeOptimistic(true); - } - - private boolean contains(final CompilationPhase phase) { - return phases.contains(phase); - } - - @Override - public Iterator iterator() { - return phases.iterator(); - } - - } - - /** - * Constructor - * @param context context - * @param phases compilation phases - * @param strict strict mode - */ - public CompilationEnvironment( - final Context context, - final CompilationPhases phases, - final boolean strict) { - this(context, phases, null, null, null, null, null, strict, false); - } - - /** - * Constructor for compilation environment of the rest-of method - * - * @param context context - * @param phases compilation phases - * @param strict strict mode - * @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 - * @param onDemand is this an on demand compilation - */ - public CompilationEnvironment( - final Context context, - final CompilationPhases phases, - final boolean strict, - final RecompilableScriptFunctionData compiledFunction, - final ScriptObject runtimeScope, - final ParamTypeMap paramTypeMap, - final Map invalidatedProgramPoints, - final int[] continuationEntryPoint, - final boolean onDemand) { - this(context, phases, paramTypeMap, invalidatedProgramPoints, compiledFunction, runtimeScope, continuationEntryPoint, strict, onDemand); - } - - /** - * Constructor - * - * @param context context - * @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 - */ - public CompilationEnvironment( - final Context context, - final CompilationPhases phases, - final boolean strict, - final RecompilableScriptFunctionData compiledFunction, - final ScriptObject runtimeScope, - final ParamTypeMap paramTypeMap, - final Map invalidatedProgramPoints, - final boolean onDemand) { - this(context, phases, paramTypeMap, invalidatedProgramPoints, compiledFunction, runtimeScope, null, strict, onDemand); - } - - private CompilationEnvironment( - final Context context, - final CompilationPhases phases, - final ParamTypeMap paramTypes, - final Map invalidatedProgramPoints, - final RecompilableScriptFunctionData compiledFunction, - final ScriptObject runtimeScope, - final int[] continuationEntryPoints, - final boolean strict, - final boolean onDemand) { - this.context = context; - this.phases = phases; - this.paramTypes = paramTypes; - this.continuationEntryPoints = continuationEntryPoints; - this.invalidatedProgramPoints = invalidatedProgramPoints == null ? new HashMap() : invalidatedProgramPoints; - this.compiledFunction = compiledFunction; - this.runtimeScope = runtimeScope; - this.strict = strict; - this.optimistic = phases.contains(CompilationPhase.PROGRAM_POINT_PHASE); - this.onDemand = onDemand; - - // If entry point array is passed, it must have at least one element - assert continuationEntryPoints == null || continuationEntryPoints.length > 0; - assert !isCompileRestOf() || isOnDemandCompilation(); // isCompileRestOf => isRecompilation - // continuation entry points must be among the invalidated program points - assert !isCompileRestOf() || invalidatedProgramPoints != null && containsAll(invalidatedProgramPoints.keySet(), continuationEntryPoints); - } - - Context getContext() { - return context; - } - - private static boolean containsAll(final Set set, final int[] array) { - for (int i = 0; i < array.length; ++i) { - if (!set.contains(array[i])) { - return false; - } - } - return true; - } - - void setData(final RecompilableScriptFunctionData data) { - assert this.compiledFunction == null : data; - this.compiledFunction = data; - } - - boolean isStrict() { - return strict; - } - - void setIsStrict(final boolean strict) { - this.strict = strict; - } - - CompilationPhases getPhases() { - return phases; - } - - /** - * Get the parameter type at a parameter position if known from previous runtime calls - * or optimistic profiles. - * - * @param functionNode function node to query - * @param pos parameter position - * @return known type of parameter 'pos' or null if no information exists - */ - Type getParamType(final FunctionNode functionNode, final int pos) { - return paramTypes == null ? null : paramTypes.get(functionNode, pos); - } - - /** - * Is this a compilation that generates the rest of method - * @return true if rest of generation - */ - boolean isCompileRestOf() { - return continuationEntryPoints != null; - } - - /** - * Is this an on-demand compilation triggered by a {@code RecompilableScriptFunctionData} - either a type - * specializing compilation, a deoptimizing recompilation, or a rest-of method compilation. - * @return true if this is an on-demand compilation, false if this is an eager compilation. - */ - boolean isOnDemandCompilation() { - return onDemand; //data != null; - } - - /** - * Is this program point one of the continuation entry points for the rest-of method being compiled? - * @param programPoint program point - * @return true if it is a continuation entry point - */ - boolean isContinuationEntryPoint(final int programPoint) { - if (continuationEntryPoints != null) { - for (final int continuationEntryPoint : continuationEntryPoints) { - if (continuationEntryPoint == programPoint) { - return true; - } - } - } - return false; - } - - int[] getContinuationEntryPoints() { - return continuationEntryPoints; - } - - /** - * Is this program point the continuation entry points for the current rest-of method being compiled? - * @param programPoint program point - * @return true if it is the current continuation entry point - */ - boolean isCurrentContinuationEntryPoint(final int programPoint) { - return hasCurrentContinuationEntryPoint() && getCurrentContinuationEntryPoint() == programPoint; - } - - boolean hasCurrentContinuationEntryPoint() { - return continuationEntryPoints != null; - } - - int getCurrentContinuationEntryPoint() { - // NOTE: we assert in the constructor that if the array is non-null, it has at least one element - return hasCurrentContinuationEntryPoint() ? continuationEntryPoints[0] : INVALID_PROGRAM_POINT; - } - - /** - * Are optimistic types enabled ? - * @param node get the optimistic type for a node - * @return most optimistic type in current environment - */ - Type getOptimisticType(final Optimistic node) { - assert useOptimisticTypes(); - 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 mostOptimisticType; - } - - /** - * Tells the compilation environment that a symbol of a particular name is a local variables in a function. Used - * with on-demand compilation, this will hide symbols of the same name from a parent scope and prevent them from - * being mistakenly found by the optimistic types heuristics. - * @param symbolName the name of the symbols to declare. - */ - void declareLocalSymbol(final String symbolName) { - assert useOptimisticTypes() && isOnDemandCompilation() && runtimeScope != null; - if(runtimeScope.findProperty(symbolName, false) == null) { - runtimeScope.set(symbolName, ScriptRuntime.UNDEFINED, true); - } - } - - 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()); - } 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 Property property = find.getProperty(); - final Class propertyClass = property.getCurrentType(); - if (propertyClass == null) { - // propertyClass == null means its value is Undefined. It is probably not initialized yet, so we won't make - // a type assumption yet. - return null; - } else if (propertyClass.isPrimitive()) { - return Type.typeFor(propertyClass); - } - - final ScriptObject owner = find.getOwner(); - if(property.hasGetterFunction(owner)) { - // Can have side effects, so we can't safely evaluate it; since !propertyClass.isPrimitive(), it's Object. - return Type.OBJECT; - } - - // Safely evaluate the property, and return the narrowest type for the actual value (e.g. Type.INT for a boxed - // integer). Continue not making guesses for undefined. - final Object value = property.getObjectValue(owner, owner); - if(value == ScriptRuntime.UNDEFINED) { - return null; - } - return Type.typeFor(JSType.unboxedFieldType(value)); - } - - private Object evaluateSafely(final 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()); - } - 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 - * be statically proven to be true. - * @return true if optimistic types should be used. - */ - boolean useOptimisticTypes() { - return optimistic; - } - - RecompilableScriptFunctionData getProgram() { - if (compiledFunction == null) { - return null; - } - return compiledFunction.getProgram(); - } - - RecompilableScriptFunctionData getScriptFunctionData(final int functionId) { - return compiledFunction == null ? null : compiledFunction.getScriptFunctionData(functionId); - } - - boolean isGlobalSymbol(final FunctionNode functionNode, final String name) { - final RecompilableScriptFunctionData data = getScriptFunctionData(functionNode.getId()); - return data.isGlobalSymbol(functionNode, name); - } -} diff -r eb9658fa0120 -r 31aed7d9c02a nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java --- a/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java Thu May 15 15:28:51 2014 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java Mon May 19 15:29:42 2014 +0200 @@ -25,6 +25,11 @@ package jdk.nashorn.internal.codegen; +import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS; +import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE; +import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.BUILTINS_TRANSFORMED; +import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.BYTECODE_GENERATED; +import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.BYTECODE_INSTALLED; import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.CONSTANT_FOLDED; import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.INITIALIZED; import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.LOCAL_VARIABLE_TYPES_CALCULATED; @@ -34,14 +39,39 @@ import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SCOPE_DEPTHS_COMPUTED; import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SPLIT; import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SYMBOLS_ASSIGNED; +import static jdk.nashorn.internal.runtime.logging.DebugLogger.quote; +import java.io.PrintWriter; +import java.lang.reflect.Field; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; import java.util.EnumSet; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +import jdk.nashorn.internal.codegen.Compiler.CompilationPhases; import jdk.nashorn.internal.ir.FunctionNode; +import jdk.nashorn.internal.ir.LexicalContext; import jdk.nashorn.internal.ir.FunctionNode.CompilationState; +import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode; +import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode.ArrayUnit; +import jdk.nashorn.internal.ir.LiteralNode; +import jdk.nashorn.internal.ir.Node; +import jdk.nashorn.internal.ir.SplitNode; import jdk.nashorn.internal.ir.debug.ASTWriter; import jdk.nashorn.internal.ir.debug.PrintVisitor; +import jdk.nashorn.internal.ir.visitor.NodeVisitor; +import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData; import jdk.nashorn.internal.runtime.ScriptEnvironment; import jdk.nashorn.internal.runtime.Timing; +import jdk.nashorn.internal.runtime.logging.DebugLogger; /** * A compilation phase is a step in the processes of turning a JavaScript @@ -52,15 +82,18 @@ * Constant folding pass Simple constant folding that will make elementary * constructs go away */ - CONSTANT_FOLDING_PHASE(EnumSet.of(INITIALIZED, PARSED)) { + CONSTANT_FOLDING_PHASE( + EnumSet.of( + INITIALIZED, + PARSED)) { @Override - FunctionNode transform(final Compiler compiler, final FunctionNode fn) { - return (FunctionNode)fn.accept(new FoldConstants(compiler.getCompilationEnvironment())); + FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { + return (FunctionNode)fn.accept(new FoldConstants(compiler)); } @Override public String toString() { - return "[Constant Folding]"; + return "'Constant Folding'"; } }, @@ -71,15 +104,19 @@ * flow cannot fall off the end. Replacing high level nodes with lower such * as runtime nodes where applicable. */ - LOWERING_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED)) { + LOWERING_PHASE( + EnumSet.of( + INITIALIZED, + PARSED, + CONSTANT_FOLDED)) { @Override - FunctionNode transform(final Compiler compiler, final FunctionNode fn) { + FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { return (FunctionNode)fn.accept(new Lower(compiler)); } @Override public String toString() { - return "[Control Flow Lowering]"; + return "'Control Flow Lowering'"; } }, @@ -88,15 +125,44 @@ * optimistic ops a program point so that an UnwarrantedException knows from where * a guess went wrong when creating the continuation to roll back this execution */ - PROGRAM_POINT_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED)) { + PROGRAM_POINT_PHASE( + EnumSet.of( + INITIALIZED, + PARSED, + CONSTANT_FOLDED, + LOWERED)) { @Override - FunctionNode transform(final Compiler compiler, final FunctionNode fn) { + FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { return (FunctionNode)fn.accept(new ProgramPoints()); } @Override public String toString() { - return "[Program Point Calculation]"; + return "'Program Point Calculation'"; + } + }, + + TRANSFORM_BUILTINS_PHASE( + EnumSet.of( + INITIALIZED, + PARSED, + CONSTANT_FOLDED, + LOWERED)) { + //we only do this if we have a param type map, otherwise this is not a specialized recompile + @Override + FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { + final FunctionNode newFunctionNode = (FunctionNode)fn.accept(new ApplySpecialization(compiler)); + return (FunctionNode)newFunctionNode.accept(new NodeVisitor(new LexicalContext()) { + @Override + public Node leaveFunctionNode(final FunctionNode node) { + return node.setState(lc, BUILTINS_TRANSFORMED); + } + }); + } + + @Override + public String toString() { + return "'Builtin Replacement'"; } }, @@ -104,18 +170,120 @@ * Splitter Split the AST into several compile units based on a heuristic size calculation. * Split IR can lead to scope information being changed. */ - SPLITTING_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED)) { + SPLITTING_PHASE( + EnumSet.of( + INITIALIZED, + PARSED, + CONSTANT_FOLDED, + LOWERED, + BUILTINS_TRANSFORMED)) { @Override - FunctionNode transform(final Compiler compiler, final FunctionNode fn) { - final CompileUnit outermostCompileUnit = compiler.addCompileUnit(compiler.firstCompileUnitName()); - - final FunctionNode newFunctionNode = new Splitter(compiler, fn, outermostCompileUnit).split(fn, true); + FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { + final CompileUnit outermostCompileUnit = compiler.addCompileUnit(0L); + final FunctionNode newFunctionNode = new Splitter(compiler, fn, outermostCompileUnit).split(fn, true); assert newFunctionNode.getCompileUnit() == outermostCompileUnit : "fn=" + fn.getName() + ", fn.compileUnit (" + newFunctionNode.getCompileUnit() + ") != " + outermostCompileUnit; + assert newFunctionNode.isStrict() == compiler.isStrict() : "functionNode.isStrict() != compiler.isStrict() for " + quote(newFunctionNode.getName()); - if (newFunctionNode.isStrict()) { - assert compiler.getCompilationEnvironment().isStrict(); - compiler.getCompilationEnvironment().setIsStrict(true); + return newFunctionNode; + } + + @Override + public String toString() { + return "'Code Splitting'"; + } + }, + + SYMBOL_ASSIGNMENT_PHASE( + EnumSet.of( + INITIALIZED, + PARSED, + CONSTANT_FOLDED, + LOWERED, + BUILTINS_TRANSFORMED, + SPLIT)) { + @Override + FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { + return (FunctionNode)fn.accept(new AssignSymbols(compiler)); + } + + @Override + public String toString() { + return "'Symbol Assignment'"; + } + }, + + SCOPE_DEPTH_COMPUTATION_PHASE( + EnumSet.of( + INITIALIZED, + PARSED, + CONSTANT_FOLDED, + LOWERED, + BUILTINS_TRANSFORMED, + SPLIT, + SYMBOLS_ASSIGNED)) { + @Override + FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { + return (FunctionNode)fn.accept(new FindScopeDepths(compiler)); + } + + @Override + public String toString() { + return "'Scope Depth Computation'"; + } + }, + + OPTIMISTIC_TYPE_ASSIGNMENT_PHASE( + EnumSet.of( + INITIALIZED, + PARSED, + CONSTANT_FOLDED, + LOWERED, + BUILTINS_TRANSFORMED, + SPLIT, + SYMBOLS_ASSIGNED, + SCOPE_DEPTHS_COMPUTED)) { + @Override + FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { + if (compiler.useOptimisticTypes()) { + return (FunctionNode)fn.accept(new OptimisticTypesCalculator(compiler)); + } + return setStates(fn, OPTIMISTIC_TYPES_ASSIGNED); + } + + @Override + public String toString() { + return "'Optimistic Type Assignment'"; + } + }, + + LOCAL_VARIABLE_TYPE_CALCULATION_PHASE( + EnumSet.of( + INITIALIZED, + PARSED, + CONSTANT_FOLDED, + LOWERED, + BUILTINS_TRANSFORMED, + SPLIT, + SYMBOLS_ASSIGNED, + SCOPE_DEPTHS_COMPUTED, + OPTIMISTIC_TYPES_ASSIGNED)) { + @Override + FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { + final FunctionNode newFunctionNode = (FunctionNode)fn.accept(new LocalVariableTypesCalculator(compiler)); + + final ScriptEnvironment senv = compiler.getEnv(); + final PrintWriter err = senv.getErr(); + + //TODO separate phase for the debug printouts for abstraction and clarity + if (senv._print_lower_ast) { + err.println("Lower AST for: " + quote(newFunctionNode.getName())); + err.println(new ASTWriter(newFunctionNode)); + } + + if (senv._print_lower_parse) { + err.println("Lower AST for: " + quote(newFunctionNode.getName())); + err.println(new PrintVisitor(newFunctionNode)); } return newFunctionNode; @@ -123,90 +291,150 @@ @Override public String toString() { - return "[Code Splitting]"; - } - }, - - SYMBOL_ASSIGNMENT_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, SPLIT)) { - @Override - FunctionNode transform(final Compiler compiler, final FunctionNode fn) { - return (FunctionNode)fn.accept(new AssignSymbols(compiler.getCompilationEnvironment())); - } - - @Override - public String toString() { - return "[Symbol Assignment]"; + return "'Local Variable Type Calculation'"; } }, - SCOPE_DEPTH_COMPUTATION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, SPLIT, SYMBOLS_ASSIGNED)) { - @Override - FunctionNode transform(final Compiler compiler, final FunctionNode fn) { - return (FunctionNode)fn.accept(new FindScopeDepths(compiler)); - } - + /** + * Reuse compile units, if they are already present. We are using the same compiler + * to recompile stuff + */ + REUSE_COMPILE_UNITS_PHASE( + EnumSet.of( + INITIALIZED, + PARSED, + CONSTANT_FOLDED, + LOWERED, + BUILTINS_TRANSFORMED, + SPLIT, + SYMBOLS_ASSIGNED, + SCOPE_DEPTHS_COMPUTED, + OPTIMISTIC_TYPES_ASSIGNED, + LOCAL_VARIABLE_TYPES_CALCULATED)) { @Override - public String toString() { - return "[Scope Depth Computation]"; - } - }, + FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { + assert phases.isRestOfCompilation() : "reuse compile units currently only used for Rest-Of methods"; + + final Map map = new HashMap<>(); + final Set newUnits = CompileUnit.createCompileUnitSet(); + + final DebugLogger log = compiler.getLogger(); + + log.fine("Clearing bytecode cache"); + + for (final CompileUnit oldUnit : compiler.getCompileUnits()) { + CompileUnit newUnit = map.get(oldUnit); + assert map.get(oldUnit) == null; + final StringBuilder sb = new StringBuilder(compiler.nextCompileUnitName()); + if (phases.isRestOfCompilation()) { + sb.append("$restOf"); + } + newUnit = compiler.createCompileUnit(sb.toString(), oldUnit.getWeight()); + log.info("Creating new compile unit ", oldUnit, " => ", newUnit); + map.put(oldUnit, newUnit); + assert newUnit != null; + newUnits.add(newUnit); + } + + log.info("Replacing compile units in Compiler..."); + compiler.replaceCompileUnits(newUnits); + log.info("Done"); + + //replace old compile units in function nodes, if any are assigned, + //for example by running the splitter on this function node in a previous + //partial code generation + final FunctionNode newFunctionNode = (FunctionNode)fn.accept(new NodeVisitor(new LexicalContext()) { + @Override + public Node leaveFunctionNode(final FunctionNode node) { + final CompileUnit oldUnit = node.getCompileUnit(); + assert oldUnit != null : "no compile unit in function node"; + + final CompileUnit newUnit = map.get(oldUnit); + assert newUnit != null : "old unit has no mapping to new unit " + oldUnit; - OPTIMISTIC_TYPE_ASSIGNMENT_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, SPLIT, SYMBOLS_ASSIGNED, SCOPE_DEPTHS_COMPUTED)) { - @Override - FunctionNode transform(final Compiler compiler, final FunctionNode fn) { - if(compiler.getCompilationEnvironment().useOptimisticTypes()) { - return (FunctionNode)fn.accept(new OptimisticTypesCalculator(compiler.getCompilationEnvironment())); - } - return fn.setState(null, OPTIMISTIC_TYPES_ASSIGNED); + log.fine("Replacing compile unit: ", oldUnit, " => ", newUnit, " in ", quote(node.getName())); + return node.setCompileUnit(lc, newUnit).setState(lc, CompilationState.COMPILE_UNITS_REUSED); + } + + @Override + public Node leaveSplitNode(final SplitNode node) { + final CompileUnit oldUnit = node.getCompileUnit(); + assert oldUnit != null : "no compile unit in function node"; + + final CompileUnit newUnit = map.get(oldUnit); + assert newUnit != null : "old unit has no mapping to new unit " + oldUnit; + + log.fine("Replacing compile unit: ", oldUnit, " => ", newUnit, " in ", quote(node.getName())); + return node.setCompileUnit(lc, newUnit); + } + + @Override + public Node leaveLiteralNode(final LiteralNode node) { + if (node instanceof ArrayLiteralNode) { + final ArrayLiteralNode aln = (ArrayLiteralNode)node; + if (aln.getUnits() == null) { + return node; + } + final List newArrayUnits = new ArrayList<>(); + for (final ArrayUnit au : aln.getUnits()) { + final CompileUnit newUnit = map.get(au.getCompileUnit()); + assert newUnit != null; + newArrayUnits.add(new ArrayUnit(newUnit, au.getLo(), au.getHi())); + } + aln.setUnits(newArrayUnits); + } + return node; + } + + @Override + public Node leaveDefault(final Node node) { + return node.ensureUniqueLabels(lc); + } + }); + + return newFunctionNode; } @Override public String toString() { - return "[Optimistic Type Assignment]"; + return "'Reuse Compile Units'"; } }, - LOCAL_VARIABLE_TYPE_CALCULATION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, SPLIT, SYMBOLS_ASSIGNED, SCOPE_DEPTHS_COMPUTED, OPTIMISTIC_TYPES_ASSIGNED)) { - @Override - FunctionNode transform(final Compiler compiler, final FunctionNode fn) { - return (FunctionNode)fn.accept(new LocalVariableTypesCalculator(compiler.getCompilationEnvironment())); - } - - @Override - public String toString() { - return "[Local Variable Type Calculation]"; - } - }, - - /** + /** * Bytecode generation: * * Generate the byte code class(es) resulting from the compiled FunctionNode */ - BYTECODE_GENERATION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, SPLIT, SYMBOLS_ASSIGNED, SCOPE_DEPTHS_COMPUTED, OPTIMISTIC_TYPES_ASSIGNED, LOCAL_VARIABLE_TYPES_CALCULATED)) { - @Override - FunctionNode transform(final Compiler compiler, final FunctionNode fn) { - final ScriptEnvironment env = compiler.getEnv(); + BYTECODE_GENERATION_PHASE( + EnumSet.of( + INITIALIZED, + PARSED, + CONSTANT_FOLDED, + LOWERED, + BUILTINS_TRANSFORMED, + SPLIT, + SYMBOLS_ASSIGNED, + SCOPE_DEPTHS_COMPUTED, + OPTIMISTIC_TYPES_ASSIGNED, + LOCAL_VARIABLE_TYPES_CALCULATED)) { - if (env._print_lower_ast) { - env.getErr().println(new ASTWriter(fn)); - } - - if (env._print_lower_parse) { - env.getErr().println(new PrintVisitor(fn)); - } + @Override + FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { + final ScriptEnvironment senv = compiler.getEnv(); FunctionNode newFunctionNode = fn; - final CodeGenerator codegen = new CodeGenerator(compiler); + compiler.getLogger().fine("Starting bytecode generation for ", quote(fn.getName()), " - restOf=", phases.isRestOfCompilation()); + final CodeGenerator codegen = new CodeGenerator(compiler, phases.isRestOfCompilation() ? compiler.getContinuationEntryPoints() : null); try { newFunctionNode = (FunctionNode)newFunctionNode.accept(codegen); codegen.generateScopeCalls(); } catch (final VerifyError e) { - if (env._verify_code || env._print_code) { - env.getErr().println(e.getClass().getSimpleName() + ": " + e.getMessage()); - if (env._dump_on_error) { - e.printStackTrace(env.getErr()); + if (senv._verify_code || senv._print_code) { + senv.getErr().println(e.getClass().getSimpleName() + ": " + e.getMessage()); + if (senv._dump_on_error) { + e.printStackTrace(senv.getErr()); } } else { throw e; @@ -228,11 +456,11 @@ compiler.addClass(className, bytecode); // should we verify the generated code? - if (env._verify_code) { + if (senv._verify_code) { compiler.getCodeInstaller().verify(bytecode); } - DumpBytecode.dumpBytecode(env, compiler.getLogger(), bytecode, className); + DumpBytecode.dumpBytecode(senv, compiler.getLogger(), bytecode, className); } return newFunctionNode; @@ -240,13 +468,139 @@ @Override public String toString() { - return "[Bytecode Generation]"; + return "'Bytecode Generation'"; } - }; + }, + + INSTALL_PHASE( + EnumSet.of( + INITIALIZED, + PARSED, + CONSTANT_FOLDED, + LOWERED, + BUILTINS_TRANSFORMED, + SPLIT, + SYMBOLS_ASSIGNED, + SCOPE_DEPTHS_COMPUTED, + OPTIMISTIC_TYPES_ASSIGNED, + LOCAL_VARIABLE_TYPES_CALCULATED, + BYTECODE_GENERATED)) { + + @Override + FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { + final DebugLogger log = compiler.getLogger(); + + final Map> installedClasses = new LinkedHashMap<>(); + + boolean first = true; + Class rootClass = null; + long length = 0L; + + for (final Entry entry : compiler.getBytecode().entrySet()) { + final String className = entry.getKey(); + //assert !first || className.equals(compiler.getFirstCompileUnit().getUnitClassName()) : "first=" + first + " className=" + className + " != " + compiler.getFirstCompileUnit().getUnitClassName(); + final byte[] code = entry.getValue(); + length += code.length; + + final Class clazz = compiler.getCodeInstaller().install(Compiler.binaryName(className), code); + if (first) { + rootClass = clazz; + first = false; + } + installedClasses.put(className, clazz); + } + + if (rootClass == null) { + throw new CompilationException("Internal compiler error: root class not found!"); + } + + // do these in a loop, to use only one privileged action - this significantly + // reduces class installation overhead + log.fine("Preparing source and constant fields..."); + try { + final Object[] constants = compiler.getConstantData().toArray(); + // Need doPrivileged because these fields are private + AccessController.doPrivileged(new PrivilegedExceptionAction() { + @Override + public Void run() throws Exception { + for (final Entry> entry : installedClasses.entrySet()) { + final Class clazz = entry.getValue(); + log.fine("Initializing source for ", clazz); + //use reflection to write source and constants table to installed classes + final Field sourceField = clazz.getDeclaredField(SOURCE.symbolName()); + sourceField.setAccessible(true); + sourceField.set(null, compiler.getSource()); + log.fine("Initializing constants for ", clazz); + final Field constantsField = clazz.getDeclaredField(CONSTANTS.symbolName()); + constantsField.setAccessible(true); + constantsField.set(null, constants); + } + return null; + } + }); + } catch (final PrivilegedActionException e) { + throw new RuntimeException(e); + } + log.fine("Done"); + + // index recompilable script function datas in the constant pool + final Map rfns = new IdentityHashMap<>(); + for (final Object constant: compiler.getConstantData().getConstants()) { + if (constant instanceof RecompilableScriptFunctionData) { + final RecompilableScriptFunctionData rfn = (RecompilableScriptFunctionData)constant; + rfns.put(rfn, rfn); + } + } + + // initialize function in the compile units + for (final CompileUnit unit : compiler.getCompileUnits()) { + unit.setCode(installedClasses.get(unit.getUnitClassName())); + unit.initializeFunctionsCode(); + } + + // remove installed bytecode from table in case compiler is reused + for (final String className : installedClasses.keySet()) { + log.fine("Removing installed class ", quote(className), " from bytecode table..."); + compiler.removeClass(className); + } + + if (log.isEnabled()) { + final StringBuilder sb = new StringBuilder(); + + sb.append("Installed class '"). + append(rootClass.getSimpleName()). + append('\''). + append(" ["). + append(rootClass.getName()). + append(", size="). + append(length). + append(" bytes, "). + append(compiler.getCompileUnits().size()). + append(" compile unit(s)]"); + + log.info(sb.toString()); + } + + return setStates(fn.setRootClass(null, rootClass), BYTECODE_INSTALLED); + } + + @Override + public String toString() { + return "'Class Installation'"; + } + }; + + /** pre conditions required for function node to which this transform is to be applied */ private final EnumSet pre; + + /** start time of transform - used for timing, see {@link jdk.nashorn.internal.runtime.Timing} */ private long startTime; + + /** start time of transform - used for timing, see {@link jdk.nashorn.internal.runtime.Timing} */ private long endTime; + + /** boolean that is true upon transform completion */ private boolean isFinished; private CompilationPhase(final EnumSet pre) { @@ -254,36 +608,56 @@ } boolean isApplicable(final FunctionNode functionNode) { + //this means that all in pre are present in state. state can be larger return functionNode.hasState(pre); } + private static FunctionNode setStates(final FunctionNode functionNode, final CompilationState state) { + return (FunctionNode)functionNode.accept(new NodeVisitor(new LexicalContext()) { + @Override + public Node leaveFunctionNode(final FunctionNode fn) { + return fn.setState(lc, state); + } + }); + } + /** * Start a compilation phase + * @param compiler * @param functionNode function to compile * @return function node */ - protected FunctionNode begin(final FunctionNode functionNode) { - if (pre != null) { - // check that everything in pre is present - for (final CompilationState state : pre) { - assert functionNode.hasState(state); - } - // check that nothing else is present - for (final CompilationState state : CompilationState.values()) { - assert !(functionNode.hasState(state) && !pre.contains(state)); - } - } + protected FunctionNode begin(final Compiler compiler, final FunctionNode functionNode) { + compiler.getLogger().indent(); + + assert pre != null; - startTime = System.currentTimeMillis(); - return functionNode; - } + if (!isApplicable(functionNode)) { + final StringBuilder sb = new StringBuilder("Compilation phase "); + sb.append(this). + append(" is not applicable to "). + append(quote(functionNode.getName())). + append("\n\tFunctionNode state = "). + append(functionNode.getState()). + append("\n\tRequired state = "). + append(this.pre); + + throw new CompilationException(sb.toString()); + } + + startTime = System.currentTimeMillis(); + + return functionNode; + } /** * End a compilation phase + * @param compiler the compiler * @param functionNode function node to compile - * @return fucntion node + * @return function node */ - protected FunctionNode end(final FunctionNode functionNode) { + protected FunctionNode end(final Compiler compiler, final FunctionNode functionNode) { + compiler.getLogger().unindent(); endTime = System.currentTimeMillis(); Timing.accumulateTime(toString(), endTime - startTime); @@ -303,13 +677,26 @@ return endTime; } - abstract FunctionNode transform(final Compiler compiler, final FunctionNode functionNode) throws CompilationException; + abstract FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode functionNode) throws CompilationException; - final FunctionNode apply(final Compiler compiler, final FunctionNode functionNode) throws CompilationException { - if (!isApplicable(functionNode)) { - throw new CompilationException("compile phase not applicable: " + this + " to " + functionNode.getName() + " state=" + functionNode.getState()); - } - return end(transform(compiler, begin(functionNode))); + /** + * Apply a transform to a function node, returning the transfored function node. If the transform is not + * applicable, an exception is thrown. Every transform requires the function to have a certain number of + * states to operate. It can have more states set, but not fewer. The state list, i.e. the constructor + * arguments to any of the CompilationPhase enum entries, is a set of REQUIRED states. + * + * @param compiler compiler + * @param phases current complete pipeline of which this phase is one + * @param functionNode function node to transform + * + * @return transformed function node + * + * @throws CompilationException if function node lacks the state required to run the transform on it + */ + final FunctionNode apply(final Compiler compiler, final CompilationPhases phases, final FunctionNode functionNode) throws CompilationException { + assert phases.contains(this); + + return end(compiler, transform(compiler, phases, begin(compiler, functionNode))); } } diff -r eb9658fa0120 -r 31aed7d9c02a nashorn/src/jdk/nashorn/internal/codegen/CompileUnit.java --- a/nashorn/src/jdk/nashorn/internal/codegen/CompileUnit.java Thu May 15 15:28:51 2014 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/CompileUnit.java Mon May 19 15:29:42 2014 +0200 @@ -28,6 +28,8 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; +import java.util.TreeSet; + import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData; @@ -36,7 +38,7 @@ */ public final class CompileUnit implements Comparable { /** Current class name */ - private final String className; + private String className; /** Current class generator */ private ClassEmitter classEmitter; @@ -67,24 +69,22 @@ @Override public boolean equals(final Object obj) { - if(obj == null || obj.getClass() != FunctionInitializer.class) { + if (obj == null || obj.getClass() != FunctionInitializer.class) { return false; } final FunctionInitializer other = (FunctionInitializer)obj; return data == other.data && functionNode == other.functionNode; } - - - } - - CompileUnit(final String className, final ClassEmitter classEmitter) { - this(className, classEmitter, 0L); } CompileUnit(final String className, final ClassEmitter classEmitter, final long initialWeight) { this.className = className; + this.weight = initialWeight; this.classEmitter = classEmitter; - this.weight = initialWeight; + } + + static Set createCompileUnitSet() { + return new TreeSet<>(); } /** @@ -126,7 +126,7 @@ } void initializeFunctionsCode() { - for(final FunctionInitializer init: functionInitializers) { + for(final FunctionInitializer init : functionInitializers) { init.initializeCode(); } functionInitializers = Collections.emptySet(); @@ -173,13 +173,25 @@ return className; } - @Override - public String toString() { - return "[classname=" + className + " weight=" + weight + '/' + Splitter.SPLIT_THRESHOLD + ']'; + /** + * Reset the class name for this compile unit + * @param className new class name + */ + public void setUnitClassName(final String className) { + this.className = className; + } + + private static String shortName(final String name) { + return name.lastIndexOf('/') == -1 ? name : name.substring(name.lastIndexOf('/') + 1); } @Override - public int compareTo(CompileUnit o) { + public String toString() { + return "[CompileUnit className=" + shortName(className) + " weight=" + weight + '/' + Splitter.SPLIT_THRESHOLD + ']'; + } + + @Override + public int compareTo(final CompileUnit o) { return className.compareTo(o.className); } } diff -r eb9658fa0120 -r 31aed7d9c02a nashorn/src/jdk/nashorn/internal/codegen/Compiler.java --- a/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java Thu May 15 15:28:51 2014 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java Mon May 19 15:29:42 2014 +0200 @@ -27,43 +27,39 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS; import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE; -import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS; import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN; import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; -import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE; import static jdk.nashorn.internal.codegen.CompilerConstants.THIS; import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS; import java.io.File; -import java.lang.reflect.Field; -import java.security.AccessController; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; +import java.lang.invoke.MethodType; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.EnumSet; import java.util.HashMap; -import java.util.IdentityHashMap; +import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; -import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import jdk.internal.dynalink.support.NameCodec; import jdk.nashorn.internal.codegen.ClassEmitter.Flag; -import jdk.nashorn.internal.codegen.CompilationEnvironment.CompilationPhases; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.FunctionNode; -import jdk.nashorn.internal.ir.FunctionNode.CompilationState; +import jdk.nashorn.internal.ir.Optimistic; import jdk.nashorn.internal.ir.debug.ClassHistogramElement; import jdk.nashorn.internal.ir.debug.ObjectSizeCalculator; import jdk.nashorn.internal.runtime.CodeInstaller; import jdk.nashorn.internal.runtime.Context; import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData; import jdk.nashorn.internal.runtime.ScriptEnvironment; +import jdk.nashorn.internal.runtime.ScriptObject; import jdk.nashorn.internal.runtime.Source; import jdk.nashorn.internal.runtime.Timing; import jdk.nashorn.internal.runtime.logging.DebugLogger; @@ -85,10 +81,13 @@ /** Name of the objects package */ public static final String OBJECTS_PACKAGE = "jdk/nashorn/internal/objects"; - private Source source; + private final Source source; + + private final String sourceName; - private String sourceName; - private String sourceURL; + private final String sourceURL; + + private final boolean optimistic; private final Map bytecode; @@ -96,21 +95,184 @@ private final ConstantData constantData; - private final CompilationEnvironment compilationEnv; - - private final ScriptEnvironment scriptEnv; - - private String scriptName; - private final CodeInstaller installer; /** logger for compiler, trampolines, splits and related code generation events * that affect classes */ private final DebugLogger log; + private final Context context; + + private final TypeMap types; + + // 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 TypeEvaluator typeEvaluator; + + private final boolean strict; + + private final boolean onDemand; + + /** + * If this is a recompilation, this is how we pass in the invalidations, e.g. programPoint=17, Type == int means + * that using whatever was at program point 17 as an int failed. + */ + private final Map invalidatedProgramPoints; + + /** + * Compile unit name of first compile unit - this prefix will be used for all + * classes that a compilation generates. + */ + private final String firstCompileUnitName; + + /** + * Contains the program point that should be used as the continuation entry point, as well as all previous + * continuation entry points executed as part of a single logical invocation of the function. In practical terms, if + * we execute a rest-of method from the program point 17, but then we hit deoptimization again during it at program + * point 42, and execute a rest-of method from the program point 42, and then we hit deoptimization again at program + * point 57 and are compiling a rest-of method for it, the values in the array will be [57, 42, 17]. This is only + * set when compiling a rest-of method. If this method is a rest-of for a non-rest-of method, the array will have + * one element. If it is a rest-of for a rest-of, the array will have two elements, and so on. + */ + private final int[] continuationEntryPoints; + + /** + * ScriptFunction data for what is being compile, where applicable. + * TODO: make this immutable, propagate it through the CompilationPhases + */ + private RecompilableScriptFunctionData compiledFunction; + private static boolean initialized = false; /** + * Compilation phases that a compilation goes through + */ + public static class CompilationPhases implements Iterable { + + /** Singleton that describes a standard eager compilation - this includes code installation */ + public final static CompilationPhases COMPILE_ALL = new CompilationPhases( + "Compile all", + new CompilationPhase[] { + CompilationPhase.CONSTANT_FOLDING_PHASE, + CompilationPhase.LOWERING_PHASE, + CompilationPhase.PROGRAM_POINT_PHASE, + CompilationPhase.TRANSFORM_BUILTINS_PHASE, + CompilationPhase.SPLITTING_PHASE, + CompilationPhase.SYMBOL_ASSIGNMENT_PHASE, + CompilationPhase.SCOPE_DEPTH_COMPUTATION_PHASE, + CompilationPhase.OPTIMISTIC_TYPE_ASSIGNMENT_PHASE, + CompilationPhase.LOCAL_VARIABLE_TYPE_CALCULATION_PHASE, + CompilationPhase.BYTECODE_GENERATION_PHASE, + CompilationPhase.INSTALL_PHASE + }); + + /** Compile all for a rest of method */ + public final static CompilationPhases COMPILE_ALL_RESTOF = + COMPILE_ALL.setDescription("Compile all, rest of").addAfter(CompilationPhase.LOCAL_VARIABLE_TYPE_CALCULATION_PHASE, CompilationPhase.REUSE_COMPILE_UNITS_PHASE); + + /** Singleton that describes a standard eager compilation, but no installation, for example used by --compile-only */ + public final static CompilationPhases COMPILE_ALL_NO_INSTALL = + COMPILE_ALL. + removeLast(). + setDescription("Compile without install"); + + /** Singleton that describes compilation up to the CodeGenerator, but not actually generating code */ + public final static CompilationPhases COMPILE_UPTO_BYTECODE = + COMPILE_ALL. + removeLast(). + removeLast(). + setDescription("Compile upto bytecode"); + + /** + * Singleton that describes back end of method generation, given that we have generated the normal + * method up to CodeGenerator as in {@link CompilationPhases#COMPILE_UPTO_BYTECODE} + */ + public final static CompilationPhases COMPILE_FROM_BYTECODE = new CompilationPhases( + "Generate bytecode and install", + new CompilationPhase[] { + CompilationPhase.BYTECODE_GENERATION_PHASE, + CompilationPhase.INSTALL_PHASE + }); + + /** + * Singleton that describes restOf method generation, given that we have generated the normal + * method up to CodeGenerator as in {@link CompilationPhases#COMPILE_UPTO_BYTECODE} + */ + public final static CompilationPhases COMPILE_FROM_BYTECODE_RESTOF = + COMPILE_FROM_BYTECODE. + addFirst(CompilationPhase.REUSE_COMPILE_UNITS_PHASE). + setDescription("Generate bytecode and install - RestOf method"); + + private final List phases; + + private final String desc; + + private CompilationPhases(final String desc, final CompilationPhase... phases) { + this.desc = desc; + + final List newPhases = new LinkedList<>(); + newPhases.addAll(Arrays.asList(phases)); + this.phases = Collections.unmodifiableList(newPhases); + } + + @Override + public String toString() { + return "'" + desc + "' " + phases.toString(); + } + + private CompilationPhases setDescription(final String desc) { + return new CompilationPhases(desc, phases.toArray(new CompilationPhase[phases.size()])); + } + + private CompilationPhases removeLast() { + final LinkedList list = new LinkedList<>(phases); + list.removeLast(); + return new CompilationPhases(desc, list.toArray(new CompilationPhase[list.size()])); + } + + private CompilationPhases addFirst(final CompilationPhase phase) { + if (phases.contains(phase)) { + return this; + } + final LinkedList list = new LinkedList<>(phases); + list.addFirst(phase); + return new CompilationPhases(desc, list.toArray(new CompilationPhase[list.size()])); + } + + private CompilationPhases addAfter(final CompilationPhase phase, final CompilationPhase newPhase) { + final LinkedList list = new LinkedList<>(); + for (final CompilationPhase p : phases) { + list.add(p); + if (p == phase) { + list.add(newPhase); + } + } + return new CompilationPhases(desc, list.toArray(new CompilationPhase[list.size()])); + } + + boolean contains(final CompilationPhase phase) { + return phases.contains(phase); + } + + @Override + public Iterator iterator() { + return phases.iterator(); + } + + boolean isRestOfCompilation() { + return this == COMPILE_ALL_RESTOF || this == COMPILE_FROM_BYTECODE_RESTOF; + } + + String toString(final String prefix) { + final StringBuilder sb = new StringBuilder(); + for (final CompilationPhase phase : phases) { + sb.append(prefix).append(phase).append('\n'); + } + return sb.toString(); + } + } + + /** * This array contains names that need to be reserved at the start * of a compile, to avoid conflict with variable names later introduced. * See {@link CompilerConstants} for special names used for structures @@ -125,29 +287,80 @@ ARGUMENTS.symbolName() }; - private void initCompiler(final String className, final FunctionNode functionNode) { - this.source = functionNode.getSource(); - this.sourceName = functionNode.getSourceName(); - this.sourceURL = functionNode.getSourceURL(); + // per instance + private final int compilationId = COMPILATION_ID.getAndIncrement(); + + // per instance + private final AtomicInteger nextCompileUnitId = new AtomicInteger(0); + + private static final AtomicInteger COMPILATION_ID = new AtomicInteger(0); - if (functionNode.isStrict()) { - compilationEnv.setIsStrict(true); - } - - final String name = className + '$' + safeSourceName(functionNode.getSource()); - final String uniqueName = functionNode.uniqueName(name); - - this.scriptName = uniqueName; + /** + * Constructor + * + * @param context context + * @param env script environment + * @param installer code installer + * @param source source to compile + * @param sourceURL source URL, or null if not present + * @param isStrict is this a strict compilation + */ + public Compiler( + final Context context, + final ScriptEnvironment env, + final CodeInstaller installer, + final Source source, + final String sourceURL, + final boolean isStrict) { + this(context, env, installer, source, sourceURL, isStrict, false, null, null, null, null, null); } - private Compiler(final CompilationEnvironment compilationEnv, final ScriptEnvironment scriptEnv, final CodeInstaller installer) { - this.scriptEnv = scriptEnv; - this.compilationEnv = compilationEnv; - this.installer = installer; - this.constantData = new ConstantData(); - this.compileUnits = new TreeSet<>(); - this.bytecode = new LinkedHashMap<>(); - this.log = initLogger(compilationEnv.getContext()); + /** + * Constructor + * + * @param context context + * @param env script environment + * @param installer code installer + * @param source source to compile + * @param sourceURL source URL, or null if not present + * @param isStrict is this a strict compilation + * @param isOnDemand is this an on demand compilation + * @param compiledFunction compiled function, if any + * @param types parameter and return value type information, if any is known + * @param invalidatedProgramPoints invalidated program points for recompilation + * @param continuationEntryPoints continuation entry points for restof method + * @param runtimeScope runtime scope for recompilation type lookup in {@code TypeEvaluator} + */ + public Compiler( + final Context context, + final ScriptEnvironment env, + final CodeInstaller installer, + final Source source, + final String sourceURL, + final boolean isStrict, + final boolean isOnDemand, + final RecompilableScriptFunctionData compiledFunction, + final TypeMap types, + final Map invalidatedProgramPoints, + final int[] continuationEntryPoints, + final ScriptObject runtimeScope) { + this.context = context; + this.installer = installer; + this.constantData = new ConstantData(); + this.compileUnits = CompileUnit.createCompileUnitSet(); + this.bytecode = new LinkedHashMap<>(); + this.log = initLogger(context); + this.source = source; + this.sourceURL = sourceURL; + this.sourceName = FunctionNode.getSourceName(source, sourceURL); + this.onDemand = isOnDemand; + this.compiledFunction = compiledFunction; + this.types = types; + this.invalidatedProgramPoints = invalidatedProgramPoints == null ? new HashMap() : invalidatedProgramPoints; + this.continuationEntryPoints = continuationEntryPoints == null ? null: continuationEntryPoints.clone(); + this.typeEvaluator = new TypeEvaluator(this, runtimeScope); + this.firstCompileUnitName = firstCompileUnitName(context.getEnv()); + this.strict = isStrict; if (!initialized) { initialized = true; @@ -155,24 +368,53 @@ log.warning("Running without optimistic types. This is a configuration that may be deprecated."); } } + + this.optimistic = ScriptEnvironment.globalOptimistic(); + } + + private static String safeSourceName(final ScriptEnvironment env, final CodeInstaller installer, final Source source) { + String baseName = new File(source.getName()).getName(); + + final int index = baseName.lastIndexOf(".js"); + if (index != -1) { + baseName = baseName.substring(0, index); + } + + baseName = baseName.replace('.', '_').replace('-', '_'); + if (!env._loader_per_compile) { + baseName = baseName + installer.getUniqueScriptId(); + } + + final String mangled = NameCodec.encode(baseName); + return mangled != null ? mangled : baseName; } - /** - * Constructor - common entry point for generating code. - * @param env compilation environment - * @param installer code installer - */ - public Compiler(final CompilationEnvironment env, final CodeInstaller installer) { - this(env, installer.getOwner(), installer); + private String firstCompileUnitName(final ScriptEnvironment env) { + final StringBuilder sb = new StringBuilder(SCRIPTS_PACKAGE). + append('/'). + append(CompilerConstants.DEFAULT_SCRIPT_NAME.symbolName()). + append('$'); + + if (isOnDemandCompilation()) { + sb.append(RecompilableScriptFunctionData.RECOMPILATION_PREFIX); + } + + if (compilationId > 0) { + sb.append(compilationId).append('$'); + } + + sb.append(Compiler.safeSourceName(env, installer, source)); + + return sb.toString(); } - /** - * ScriptEnvironment constructor for compiler. Used only from Shell and --compile-only flag - * No code installer supplied - * @param scriptEnv script environment - */ - public Compiler(final ScriptEnvironment scriptEnv) { - this(new CompilationEnvironment(Context.getContext(), CompilationPhases.EAGER, scriptEnv._strict), scriptEnv, null); + void declareLocalSymbol(final String symbolName) { + typeEvaluator.declareLocalSymbol(symbolName); + } + + void setData(final RecompilableScriptFunctionData data) { + assert this.compiledFunction == null : data; + this.compiledFunction = data; } @Override @@ -181,11 +423,268 @@ } @Override - public DebugLogger initLogger(final Context context) { - return context.getLogger(this.getClass()); + public DebugLogger initLogger(final Context ctxt) { + return ctxt.getLogger(this.getClass()); + } + + boolean isOnDemandCompilation() { + return onDemand; + } + + boolean useOptimisticTypes() { + return optimistic; + } + + Context getContext() { + return context; + } + + Type getOptimisticType(final Optimistic node) { + return typeEvaluator.getOptimisticType(node); + } + + void addInvalidatedProgramPoint(final int programPoint, final Type type) { + invalidatedProgramPoints.put(programPoint, type); + } + + TypeMap getTypeMap() { + return types; + } + + MethodType getCallSiteType(final FunctionNode fn) { + if (types == null || !isOnDemandCompilation()) { + return null; + } + return types.getCallSiteType(fn); + } + + Type getParamType(final FunctionNode fn, final int pos) { + return types == null ? null : types.get(fn, pos); + } + + /** + * Do a compilation job + * + * @param functionNode function node to compile + * @param phases phases of compilation transforms to apply to function + + * @return transformed function + * + * @throws CompilationException if error occurs during compilation + */ + public FunctionNode compile(final FunctionNode functionNode, final CompilationPhases phases) throws CompilationException { + + log.info("Starting compile job for ", DebugLogger.quote(functionNode.getName()), " phases=", phases); + log.indent(); + + final String name = DebugLogger.quote(functionNode.getName()); + + FunctionNode newFunctionNode = functionNode; + + for (final String reservedName : RESERVED_NAMES) { + newFunctionNode.uniqueName(reservedName); + } + + final boolean fine = log.levelFinerThanOrEqual(Level.FINE); + final boolean info = log.levelFinerThanOrEqual(Level.INFO); + + long time = 0L; + + for (final CompilationPhase phase : phases) { + if (fine) { + log.fine("Phase ", phase.toString(), " starting for ", name); + } + + newFunctionNode = phase.apply(this, phases, newFunctionNode); + + if (getEnv()._print_mem_usage) { + printMemoryUsage(functionNode, phase.toString()); + } + + final long duration = Timing.isEnabled() ? phase.getEndTime() - phase.getStartTime() : 0L; + time += duration; + + if (fine) { + final StringBuilder sb = new StringBuilder(); + + sb.append("Phase "). + append(phase.toString()). + append(" done for function "). + append(name); + + if (duration > 0L) { + sb.append(" in "). + append(duration). + append(" ms "); + } + + log.fine(sb); + } + } + + log.unindent(); + + if (info) { + final StringBuilder sb = new StringBuilder(); + sb.append("Compile job for "). + append(newFunctionNode.getSource()). + append(':'). + append(DebugLogger.quote(newFunctionNode.getName())). + append(" finished"); + + if (time > 0L) { + sb.append(" in "). + append(time). + append(" ms"); + } + + log.info(sb); + } + + return newFunctionNode; + } + + Source getSource() { + return source; + } + + Map getBytecode() { + return Collections.unmodifiableMap(bytecode); } - private void printMemoryUsage(final String phaseName, final FunctionNode functionNode) { + byte[] getBytecode(final String className) { + return bytecode.get(className); + } + + CompileUnit getFirstCompileUnit() { + assert !compileUnits.isEmpty(); + return compileUnits.iterator().next(); + } + + Set getCompileUnits() { + return compileUnits; + } + + ConstantData getConstantData() { + return constantData; + } + + CodeInstaller getCodeInstaller() { + return installer; + } + + void addClass(final String name, final byte[] code) { + bytecode.put(name, code); + } + + void removeClass(final String name) { + assert bytecode.get(name) != null; + bytecode.remove(name); + } + + ScriptEnvironment getEnv() { + return context.getEnv(); + } + + String getSourceURL() { + return sourceURL; + } + + String nextCompileUnitName() { + final StringBuilder sb = new StringBuilder(firstCompileUnitName); + final int cuid = nextCompileUnitId.getAndIncrement(); + if (cuid > 0) { + sb.append("$cu").append(cuid); + } + + return sb.toString(); + } + + void clearCompileUnits() { + compileUnits.clear(); + } + + CompileUnit addCompileUnit(final long initialWeight) { + final CompileUnit compileUnit = createCompileUnit(initialWeight); + compileUnits.add(compileUnit); + log.fine("Added compile unit ", compileUnit); + return compileUnit; + } + + CompileUnit createCompileUnit(final String unitClassName, final long initialWeight) { + final ClassEmitter classEmitter = new ClassEmitter(context, sourceName, unitClassName, isStrict()); + final CompileUnit compileUnit = new CompileUnit(unitClassName, classEmitter, initialWeight); + + classEmitter.begin(); + + final MethodEmitter initMethod = classEmitter.init(EnumSet.of(Flag.PRIVATE)); + initMethod.begin(); + initMethod.load(Type.OBJECT, 0); + initMethod.newInstance(jdk.nashorn.internal.scripts.JS.class); + initMethod.returnVoid(); + initMethod.end(); + + return compileUnit; + } + + private CompileUnit createCompileUnit(final long initialWeight) { + return createCompileUnit(nextCompileUnitName(), initialWeight); + } + + boolean isStrict() { + return strict; + } + + void replaceCompileUnits(final Set newUnits) { + compileUnits.clear(); + compileUnits.addAll(newUnits); + } + + CompileUnit findUnit(final long weight) { + for (final CompileUnit unit : compileUnits) { + if (unit.canHold(weight)) { + unit.addWeight(weight); + return unit; + } + } + + return addCompileUnit(weight); + } + + /** + * Convert a package/class name to a binary name. + * + * @param name Package/class name. + * @return Binary name. + */ + public static String binaryName(final String name) { + return name.replace('/', '.'); + } + + RecompilableScriptFunctionData getProgram() { + if (compiledFunction == null) { + return null; + } + return compiledFunction.getProgram(); + } + + RecompilableScriptFunctionData getScriptFunctionData(final int functionId) { + return compiledFunction == null ? null : compiledFunction.getScriptFunctionData(functionId); + } + + boolean isGlobalSymbol(final FunctionNode fn, final String name) { + return getScriptFunctionData(fn.getId()).isGlobalSymbol(fn, name); + } + + int[] getContinuationEntryPoints() { + return continuationEntryPoints; + } + + Type getInvalidatedProgramPointType(final int programPoint) { + return invalidatedProgramPoints.get(programPoint); + } + + private void printMemoryUsage(final FunctionNode functionNode, final String phaseName) { if (!log.isEnabled()) { return; } @@ -227,304 +726,4 @@ } } } - - CompilationEnvironment getCompilationEnvironment() { - return compilationEnv; - } - - /** - * Execute the compilation this Compiler was created with with default class name - * @param functionNode function node to compile from its current state - * @throws CompilationException if something goes wrong - * @return function node that results from code transforms - */ - public FunctionNode compile(final FunctionNode functionNode) throws CompilationException { - return compile(CompilerConstants.DEFAULT_SCRIPT_NAME.symbolName(), functionNode); - } - - /** - * Execute the compilation this Compiler was created with - * @param className class name for the compile - * @param functionNode function node to compile from its current state - * @throws CompilationException if something goes wrong - * @return function node that results from code transforms - */ - public FunctionNode compile(final String className, final FunctionNode functionNode) throws CompilationException { - try { - return compileInternal(className, functionNode); - } catch (final AssertionError e) { - throw new AssertionError("Assertion failure compiling " + functionNode.getSource(), e); - } - } - - private FunctionNode compileInternal(final String className, final FunctionNode functionNode) throws CompilationException { - FunctionNode newFunctionNode = functionNode; - - initCompiler(className, newFunctionNode); //TODO move this state into functionnode? - - for (final String reservedName : RESERVED_NAMES) { - newFunctionNode.uniqueName(reservedName); - } - - final boolean fine = log.levelFinerThanOrEqual(Level.FINE); - final boolean info = log.levelFinerThanOrEqual(Level.INFO); - - long time = 0L; - - for (final CompilationPhase phase : compilationEnv.getPhases()) { - newFunctionNode = phase.apply(this, newFunctionNode); - - if (scriptEnv._print_mem_usage) { - printMemoryUsage(phase.toString(), newFunctionNode); - } - - final long duration = Timing.isEnabled() ? phase.getEndTime() - phase.getStartTime() : 0L; - time += duration; - - if (fine) { - final StringBuilder sb = new StringBuilder(); - - sb.append(phase.toString()). - append(" done for function '"). - append(newFunctionNode.getName()). - append('\''); - - if (duration > 0L) { - sb.append(" in "). - append(duration). - append(" ms "); - } - - log.fine(sb); - } - } - - if (info) { - final StringBuilder sb = new StringBuilder(); - sb.append("Compile job for '"). - append(newFunctionNode.getSource()). - append(':'). - append(newFunctionNode.getName()). - append("' finished"); - - if (time > 0L) { - sb.append(" in "). - append(time). - append(" ms"); - } - - log.info(sb); - } - - return newFunctionNode; - } - - private Class install(final String className, final byte[] code) { - log.fine("Installing class ", className); - - final Class clazz = installer.install(Compiler.binaryName(className), code); - - try { - final Object[] constants = getConstantData().toArray(); - // Need doPrivileged because these fields are private - AccessController.doPrivileged(new PrivilegedExceptionAction() { - @Override - public Void run() throws Exception { - //use reflection to write source and constants table to installed classes - final Field sourceField = clazz.getDeclaredField(SOURCE.symbolName()); - final Field constantsField = clazz.getDeclaredField(CONSTANTS.symbolName()); - sourceField.setAccessible(true); - constantsField.setAccessible(true); - sourceField.set(null, source); - constantsField.set(null, constants); - return null; - } - }); - } catch (final PrivilegedActionException e) { - throw new RuntimeException(e); - } - - return clazz; - } - - /** - * Install compiled classes into a given loader - * @param functionNode function node to install - must be in {@link CompilationState#EMITTED} state - * @return root script class - if there are several compile units they will also be installed - */ - public Class install(final FunctionNode functionNode) { - final long t0 = Timing.isEnabled() ? System.currentTimeMillis() : 0L; - - assert functionNode.hasState(CompilationState.EMITTED) : functionNode.getName() + " has unexpected compilation state"; - - final Map> installedClasses = new HashMap<>(); - - final String rootClassName = firstCompileUnitName(); - final byte[] rootByteCode = bytecode.get(rootClassName); - final Class rootClass = install(rootClassName, rootByteCode); - - int length = rootByteCode.length; - - installedClasses.put(rootClassName, rootClass); - - for (final Entry entry : bytecode.entrySet()) { - final String className = entry.getKey(); - if (className.equals(rootClassName)) { - continue; - } - final byte[] code = entry.getValue(); - length += code.length; - - installedClasses.put(className, install(className, code)); - } - - final Map rfns = new IdentityHashMap<>(); - for(final Object constant: getConstantData().constants) { - if(constant instanceof RecompilableScriptFunctionData) { - final RecompilableScriptFunctionData rfn = (RecompilableScriptFunctionData)constant; - rfns.put(rfn, rfn); - } - } - - for (final CompileUnit unit : compileUnits) { - unit.setCode(installedClasses.get(unit.getUnitClassName())); - unit.initializeFunctionsCode(); - } - - final StringBuilder sb; - if (log.isEnabled()) { - sb = new StringBuilder(); - sb.append("Installed class '"). - append(rootClass.getSimpleName()). - append('\''). - append(" bytes="). - append(length). - append('.'); - if (bytecode.size() > 1) { - sb.append(' ').append(bytecode.size()).append(" compile units."); - } - } else { - sb = null; - } - - if (Timing.isEnabled()) { - final long duration = System.currentTimeMillis() - t0; - Timing.accumulateTime("[Code Installation]", duration); - if (sb != null) { - sb.append(" Install time: ").append(duration).append(" ms"); - } - } - - if (sb != null) { - log.fine(sb); - } - - return rootClass; - } - - Set getCompileUnits() { - return compileUnits; - } - - ConstantData getConstantData() { - return constantData; - } - - CodeInstaller getCodeInstaller() { - return installer; - } - - void addClass(final String name, final byte[] code) { - bytecode.put(name, code); - } - - ScriptEnvironment getEnv() { - return this.scriptEnv; - } - - String getSourceURL() { - return sourceURL; - } - - private String safeSourceName(final Source src) { - String baseName = new File(src.getName()).getName(); - - final int index = baseName.lastIndexOf(".js"); - if (index != -1) { - baseName = baseName.substring(0, index); - } - - baseName = baseName.replace('.', '_').replace('-', '_'); - if (!scriptEnv._loader_per_compile) { - baseName = baseName + installer.getUniqueScriptId(); - } - - final String mangled = NameCodec.encode(baseName); - return mangled != null ? mangled : baseName; - } - - private int nextCompileUnitIndex() { - return compileUnits.size() + 1; - } - - String firstCompileUnitName() { - return SCRIPTS_PACKAGE + '/' + scriptName; - } - - private String nextCompileUnitName() { - return firstCompileUnitName() + '$' + nextCompileUnitIndex(); - } - - CompileUnit addCompileUnit(final long initialWeight) { - return addCompileUnit(nextCompileUnitName(), initialWeight); - } - - CompileUnit addCompileUnit(final String unitClassName) { - return addCompileUnit(unitClassName, 0L); - } - - private CompileUnit addCompileUnit(final String unitClassName, final long initialWeight) { - final CompileUnit compileUnit = initCompileUnit(unitClassName, initialWeight); - compileUnits.add(compileUnit); - log.fine("Added compile unit ", compileUnit); - return compileUnit; - } - - private CompileUnit initCompileUnit(final String unitClassName, final long initialWeight) { - final ClassEmitter classEmitter = new ClassEmitter(compilationEnv.getContext(), sourceName, unitClassName, compilationEnv.isStrict()); - final CompileUnit compileUnit = new CompileUnit(unitClassName, classEmitter, initialWeight); - - classEmitter.begin(); - - final MethodEmitter initMethod = classEmitter.init(EnumSet.of(Flag.PRIVATE)); - initMethod.begin(); - initMethod.load(Type.OBJECT, 0); - initMethod.newInstance(jdk.nashorn.internal.scripts.JS.class); - initMethod.returnVoid(); - initMethod.end(); - - return compileUnit; - } - - CompileUnit findUnit(final long weight) { - for (final CompileUnit unit : compileUnits) { - if (unit.canHold(weight)) { - unit.addWeight(weight); - return unit; - } - } - - return addCompileUnit(weight); - } - - /** - * Convert a package/class name to a binary name. - * - * @param name Package/class name. - * @return Binary name. - */ - public static String binaryName(final String name) { - return name.replace('/', '.'); - } - } diff -r eb9658fa0120 -r 31aed7d9c02a nashorn/src/jdk/nashorn/internal/codegen/ConstantData.java --- a/nashorn/src/jdk/nashorn/internal/codegen/ConstantData.java Thu May 15 15:28:51 2014 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/ConstantData.java Mon May 19 15:29:42 2014 +0200 @@ -27,9 +27,12 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; + import jdk.nashorn.internal.runtime.Property; import jdk.nashorn.internal.runtime.PropertyMap; @@ -37,7 +40,7 @@ * Manages constants needed by code generation. Objects are maintained in an * interning maps to remove duplicates. */ -class ConstantData { +final class ConstantData { /** Constant table. */ final List constants; @@ -206,6 +209,10 @@ return index; } + Collection getConstants() { + return Collections.unmodifiableList(constants); + } + Object[] toArray() { return constants.toArray(); } diff -r eb9658fa0120 -r 31aed7d9c02a nashorn/src/jdk/nashorn/internal/codegen/FindScopeDepths.java --- a/nashorn/src/jdk/nashorn/internal/codegen/FindScopeDepths.java Thu May 15 15:28:51 2014 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/FindScopeDepths.java Mon May 19 15:29:42 2014 +0200 @@ -60,7 +60,6 @@ final class FindScopeDepths extends NodeVisitor implements Loggable { private final Compiler compiler; - private final CompilationEnvironment env; private final Map> fnIdToNestedFunctions = new HashMap<>(); private final Map> externalSymbolDepths = new HashMap<>(); private final Map> internalSymbols = new HashMap<>(); @@ -73,8 +72,7 @@ FindScopeDepths(final Compiler compiler) { super(new LexicalContext()); this.compiler = compiler; - this.env = compiler.getCompilationEnvironment(); - this.log = initLogger(compiler.getCompilationEnvironment().getContext()); + this.log = initLogger(compiler.getContext()); } @Override @@ -165,7 +163,7 @@ @Override public boolean enterFunctionNode(final FunctionNode functionNode) { - if (env.isOnDemandCompilation()) { + if (compiler.isOnDemandCompilation()) { return true; } @@ -189,8 +187,8 @@ final String name = functionNode.getName(); FunctionNode newFunctionNode = functionNode.setState(lc, CompilationState.SCOPE_DEPTHS_COMPUTED); - if (env.isOnDemandCompilation()) { - final RecompilableScriptFunctionData data = env.getScriptFunctionData(newFunctionNode.getId()); + if (compiler.isOnDemandCompilation()) { + final RecompilableScriptFunctionData data = compiler.getScriptFunctionData(newFunctionNode.getId()); assert data != null : newFunctionNode.getName() + " lacks data"; if (data.inDynamicContext()) { log.fine("Reviving scriptfunction ", quote(name), " as defined in previous (now lost) dynamic scope."); @@ -214,7 +212,7 @@ final String allocatorClassName = Compiler.binaryName(getClassName(fieldCount)); final PropertyMap allocatorMap = PropertyMap.newMap(null, 0, fieldCount, 0); final RecompilableScriptFunctionData data = new RecompilableScriptFunctionData( - compiler.getCompilationEnvironment().getContext(), + compiler.getContext(), newFunctionNode, compiler.getCodeInstaller(), allocatorClassName, @@ -231,7 +229,7 @@ fnIdToNestedFunctions.get(parentFn.getId()).put(fnId, data); } } else { - env.setData(data); + compiler.setData(data); } if (isDynamicScopeBoundary(functionNode)) { @@ -269,7 +267,7 @@ @Override public boolean enterBlock(final Block block) { - if (env.isOnDemandCompilation()) { + if (compiler.isOnDemandCompilation()) { return true; } @@ -290,7 +288,7 @@ block.accept(new NodeVisitor(new LexicalContext()) { @Override public final boolean enterDefault(final Node node) { - if (!env.isOnDemandCompilation()) { + if (!compiler.isOnDemandCompilation()) { if (node instanceof IdentNode) { final Symbol symbol = ((IdentNode)node).getSymbol(); if (symbol != null && symbol.isScope()) { @@ -351,7 +349,7 @@ @Override public Node leaveBlock(final Block block) { - if (env.isOnDemandCompilation()) { + if (compiler.isOnDemandCompilation()) { return block; } if (isDynamicScopeBoundary(block)) { diff -r eb9658fa0120 -r 31aed7d9c02a nashorn/src/jdk/nashorn/internal/codegen/FoldConstants.java --- a/nashorn/src/jdk/nashorn/internal/codegen/FoldConstants.java Thu May 15 15:28:51 2014 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/FoldConstants.java Mon May 19 15:29:42 2014 +0200 @@ -60,9 +60,9 @@ private final DebugLogger log; - FoldConstants(final CompilationEnvironment env) { + FoldConstants(final Compiler compiler) { super(new LexicalContext()); - this.log = initLogger(env.getContext()); + this.log = initLogger(compiler.getContext()); } @Override diff -r eb9658fa0120 -r 31aed7d9c02a nashorn/src/jdk/nashorn/internal/codegen/Label.java --- a/nashorn/src/jdk/nashorn/internal/codegen/Label.java Thu May 15 15:28:51 2014 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/Label.java Mon May 19 15:29:42 2014 +0200 @@ -30,6 +30,7 @@ import java.util.Iterator; import java.util.List; import java.util.ListIterator; + import jdk.nashorn.internal.codegen.types.Type; /** @@ -93,7 +94,7 @@ } Type peek(final int n) { - int pos = sp - 1 - n; + final int pos = sp - 1 - n; return pos < 0 ? null : data[pos]; } @@ -168,6 +169,7 @@ private void mergeVariableTypes(final Stack joinOrigin, final int toSlot) { final ListIterator it1 = localVariableTypes.listIterator(); final Iterator it2 = joinOrigin.localVariableTypes.iterator(); + for(int i = 0; i < toSlot; ++i) { final Type thisType = it1.next(); final Type otherType = it2.next(); @@ -194,11 +196,13 @@ mergeVariableTypes(joinOrigin, firstTemp); } - private int getFirstDeadLocal(List types) { + private int getFirstDeadLocal(final List types) { int i = types.size(); for(final ListIterator it = types.listIterator(i); it.hasPrevious() && it.previous() == Type.UNKNOWN; - --i); // no body + --i) { + // no body + } // Respect symbol boundaries; we never chop off half a symbol's storage while(!symbolBoundary.get(i - 1)) { @@ -253,7 +257,7 @@ * @return a list of widest local variable slot types. */ List getWidestLiveLocals(final List lvarTypes) { - List widestLiveLocals = new ArrayList<>(lvarTypes); + final List widestLiveLocals = new ArrayList<>(lvarTypes); boolean keepNextValue = true; final int size = widestLiveLocals.size(); for(int i = size - 1; i-- > 0;) { @@ -523,7 +527,6 @@ this.id = label.id; } - jdk.internal.org.objectweb.asm.Label getLabel() { if (this.label == null) { this.label = new jdk.internal.org.objectweb.asm.Label(); diff -r eb9658fa0120 -r 31aed7d9c02a nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java --- a/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java Thu May 15 15:28:51 2014 +0200 +++ b/nashorn/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java Mon May 19 15:29:42 2014 +0200 @@ -106,7 +106,7 @@ } private static class JumpTarget { - private List origins = new LinkedList<>(); + private final List origins = new LinkedList<>(); private Map types = Collections.emptyMap(); void addOrigin(final JoinPredecessor originNode, final Map originTypes) { @@ -143,7 +143,7 @@ private LocalVariableConversion createConversion(final Symbol symbol, final LvarType branchLvarType, final Map joinLvarTypes, final LocalVariableConversion next) { - LvarType targetType = joinLvarTypes.get(symbol); + final LvarType targetType = joinLvarTypes.get(symbol); assert targetType != null; if(targetType == branchLvarType) { return next; @@ -193,7 +193,8 @@ union = cloneMap(types2); } } - if(!(matches1 || matches2)) { + if(!(matches1 || matches2) && union != null) { //remove overly enthusiastic "union can be null" warning + assert union != null; union.put(symbol, widest); } } @@ -344,7 +345,7 @@ // Int64 type anyway, so this loss of precision is actually more conformant to the specification... return LvarType.values()[Math.max(t1.ordinal(), t2.ordinal())]; } - private final CompilationEnvironment env; + private final Compiler compiler; private final Map jumpTargets = new IdentityHashMap<>(); // Local variable type mapping at the currently evaluated point. No map instance is ever modified; setLvarType() always // allocates a new map. Immutability of maps allows for cheap snapshots by just keeping the reference to the current @@ -378,9 +379,9 @@ // variables). private final Deque