changeset 26503:3ed48a01100c

8057148: Skip nested functions on reparse Reviewed-by: hannesw, lagergren
author attila
date Mon, 08 Sep 2014 18:40:58 +0200
parents 71d3891037bb
children ed05e2f4c2db
files nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/Block.java nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/FunctionNode.java nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/LexicalContext.java nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Parser.java nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/TokenStream.java nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptFunctionData.java nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Timing.java nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/Shell.java nashorn/test/script/basic/optimistic_check_type.js
diffstat 11 files changed, 386 insertions(+), 116 deletions(-) [+]
line wrap: on
line diff
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java	Mon Sep 08 15:37:50 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/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<LexicalContext>(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<String>());
 
-        //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<Block> 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));
     }
 
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/Block.java	Mon Sep 08 15:37:50 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/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.
      */
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/FunctionNode.java	Mon Sep 08 15:37:50 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/FunctionNode.java	Mon Sep 08 18:40:58 2014 +0200
@@ -46,6 +46,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;
@@ -108,8 +109,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
@@ -254,6 +258,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 |
@@ -269,16 +281,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;
 
@@ -286,11 +291,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
@@ -300,7 +304,6 @@
      */
     public FunctionNode(
         final Source source,
-        final int id,
         final int lineNumber,
         final long token,
         final int finish,
@@ -314,7 +317,6 @@
         super(token, finish);
 
         this.source           = source;
-        this.id               = id;
         this.lineNumber       = lineNumber;
         this.ident            = ident;
         this.name             = name;
@@ -329,11 +331,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,
@@ -345,6 +349,7 @@
         final Class<?> rootClass) {
         super(functionNode);
 
+        this.endParserState    = endParserState;
         this.lineNumber       = functionNode.lineNumber;
         this.flags            = flags;
         this.name             = name;
@@ -359,7 +364,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;
@@ -427,11 +431,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();
     }
 
     /**
@@ -533,6 +537,7 @@
                 new FunctionNode(
                         this,
                         lastToken,
+                        endParserState,
                         flags,
                         name,
                         returnType,
@@ -604,6 +609,7 @@
                 new FunctionNode(
                         this,
                         lastToken,
+                        endParserState,
                         flags,
                         name,
                         returnType,
@@ -642,15 +648,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
      */
@@ -739,6 +754,7 @@
                 new FunctionNode(
                         this,
                         lastToken,
+                        endParserState,
                         flags |
                             (body.needsScope() ?
                                     FunctionNode.HAS_SCOPE_BLOCK :
@@ -837,6 +853,7 @@
                 new FunctionNode(
                         this,
                         lastToken,
+                        endParserState,
                         flags,
                         name,
                         returnType,
@@ -897,6 +914,7 @@
                 new FunctionNode(
                         this,
                         lastToken,
+                        endParserState,
                         flags,
                         name,
                         returnType,
@@ -909,6 +927,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
      */
@@ -932,6 +985,7 @@
                 new FunctionNode(
                         this,
                         lastToken,
+                        endParserState,
                         flags,
                         name,
                         returnType,
@@ -997,6 +1051,7 @@
                 new FunctionNode(
                         this,
                         lastToken,
+                        endParserState,
                         flags,
                         name,
                         returnType,
@@ -1075,6 +1130,7 @@
             new FunctionNode(
                 this,
                 lastToken,
+                endParserState,
                 flags,
                 name,
                 type,
@@ -1122,6 +1178,7 @@
                 new FunctionNode(
                         this,
                         lastToken,
+                        endParserState,
                         flags,
                         name,
                         returnType,
@@ -1177,6 +1234,7 @@
                 new FunctionNode(
                         this,
                         lastToken,
+                        endParserState,
                         flags,
                         name,
                         returnType,
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/LexicalContext.java	Mon Sep 08 15:37:50 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/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
      */
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Parser.java	Mon Sep 08 15:37:50 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/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<Statement> 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<Statement> 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.<Statement>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));
         }
     }
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/TokenStream.java	Mon Sep 08 15:37:50 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/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;
+    }
 }
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java	Mon Sep 08 15:37:50 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/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<ScriptEnvironment> 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<String> getExternalSymbolNames() {
+        return externalScopeDepths == null ? Collections.<String>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
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptFunctionData.java	Mon Sep 08 15:37:50 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/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;
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Timing.java	Mon Sep 08 15:37:50 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/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<String, Long> entry : timings.entrySet()) {
                 int len;
 
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/Shell.java	Mon Sep 08 15:37:50 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/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;
--- a/nashorn/test/script/basic/optimistic_check_type.js	Mon Sep 08 15:37:50 2014 +0400
+++ b/nashorn/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() {