# HG changeset patch # User attila # Date 1410194458 -7200 # Node ID f5be4bdd0f6edc0f39d636f5fc6f154f37e2a993 # Parent 45f9decf4fb56e5f913649de1983288212eda05b 8057148: Skip nested functions on reparse Reviewed-by: hannesw, lagergren diff -r 45f9decf4fb5 -r f5be4bdd0f6e src/jdk/nashorn/internal/codegen/AssignSymbols.java --- a/src/jdk/nashorn/internal/codegen/AssignSymbols.java Thu Sep 04 18:57:14 2014 +0200 +++ b/src/jdk/nashorn/internal/codegen/AssignSymbols.java Mon Sep 08 18:40:58 2014 +0200 @@ -194,12 +194,12 @@ */ private void acceptDeclarations(final FunctionNode functionNode, final Block body) { // This visitor will assign symbol to all declared variables, except "var" declarations in for loop initializers. - // body.accept(new NodeVisitor(new LexicalContext()) { @Override - public boolean enterFunctionNode(final FunctionNode nestedFn) { - // Don't descend into nested functions - return false; + protected boolean enterDefault(final Node node) { + // Don't bother visiting expressions; var is a statement, it can't be inside an expression. + // This will also prevent visiting nested functions (as FunctionNode is an expression). + return !(node instanceof Expression); } @Override @@ -443,12 +443,27 @@ if (lc.isFunctionBody()) { block.clearSymbols(); + final FunctionNode fn = lc.getCurrentFunction(); + if (isUnparsedFunction(fn)) { + // It's a skipped nested function. Just mark the symbols being used by it as being in use. + for(final String name: compiler.getScriptFunctionData(fn.getId()).getExternalSymbolNames()) { + nameIsUsed(name, null); + } + // Don't bother descending into it, it must be empty anyway. + assert block.getStatements().isEmpty(); + return false; + } + enterFunctionBody(); } return true; } + private boolean isUnparsedFunction(final FunctionNode fn) { + return compiler.isOnDemandCompilation() && fn != lc.getOutermostFunction(); + } + @Override public boolean enterCatchNode(final CatchNode catchNode) { final IdentNode exception = catchNode.getException(); @@ -492,18 +507,13 @@ @Override public boolean enterFunctionNode(final FunctionNode functionNode) { - // TODO: once we have information on symbols used by nested functions, we can stop descending into nested - // functions with on-demand compilation, e.g. add - // if(!thisProperties.isEmpty() && env.isOnDemandCompilation()) { - // return false; - // } start(functionNode, false); thisProperties.push(new HashSet()); - //an outermost function in our lexical context that is not a program - //is possible - it is a function being compiled lazily if (functionNode.isDeclared()) { + // Can't use lc.getCurrentBlock() as we can have an outermost function in our lexical context that + // is not a program - it is a function being compiled on-demand. final Iterator blocks = lc.getBlocks(); if (blocks.hasNext()) { final IdentNode ident = functionNode.getIdent(); @@ -511,6 +521,11 @@ } } + // Every function has a body, even the ones skipped on reparse (they have an empty one). We're + // asserting this as even for those, enterBlock() must be invoked to correctly process symbols that + // are used in them. + assert functionNode.getBody() != null; + return true; } @@ -533,7 +548,7 @@ /** * This has to run before fix assignment types, store any type specializations for - * paramters, then turn then to objects for the generic version of this method + * parameters, then turn them into objects for the generic version of this method. * * @param functionNode functionNode */ @@ -733,14 +748,20 @@ @Override public Node leaveBlock(final Block block) { - // It's not necessary to guard the marking of symbols as locals with this "if"condition for correctness, it's - // just an optimization -- runtime type calculation is not used when the compilation is not an on-demand - // optimistic compilation, so we can skip locals marking then. + // It's not necessary to guard the marking of symbols as locals with this "if" condition for + // correctness, it's just an optimization -- runtime type calculation is not used when the compilation + // is not an on-demand optimistic compilation, so we can skip locals marking then. if (compiler.useOptimisticTypes() && compiler.isOnDemandCompilation()) { - for (final Symbol symbol: block.getSymbols()) { - if (!symbol.isScope()) { - assert symbol.isVar() || symbol.isParam(); - compiler.declareLocalSymbol(symbol.getName()); + // OTOH, we must not declare symbols from nested functions to be locals. As we're doing on-demand + // compilation, and we're skipping parsing the function bodies for nested functions, this + // basically only means their parameters. It'd be enough to mistakenly declare to be a local a + // symbol in the outer function named the same as one of the parameters, though. + if (lc.getFunction(block) == lc.getOutermostFunction()) { + for (final Symbol symbol: block.getSymbols()) { + if (!symbol.isScope()) { + assert symbol.isVar() || symbol.isParam(); + compiler.declareLocalSymbol(symbol.getName()); + } } } } @@ -811,24 +832,45 @@ @Override public Node leaveFunctionNode(final FunctionNode functionNode) { - - return markProgramBlock( + final FunctionNode finalizedFunction; + if (isUnparsedFunction(functionNode)) { + finalizedFunction = functionNode; + } else { + finalizedFunction = + markProgramBlock( removeUnusedSlots( createSyntheticInitializers( finalizeParameters( lc.applyTopFlags(functionNode)))) - .setThisProperties(lc, thisProperties.pop().size()) - .setState(lc, CompilationState.SYMBOLS_ASSIGNED)); + .setThisProperties(lc, thisProperties.pop().size())); + } + return finalizedFunction.setState(lc, CompilationState.SYMBOLS_ASSIGNED); } @Override public Node leaveIdentNode(final IdentNode identNode) { - final String name = identNode.getName(); - if (identNode.isPropertyName()) { return identNode; } + final Symbol symbol = nameIsUsed(identNode.getName(), identNode); + + if (!identNode.isInitializedHere()) { + symbol.increaseUseCount(); + } + + IdentNode newIdentNode = identNode.setSymbol(symbol); + + // If a block-scoped var is used before its declaration mark it as dead. + // We can only statically detect this for local vars, cross-function symbols require runtime checks. + if (symbol.isBlockScoped() && !symbol.hasBeenDeclared() && !identNode.isDeclaredHere() && isLocal(lc.getCurrentFunction(), symbol)) { + newIdentNode = newIdentNode.markDead(); + } + + return end(newIdentNode); + } + + private Symbol nameIsUsed(final String name, final IdentNode origin) { final Block block = lc.getCurrentBlock(); Symbol symbol = findSymbol(block, name); @@ -847,24 +889,11 @@ maybeForceScope(symbol); } else { log.info("No symbol exists. Declare as global: ", name); - symbol = defineSymbol(block, name, identNode, IS_GLOBAL | IS_SCOPE); + symbol = defineSymbol(block, name, origin, IS_GLOBAL | IS_SCOPE); } functionUsesSymbol(symbol); - - if (!identNode.isInitializedHere()) { - symbol.increaseUseCount(); - } - - IdentNode newIdentNode = identNode.setSymbol(symbol); - - // If a block-scoped var is used before its declaration mark it as dead. - // We can only statically detect this for local vars, cross-function symbols require runtime checks. - if (symbol.isBlockScoped() && !symbol.hasBeenDeclared() && !identNode.isDeclaredHere() && isLocal(lc.getCurrentFunction(), symbol)) { - newIdentNode = newIdentNode.markDead(); - } - - return end(newIdentNode); + return symbol; } @Override @@ -912,7 +941,6 @@ return functionNode; } - assert functionNode.getId() == 1; return functionNode.setBody(lc, functionNode.getBody().setFlag(lc, Block.IS_GLOBAL_SCOPE)); } diff -r 45f9decf4fb5 -r f5be4bdd0f6e src/jdk/nashorn/internal/ir/Block.java --- a/src/jdk/nashorn/internal/ir/Block.java Thu Sep 04 18:57:14 2014 +0200 +++ b/src/jdk/nashorn/internal/ir/Block.java Mon Sep 08 18:40:58 2014 +0200 @@ -277,6 +277,14 @@ } /** + * Returns the number of statements in the block. + * @return the number of statements in the block. + */ + public int getStatementCount() { + return statements.size(); + } + + /** * Returns the line number of the first statement in the block. * @return the line number of the first statement in the block, or -1 if the block has no statements. */ diff -r 45f9decf4fb5 -r f5be4bdd0f6e src/jdk/nashorn/internal/ir/FunctionNode.java --- a/src/jdk/nashorn/internal/ir/FunctionNode.java Thu Sep 04 18:57:14 2014 +0200 +++ b/src/jdk/nashorn/internal/ir/FunctionNode.java Mon Sep 08 18:40:58 2014 +0200 @@ -48,6 +48,7 @@ import jdk.nashorn.internal.ir.annotations.Ignore; import jdk.nashorn.internal.ir.annotations.Immutable; import jdk.nashorn.internal.ir.visitor.NodeVisitor; +import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData; import jdk.nashorn.internal.runtime.ScriptFunction; import jdk.nashorn.internal.runtime.Source; import jdk.nashorn.internal.runtime.UserAccessorProperty; @@ -110,8 +111,11 @@ /** Source of entity. */ private final Source source; - /** Unique ID used for recompilation among other things */ - private final int id; + /** + * Opaque object representing parser state at the end of the function. Used when reparsing outer functions + * to skip parsing inner functions. + */ + private final Object endParserState; /** External function identifier. */ @Ignore @@ -256,6 +260,14 @@ /** trace callsite values in this function? */ public static final int IS_TRACE_VALUES = 1 << 26; + /** + * Whether this function needs the callee {@link ScriptFunction} instance passed to its code as a + * parameter on invocation. Note that we aren't, in fact using this flag in function nodes. + * Rather, it is always calculated (see {@link #needsCallee()}). {@link RecompilableScriptFunctionData} + * will, however, cache the value of this flag. + */ + public static final int NEEDS_CALLEE = 1 << 27; + /** extension callsite flags mask */ public static final int EXTENSION_CALLSITE_FLAGS = IS_PRINT_PARSE | IS_PRINT_LOWER_PARSE | IS_PRINT_AST | IS_PRINT_LOWER_AST | @@ -271,16 +283,9 @@ /** Does this function potentially need "arguments"? Note that this is not a full test, as further negative check of REDEFINES_ARGS is needed. */ private static final int MAYBE_NEEDS_ARGUMENTS = USES_ARGUMENTS | HAS_EVAL; - /** 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. - * We also pessimistically need a parent scope if we have lazy children that have not yet been compiled */ + /** 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. */ private static final int NEEDS_PARENT_SCOPE = USES_ANCESTOR_SCOPE | HAS_DEEP_EVAL; - /** Used to signify "null", e.g. if someone asks for the parent of the program node */ - public static final int NO_FUNCTION_ID = 0; - - /** Where to start assigning global and unique function node ids */ - public static final int FIRST_FUNCTION_ID = NO_FUNCTION_ID + 1; - /** What is the return type of this function? */ private Type returnType = Type.UNKNOWN; @@ -288,11 +293,10 @@ * Constructor * * @param source the source - * @param id unique id * @param lineNumber line number * @param token token * @param finish finish - * @param firstToken first token of the funtion node (including the function declaration) + * @param firstToken first token of the function node (including the function declaration) * @param namespace the namespace * @param ident the identifier * @param name the name of the function @@ -302,7 +306,6 @@ */ public FunctionNode( final Source source, - final int id, final int lineNumber, final long token, final int finish, @@ -316,7 +319,6 @@ super(token, finish); this.source = source; - this.id = id; this.lineNumber = lineNumber; this.ident = ident; this.name = name; @@ -331,11 +333,13 @@ this.body = null; this.thisProperties = 0; this.rootClass = null; + this.endParserState = null; } private FunctionNode( final FunctionNode functionNode, final long lastToken, + Object endParserState, final int flags, final String name, final Type returnType, @@ -347,6 +351,7 @@ final Class rootClass) { super(functionNode); + this.endParserState = endParserState; this.lineNumber = functionNode.lineNumber; this.flags = flags; this.name = name; @@ -361,7 +366,6 @@ // the fields below never change - they are final and assigned in constructor this.source = functionNode.source; - this.id = functionNode.id; this.ident = functionNode.ident; this.namespace = functionNode.namespace; this.kind = functionNode.kind; @@ -429,11 +433,11 @@ } /** - * Get the unique ID for this function + * Get the unique ID for this function within the script file. * @return the id */ public int getId() { - return id; + return position(); } /** @@ -535,6 +539,7 @@ new FunctionNode( this, lastToken, + endParserState, flags, name, returnType, @@ -606,6 +611,7 @@ new FunctionNode( this, lastToken, + endParserState, flags, name, returnType, @@ -644,15 +650,24 @@ } /** - * Check if the {@code eval} keyword is used in this function + * Check if this function has a call expression for the identifier "eval" (that is, {@code eval(...)}). * - * @return true if {@code eval} is used + * @return true if {@code eval} is called. */ public boolean hasEval() { return getFlag(HAS_EVAL); } /** + * Returns true if a function nested (directly or transitively) within this function {@link #hasEval()}. + * + * @return true if a nested function calls {@code eval}. + */ + public boolean hasNestedEval() { + return getFlag(HAS_NESTED_EVAL); + } + + /** * Get the first token for this function * @return the first token */ @@ -741,6 +756,7 @@ new FunctionNode( this, lastToken, + endParserState, flags | (body.needsScope() ? FunctionNode.HAS_SCOPE_BLOCK : @@ -839,6 +855,7 @@ new FunctionNode( this, lastToken, + endParserState, flags, name, returnType, @@ -899,6 +916,7 @@ new FunctionNode( this, lastToken, + endParserState, flags, name, returnType, @@ -911,6 +929,41 @@ } /** + * Returns the end parser state for this function. + * @return the end parser state for this function. + */ + public Object getEndParserState() { + return endParserState; + } + + /** + * Set the end parser state for this function. + * @param lc lexical context + * @param endParserState the parser state to set + * @return function node or a new one if state was changed + */ + public FunctionNode setEndParserState(final LexicalContext lc, final Object endParserState) { + if (this.endParserState == endParserState) { + return this; + } + return Node.replaceInLexicalContext( + lc, + this, + new FunctionNode( + this, + lastToken, + endParserState, + flags, + name, + returnType, + compileUnit, + compilationState, + body, + parameters, + thisProperties, rootClass)); + } + + /** * Get the name of this function * @return the name */ @@ -934,6 +987,7 @@ new FunctionNode( this, lastToken, + endParserState, flags, name, returnType, @@ -999,6 +1053,7 @@ new FunctionNode( this, lastToken, + endParserState, flags, name, returnType, @@ -1077,6 +1132,7 @@ new FunctionNode( this, lastToken, + endParserState, flags, name, type, @@ -1123,6 +1179,7 @@ new FunctionNode( this, lastToken, + endParserState, flags, name, returnType, @@ -1178,6 +1235,7 @@ new FunctionNode( this, lastToken, + endParserState, flags, name, returnType, diff -r 45f9decf4fb5 -r f5be4bdd0f6e src/jdk/nashorn/internal/ir/LexicalContext.java --- a/src/jdk/nashorn/internal/ir/LexicalContext.java Thu Sep 04 18:57:14 2014 +0200 +++ b/src/jdk/nashorn/internal/ir/LexicalContext.java Mon Sep 08 18:40:58 2014 +0200 @@ -351,8 +351,7 @@ } /** - * Get the function for this block. If the block is itself a function - * this returns identity + * Get the function for this block. * @param block block for which to get function * @return function for block */ diff -r 45f9decf4fb5 -r f5be4bdd0f6e src/jdk/nashorn/internal/parser/Parser.java --- a/src/jdk/nashorn/internal/parser/Parser.java Thu Sep 04 18:57:14 2014 +0200 +++ b/src/jdk/nashorn/internal/parser/Parser.java Mon Sep 08 18:40:58 2014 +0200 @@ -148,7 +148,7 @@ /** to receive line information from Lexer when scanning multine literals. */ protected final Lexer.LineInfoReceiver lineInfoReceiver; - private int nextFunctionId; + private RecompilableScriptFunctionData reparsedFunction; /** * Constructor @@ -171,7 +171,7 @@ * @param log debug logger if one is needed */ public Parser(final ScriptEnvironment env, final Source source, final ErrorManager errors, final boolean strict, final DebugLogger log) { - this(env, source, errors, strict, FunctionNode.FIRST_FUNCTION_ID, 0, log); + this(env, source, errors, strict, 0, log); } /** @@ -181,15 +181,13 @@ * @param source source to parse * @param errors error manager * @param strict parser created with strict mode enabled. - * @param nextFunctionId starting value for assigning new unique ids to function nodes * @param lineOffset line offset to start counting lines from * @param log debug logger if one is needed */ - public Parser(final ScriptEnvironment env, final Source source, final ErrorManager errors, final boolean strict, final int nextFunctionId, final int lineOffset, final DebugLogger log) { + public Parser(final ScriptEnvironment env, final Source source, final ErrorManager errors, final boolean strict, final int lineOffset, final DebugLogger log) { super(source, errors, strict, lineOffset); this.env = env; this.namespace = new Namespace(env.getNamespace()); - this.nextFunctionId = nextFunctionId; this.scripting = env._scripting; if (this.scripting) { this.lineInfoReceiver = new Lexer.LineInfoReceiver() { @@ -228,6 +226,16 @@ } /** + * Sets the {@link RecompilableScriptFunctionData} representing the function being reparsed (when this + * parser instance is used to reparse a previously parsed function, as part of its on-demand compilation). + * This will trigger various special behaviors, such as skipping nested function bodies. + * @param reparsedFunction the function being reparsed. + */ + public void setReparsedFunction(final RecompilableScriptFunctionData reparsedFunction) { + this.reparsedFunction = reparsedFunction; + } + + /** * Execute parse and return the resulting function node. * Errors will be thrown and the error manager will contain information * if parsing should fail @@ -472,7 +480,6 @@ final FunctionNode functionNode = new FunctionNode( source, - nextFunctionId++, functionLine, token, Token.descPosition(token), @@ -2828,10 +2835,14 @@ FunctionNode functionNode = null; long lastToken = 0L; + final boolean parseBody; + Object endParserState = null; try { // Create a new function block. functionNode = newFunctionNode(firstToken, ident, parameters, kind, functionLine); - + assert functionNode != null; + final int functionId = functionNode.getId(); + parseBody = reparsedFunction == null || functionId <= reparsedFunction.getFunctionNodeId(); // Nashorn extension: expression closures if (!env._no_syntax_extensions && type != LBRACE) { /* @@ -2847,34 +2858,152 @@ assert lc.getCurrentBlock() == lc.getFunctionBody(functionNode); // EOL uses length field to store the line number final int lastFinish = Token.descPosition(lastToken) + (Token.descType(lastToken) == EOL ? 0 : Token.descLength(lastToken)); - final ReturnNode returnNode = new ReturnNode(functionNode.getLineNumber(), expr.getToken(), lastFinish, expr); - appendStatement(returnNode); + // Only create the return node if we aren't skipping nested functions. Note that we aren't + // skipping parsing of these extended functions; they're considered to be small anyway. Also, + // they don't end with a single well known token, so it'd be very hard to get correctly (see + // the note below for reasoning on skipping happening before instead of after RBRACE for + // details). + if (parseBody) { + final ReturnNode returnNode = new ReturnNode(functionNode.getLineNumber(), expr.getToken(), lastFinish, expr); + appendStatement(returnNode); + } functionNode.setFinish(lastFinish); - } else { expect(LBRACE); - - // Gather the function elements. - final List prevFunctionDecls = functionDeclarations; - functionDeclarations = new ArrayList<>(); - try { - sourceElements(false); - addFunctionDeclarations(functionNode); - } finally { - functionDeclarations = prevFunctionDecls; + final int lastLexed = stream.last(); + if (parseBody || !skipFunctionBody(functionNode)) { + // Gather the function elements. + final List prevFunctionDecls = functionDeclarations; + functionDeclarations = new ArrayList<>(); + try { + sourceElements(false); + addFunctionDeclarations(functionNode); + } finally { + functionDeclarations = prevFunctionDecls; + } + + lastToken = token; + // Avoiding storing parser state if the function body was small (that is, the next token + // to be read from the token stream is before the last token lexed before we entered + // function body). That'll force the function to be reparsed instead of skipped. Skipping + // involves throwing away and recreating the lexer and the token stream, so for small + // functions it is likely more economical to not bother with skipping (both in terms of + // storing the state, and in terms of throwing away lexer and token stream). + if (parseBody && lastLexed < stream.first()) { + // Since the lexer can read ahead and lexify some number of tokens in advance and have + // them buffered in the TokenStream, we need to produce a lexer state as it was just + // before it lexified RBRACE, and not whatever is its current (quite possibly well read + // ahead) state. + endParserState = new ParserState(Token.descPosition(token), line, linePosition); + + // NOTE: you might wonder why do we capture/restore parser state before RBRACE instead of + // after RBRACE; after all, we could skip the below "expect(RBRACE);" if we captured the + // state after it. The reason is that RBRACE is a well-known token that we can expect and + // will never involve us getting into a weird lexer state, and as such is a great reparse + // point. Typical example of a weird lexer state after RBRACE would be: + // function this_is_skipped() { ... } "use strict"; + // because lexer is doing weird off-by-one maneuvers around string literal quotes. Instead + // of compensating for the possibility of a string literal (or similar) after RBRACE, + // we'll rather just restart parsing from this well-known, friendly token instead. + } } - - lastToken = token; expect(RBRACE); functionNode.setFinish(finish); } } finally { functionNode = restoreFunctionNode(functionNode, lastToken); } + + // NOTE: we can only do alterations to the function node after restoreFunctionNode. + + if (parseBody) { + functionNode = functionNode.setEndParserState(lc, endParserState); + } else if (functionNode.getBody().getStatementCount() > 0){ + // This is to ensure the body is empty when !parseBody but we couldn't skip parsing it (see + // skipFunctionBody() for possible reasons). While it is not strictly necessary for correctness to + // enforce empty bodies in nested functions that were supposed to be skipped, we do assert it as + // an invariant in few places in the compiler pipeline, so for consistency's sake we'll throw away + // nested bodies early if we were supposed to skip 'em. + functionNode = functionNode.setBody(null, functionNode.getBody().setStatements(null, + Collections.emptyList())); + } + + if (reparsedFunction != null) { + // We restore the flags stored in the function's ScriptFunctionData that we got when we first + // eagerly parsed the code. We're doing it because some flags would be set based on the + // content of the function, or even content of its nested functions, most of which are normally + // skipped during an on-demand compilation. + final RecompilableScriptFunctionData data = reparsedFunction.getScriptFunctionData(functionNode.getId()); + if (data != null) { + // Data can be null if when we originally parsed the file, we removed the function declaration + // as it was dead code. + functionNode = functionNode.setFlags(lc, data.getFunctionFlags()); + // This compensates for missing markEval() in case the function contains an inner function + // that contains eval(), that now we didn't discover since we skipped the inner function. + if (functionNode.hasNestedEval()) { + assert functionNode.hasScopeBlock(); + functionNode = functionNode.setBody(lc, functionNode.getBody().setNeedsScope(null)); + } + } + } printAST(functionNode); return functionNode; } + private boolean skipFunctionBody(final FunctionNode functionNode) { + if (reparsedFunction == null) { + // Not reparsing, so don't skip any function body. + return false; + } + // Skip to the RBRACE of this function, and continue parsing from there. + final RecompilableScriptFunctionData data = reparsedFunction.getScriptFunctionData(functionNode.getId()); + if (data == null) { + // Nested function is not known to the reparsed function. This can happen if the FunctionNode was + // in dead code that was removed. Both FoldConstants and Lower prune dead code. In that case, the + // FunctionNode was dropped before a RecompilableScriptFunctionData could've been created for it. + return false; + } + final ParserState parserState = (ParserState)data.getEndParserState(); + if (parserState == null) { + // The function has no stored parser state; it was deemed too small to be skipped. + return false; + } + + stream.reset(); + lexer = parserState.createLexer(source, lexer, stream, scripting && !env._no_syntax_extensions); + line = parserState.line; + linePosition = parserState.linePosition; + // Doesn't really matter, but it's safe to treat it as if there were a semicolon before + // the RBRACE. + type = SEMICOLON; + k = -1; + next(); + + return true; + } + + /** + * Encapsulates part of the state of the parser, enough to reconstruct the state of both parser and lexer + * for resuming parsing after skipping a function body. + */ + private static class ParserState { + private final int position; + private final int line; + private final int linePosition; + + ParserState(final int position, final int line, final int linePosition) { + this.position = position; + this.line = line; + this.linePosition = linePosition; + } + + Lexer createLexer(final Source source, final Lexer lexer, final TokenStream stream, final boolean scripting) { + final Lexer newLexer = new Lexer(source, position, lexer.limit - position, stream, scripting); + newLexer.restoreState(new Lexer.State(position, Integer.MAX_VALUE, line, -1, linePosition, SEMICOLON)); + return newLexer; + } + } + private void printAST(final FunctionNode functionNode) { if (functionNode.getFlag(FunctionNode.IS_PRINT_AST)) { env.getErr().println(new ASTWriter(functionNode)); @@ -3247,6 +3376,9 @@ } else { lc.setFlag(fn, FunctionNode.HAS_NESTED_EVAL); } + // NOTE: it is crucial to mark the body of the outer function as needing scope even when we skip + // parsing a nested function. functionBody() contains code to compensate for the lack of invoking + // this method when the parser skips a nested function. lc.setBlockNeedsScope(lc.getFunctionBody(fn)); } } diff -r 45f9decf4fb5 -r f5be4bdd0f6e src/jdk/nashorn/internal/parser/TokenStream.java --- a/src/jdk/nashorn/internal/parser/TokenStream.java Thu Sep 04 18:57:14 2014 +0200 +++ b/src/jdk/nashorn/internal/parser/TokenStream.java Mon Sep 08 18:40:58 2014 +0200 @@ -209,4 +209,8 @@ in = count; buffer = newBuffer; } + + void reset() { + in = out = count = base = 0; + } } diff -r 45f9decf4fb5 -r f5be4bdd0f6e src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java --- a/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Thu Sep 04 18:57:14 2014 +0200 +++ b/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Mon Sep 08 18:40:58 2014 +0200 @@ -84,6 +84,12 @@ /** Allocator map from makeMap() */ private final PropertyMap allocatorMap; + /** + * Opaque object representing parser state at the end of the function. Used when reparsing outer function + * to help with skipping parsing inner functions. + */ + private final Object endParserState; + /** Code installer used for all further recompilation/specialization of this ScriptFunction */ private transient CodeInstaller installer; @@ -98,9 +104,8 @@ /** Id to parent function if one exists */ private RecompilableScriptFunctionData parent; - private final boolean isDeclared; - private final boolean isAnonymous; - private final boolean needsCallee; + /** Copy of the {@link FunctionNode} flags. */ + private final int functionFlags; private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); @@ -136,15 +141,14 @@ super(functionName(functionNode), Math.min(functionNode.getParameters().size(), MAX_ARITY), - getFlags(functionNode)); + getDataFlags(functionNode)); this.functionName = functionNode.getName(); this.lineNumber = functionNode.getLineNumber(); - this.isDeclared = functionNode.isDeclared(); - this.needsCallee = functionNode.needsCallee(); - this.isAnonymous = functionNode.isAnonymous(); + this.functionFlags = functionNode.getFlags() | (functionNode.needsCallee() ? FunctionNode.NEEDS_CALLEE : 0); this.functionNodeId = functionNode.getId(); this.source = functionNode.getSource(); + this.endParserState = functionNode.getEndParserState(); this.token = tokenFor(functionNode); this.installer = installer; this.allocatorClassName = allocatorClassName; @@ -202,6 +206,24 @@ } /** + * Returns the names of all external symbols this function uses. + * @return the names of all external symbols this function uses. + */ + public Set getExternalSymbolNames() { + return externalScopeDepths == null ? Collections.emptySet() : + Collections.unmodifiableSet(externalScopeDepths.keySet()); + } + + /** + * Returns the opaque object representing the parser state at the end of this function's body, used to + * skip parsing this function when reparsing its containing outer function. + * @return the object representing the end parser state + */ + public Object getEndParserState() { + return endParserState; + } + + /** * Get the parent of this RecompilableScriptFunctionData. If we are * a nested function, we have a parent. Note that "null" return value * can also mean that we have a parent but it is unknown, so this can @@ -269,7 +291,7 @@ @Override public boolean inDynamicContext() { - return (flags & IN_DYNAMIC_CONTEXT) != 0; + return getFunctionFlag(FunctionNode.IN_DYNAMIC_CONTEXT); } private static String functionName(final FunctionNode fn) { @@ -293,7 +315,7 @@ return Token.toDesc(TokenType.FUNCTION, position, length); } - private static int getFlags(final FunctionNode functionNode) { + private static int getDataFlags(final FunctionNode functionNode) { int flags = IS_CONSTRUCTOR; if (functionNode.isStrict()) { flags |= IS_STRICT; @@ -307,9 +329,6 @@ if (functionNode.isVarArg()) { flags |= IS_VARIABLE_ARITY; } - if (functionNode.inDynamicContext()) { - flags |= IN_DYNAMIC_CONTEXT; - } return flags; } @@ -337,7 +356,6 @@ } FunctionNode reparse() { - final boolean isProgram = functionNodeId == FunctionNode.FIRST_FUNCTION_ID; // NOTE: If we aren't recompiling the top-level program, we decrease functionNodeId 'cause we'll have a synthetic program node final int descPosition = Token.descPosition(token); final Context context = Context.getContextTrusted(); @@ -346,18 +364,27 @@ source, new Context.ThrowErrorManager(), isStrict(), - functionNodeId - (isProgram ? 0 : 1), lineNumber - 1, 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 - if (isAnonymous) { + if (getFunctionFlag(FunctionNode.IS_ANONYMOUS)) { parser.setFunctionName(functionName); } + parser.setReparsedFunction(this); - final FunctionNode program = parser.parse(CompilerConstants.PROGRAM.symbolName(), descPosition, Token.descLength(token), true); - // Parser generates a program AST even if we're recompiling a single function, so when we are only recompiling a - // single function, extract it from the program. - return (isProgram ? program : extractFunctionFromScript(program)).setName(null, functionName); + final FunctionNode program = parser.parse(CompilerConstants.PROGRAM.symbolName(), descPosition, + Token.descLength(token), true); + // Parser generates a program AST even if we're recompiling a single function, so when we are only + // recompiling a single function, extract it from the program. + return (isProgram() ? program : extractFunctionFromScript(program)).setName(null, functionName); + } + + private boolean getFunctionFlag(final int flag) { + return (functionFlags & flag) != 0; + } + + private boolean isProgram() { + return getFunctionFlag(FunctionNode.IS_PROGRAM); } TypeMap typeMap(final MethodType fnCallSiteType) { @@ -546,7 +573,7 @@ assert fns.size() == 1 : "got back more than one method in recompilation"; final FunctionNode f = fns.iterator().next(); assert f.getId() == functionNodeId; - if (!isDeclared && f.isDeclared()) { + if (!getFunctionFlag(FunctionNode.IS_DECLARED) && f.isDeclared()) { return f.clearFlag(null, FunctionNode.IS_DECLARED); } return f; @@ -669,7 +696,15 @@ @Override public boolean needsCallee() { - return needsCallee; + return getFunctionFlag(FunctionNode.NEEDS_CALLEE); + } + + /** + * Returns the {@link FunctionNode} flags associated with this function data. + * @return the {@link FunctionNode} flags associated with this function data. + */ + public int getFunctionFlags() { + return functionFlags; } @Override diff -r 45f9decf4fb5 -r f5be4bdd0f6e src/jdk/nashorn/internal/runtime/ScriptFunctionData.java --- a/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java Thu Sep 04 18:57:14 2014 +0200 +++ b/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java Mon Sep 08 18:40:58 2014 +0200 @@ -90,8 +90,6 @@ public static final int USES_THIS = 1 << 4; /** Is this a variable arity function? */ public static final int IS_VARIABLE_ARITY = 1 << 5; - /** Is this declared in a dynamic context */ - public static final int IN_DYNAMIC_CONTEXT = 1 << 6; /** Flag for strict or built-in functions */ public static final int IS_STRICT_OR_BUILTIN = IS_STRICT | IS_BUILTIN; diff -r 45f9decf4fb5 -r f5be4bdd0f6e src/jdk/nashorn/internal/runtime/Timing.java --- a/src/jdk/nashorn/internal/runtime/Timing.java Thu Sep 04 18:57:14 2014 +0200 +++ b/src/jdk/nashorn/internal/runtime/Timing.java Mon Sep 08 18:40:58 2014 +0200 @@ -189,7 +189,7 @@ maxKeyLength++; final StringBuilder sb = new StringBuilder(); - sb.append("Accumulated complation phase Timings:\n\n"); + sb.append("Accumulated compilation phase timings:\n\n"); for (final Map.Entry entry : timings.entrySet()) { int len; diff -r 45f9decf4fb5 -r f5be4bdd0f6e src/jdk/nashorn/tools/Shell.java --- a/src/jdk/nashorn/tools/Shell.java Thu Sep 04 18:57:14 2014 +0200 +++ b/src/jdk/nashorn/tools/Shell.java Mon Sep 08 18:40:58 2014 +0200 @@ -246,7 +246,7 @@ // For each file on the command line. for (final String fileName : files) { - final FunctionNode functionNode = new Parser(env, sourceFor(fileName, new File(fileName)), errors, env._strict, FunctionNode.FIRST_FUNCTION_ID, 0, context.getLogger(Parser.class)).parse(); + final FunctionNode functionNode = new Parser(env, sourceFor(fileName, new File(fileName)), errors, env._strict, 0, context.getLogger(Parser.class)).parse(); if (errors.getNumberOfErrors() != 0) { return COMPILATION_ERROR; diff -r 45f9decf4fb5 -r f5be4bdd0f6e test/script/basic/optimistic_check_type.js --- a/test/script/basic/optimistic_check_type.js Thu Sep 04 18:57:14 2014 +0200 +++ b/test/script/basic/optimistic_check_type.js Mon Sep 08 18:40:58 2014 +0200 @@ -36,13 +36,18 @@ // Testing conditional operator print(inspect("" ? b : x.a, "ternary operator")) -print(inspect(x.b ? b : x.a, "ternary operator")) -print(inspect(c ? b : a, "ternary operator")) -print(inspect(!c ? b : a, "ternary operator")) -print(inspect(d ? b : x.c, "ternary operator")) +var b1 = b; +print(inspect(x.b ? b1 : x.a, "ternary operator")) +var b2 = b; +print(inspect(c ? b2 : a, "ternary operator")) +var b3 = b; +print(inspect(!c ? b3 : a, "ternary operator")) +var b4 = b; +print(inspect(d ? b4 : x.c, "ternary operator")) print(inspect(x.c ? a : c, "ternary operator")) print(inspect(c ? d : a, "ternary operator")) -print(inspect(c ? +a : b, "ternary operator")) +var b5 = b; +print(inspect(c ? +a : b5, "ternary operator")) // Testing format methods print(inspect(b.toFixed(2), "global double toFixed()")) @@ -53,11 +58,14 @@ print(inspect(trees[1], "member object")) trees[1] = undefined; print(inspect(trees[1], "member undefined")) -print(inspect(1 in trees ? b : a, "conditional on array member")) +var b6=b; +print(inspect(1 in trees ? b6 : a, "conditional on array member")) delete trees[2] -print(inspect(2 in trees ? b : a, "conditional on array member")) +var b7=b; +print(inspect(2 in trees ? b7 : a, "conditional on array member")) print(inspect(3 in trees ? trees[2]="bay" : a, "conditional on array member")) -print(inspect("oak" in trees ? b : a, "conditional on array member")) +var b8=b; +print(inspect("oak" in trees ? b8 : a, "conditional on array member")) // Testing nested functions and return value function f1() {