--- 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() {