8057148: Skip nested functions on reparse

Reviewed-by: hannesw, lagergren
This commit is contained in:
Attila Szegedi 2014-09-08 18:40:58 +02:00
parent 5c841f8049
commit c6dd744ff0
11 changed files with 385 additions and 115 deletions

View File

@ -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));
}

View File

@ -276,6 +276,14 @@ public class Block extends Node implements BreakableNode, Terminal, Flags<Block>
return Collections.unmodifiableList(statements);
}
/**
* 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.

View File

@ -46,6 +46,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;
@ -108,8 +109,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
@ -254,6 +258,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 |
@ -269,16 +281,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;
@ -286,11 +291,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
@ -300,7 +304,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,
@ -314,7 +317,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;
@ -329,11 +331,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,
@ -345,6 +349,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;
@ -359,7 +364,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;
@ -427,11 +431,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();
}
/**
@ -533,6 +537,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
new FunctionNode(
this,
lastToken,
endParserState,
flags,
name,
returnType,
@ -604,6 +609,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
new FunctionNode(
this,
lastToken,
endParserState,
flags,
name,
returnType,
@ -642,14 +648,23 @@ 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
@ -739,6 +754,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
new FunctionNode(
this,
lastToken,
endParserState,
flags |
(body.needsScope() ?
FunctionNode.HAS_SCOPE_BLOCK :
@ -837,6 +853,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
new FunctionNode(
this,
lastToken,
endParserState,
flags,
name,
returnType,
@ -897,6 +914,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
new FunctionNode(
this,
lastToken,
endParserState,
flags,
name,
returnType,
@ -908,6 +926,41 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
rootClass));
}
/**
* 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 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
new FunctionNode(
this,
lastToken,
endParserState,
flags,
name,
returnType,
@ -997,6 +1051,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
new FunctionNode(
this,
lastToken,
endParserState,
flags,
name,
returnType,
@ -1075,6 +1130,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
new FunctionNode(
this,
lastToken,
endParserState,
flags,
name,
type,
@ -1122,6 +1178,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
new FunctionNode(
this,
lastToken,
endParserState,
flags,
name,
returnType,
@ -1177,6 +1234,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
new FunctionNode(
this,
lastToken,
endParserState,
flags,
name,
returnType,

View File

@ -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
*/

View File

@ -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() {
@ -227,6 +225,16 @@ public class Parser extends AbstractParser implements Loggable {
defaultNames.push(createIdentNode(0, 0, name));
}
/**
* 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
@ -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));
}
}

View File

@ -209,4 +209,8 @@ public class TokenStream {
in = count;
buffer = newBuffer;
}
void reset() {
in = out = count = base = 0;
}
}

View File

@ -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;
@ -201,6 +205,24 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
return depth;
}
/**
* 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
@ -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);
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 @@ 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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -36,13 +36,18 @@ var trees = new Array("redwood", "bay", "cedar", "oak");
// 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(b.toExponential(2), "global double toExponential()"))
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() {