aboutsummaryrefslogtreecommitdiff
path: root/src/jdk
diff options
context:
space:
mode:
authorattila <none@none>2014-09-08 18:40:58 +0200
committerattila <none@none>2014-09-08 18:40:58 +0200
commitb7c0f4b680471e378cbc147160f5177b73497246 (patch)
treeb9cef6239cf6401c1d53e6ae1e70dacbd0055dc9 /src/jdk
parente9ef9cef6e67cc4e03752d7e7d94e070e01d17bd (diff)
downloadjdk8u_nashorn-b7c0f4b680471e378cbc147160f5177b73497246.tar.gz
8057148: Skip nested functions on reparse
Reviewed-by: hannesw, lagergren
Diffstat (limited to 'src/jdk')
-rw-r--r--src/jdk/nashorn/internal/codegen/AssignSymbols.java110
-rw-r--r--src/jdk/nashorn/internal/ir/Block.java8
-rw-r--r--src/jdk/nashorn/internal/ir/FunctionNode.java96
-rw-r--r--src/jdk/nashorn/internal/ir/LexicalContext.java3
-rw-r--r--src/jdk/nashorn/internal/parser/Parser.java172
-rw-r--r--src/jdk/nashorn/internal/parser/TokenStream.java4
-rw-r--r--src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java77
-rw-r--r--src/jdk/nashorn/internal/runtime/ScriptFunctionData.java2
-rw-r--r--src/jdk/nashorn/internal/runtime/Timing.java2
-rw-r--r--src/jdk/nashorn/tools/Shell.java2
10 files changed, 369 insertions, 107 deletions
diff --git a/src/jdk/nashorn/internal/codegen/AssignSymbols.java b/src/jdk/nashorn/internal/codegen/AssignSymbols.java
index 697675a3..fa2b8a19 100644
--- a/src/jdk/nashorn/internal/codegen/AssignSymbols.java
+++ b/src/jdk/nashorn/internal/codegen/AssignSymbols.java
@@ -194,12 +194,12 @@ final class AssignSymbols extends NodeVisitor<LexicalContext> implements Loggabl
*/
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 @@ final class AssignSymbols extends NodeVisitor<LexicalContext> implements Loggabl
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 @@ final class AssignSymbols extends NodeVisitor<LexicalContext> implements Loggabl
@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 @@ final class AssignSymbols extends NodeVisitor<LexicalContext> implements Loggabl
}
}
+ // 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 @@ final class AssignSymbols extends NodeVisitor<LexicalContext> implements Loggabl
/**
* 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 @@ final class AssignSymbols extends NodeVisitor<LexicalContext> implements Loggabl
@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 @@ final class AssignSymbols extends NodeVisitor<LexicalContext> implements Loggabl
@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 @@ final class AssignSymbols extends NodeVisitor<LexicalContext> implements Loggabl
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 @@ final class AssignSymbols extends NodeVisitor<LexicalContext> implements Loggabl
return functionNode;
}
- assert functionNode.getId() == 1;
return functionNode.setBody(lc, functionNode.getBody().setFlag(lc, Block.IS_GLOBAL_SCOPE));
}
diff --git a/src/jdk/nashorn/internal/ir/Block.java b/src/jdk/nashorn/internal/ir/Block.java
index c1576265..86a84ca6 100644
--- a/src/jdk/nashorn/internal/ir/Block.java
+++ b/src/jdk/nashorn/internal/ir/Block.java
@@ -277,6 +277,14 @@ public class Block extends Node implements BreakableNode, Terminal, Flags<Block>
}
/**
+ * 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 --git a/src/jdk/nashorn/internal/ir/FunctionNode.java b/src/jdk/nashorn/internal/ir/FunctionNode.java
index 4dd1bc3b..4ddf8601 100644
--- a/src/jdk/nashorn/internal/ir/FunctionNode.java
+++ b/src/jdk/nashorn/internal/ir/FunctionNode.java
@@ -48,6 +48,7 @@ import jdk.nashorn.internal.codegen.types.Type;
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 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
/** 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 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
/** 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 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
/** 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 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
* 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 final class FunctionNode extends LexicalContextExpression implements Flag
*/
public FunctionNode(
final Source source,
- final int id,
final int lineNumber,
final long token,
final int finish,
@@ -316,7 +319,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
super(token, finish);
this.source = source;
- this.id = id;
this.lineNumber = lineNumber;
this.ident = ident;
this.name = name;
@@ -331,11 +333,13 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
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 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
final Class<?> rootClass) {
super(functionNode);
+ this.endParserState = endParserState;
this.lineNumber = functionNode.lineNumber;
this.flags = flags;
this.name = name;
@@ -361,7 +366,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
// 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 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
}
/**
- * 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 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
new FunctionNode(
this,
lastToken,
+ endParserState,
flags,
name,
returnType,
@@ -606,6 +611,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
new FunctionNode(
this,
lastToken,
+ endParserState,
flags,
name,
returnType,
@@ -644,15 +650,24 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
}
/**
- * 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 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
new FunctionNode(
this,
lastToken,
+ endParserState,
flags |
(body.needsScope() ?
FunctionNode.HAS_SCOPE_BLOCK :
@@ -839,6 +855,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
new FunctionNode(
this,
lastToken,
+ endParserState,
flags,
name,
returnType,
@@ -899,6 +916,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
new FunctionNode(
this,
lastToken,
+ endParserState,
flags,
name,
returnType,
@@ -911,6 +929,41 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
}
/**
+ * 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 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
new FunctionNode(
this,
lastToken,
+ endParserState,
flags,
name,
returnType,
@@ -999,6 +1053,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
new FunctionNode(
this,
lastToken,
+ endParserState,
flags,
name,
returnType,
@@ -1077,6 +1132,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
new FunctionNode(
this,
lastToken,
+ endParserState,
flags,
name,
type,
@@ -1123,6 +1179,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
new FunctionNode(
this,
lastToken,
+ endParserState,
flags,
name,
returnType,
@@ -1178,6 +1235,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
new FunctionNode(
this,
lastToken,
+ endParserState,
flags,
name,
returnType,
diff --git a/src/jdk/nashorn/internal/ir/LexicalContext.java b/src/jdk/nashorn/internal/ir/LexicalContext.java
index ef56ae11..8048ea31 100644
--- a/src/jdk/nashorn/internal/ir/LexicalContext.java
+++ b/src/jdk/nashorn/internal/ir/LexicalContext.java
@@ -351,8 +351,7 @@ public class LexicalContext {
}
/**
- * 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 --git a/src/jdk/nashorn/internal/parser/Parser.java b/src/jdk/nashorn/internal/parser/Parser.java
index 8b4e53cb..e4121d22 100644
--- a/src/jdk/nashorn/internal/parser/Parser.java
+++ b/src/jdk/nashorn/internal/parser/Parser.java
@@ -148,7 +148,7 @@ public class Parser extends AbstractParser implements Loggable {
/** 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 @@ public class Parser extends AbstractParser implements Loggable {
* @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 @@ public class Parser extends AbstractParser implements Loggable {
* @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 @@ public class Parser extends AbstractParser implements Loggable {
}
/**
+ * 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 @@ loop:
final FunctionNode functionNode =
new FunctionNode(
source,
- nextFunctionId++,
functionLine,
token,
Token.descPosition(token),
@@ -2828,10 +2835,14 @@ loop:
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 @@ loop:
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);
+ 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;
+ }
- // 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 @@ loop:
} 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 --git a/src/jdk/nashorn/internal/parser/TokenStream.java b/src/jdk/nashorn/internal/parser/TokenStream.java
index ee81ce39..f7df999f 100644
--- a/src/jdk/nashorn/internal/parser/TokenStream.java
+++ b/src/jdk/nashorn/internal/parser/TokenStream.java
@@ -209,4 +209,8 @@ public class TokenStream {
in = count;
buffer = newBuffer;
}
+
+ void reset() {
+ in = out = count = base = 0;
+ }
}
diff --git a/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java b/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java
index 12f4bac8..95bba475 100644
--- a/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java
+++ b/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java
@@ -84,6 +84,12 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
/** 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 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
/** 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 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
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 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
}
/**
+ * 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 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
@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 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
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 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
if (functionNode.isVarArg()) {
flags |= IS_VARIABLE_ARITY;
}
- if (functionNode.inDynamicContext()) {
- flags |= IN_DYNAMIC_CONTEXT;
- }
return flags;
}
@@ -337,7 +356,6 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
}
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 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
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);
+ }
+
+ private boolean getFunctionFlag(final int flag) {
+ return (functionFlags & flag) != 0;
+ }
- 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 isProgram() {
+ return getFunctionFlag(FunctionNode.IS_PROGRAM);
}
TypeMap typeMap(final MethodType fnCallSiteType) {
@@ -546,7 +573,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
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 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
@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 --git a/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java b/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java
index 3cd292a7..d92b12d9 100644
--- a/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java
+++ b/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java
@@ -90,8 +90,6 @@ public abstract class ScriptFunctionData implements Serializable {
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 --git a/src/jdk/nashorn/internal/runtime/Timing.java b/src/jdk/nashorn/internal/runtime/Timing.java
index ae61345a..70eaa353 100644
--- a/src/jdk/nashorn/internal/runtime/Timing.java
+++ b/src/jdk/nashorn/internal/runtime/Timing.java
@@ -189,7 +189,7 @@ public final class Timing implements Loggable {
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;
diff --git a/src/jdk/nashorn/tools/Shell.java b/src/jdk/nashorn/tools/Shell.java
index 2c8f7951..58ab97c1 100644
--- a/src/jdk/nashorn/tools/Shell.java
+++ b/src/jdk/nashorn/tools/Shell.java
@@ -246,7 +246,7 @@ public class Shell {
// 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;