Mon, 08 Sep 2014 18:40:58 +0200
8057148: Skip nested functions on reparse
Reviewed-by: hannesw, lagergren
1.1 --- a/src/jdk/nashorn/internal/codegen/AssignSymbols.java Thu Sep 04 18:57:14 2014 +0200 1.2 +++ b/src/jdk/nashorn/internal/codegen/AssignSymbols.java Mon Sep 08 18:40:58 2014 +0200 1.3 @@ -194,12 +194,12 @@ 1.4 */ 1.5 private void acceptDeclarations(final FunctionNode functionNode, final Block body) { 1.6 // This visitor will assign symbol to all declared variables, except "var" declarations in for loop initializers. 1.7 - // 1.8 body.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { 1.9 @Override 1.10 - public boolean enterFunctionNode(final FunctionNode nestedFn) { 1.11 - // Don't descend into nested functions 1.12 - return false; 1.13 + protected boolean enterDefault(final Node node) { 1.14 + // Don't bother visiting expressions; var is a statement, it can't be inside an expression. 1.15 + // This will also prevent visiting nested functions (as FunctionNode is an expression). 1.16 + return !(node instanceof Expression); 1.17 } 1.18 1.19 @Override 1.20 @@ -443,12 +443,27 @@ 1.21 1.22 if (lc.isFunctionBody()) { 1.23 block.clearSymbols(); 1.24 + final FunctionNode fn = lc.getCurrentFunction(); 1.25 + if (isUnparsedFunction(fn)) { 1.26 + // It's a skipped nested function. Just mark the symbols being used by it as being in use. 1.27 + for(final String name: compiler.getScriptFunctionData(fn.getId()).getExternalSymbolNames()) { 1.28 + nameIsUsed(name, null); 1.29 + } 1.30 + // Don't bother descending into it, it must be empty anyway. 1.31 + assert block.getStatements().isEmpty(); 1.32 + return false; 1.33 + } 1.34 + 1.35 enterFunctionBody(); 1.36 } 1.37 1.38 return true; 1.39 } 1.40 1.41 + private boolean isUnparsedFunction(final FunctionNode fn) { 1.42 + return compiler.isOnDemandCompilation() && fn != lc.getOutermostFunction(); 1.43 + } 1.44 + 1.45 @Override 1.46 public boolean enterCatchNode(final CatchNode catchNode) { 1.47 final IdentNode exception = catchNode.getException(); 1.48 @@ -492,18 +507,13 @@ 1.49 1.50 @Override 1.51 public boolean enterFunctionNode(final FunctionNode functionNode) { 1.52 - // TODO: once we have information on symbols used by nested functions, we can stop descending into nested 1.53 - // functions with on-demand compilation, e.g. add 1.54 - // if(!thisProperties.isEmpty() && env.isOnDemandCompilation()) { 1.55 - // return false; 1.56 - // } 1.57 start(functionNode, false); 1.58 1.59 thisProperties.push(new HashSet<String>()); 1.60 1.61 - //an outermost function in our lexical context that is not a program 1.62 - //is possible - it is a function being compiled lazily 1.63 if (functionNode.isDeclared()) { 1.64 + // Can't use lc.getCurrentBlock() as we can have an outermost function in our lexical context that 1.65 + // is not a program - it is a function being compiled on-demand. 1.66 final Iterator<Block> blocks = lc.getBlocks(); 1.67 if (blocks.hasNext()) { 1.68 final IdentNode ident = functionNode.getIdent(); 1.69 @@ -511,6 +521,11 @@ 1.70 } 1.71 } 1.72 1.73 + // Every function has a body, even the ones skipped on reparse (they have an empty one). We're 1.74 + // asserting this as even for those, enterBlock() must be invoked to correctly process symbols that 1.75 + // are used in them. 1.76 + assert functionNode.getBody() != null; 1.77 + 1.78 return true; 1.79 } 1.80 1.81 @@ -533,7 +548,7 @@ 1.82 1.83 /** 1.84 * This has to run before fix assignment types, store any type specializations for 1.85 - * paramters, then turn then to objects for the generic version of this method 1.86 + * parameters, then turn them into objects for the generic version of this method. 1.87 * 1.88 * @param functionNode functionNode 1.89 */ 1.90 @@ -733,14 +748,20 @@ 1.91 1.92 @Override 1.93 public Node leaveBlock(final Block block) { 1.94 - // It's not necessary to guard the marking of symbols as locals with this "if"condition for correctness, it's 1.95 - // just an optimization -- runtime type calculation is not used when the compilation is not an on-demand 1.96 - // optimistic compilation, so we can skip locals marking then. 1.97 + // It's not necessary to guard the marking of symbols as locals with this "if" condition for 1.98 + // correctness, it's just an optimization -- runtime type calculation is not used when the compilation 1.99 + // is not an on-demand optimistic compilation, so we can skip locals marking then. 1.100 if (compiler.useOptimisticTypes() && compiler.isOnDemandCompilation()) { 1.101 - for (final Symbol symbol: block.getSymbols()) { 1.102 - if (!symbol.isScope()) { 1.103 - assert symbol.isVar() || symbol.isParam(); 1.104 - compiler.declareLocalSymbol(symbol.getName()); 1.105 + // OTOH, we must not declare symbols from nested functions to be locals. As we're doing on-demand 1.106 + // compilation, and we're skipping parsing the function bodies for nested functions, this 1.107 + // basically only means their parameters. It'd be enough to mistakenly declare to be a local a 1.108 + // symbol in the outer function named the same as one of the parameters, though. 1.109 + if (lc.getFunction(block) == lc.getOutermostFunction()) { 1.110 + for (final Symbol symbol: block.getSymbols()) { 1.111 + if (!symbol.isScope()) { 1.112 + assert symbol.isVar() || symbol.isParam(); 1.113 + compiler.declareLocalSymbol(symbol.getName()); 1.114 + } 1.115 } 1.116 } 1.117 } 1.118 @@ -811,24 +832,45 @@ 1.119 1.120 @Override 1.121 public Node leaveFunctionNode(final FunctionNode functionNode) { 1.122 - 1.123 - return markProgramBlock( 1.124 + final FunctionNode finalizedFunction; 1.125 + if (isUnparsedFunction(functionNode)) { 1.126 + finalizedFunction = functionNode; 1.127 + } else { 1.128 + finalizedFunction = 1.129 + markProgramBlock( 1.130 removeUnusedSlots( 1.131 createSyntheticInitializers( 1.132 finalizeParameters( 1.133 lc.applyTopFlags(functionNode)))) 1.134 - .setThisProperties(lc, thisProperties.pop().size()) 1.135 - .setState(lc, CompilationState.SYMBOLS_ASSIGNED)); 1.136 + .setThisProperties(lc, thisProperties.pop().size())); 1.137 + } 1.138 + return finalizedFunction.setState(lc, CompilationState.SYMBOLS_ASSIGNED); 1.139 } 1.140 1.141 @Override 1.142 public Node leaveIdentNode(final IdentNode identNode) { 1.143 - final String name = identNode.getName(); 1.144 - 1.145 if (identNode.isPropertyName()) { 1.146 return identNode; 1.147 } 1.148 1.149 + final Symbol symbol = nameIsUsed(identNode.getName(), identNode); 1.150 + 1.151 + if (!identNode.isInitializedHere()) { 1.152 + symbol.increaseUseCount(); 1.153 + } 1.154 + 1.155 + IdentNode newIdentNode = identNode.setSymbol(symbol); 1.156 + 1.157 + // If a block-scoped var is used before its declaration mark it as dead. 1.158 + // We can only statically detect this for local vars, cross-function symbols require runtime checks. 1.159 + if (symbol.isBlockScoped() && !symbol.hasBeenDeclared() && !identNode.isDeclaredHere() && isLocal(lc.getCurrentFunction(), symbol)) { 1.160 + newIdentNode = newIdentNode.markDead(); 1.161 + } 1.162 + 1.163 + return end(newIdentNode); 1.164 + } 1.165 + 1.166 + private Symbol nameIsUsed(final String name, final IdentNode origin) { 1.167 final Block block = lc.getCurrentBlock(); 1.168 1.169 Symbol symbol = findSymbol(block, name); 1.170 @@ -847,24 +889,11 @@ 1.171 maybeForceScope(symbol); 1.172 } else { 1.173 log.info("No symbol exists. Declare as global: ", name); 1.174 - symbol = defineSymbol(block, name, identNode, IS_GLOBAL | IS_SCOPE); 1.175 + symbol = defineSymbol(block, name, origin, IS_GLOBAL | IS_SCOPE); 1.176 } 1.177 1.178 functionUsesSymbol(symbol); 1.179 - 1.180 - if (!identNode.isInitializedHere()) { 1.181 - symbol.increaseUseCount(); 1.182 - } 1.183 - 1.184 - IdentNode newIdentNode = identNode.setSymbol(symbol); 1.185 - 1.186 - // If a block-scoped var is used before its declaration mark it as dead. 1.187 - // We can only statically detect this for local vars, cross-function symbols require runtime checks. 1.188 - if (symbol.isBlockScoped() && !symbol.hasBeenDeclared() && !identNode.isDeclaredHere() && isLocal(lc.getCurrentFunction(), symbol)) { 1.189 - newIdentNode = newIdentNode.markDead(); 1.190 - } 1.191 - 1.192 - return end(newIdentNode); 1.193 + return symbol; 1.194 } 1.195 1.196 @Override 1.197 @@ -912,7 +941,6 @@ 1.198 return functionNode; 1.199 } 1.200 1.201 - assert functionNode.getId() == 1; 1.202 return functionNode.setBody(lc, functionNode.getBody().setFlag(lc, Block.IS_GLOBAL_SCOPE)); 1.203 } 1.204
2.1 --- a/src/jdk/nashorn/internal/ir/Block.java Thu Sep 04 18:57:14 2014 +0200 2.2 +++ b/src/jdk/nashorn/internal/ir/Block.java Mon Sep 08 18:40:58 2014 +0200 2.3 @@ -277,6 +277,14 @@ 2.4 } 2.5 2.6 /** 2.7 + * Returns the number of statements in the block. 2.8 + * @return the number of statements in the block. 2.9 + */ 2.10 + public int getStatementCount() { 2.11 + return statements.size(); 2.12 + } 2.13 + 2.14 + /** 2.15 * Returns the line number of the first statement in the block. 2.16 * @return the line number of the first statement in the block, or -1 if the block has no statements. 2.17 */
3.1 --- a/src/jdk/nashorn/internal/ir/FunctionNode.java Thu Sep 04 18:57:14 2014 +0200 3.2 +++ b/src/jdk/nashorn/internal/ir/FunctionNode.java Mon Sep 08 18:40:58 2014 +0200 3.3 @@ -48,6 +48,7 @@ 3.4 import jdk.nashorn.internal.ir.annotations.Ignore; 3.5 import jdk.nashorn.internal.ir.annotations.Immutable; 3.6 import jdk.nashorn.internal.ir.visitor.NodeVisitor; 3.7 +import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData; 3.8 import jdk.nashorn.internal.runtime.ScriptFunction; 3.9 import jdk.nashorn.internal.runtime.Source; 3.10 import jdk.nashorn.internal.runtime.UserAccessorProperty; 3.11 @@ -110,8 +111,11 @@ 3.12 /** Source of entity. */ 3.13 private final Source source; 3.14 3.15 - /** Unique ID used for recompilation among other things */ 3.16 - private final int id; 3.17 + /** 3.18 + * Opaque object representing parser state at the end of the function. Used when reparsing outer functions 3.19 + * to skip parsing inner functions. 3.20 + */ 3.21 + private final Object endParserState; 3.22 3.23 /** External function identifier. */ 3.24 @Ignore 3.25 @@ -256,6 +260,14 @@ 3.26 /** trace callsite values in this function? */ 3.27 public static final int IS_TRACE_VALUES = 1 << 26; 3.28 3.29 + /** 3.30 + * Whether this function needs the callee {@link ScriptFunction} instance passed to its code as a 3.31 + * parameter on invocation. Note that we aren't, in fact using this flag in function nodes. 3.32 + * Rather, it is always calculated (see {@link #needsCallee()}). {@link RecompilableScriptFunctionData} 3.33 + * will, however, cache the value of this flag. 3.34 + */ 3.35 + public static final int NEEDS_CALLEE = 1 << 27; 3.36 + 3.37 /** extension callsite flags mask */ 3.38 public static final int EXTENSION_CALLSITE_FLAGS = IS_PRINT_PARSE | 3.39 IS_PRINT_LOWER_PARSE | IS_PRINT_AST | IS_PRINT_LOWER_AST | 3.40 @@ -271,16 +283,9 @@ 3.41 /** Does this function potentially need "arguments"? Note that this is not a full test, as further negative check of REDEFINES_ARGS is needed. */ 3.42 private static final int MAYBE_NEEDS_ARGUMENTS = USES_ARGUMENTS | HAS_EVAL; 3.43 3.44 - /** Does this function need the parent scope? It needs it if either it or its descendants use variables from it, or have a deep eval. 3.45 - * We also pessimistically need a parent scope if we have lazy children that have not yet been compiled */ 3.46 + /** Does this function need the parent scope? It needs it if either it or its descendants use variables from it, or have a deep eval. */ 3.47 private static final int NEEDS_PARENT_SCOPE = USES_ANCESTOR_SCOPE | HAS_DEEP_EVAL; 3.48 3.49 - /** Used to signify "null", e.g. if someone asks for the parent of the program node */ 3.50 - public static final int NO_FUNCTION_ID = 0; 3.51 - 3.52 - /** Where to start assigning global and unique function node ids */ 3.53 - public static final int FIRST_FUNCTION_ID = NO_FUNCTION_ID + 1; 3.54 - 3.55 /** What is the return type of this function? */ 3.56 private Type returnType = Type.UNKNOWN; 3.57 3.58 @@ -288,11 +293,10 @@ 3.59 * Constructor 3.60 * 3.61 * @param source the source 3.62 - * @param id unique id 3.63 * @param lineNumber line number 3.64 * @param token token 3.65 * @param finish finish 3.66 - * @param firstToken first token of the funtion node (including the function declaration) 3.67 + * @param firstToken first token of the function node (including the function declaration) 3.68 * @param namespace the namespace 3.69 * @param ident the identifier 3.70 * @param name the name of the function 3.71 @@ -302,7 +306,6 @@ 3.72 */ 3.73 public FunctionNode( 3.74 final Source source, 3.75 - final int id, 3.76 final int lineNumber, 3.77 final long token, 3.78 final int finish, 3.79 @@ -316,7 +319,6 @@ 3.80 super(token, finish); 3.81 3.82 this.source = source; 3.83 - this.id = id; 3.84 this.lineNumber = lineNumber; 3.85 this.ident = ident; 3.86 this.name = name; 3.87 @@ -331,11 +333,13 @@ 3.88 this.body = null; 3.89 this.thisProperties = 0; 3.90 this.rootClass = null; 3.91 + this.endParserState = null; 3.92 } 3.93 3.94 private FunctionNode( 3.95 final FunctionNode functionNode, 3.96 final long lastToken, 3.97 + Object endParserState, 3.98 final int flags, 3.99 final String name, 3.100 final Type returnType, 3.101 @@ -347,6 +351,7 @@ 3.102 final Class<?> rootClass) { 3.103 super(functionNode); 3.104 3.105 + this.endParserState = endParserState; 3.106 this.lineNumber = functionNode.lineNumber; 3.107 this.flags = flags; 3.108 this.name = name; 3.109 @@ -361,7 +366,6 @@ 3.110 3.111 // the fields below never change - they are final and assigned in constructor 3.112 this.source = functionNode.source; 3.113 - this.id = functionNode.id; 3.114 this.ident = functionNode.ident; 3.115 this.namespace = functionNode.namespace; 3.116 this.kind = functionNode.kind; 3.117 @@ -429,11 +433,11 @@ 3.118 } 3.119 3.120 /** 3.121 - * Get the unique ID for this function 3.122 + * Get the unique ID for this function within the script file. 3.123 * @return the id 3.124 */ 3.125 public int getId() { 3.126 - return id; 3.127 + return position(); 3.128 } 3.129 3.130 /** 3.131 @@ -535,6 +539,7 @@ 3.132 new FunctionNode( 3.133 this, 3.134 lastToken, 3.135 + endParserState, 3.136 flags, 3.137 name, 3.138 returnType, 3.139 @@ -606,6 +611,7 @@ 3.140 new FunctionNode( 3.141 this, 3.142 lastToken, 3.143 + endParserState, 3.144 flags, 3.145 name, 3.146 returnType, 3.147 @@ -644,15 +650,24 @@ 3.148 } 3.149 3.150 /** 3.151 - * Check if the {@code eval} keyword is used in this function 3.152 + * Check if this function has a call expression for the identifier "eval" (that is, {@code eval(...)}). 3.153 * 3.154 - * @return true if {@code eval} is used 3.155 + * @return true if {@code eval} is called. 3.156 */ 3.157 public boolean hasEval() { 3.158 return getFlag(HAS_EVAL); 3.159 } 3.160 3.161 /** 3.162 + * Returns true if a function nested (directly or transitively) within this function {@link #hasEval()}. 3.163 + * 3.164 + * @return true if a nested function calls {@code eval}. 3.165 + */ 3.166 + public boolean hasNestedEval() { 3.167 + return getFlag(HAS_NESTED_EVAL); 3.168 + } 3.169 + 3.170 + /** 3.171 * Get the first token for this function 3.172 * @return the first token 3.173 */ 3.174 @@ -741,6 +756,7 @@ 3.175 new FunctionNode( 3.176 this, 3.177 lastToken, 3.178 + endParserState, 3.179 flags | 3.180 (body.needsScope() ? 3.181 FunctionNode.HAS_SCOPE_BLOCK : 3.182 @@ -839,6 +855,7 @@ 3.183 new FunctionNode( 3.184 this, 3.185 lastToken, 3.186 + endParserState, 3.187 flags, 3.188 name, 3.189 returnType, 3.190 @@ -899,6 +916,7 @@ 3.191 new FunctionNode( 3.192 this, 3.193 lastToken, 3.194 + endParserState, 3.195 flags, 3.196 name, 3.197 returnType, 3.198 @@ -911,6 +929,41 @@ 3.199 } 3.200 3.201 /** 3.202 + * Returns the end parser state for this function. 3.203 + * @return the end parser state for this function. 3.204 + */ 3.205 + public Object getEndParserState() { 3.206 + return endParserState; 3.207 + } 3.208 + 3.209 + /** 3.210 + * Set the end parser state for this function. 3.211 + * @param lc lexical context 3.212 + * @param endParserState the parser state to set 3.213 + * @return function node or a new one if state was changed 3.214 + */ 3.215 + public FunctionNode setEndParserState(final LexicalContext lc, final Object endParserState) { 3.216 + if (this.endParserState == endParserState) { 3.217 + return this; 3.218 + } 3.219 + return Node.replaceInLexicalContext( 3.220 + lc, 3.221 + this, 3.222 + new FunctionNode( 3.223 + this, 3.224 + lastToken, 3.225 + endParserState, 3.226 + flags, 3.227 + name, 3.228 + returnType, 3.229 + compileUnit, 3.230 + compilationState, 3.231 + body, 3.232 + parameters, 3.233 + thisProperties, rootClass)); 3.234 + } 3.235 + 3.236 + /** 3.237 * Get the name of this function 3.238 * @return the name 3.239 */ 3.240 @@ -934,6 +987,7 @@ 3.241 new FunctionNode( 3.242 this, 3.243 lastToken, 3.244 + endParserState, 3.245 flags, 3.246 name, 3.247 returnType, 3.248 @@ -999,6 +1053,7 @@ 3.249 new FunctionNode( 3.250 this, 3.251 lastToken, 3.252 + endParserState, 3.253 flags, 3.254 name, 3.255 returnType, 3.256 @@ -1077,6 +1132,7 @@ 3.257 new FunctionNode( 3.258 this, 3.259 lastToken, 3.260 + endParserState, 3.261 flags, 3.262 name, 3.263 type, 3.264 @@ -1123,6 +1179,7 @@ 3.265 new FunctionNode( 3.266 this, 3.267 lastToken, 3.268 + endParserState, 3.269 flags, 3.270 name, 3.271 returnType, 3.272 @@ -1178,6 +1235,7 @@ 3.273 new FunctionNode( 3.274 this, 3.275 lastToken, 3.276 + endParserState, 3.277 flags, 3.278 name, 3.279 returnType,
4.1 --- a/src/jdk/nashorn/internal/ir/LexicalContext.java Thu Sep 04 18:57:14 2014 +0200 4.2 +++ b/src/jdk/nashorn/internal/ir/LexicalContext.java Mon Sep 08 18:40:58 2014 +0200 4.3 @@ -351,8 +351,7 @@ 4.4 } 4.5 4.6 /** 4.7 - * Get the function for this block. If the block is itself a function 4.8 - * this returns identity 4.9 + * Get the function for this block. 4.10 * @param block block for which to get function 4.11 * @return function for block 4.12 */
5.1 --- a/src/jdk/nashorn/internal/parser/Parser.java Thu Sep 04 18:57:14 2014 +0200 5.2 +++ b/src/jdk/nashorn/internal/parser/Parser.java Mon Sep 08 18:40:58 2014 +0200 5.3 @@ -148,7 +148,7 @@ 5.4 /** to receive line information from Lexer when scanning multine literals. */ 5.5 protected final Lexer.LineInfoReceiver lineInfoReceiver; 5.6 5.7 - private int nextFunctionId; 5.8 + private RecompilableScriptFunctionData reparsedFunction; 5.9 5.10 /** 5.11 * Constructor 5.12 @@ -171,7 +171,7 @@ 5.13 * @param log debug logger if one is needed 5.14 */ 5.15 public Parser(final ScriptEnvironment env, final Source source, final ErrorManager errors, final boolean strict, final DebugLogger log) { 5.16 - this(env, source, errors, strict, FunctionNode.FIRST_FUNCTION_ID, 0, log); 5.17 + this(env, source, errors, strict, 0, log); 5.18 } 5.19 5.20 /** 5.21 @@ -181,15 +181,13 @@ 5.22 * @param source source to parse 5.23 * @param errors error manager 5.24 * @param strict parser created with strict mode enabled. 5.25 - * @param nextFunctionId starting value for assigning new unique ids to function nodes 5.26 * @param lineOffset line offset to start counting lines from 5.27 * @param log debug logger if one is needed 5.28 */ 5.29 - public Parser(final ScriptEnvironment env, final Source source, final ErrorManager errors, final boolean strict, final int nextFunctionId, final int lineOffset, final DebugLogger log) { 5.30 + public Parser(final ScriptEnvironment env, final Source source, final ErrorManager errors, final boolean strict, final int lineOffset, final DebugLogger log) { 5.31 super(source, errors, strict, lineOffset); 5.32 this.env = env; 5.33 this.namespace = new Namespace(env.getNamespace()); 5.34 - this.nextFunctionId = nextFunctionId; 5.35 this.scripting = env._scripting; 5.36 if (this.scripting) { 5.37 this.lineInfoReceiver = new Lexer.LineInfoReceiver() { 5.38 @@ -228,6 +226,16 @@ 5.39 } 5.40 5.41 /** 5.42 + * Sets the {@link RecompilableScriptFunctionData} representing the function being reparsed (when this 5.43 + * parser instance is used to reparse a previously parsed function, as part of its on-demand compilation). 5.44 + * This will trigger various special behaviors, such as skipping nested function bodies. 5.45 + * @param reparsedFunction the function being reparsed. 5.46 + */ 5.47 + public void setReparsedFunction(final RecompilableScriptFunctionData reparsedFunction) { 5.48 + this.reparsedFunction = reparsedFunction; 5.49 + } 5.50 + 5.51 + /** 5.52 * Execute parse and return the resulting function node. 5.53 * Errors will be thrown and the error manager will contain information 5.54 * if parsing should fail 5.55 @@ -472,7 +480,6 @@ 5.56 final FunctionNode functionNode = 5.57 new FunctionNode( 5.58 source, 5.59 - nextFunctionId++, 5.60 functionLine, 5.61 token, 5.62 Token.descPosition(token), 5.63 @@ -2828,10 +2835,14 @@ 5.64 FunctionNode functionNode = null; 5.65 long lastToken = 0L; 5.66 5.67 + final boolean parseBody; 5.68 + Object endParserState = null; 5.69 try { 5.70 // Create a new function block. 5.71 functionNode = newFunctionNode(firstToken, ident, parameters, kind, functionLine); 5.72 - 5.73 + assert functionNode != null; 5.74 + final int functionId = functionNode.getId(); 5.75 + parseBody = reparsedFunction == null || functionId <= reparsedFunction.getFunctionNodeId(); 5.76 // Nashorn extension: expression closures 5.77 if (!env._no_syntax_extensions && type != LBRACE) { 5.78 /* 5.79 @@ -2847,34 +2858,152 @@ 5.80 assert lc.getCurrentBlock() == lc.getFunctionBody(functionNode); 5.81 // EOL uses length field to store the line number 5.82 final int lastFinish = Token.descPosition(lastToken) + (Token.descType(lastToken) == EOL ? 0 : Token.descLength(lastToken)); 5.83 - final ReturnNode returnNode = new ReturnNode(functionNode.getLineNumber(), expr.getToken(), lastFinish, expr); 5.84 - appendStatement(returnNode); 5.85 + // Only create the return node if we aren't skipping nested functions. Note that we aren't 5.86 + // skipping parsing of these extended functions; they're considered to be small anyway. Also, 5.87 + // they don't end with a single well known token, so it'd be very hard to get correctly (see 5.88 + // the note below for reasoning on skipping happening before instead of after RBRACE for 5.89 + // details). 5.90 + if (parseBody) { 5.91 + final ReturnNode returnNode = new ReturnNode(functionNode.getLineNumber(), expr.getToken(), lastFinish, expr); 5.92 + appendStatement(returnNode); 5.93 + } 5.94 functionNode.setFinish(lastFinish); 5.95 - 5.96 } else { 5.97 expect(LBRACE); 5.98 - 5.99 - // Gather the function elements. 5.100 - final List<Statement> prevFunctionDecls = functionDeclarations; 5.101 - functionDeclarations = new ArrayList<>(); 5.102 - try { 5.103 - sourceElements(false); 5.104 - addFunctionDeclarations(functionNode); 5.105 - } finally { 5.106 - functionDeclarations = prevFunctionDecls; 5.107 + final int lastLexed = stream.last(); 5.108 + if (parseBody || !skipFunctionBody(functionNode)) { 5.109 + // Gather the function elements. 5.110 + final List<Statement> prevFunctionDecls = functionDeclarations; 5.111 + functionDeclarations = new ArrayList<>(); 5.112 + try { 5.113 + sourceElements(false); 5.114 + addFunctionDeclarations(functionNode); 5.115 + } finally { 5.116 + functionDeclarations = prevFunctionDecls; 5.117 + } 5.118 + 5.119 + lastToken = token; 5.120 + // Avoiding storing parser state if the function body was small (that is, the next token 5.121 + // to be read from the token stream is before the last token lexed before we entered 5.122 + // function body). That'll force the function to be reparsed instead of skipped. Skipping 5.123 + // involves throwing away and recreating the lexer and the token stream, so for small 5.124 + // functions it is likely more economical to not bother with skipping (both in terms of 5.125 + // storing the state, and in terms of throwing away lexer and token stream). 5.126 + if (parseBody && lastLexed < stream.first()) { 5.127 + // Since the lexer can read ahead and lexify some number of tokens in advance and have 5.128 + // them buffered in the TokenStream, we need to produce a lexer state as it was just 5.129 + // before it lexified RBRACE, and not whatever is its current (quite possibly well read 5.130 + // ahead) state. 5.131 + endParserState = new ParserState(Token.descPosition(token), line, linePosition); 5.132 + 5.133 + // NOTE: you might wonder why do we capture/restore parser state before RBRACE instead of 5.134 + // after RBRACE; after all, we could skip the below "expect(RBRACE);" if we captured the 5.135 + // state after it. The reason is that RBRACE is a well-known token that we can expect and 5.136 + // will never involve us getting into a weird lexer state, and as such is a great reparse 5.137 + // point. Typical example of a weird lexer state after RBRACE would be: 5.138 + // function this_is_skipped() { ... } "use strict"; 5.139 + // because lexer is doing weird off-by-one maneuvers around string literal quotes. Instead 5.140 + // of compensating for the possibility of a string literal (or similar) after RBRACE, 5.141 + // we'll rather just restart parsing from this well-known, friendly token instead. 5.142 + } 5.143 } 5.144 - 5.145 - lastToken = token; 5.146 expect(RBRACE); 5.147 functionNode.setFinish(finish); 5.148 } 5.149 } finally { 5.150 functionNode = restoreFunctionNode(functionNode, lastToken); 5.151 } 5.152 + 5.153 + // NOTE: we can only do alterations to the function node after restoreFunctionNode. 5.154 + 5.155 + if (parseBody) { 5.156 + functionNode = functionNode.setEndParserState(lc, endParserState); 5.157 + } else if (functionNode.getBody().getStatementCount() > 0){ 5.158 + // This is to ensure the body is empty when !parseBody but we couldn't skip parsing it (see 5.159 + // skipFunctionBody() for possible reasons). While it is not strictly necessary for correctness to 5.160 + // enforce empty bodies in nested functions that were supposed to be skipped, we do assert it as 5.161 + // an invariant in few places in the compiler pipeline, so for consistency's sake we'll throw away 5.162 + // nested bodies early if we were supposed to skip 'em. 5.163 + functionNode = functionNode.setBody(null, functionNode.getBody().setStatements(null, 5.164 + Collections.<Statement>emptyList())); 5.165 + } 5.166 + 5.167 + if (reparsedFunction != null) { 5.168 + // We restore the flags stored in the function's ScriptFunctionData that we got when we first 5.169 + // eagerly parsed the code. We're doing it because some flags would be set based on the 5.170 + // content of the function, or even content of its nested functions, most of which are normally 5.171 + // skipped during an on-demand compilation. 5.172 + final RecompilableScriptFunctionData data = reparsedFunction.getScriptFunctionData(functionNode.getId()); 5.173 + if (data != null) { 5.174 + // Data can be null if when we originally parsed the file, we removed the function declaration 5.175 + // as it was dead code. 5.176 + functionNode = functionNode.setFlags(lc, data.getFunctionFlags()); 5.177 + // This compensates for missing markEval() in case the function contains an inner function 5.178 + // that contains eval(), that now we didn't discover since we skipped the inner function. 5.179 + if (functionNode.hasNestedEval()) { 5.180 + assert functionNode.hasScopeBlock(); 5.181 + functionNode = functionNode.setBody(lc, functionNode.getBody().setNeedsScope(null)); 5.182 + } 5.183 + } 5.184 + } 5.185 printAST(functionNode); 5.186 return functionNode; 5.187 } 5.188 5.189 + private boolean skipFunctionBody(final FunctionNode functionNode) { 5.190 + if (reparsedFunction == null) { 5.191 + // Not reparsing, so don't skip any function body. 5.192 + return false; 5.193 + } 5.194 + // Skip to the RBRACE of this function, and continue parsing from there. 5.195 + final RecompilableScriptFunctionData data = reparsedFunction.getScriptFunctionData(functionNode.getId()); 5.196 + if (data == null) { 5.197 + // Nested function is not known to the reparsed function. This can happen if the FunctionNode was 5.198 + // in dead code that was removed. Both FoldConstants and Lower prune dead code. In that case, the 5.199 + // FunctionNode was dropped before a RecompilableScriptFunctionData could've been created for it. 5.200 + return false; 5.201 + } 5.202 + final ParserState parserState = (ParserState)data.getEndParserState(); 5.203 + if (parserState == null) { 5.204 + // The function has no stored parser state; it was deemed too small to be skipped. 5.205 + return false; 5.206 + } 5.207 + 5.208 + stream.reset(); 5.209 + lexer = parserState.createLexer(source, lexer, stream, scripting && !env._no_syntax_extensions); 5.210 + line = parserState.line; 5.211 + linePosition = parserState.linePosition; 5.212 + // Doesn't really matter, but it's safe to treat it as if there were a semicolon before 5.213 + // the RBRACE. 5.214 + type = SEMICOLON; 5.215 + k = -1; 5.216 + next(); 5.217 + 5.218 + return true; 5.219 + } 5.220 + 5.221 + /** 5.222 + * Encapsulates part of the state of the parser, enough to reconstruct the state of both parser and lexer 5.223 + * for resuming parsing after skipping a function body. 5.224 + */ 5.225 + private static class ParserState { 5.226 + private final int position; 5.227 + private final int line; 5.228 + private final int linePosition; 5.229 + 5.230 + ParserState(final int position, final int line, final int linePosition) { 5.231 + this.position = position; 5.232 + this.line = line; 5.233 + this.linePosition = linePosition; 5.234 + } 5.235 + 5.236 + Lexer createLexer(final Source source, final Lexer lexer, final TokenStream stream, final boolean scripting) { 5.237 + final Lexer newLexer = new Lexer(source, position, lexer.limit - position, stream, scripting); 5.238 + newLexer.restoreState(new Lexer.State(position, Integer.MAX_VALUE, line, -1, linePosition, SEMICOLON)); 5.239 + return newLexer; 5.240 + } 5.241 + } 5.242 + 5.243 private void printAST(final FunctionNode functionNode) { 5.244 if (functionNode.getFlag(FunctionNode.IS_PRINT_AST)) { 5.245 env.getErr().println(new ASTWriter(functionNode)); 5.246 @@ -3247,6 +3376,9 @@ 5.247 } else { 5.248 lc.setFlag(fn, FunctionNode.HAS_NESTED_EVAL); 5.249 } 5.250 + // NOTE: it is crucial to mark the body of the outer function as needing scope even when we skip 5.251 + // parsing a nested function. functionBody() contains code to compensate for the lack of invoking 5.252 + // this method when the parser skips a nested function. 5.253 lc.setBlockNeedsScope(lc.getFunctionBody(fn)); 5.254 } 5.255 }
6.1 --- a/src/jdk/nashorn/internal/parser/TokenStream.java Thu Sep 04 18:57:14 2014 +0200 6.2 +++ b/src/jdk/nashorn/internal/parser/TokenStream.java Mon Sep 08 18:40:58 2014 +0200 6.3 @@ -209,4 +209,8 @@ 6.4 in = count; 6.5 buffer = newBuffer; 6.6 } 6.7 + 6.8 + void reset() { 6.9 + in = out = count = base = 0; 6.10 + } 6.11 }
7.1 --- a/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Thu Sep 04 18:57:14 2014 +0200 7.2 +++ b/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Mon Sep 08 18:40:58 2014 +0200 7.3 @@ -84,6 +84,12 @@ 7.4 /** Allocator map from makeMap() */ 7.5 private final PropertyMap allocatorMap; 7.6 7.7 + /** 7.8 + * Opaque object representing parser state at the end of the function. Used when reparsing outer function 7.9 + * to help with skipping parsing inner functions. 7.10 + */ 7.11 + private final Object endParserState; 7.12 + 7.13 /** Code installer used for all further recompilation/specialization of this ScriptFunction */ 7.14 private transient CodeInstaller<ScriptEnvironment> installer; 7.15 7.16 @@ -98,9 +104,8 @@ 7.17 /** Id to parent function if one exists */ 7.18 private RecompilableScriptFunctionData parent; 7.19 7.20 - private final boolean isDeclared; 7.21 - private final boolean isAnonymous; 7.22 - private final boolean needsCallee; 7.23 + /** Copy of the {@link FunctionNode} flags. */ 7.24 + private final int functionFlags; 7.25 7.26 private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); 7.27 7.28 @@ -136,15 +141,14 @@ 7.29 7.30 super(functionName(functionNode), 7.31 Math.min(functionNode.getParameters().size(), MAX_ARITY), 7.32 - getFlags(functionNode)); 7.33 + getDataFlags(functionNode)); 7.34 7.35 this.functionName = functionNode.getName(); 7.36 this.lineNumber = functionNode.getLineNumber(); 7.37 - this.isDeclared = functionNode.isDeclared(); 7.38 - this.needsCallee = functionNode.needsCallee(); 7.39 - this.isAnonymous = functionNode.isAnonymous(); 7.40 + this.functionFlags = functionNode.getFlags() | (functionNode.needsCallee() ? FunctionNode.NEEDS_CALLEE : 0); 7.41 this.functionNodeId = functionNode.getId(); 7.42 this.source = functionNode.getSource(); 7.43 + this.endParserState = functionNode.getEndParserState(); 7.44 this.token = tokenFor(functionNode); 7.45 this.installer = installer; 7.46 this.allocatorClassName = allocatorClassName; 7.47 @@ -202,6 +206,24 @@ 7.48 } 7.49 7.50 /** 7.51 + * Returns the names of all external symbols this function uses. 7.52 + * @return the names of all external symbols this function uses. 7.53 + */ 7.54 + public Set<String> getExternalSymbolNames() { 7.55 + return externalScopeDepths == null ? Collections.<String>emptySet() : 7.56 + Collections.unmodifiableSet(externalScopeDepths.keySet()); 7.57 + } 7.58 + 7.59 + /** 7.60 + * Returns the opaque object representing the parser state at the end of this function's body, used to 7.61 + * skip parsing this function when reparsing its containing outer function. 7.62 + * @return the object representing the end parser state 7.63 + */ 7.64 + public Object getEndParserState() { 7.65 + return endParserState; 7.66 + } 7.67 + 7.68 + /** 7.69 * Get the parent of this RecompilableScriptFunctionData. If we are 7.70 * a nested function, we have a parent. Note that "null" return value 7.71 * can also mean that we have a parent but it is unknown, so this can 7.72 @@ -269,7 +291,7 @@ 7.73 7.74 @Override 7.75 public boolean inDynamicContext() { 7.76 - return (flags & IN_DYNAMIC_CONTEXT) != 0; 7.77 + return getFunctionFlag(FunctionNode.IN_DYNAMIC_CONTEXT); 7.78 } 7.79 7.80 private static String functionName(final FunctionNode fn) { 7.81 @@ -293,7 +315,7 @@ 7.82 return Token.toDesc(TokenType.FUNCTION, position, length); 7.83 } 7.84 7.85 - private static int getFlags(final FunctionNode functionNode) { 7.86 + private static int getDataFlags(final FunctionNode functionNode) { 7.87 int flags = IS_CONSTRUCTOR; 7.88 if (functionNode.isStrict()) { 7.89 flags |= IS_STRICT; 7.90 @@ -307,9 +329,6 @@ 7.91 if (functionNode.isVarArg()) { 7.92 flags |= IS_VARIABLE_ARITY; 7.93 } 7.94 - if (functionNode.inDynamicContext()) { 7.95 - flags |= IN_DYNAMIC_CONTEXT; 7.96 - } 7.97 return flags; 7.98 } 7.99 7.100 @@ -337,7 +356,6 @@ 7.101 } 7.102 7.103 FunctionNode reparse() { 7.104 - final boolean isProgram = functionNodeId == FunctionNode.FIRST_FUNCTION_ID; 7.105 // NOTE: If we aren't recompiling the top-level program, we decrease functionNodeId 'cause we'll have a synthetic program node 7.106 final int descPosition = Token.descPosition(token); 7.107 final Context context = Context.getContextTrusted(); 7.108 @@ -346,18 +364,27 @@ 7.109 source, 7.110 new Context.ThrowErrorManager(), 7.111 isStrict(), 7.112 - functionNodeId - (isProgram ? 0 : 1), 7.113 lineNumber - 1, 7.114 context.getLogger(Parser.class)); // source starts at line 0, so even though lineNumber is the correct declaration line, back off one to make it exclusive 7.115 7.116 - if (isAnonymous) { 7.117 + if (getFunctionFlag(FunctionNode.IS_ANONYMOUS)) { 7.118 parser.setFunctionName(functionName); 7.119 } 7.120 + parser.setReparsedFunction(this); 7.121 7.122 - final FunctionNode program = parser.parse(CompilerConstants.PROGRAM.symbolName(), descPosition, Token.descLength(token), true); 7.123 - // Parser generates a program AST even if we're recompiling a single function, so when we are only recompiling a 7.124 - // single function, extract it from the program. 7.125 - return (isProgram ? program : extractFunctionFromScript(program)).setName(null, functionName); 7.126 + final FunctionNode program = parser.parse(CompilerConstants.PROGRAM.symbolName(), descPosition, 7.127 + Token.descLength(token), true); 7.128 + // Parser generates a program AST even if we're recompiling a single function, so when we are only 7.129 + // recompiling a single function, extract it from the program. 7.130 + return (isProgram() ? program : extractFunctionFromScript(program)).setName(null, functionName); 7.131 + } 7.132 + 7.133 + private boolean getFunctionFlag(final int flag) { 7.134 + return (functionFlags & flag) != 0; 7.135 + } 7.136 + 7.137 + private boolean isProgram() { 7.138 + return getFunctionFlag(FunctionNode.IS_PROGRAM); 7.139 } 7.140 7.141 TypeMap typeMap(final MethodType fnCallSiteType) { 7.142 @@ -546,7 +573,7 @@ 7.143 assert fns.size() == 1 : "got back more than one method in recompilation"; 7.144 final FunctionNode f = fns.iterator().next(); 7.145 assert f.getId() == functionNodeId; 7.146 - if (!isDeclared && f.isDeclared()) { 7.147 + if (!getFunctionFlag(FunctionNode.IS_DECLARED) && f.isDeclared()) { 7.148 return f.clearFlag(null, FunctionNode.IS_DECLARED); 7.149 } 7.150 return f; 7.151 @@ -669,7 +696,15 @@ 7.152 7.153 @Override 7.154 public boolean needsCallee() { 7.155 - return needsCallee; 7.156 + return getFunctionFlag(FunctionNode.NEEDS_CALLEE); 7.157 + } 7.158 + 7.159 + /** 7.160 + * Returns the {@link FunctionNode} flags associated with this function data. 7.161 + * @return the {@link FunctionNode} flags associated with this function data. 7.162 + */ 7.163 + public int getFunctionFlags() { 7.164 + return functionFlags; 7.165 } 7.166 7.167 @Override
8.1 --- a/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java Thu Sep 04 18:57:14 2014 +0200 8.2 +++ b/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java Mon Sep 08 18:40:58 2014 +0200 8.3 @@ -90,8 +90,6 @@ 8.4 public static final int USES_THIS = 1 << 4; 8.5 /** Is this a variable arity function? */ 8.6 public static final int IS_VARIABLE_ARITY = 1 << 5; 8.7 - /** Is this declared in a dynamic context */ 8.8 - public static final int IN_DYNAMIC_CONTEXT = 1 << 6; 8.9 8.10 /** Flag for strict or built-in functions */ 8.11 public static final int IS_STRICT_OR_BUILTIN = IS_STRICT | IS_BUILTIN;
9.1 --- a/src/jdk/nashorn/internal/runtime/Timing.java Thu Sep 04 18:57:14 2014 +0200 9.2 +++ b/src/jdk/nashorn/internal/runtime/Timing.java Mon Sep 08 18:40:58 2014 +0200 9.3 @@ -189,7 +189,7 @@ 9.4 maxKeyLength++; 9.5 9.6 final StringBuilder sb = new StringBuilder(); 9.7 - sb.append("Accumulated complation phase Timings:\n\n"); 9.8 + sb.append("Accumulated compilation phase timings:\n\n"); 9.9 for (final Map.Entry<String, Long> entry : timings.entrySet()) { 9.10 int len; 9.11
10.1 --- a/src/jdk/nashorn/tools/Shell.java Thu Sep 04 18:57:14 2014 +0200 10.2 +++ b/src/jdk/nashorn/tools/Shell.java Mon Sep 08 18:40:58 2014 +0200 10.3 @@ -246,7 +246,7 @@ 10.4 10.5 // For each file on the command line. 10.6 for (final String fileName : files) { 10.7 - final FunctionNode functionNode = new Parser(env, sourceFor(fileName, new File(fileName)), errors, env._strict, FunctionNode.FIRST_FUNCTION_ID, 0, context.getLogger(Parser.class)).parse(); 10.8 + final FunctionNode functionNode = new Parser(env, sourceFor(fileName, new File(fileName)), errors, env._strict, 0, context.getLogger(Parser.class)).parse(); 10.9 10.10 if (errors.getNumberOfErrors() != 0) { 10.11 return COMPILATION_ERROR;
11.1 --- a/test/script/basic/optimistic_check_type.js Thu Sep 04 18:57:14 2014 +0200 11.2 +++ b/test/script/basic/optimistic_check_type.js Mon Sep 08 18:40:58 2014 +0200 11.3 @@ -36,13 +36,18 @@ 11.4 11.5 // Testing conditional operator 11.6 print(inspect("" ? b : x.a, "ternary operator")) 11.7 -print(inspect(x.b ? b : x.a, "ternary operator")) 11.8 -print(inspect(c ? b : a, "ternary operator")) 11.9 -print(inspect(!c ? b : a, "ternary operator")) 11.10 -print(inspect(d ? b : x.c, "ternary operator")) 11.11 +var b1 = b; 11.12 +print(inspect(x.b ? b1 : x.a, "ternary operator")) 11.13 +var b2 = b; 11.14 +print(inspect(c ? b2 : a, "ternary operator")) 11.15 +var b3 = b; 11.16 +print(inspect(!c ? b3 : a, "ternary operator")) 11.17 +var b4 = b; 11.18 +print(inspect(d ? b4 : x.c, "ternary operator")) 11.19 print(inspect(x.c ? a : c, "ternary operator")) 11.20 print(inspect(c ? d : a, "ternary operator")) 11.21 -print(inspect(c ? +a : b, "ternary operator")) 11.22 +var b5 = b; 11.23 +print(inspect(c ? +a : b5, "ternary operator")) 11.24 11.25 // Testing format methods 11.26 print(inspect(b.toFixed(2), "global double toFixed()")) 11.27 @@ -53,11 +58,14 @@ 11.28 print(inspect(trees[1], "member object")) 11.29 trees[1] = undefined; 11.30 print(inspect(trees[1], "member undefined")) 11.31 -print(inspect(1 in trees ? b : a, "conditional on array member")) 11.32 +var b6=b; 11.33 +print(inspect(1 in trees ? b6 : a, "conditional on array member")) 11.34 delete trees[2] 11.35 -print(inspect(2 in trees ? b : a, "conditional on array member")) 11.36 +var b7=b; 11.37 +print(inspect(2 in trees ? b7 : a, "conditional on array member")) 11.38 print(inspect(3 in trees ? trees[2]="bay" : a, "conditional on array member")) 11.39 -print(inspect("oak" in trees ? b : a, "conditional on array member")) 11.40 +var b8=b; 11.41 +print(inspect("oak" in trees ? b8 : a, "conditional on array member")) 11.42 11.43 // Testing nested functions and return value 11.44 function f1() {