diff options
Diffstat (limited to 'src/jdk/nashorn/internal/ir/FunctionNode.java')
-rw-r--r-- | src/jdk/nashorn/internal/ir/FunctionNode.java | 818 |
1 files changed, 589 insertions, 229 deletions
diff --git a/src/jdk/nashorn/internal/ir/FunctionNode.java b/src/jdk/nashorn/internal/ir/FunctionNode.java index a0f6a788..0806fea9 100644 --- a/src/jdk/nashorn/internal/ir/FunctionNode.java +++ b/src/jdk/nashorn/internal/ir/FunctionNode.java @@ -25,12 +25,19 @@ package jdk.nashorn.internal.ir; +import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_PROFILE; +import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_STRICT; +import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_TRACE; +import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_TRACE_ENTEREXIT; +import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_TRACE_MISSES; +import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_TRACE_VALUES; + import java.util.Collections; import java.util.EnumSet; -import java.util.HashSet; +import java.util.Iterator; import java.util.List; -import java.util.Objects; -import java.util.Set; +import java.util.function.Function; +import jdk.nashorn.internal.AssertsEnabled; import jdk.nashorn.internal.codegen.CompileUnit; import jdk.nashorn.internal.codegen.Compiler; import jdk.nashorn.internal.codegen.CompilerConstants; @@ -39,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; @@ -48,7 +56,8 @@ import jdk.nashorn.internal.runtime.linker.LinkerCallSite; * IR representation for function (or script.) */ @Immutable -public final class FunctionNode extends LexicalContextExpression implements Flags<FunctionNode> { +public final class FunctionNode extends LexicalContextExpression implements Flags<FunctionNode>, CompileUnitHolder { + private static final long serialVersionUID = 1L; /** Type used for all FunctionNodes */ public static final Type FUNCTION_TYPE = Type.typeFor(ScriptFunction.class); @@ -77,26 +86,41 @@ public final class FunctionNode extends LexicalContextExpression implements Flag CONSTANT_FOLDED, /** method has been lowered */ LOWERED, - /** method hass been attributed */ - ATTR, + /** program points have been assigned to unique locations */ + PROGRAM_POINTS_ASSIGNED, + /** any transformations of builtins have taken place, e.g. apply=>call */ + BUILTINS_TRANSFORMED, /** method has been split */ SPLIT, - /** method has had its types finalized */ - FINALIZED, + /** method has had symbols assigned */ + SYMBOLS_ASSIGNED, + /** computed scope depths for symbols */ + SCOPE_DEPTHS_COMPUTED, + /** method has had types calculated*/ + OPTIMISTIC_TYPES_ASSIGNED, + /** method has had types calculated */ + LOCAL_VARIABLE_TYPES_CALCULATED, + /** compile units reused (optional) */ + COMPILE_UNITS_REUSED, /** method has been emitted to bytecode */ - EMITTED + BYTECODE_GENERATED, + /** method has been installed */ + BYTECODE_INSTALLED } + /** Source of entity. */ - private final Source source; + private transient final Source source; + + /** + * 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 private final IdentNode ident; - /** Parsed version of functionNode */ - @Ignore - private final FunctionNode snapshot; - /** The body of the function node */ private final Block body; @@ -118,32 +142,26 @@ public final class FunctionNode extends LexicalContextExpression implements Flag /** Last token of function. **/ private final long lastToken; - /** Declared symbols in this function node */ - @Ignore - private final Set<Symbol> declaredSymbols; - /** Method's namespace. */ - private final Namespace namespace; + private transient final Namespace namespace; /** Current compilation state */ @Ignore private final EnumSet<CompilationState> compilationState; + /** Number of properties of "this" object assigned in this function */ @Ignore - private final Compiler.Hints hints; - - /** Properties of this object assigned in this function */ - @Ignore - private HashSet<String> thisProperties; + private final int thisProperties; /** Function flags. */ private final int flags; - /** //@ sourceURL or //# sourceURL for program function nodes */ - private final String sourceURL; - + /** Line number of function start */ private final int lineNumber; + /** Root class for function */ + private final Class<?> rootClass; + /** Is anonymous function flag. */ public static final int IS_ANONYMOUS = 1 << 0; @@ -156,7 +174,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag /** Does the function use the "arguments" identifier ? */ public static final int USES_ARGUMENTS = 1 << 3; - /** Has this node been split because it was too large? */ + /** Has this function been split because it was too large? */ public static final int IS_SPLIT = 1 << 4; /** Does the function call eval? If it does, then all variables in this function might be get/set by it and it can @@ -182,36 +200,87 @@ public final class FunctionNode extends LexicalContextExpression implements Flag /** Does this function or any of its descendants use variables from an ancestor function's scope (incl. globals)? */ public static final int USES_ANCESTOR_SCOPE = 1 << 9; - /** Is this function lazily compiled? */ - public static final int IS_LAZY = 1 << 10; + /** Does this function have nested declarations? */ + public static final int HAS_FUNCTION_DECLARATIONS = 1 << 10; - /** Does this function have lazy, yet uncompiled children */ - public static final int HAS_LAZY_CHILDREN = 1 << 11; + /** Does this function have optimistic expressions? (If it does, it can undergo deoptimizing recompilation.) */ + public static final int IS_DEOPTIMIZABLE = 1 << 11; - /** Does this function have lazy, yet uncompiled children */ - public static final int IS_PROGRAM = 1 << 12; + /** Are we vararg, but do we just pass the arguments along to apply or call */ + public static final int HAS_APPLY_TO_CALL_SPECIALIZATION = 1 << 12; - /** Does this function have nested declarations? */ - public static final int HAS_FUNCTION_DECLARATIONS = 1 << 13; + /** + * Is this function the top-level program? + */ + public static final int IS_PROGRAM = 1 << 13; - /** Can this function be specialized? */ - public static final int CAN_SPECIALIZE = 1 << 14; + /** + * Flag indicating whether this function uses the local variable symbol for itself. Only named function expressions + * can have this flag set if they reference themselves (e.g. "(function f() { return f })". Declared functions will + * use the symbol in their parent scope instead when they reference themselves by name. + */ + public static final int USES_SELF_SYMBOL = 1 << 14; /** Does this function use the "this" keyword? */ - public static final int USES_THIS = 1 << 15; + public static final int USES_THIS = 1 << 15; + + /** Is this declared in a dynamic context */ + public static final int IN_DYNAMIC_CONTEXT = 1 << 16; + + /** + * The following flags are derived from directive comments within this function. + * Note that even IS_STRICT is one such flag but that requires special handling. + */ + + /** parser, print parse tree */ + public static final int IS_PRINT_PARSE = 1 << 17; + /** parser, print lower parse tree */ + public static final int IS_PRINT_LOWER_PARSE = 1 << 18; + /** parser, print AST */ + public static final int IS_PRINT_AST = 1 << 19; + /** parser, print lower AST */ + public static final int IS_PRINT_LOWER_AST = 1 << 20; + /** parser, print symbols */ + public static final int IS_PRINT_SYMBOLS = 1 << 21; + + // callsite tracing, profiling within this function + /** profile callsites in this function? */ + public static final int IS_PROFILE = 1 << 22; + + /** trace callsite enterexit in this function? */ + public static final int IS_TRACE_ENTEREXIT = 1 << 23; + + /** trace callsite misses in this function? */ + public static final int IS_TRACE_MISSES = 1 << 24; + + /** trace callsite values in this function? */ + public static final int IS_TRACE_VALUES = 1 << 25; + + /** + * 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 << 26; + + /** 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 | + IS_PRINT_SYMBOLS | IS_PROFILE | IS_TRACE_ENTEREXIT | + IS_TRACE_MISSES | IS_TRACE_VALUES; /** Does this function or any nested functions contain an eval? */ private static final int HAS_DEEP_EVAL = HAS_EVAL | HAS_NESTED_EVAL; /** Does this function need to store all its variables in scope? */ - private static final int HAS_ALL_VARS_IN_SCOPE = HAS_DEEP_EVAL | IS_SPLIT | HAS_LAZY_CHILDREN; + private static final int HAS_ALL_VARS_IN_SCOPE = HAS_DEEP_EVAL; /** 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 */ - private static final int NEEDS_PARENT_SCOPE = USES_ANCESTOR_SCOPE | HAS_DEEP_EVAL | HAS_LAZY_CHILDREN; + /** 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, or it's the program. */ + public static final int NEEDS_PARENT_SCOPE = USES_ANCESTOR_SCOPE | HAS_DEEP_EVAL | IS_PROGRAM; /** What is the return type of this function? */ private Type returnType = Type.UNKNOWN; @@ -223,14 +292,13 @@ public final class FunctionNode extends LexicalContextExpression implements Flag * @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 * @param parameters parameter list * @param kind kind of function as in {@link FunctionNode.Kind} * @param flags initial flags - * @param sourceURL sourceURL specified in script (optional) */ public FunctionNode( final Source source, @@ -243,8 +311,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag final String name, final List<IdentNode> parameters, final FunctionNode.Kind kind, - final int flags, - final String sourceURL) { + final int flags) { super(token, finish); this.source = source; @@ -257,32 +324,33 @@ public final class FunctionNode extends LexicalContextExpression implements Flag this.lastToken = token; this.namespace = namespace; this.compilationState = EnumSet.of(CompilationState.INITIALIZED); - this.declaredSymbols = new HashSet<>(); this.flags = flags; - this.sourceURL = sourceURL; this.compileUnit = null; this.body = null; - this.snapshot = null; - this.hints = null; + this.thisProperties = 0; + this.rootClass = null; + this.endParserState = null; } private FunctionNode( final FunctionNode functionNode, final long lastToken, + final Object endParserState, final int flags, - final String sourceURL, final String name, final Type returnType, final CompileUnit compileUnit, final EnumSet<CompilationState> compilationState, final Block body, final List<IdentNode> parameters, - final FunctionNode snapshot, - final Compiler.Hints hints) { + final int thisProperties, + final Class<?> rootClass, + final Source source, Namespace namespace) { super(functionNode); + + this.endParserState = endParserState; this.lineNumber = functionNode.lineNumber; this.flags = flags; - this.sourceURL = sourceURL; this.name = name; this.returnType = returnType; this.compileUnit = compileUnit; @@ -290,17 +358,15 @@ public final class FunctionNode extends LexicalContextExpression implements Flag this.compilationState = compilationState; this.body = body; this.parameters = parameters; - this.snapshot = snapshot; - this.hints = hints; + this.thisProperties = thisProperties; + this.rootClass = rootClass; + this.source = source; + this.namespace = namespace; // the fields below never change - they are final and assigned in constructor - this.source = functionNode.source; this.ident = functionNode.ident; - this.namespace = functionNode.namespace; - this.declaredSymbols = functionNode.declaredSymbols; this.kind = functionNode.kind; this.firstToken = functionNode.firstToken; - this.thisProperties = functionNode.thisProperties; } @Override @@ -312,98 +378,156 @@ public final class FunctionNode extends LexicalContextExpression implements Flag } /** - * Get the source for this function - * @return the source + * Visits the parameter nodes of this function. Parameters are normally not visited automatically. + * @param visitor the visitor to apply to the nodes. + * @return a list of parameter nodes, potentially modified from original ones by the visitor. */ - public Source getSource() { - return source; + public List<IdentNode> visitParameters(final NodeVisitor<? extends LexicalContext> visitor) { + return Node.accept(visitor, parameters); } /** - * get source name - sourceURL or name derived from Source. + * Get additional callsite flags to be used specific to this function. * - * @return name for the script source + * @return callsite flags */ - public String getSourceName() { - return (sourceURL != null)? sourceURL : source.getName(); + public int getCallSiteFlags() { + int callsiteFlags = 0; + if (getFlag(IS_STRICT)) { + callsiteFlags |= CALLSITE_STRICT; + } + + // quick check for extension callsite flags turned on by directives. + if ((flags & EXTENSION_CALLSITE_FLAGS) == 0) { + return callsiteFlags; + } + + if (getFlag(IS_PROFILE)) { + callsiteFlags |= CALLSITE_PROFILE; + } + + if (getFlag(IS_TRACE_MISSES)) { + callsiteFlags |= CALLSITE_TRACE | CALLSITE_TRACE_MISSES; + } + + if (getFlag(IS_TRACE_VALUES)) { + callsiteFlags |= CALLSITE_TRACE | CALLSITE_TRACE_ENTEREXIT | CALLSITE_TRACE_VALUES; + } + + if (getFlag(IS_TRACE_ENTEREXIT)) { + callsiteFlags |= CALLSITE_TRACE | CALLSITE_TRACE_ENTEREXIT; + } + + return callsiteFlags; } /** - * get the sourceURL - * @return the sourceURL + * Get the source for this function + * @return the source */ - public String getSourceURL() { - return sourceURL; + public Source getSource() { + return source; } /** - * Set the sourceURL - * - * @param lc lexical context - * @param newSourceURL source url string to set - * @return function node or a new one if state was changed + * Sets the source and namespace for this function. It can only set a non-null source and namespace for a function + * that currently has both a null source and a null namespace. This is used to re-set the source and namespace for + * a deserialized function node. + * @param source the source for the function. + * @param namespace the namespace for the function + * @return a new function node with the set source and namespace + * @throws IllegalArgumentException if the specified source or namespace is null + * @throws IllegalStateException if the function already has either a source or namespace set. */ - public FunctionNode setSourceURL(final LexicalContext lc, final String newSourceURL) { - if (Objects.equals(sourceURL, newSourceURL)) { + public FunctionNode initializeDeserialized(final Source source, final Namespace namespace) { + if (source == null || namespace == null) { + throw new IllegalArgumentException(); + } else if (this.source == source && this.namespace == namespace) { return this; + } else if (this.source != null || this.namespace != null) { + throw new IllegalStateException(); } - - return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, newSourceURL, name, returnType, compileUnit, compilationState, body, parameters, null, hints)); + return new FunctionNode( + this, + lastToken, + endParserState, + flags, + name, + returnType, + compileUnit, + compilationState, + body, + parameters, + thisProperties, + rootClass, source, namespace); } /** - * Returns the line number. - * @return the line number. + * Get the unique ID for this function within the script file. + * @return the id */ - public int getLineNumber() { - return lineNumber; + public int getId() { + return position(); } /** - * Get the version of this function node's code as it looked upon construction - * i.e typically parsed and nothing else - * @return initial version of function node + * get source name - sourceURL or name derived from Source. + * + * @return name for the script source */ - public FunctionNode getSnapshot() { - return snapshot; + public String getSourceName() { + return getSourceName(source); } /** - * Throw away the snapshot, if any, to save memory. Used when heuristic - * determines that a method is not worth specializing + * Static source name getter * - * @param lc lexical context - * @return new function node if a snapshot was present, now with snapsnot null + * @param source the source + * @return source name */ - public FunctionNode clearSnapshot(final LexicalContext lc) { - if (this.snapshot == null) { - return this; - } - return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, null, hints)); + public static String getSourceName(final Source source) { + final String explicitURL = source.getExplicitURL(); + return explicitURL != null ? explicitURL : source.getName(); } /** - * Take a snapshot of this function node at a given point in time - * and store it in the function node - * @param lc lexical context - * @return function node + * Function to parse nashorn per-function extension directive comments. + * + * @param directive nashorn extension directive string + * @return integer flag for the given directive. */ - public FunctionNode snapshot(final LexicalContext lc) { - if (this.snapshot == this) { - return this; - } - if (isProgram() || parameters.isEmpty()) { - return this; //never specialize anything that won't be recompiled + public static int getDirectiveFlag(final String directive) { + switch (directive) { + case "nashorn callsite trace enterexit": + return IS_TRACE_ENTEREXIT; + case "nashorn callsite trace misses": + return IS_TRACE_MISSES; + case "nashorn callsite trace objects": + return IS_TRACE_VALUES; + case "nashorn callsite profile": + return IS_PROFILE; + case "nashorn print parse": + return IS_PRINT_PARSE; + case "nashorn print lower parse": + return IS_PRINT_LOWER_PARSE; + case "nashorn print ast": + return IS_PRINT_AST; + case "nashorn print lower ast": + return IS_PRINT_LOWER_AST; + case "nashorn print symbols": + return IS_PRINT_SYMBOLS; + default: + // unknown/unsupported directive + return 0; } - return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, this, hints)); } /** - * Can this function node be regenerated with more specific type args? - * @return true if specialization is possible + * Returns the line number. + * @return the line number. */ - public boolean canSpecialize() { - return snapshot != null && getFlag(CAN_SPECIALIZE); + public int getLineNumber() { + return lineNumber; } /** @@ -421,21 +545,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag * @return true of the node is in the given state */ public boolean hasState(final EnumSet<CompilationState> state) { - return compilationState.equals(state); - } - - /** - * Check whether the state of this FunctionNode contains a given compilation - * state. - * - * A node can be in many states at once, e.g. both lowered and initialized. - * To check for an exact state, use {FunctionNode{@link #hasState(EnumSet)} - * - * @param state state to check for - * @return true if state is present in the total compilation state of this FunctionNode - */ - public boolean hasState(final CompilationState state) { - return compilationState.contains(state); + return !AssertsEnabled.assertsEnabled() || compilationState.containsAll(state); } /** @@ -448,35 +558,52 @@ public final class FunctionNode extends LexicalContextExpression implements Flag * @return function node or a new one if state was changed */ public FunctionNode setState(final LexicalContext lc, final CompilationState state) { - if (this.compilationState.contains(state)) { + if (!AssertsEnabled.assertsEnabled() || this.compilationState.contains(state)) { return this; } final EnumSet<CompilationState> newState = EnumSet.copyOf(this.compilationState); newState.add(state); - return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, newState, body, parameters, snapshot, hints)); - } - - /** - * Get any compiler hints that may associated with the function - * @return compiler hints - */ - public Compiler.Hints getHints() { - return this.hints == null ? Compiler.Hints.EMPTY : hints; + return setCompilationState(lc, newState); } /** - * Set compiler hints for this function - * @param lc lexical context - * @param hints compiler hints - * @return new function if hints changed + * Copy a compilation state from an original function to this function. Used when creating synthetic + * function nodes by the splitter. + * + * @param lc lexical context + * @param original the original function node to copy compilation state from + * @return function node or a new one if state was changed */ - public FunctionNode setHints(final LexicalContext lc, final Compiler.Hints hints) { - if (this.hints == hints) { + public FunctionNode copyCompilationState(final LexicalContext lc, final FunctionNode original) { + final EnumSet<CompilationState> origState = original.compilationState; + if (!AssertsEnabled.assertsEnabled() || this.compilationState.containsAll(origState)) { return this; } - return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints)); + final EnumSet<CompilationState> newState = EnumSet.copyOf(this.compilationState); + newState.addAll(origState); + return setCompilationState(lc, newState); } + private FunctionNode setCompilationState(final LexicalContext lc, final EnumSet<CompilationState> compilationState) { + return Node.replaceInLexicalContext( + lc, + this, + new FunctionNode( + this, + lastToken, + endParserState, + flags, + name, + returnType, + compileUnit, + compilationState, + body, + parameters, + thisProperties, + rootClass, source, namespace)); + } + + /** * Create a unique name in the namespace of this FunctionNode * @param base prefix for name @@ -486,48 +613,67 @@ public final class FunctionNode extends LexicalContextExpression implements Flag return namespace.uniqueName(base); } - @Override - public void toString(final StringBuilder sb) { - sb.append('['); - sb.append(returnType); - sb.append(']'); - sb.append(' '); + public void toString(final StringBuilder sb, final boolean printTypes) { + sb.append('['). + append(returnType). + append(']'). + append(' '); sb.append("function"); if (ident != null) { sb.append(' '); - ident.toString(sb); + ident.toString(sb, printTypes); } sb.append('('); - boolean first = true; - for (final IdentNode parameter : parameters) { - if (!first) { + for (final Iterator<IdentNode> iter = parameters.iterator(); iter.hasNext(); ) { + final IdentNode parameter = iter.next(); + if (parameter.getSymbol() != null) { + sb.append('[').append(parameter.getType()).append(']').append(' '); + } + parameter.toString(sb, printTypes); + if (iter.hasNext()) { sb.append(", "); - } else { - first = false; } - - parameter.toString(sb); } sb.append(')'); } @Override + public int getFlags() { + return flags; + } + + @Override public boolean getFlag(final int flag) { return (flags & flag) != 0; } @Override - public FunctionNode setFlags(final LexicalContext lc, int flags) { + public FunctionNode setFlags(final LexicalContext lc, final int flags) { if (this.flags == flags) { return this; } - return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints)); + return Node.replaceInLexicalContext( + lc, + this, + new FunctionNode( + this, + lastToken, + endParserState, + flags, + name, + returnType, + compileUnit, + compilationState, + body, + parameters, + thisProperties, + rootClass, source, namespace)); } @Override @@ -549,23 +695,32 @@ public final class FunctionNode extends LexicalContextExpression implements Flag } /** - * Should this function node be lazily code generated, i.e. first at link time - * @return true if lazy + * Returns true if the function contains at least one optimistic operation (and thus can be deoptimized). + * @return true if the function contains at least one optimistic operation (and thus can be deoptimized). */ - public boolean isLazy() { - return getFlag(IS_LAZY); + public boolean canBeDeoptimized() { + return getFlag(IS_DEOPTIMIZABLE); } /** - * 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 */ @@ -587,10 +742,14 @@ public final class FunctionNode extends LexicalContextExpression implements Flag * (since it exposes {@code arguments.callee} property) will need to have a callee parameter. We also return true * for split functions to make sure symbols slots are the same in the main and split methods. * + * A function that has had an apply(this,arguments) turned into a call doesn't need arguments anymore, but still + * has to fit the old callsite, thus, we require a dummy callee parameter for those functions as well + * * @return true if the function's generated Java method needs a {@code callee} parameter. */ public boolean needsCallee() { - return needsParentScope() || needsSelfSymbol() || isSplit() || (needsArguments() && !isStrict()); + // NOTE: we only need isSplit() here to ensure that :scope can never drop below slot 2 for splitting array units. + return needsParentScope() || usesSelfSymbol() || isSplit() || (needsArguments() && !isStrict()) || hasOptimisticApplyToCall(); } /** @@ -602,30 +761,21 @@ public final class FunctionNode extends LexicalContextExpression implements Flag return getFlag(USES_THIS); } - /** - * Get the identifier for this function, this is its symbol. - * @return the identifier as an IdentityNode - */ - public IdentNode getIdent() { - return ident; - } /** - * Return a set of symbols declared in this function node. This - * is only relevant after Attr, otherwise it will be an empty - * set as no symbols have been introduced - * @return set of declared symbols in function + * Return true if function contains an apply to call transform + * @return true if this function has transformed apply to call */ - public Set<Symbol> getDeclaredSymbols() { - return Collections.unmodifiableSet(declaredSymbols); + public boolean hasOptimisticApplyToCall() { + return getFlag(HAS_APPLY_TO_CALL_SPECIALIZATION); } /** - * Add a declared symbol to this function node - * @param symbol symbol that is declared + * Get the identifier for this function, this is its symbol. + * @return the identifier as an IdentityNode */ - public void addDeclaredSymbol(final Symbol symbol) { - declaredSymbols.add(symbol); + public IdentNode getIdent() { + return ident; } /** @@ -643,10 +793,28 @@ public final class FunctionNode extends LexicalContextExpression implements Flag * @return new function node if body changed, same if not */ public FunctionNode setBody(final LexicalContext lc, final Block body) { - if(this.body == body) { + if (this.body == body) { return this; } - return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags | (body.needsScope() ? FunctionNode.HAS_SCOPE_BLOCK : 0), sourceURL, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints)); + return Node.replaceInLexicalContext( + lc, + this, + new FunctionNode( + this, + lastToken, + endParserState, + flags | + (body.needsScope() ? + FunctionNode.HAS_SCOPE_BLOCK : + 0), + name, + returnType, + compileUnit, + compilationState, + body, + parameters, + thisProperties, + rootClass, source, namespace)); } /** @@ -662,6 +830,36 @@ public final class FunctionNode extends LexicalContextExpression implements Flag } /** + * Was this function declared in a dynamic context, i.e. in a with or eval style + * chain + * @return true if in dynamic context + */ + public boolean inDynamicContext() { + return getFlag(IN_DYNAMIC_CONTEXT); + } + + /** + * Check whether a function would need dynamic scope, which is does if it has + * evals and isn't strict. + * @return true if dynamic scope is needed + */ + public boolean needsDynamicScope() { + // Function has a direct eval in it (so a top-level "var ..." in the eval code can introduce a new + // variable into the function's scope), and it isn't strict (as evals in strict functions get an + // isolated scope). + return hasEval() && !isStrict(); + } + + /** + * Flag this function as declared in a dynamic context + * @param lc lexical context + * @return new function node, or same if unmodified + */ + public FunctionNode setInDynamicContext(final LexicalContext lc) { + return setFlag(lc, IN_DYNAMIC_CONTEXT); + } + + /** * Returns true if this function needs to have an Arguments object defined as a local variable named "arguments". * Functions that use "arguments" as identifier and don't define it as a name of a parameter or a nested function * (see ECMAScript 5.1 Chapter 10.5), as well as any function that uses eval or with, or has a nested function that @@ -684,26 +882,43 @@ public final class FunctionNode extends LexicalContextExpression implements Flag * @return true if the function needs parent scope. */ public boolean needsParentScope() { - return getFlag(NEEDS_PARENT_SCOPE) || isProgram(); + return getFlag(NEEDS_PARENT_SCOPE); } /** - * Register a property assigned to the this object in this function. - * @param key the property name + * Set the number of properties assigned to the this object in this function. + * @param lc the current lexical context. + * @param thisProperties number of properties + * @return a potentially modified function node */ - public void addThisProperty(final String key) { - if (thisProperties == null) { - thisProperties = new HashSet<>(); + public FunctionNode setThisProperties(final LexicalContext lc, final int thisProperties) { + if (this.thisProperties == thisProperties) { + return this; } - thisProperties.add(key); + return Node.replaceInLexicalContext( + lc, + this, + new FunctionNode( + this, + lastToken, + endParserState, + flags, + name, + returnType, + compileUnit, + compilationState, + body, + parameters, + thisProperties, + rootClass, source, namespace)); } /** * Get the number of properties assigned to the this object in this function. * @return number of properties */ - public int countThisProperties() { - return thisProperties == null ? 0 : thisProperties.size(); + public int getThisProperties() { + return thisProperties; } /** @@ -741,7 +956,60 @@ public final class FunctionNode extends LexicalContextExpression implements Flag if (this.lastToken == lastToken) { return this; } - return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints)); + return Node.replaceInLexicalContext( + lc, + this, + new FunctionNode( + this, + lastToken, + endParserState, + flags, + name, + returnType, + compileUnit, + compilationState, + body, + parameters, + thisProperties, + rootClass, source, namespace)); + } + + /** + * 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, + source, + namespace)); } /** @@ -752,7 +1020,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag return name; } - /** * Set the internal name for this function * @param lc lexical context @@ -763,38 +1030,46 @@ public final class FunctionNode extends LexicalContextExpression implements Flag if (this.name.equals(name)) { return this; } - return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints)); + return Node.replaceInLexicalContext( + lc, + this, + new FunctionNode( + this, + lastToken, + endParserState, + flags, + name, + returnType, + compileUnit, + compilationState, + body, + parameters, + thisProperties, + rootClass, + source, + namespace)); } /** - * Check if this function should have all its variables in its own scope. Scripts, split sub-functions, and + * Check if this function should have all its variables in its own scope. Split sub-functions, and * functions having with and/or eval blocks are such. * * @return true if all variables should be in scope */ public boolean allVarsInScope() { - return isProgram() || getFlag(HAS_ALL_VARS_IN_SCOPE); + return getFlag(HAS_ALL_VARS_IN_SCOPE); } /** - * Checks if this function is a sub-function generated by splitting a larger one + * Checks if this function is split into several smaller fragments. * - * @return true if this function is split from a larger one + * @return true if this function is split into several smaller fragments. */ public boolean isSplit() { return getFlag(IS_SPLIT); } /** - * Checks if this function has yet-to-be-generated child functions - * - * @return true if there are lazy child functions - */ - public boolean hasLazyChildren() { - return getFlag(HAS_LAZY_CHILDREN); - } - - /** * Get the parameters to this function * @return a list of IdentNodes which represent the function parameters, in order */ @@ -803,6 +1078,16 @@ public final class FunctionNode extends LexicalContextExpression implements Flag } /** + * Returns the identifier for a named parameter at the specified position in this function's parameter list. + * @param index the parameter's position. + * @return the identifier for the requested named parameter. + * @throws IndexOutOfBoundsException if the index is invalid. + */ + public IdentNode getParameter(final int index) { + return parameters.get(index); + } + + /** * Reset the compile unit used to compile this function * @see Compiler * @param lc lexical context @@ -813,7 +1098,22 @@ public final class FunctionNode extends LexicalContextExpression implements Flag if (this.parameters == parameters) { return this; } - return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints)); + return Node.replaceInLexicalContext( + lc, + this, + new FunctionNode( + this, + lastToken, + endParserState, + flags, + name, + returnType, + compileUnit, + compilationState, + body, + parameters, + thisProperties, + rootClass, source, namespace)); } /** @@ -833,16 +1133,22 @@ public final class FunctionNode extends LexicalContextExpression implements Flag } /** - * Does this function need a self symbol - this is needed only for self - * referring functions - * @return true if function needs a symbol for self + * Does this function use its self symbol - this is needed only for self-referencing named function expressions. + * Self-referencing declared functions won't have this flag set, as they can access their own symbol through the + * scope (since they're bound to the symbol with their name in their enclosing scope). + * @return true if this function node is a named function expression that uses the symbol for itself. */ - public boolean needsSelfSymbol() { - return body.getFlag(Block.NEEDS_SELF_SYMBOL); + public boolean usesSelfSymbol() { + return getFlag(USES_SELF_SYMBOL); + } + + @Override + public Type getType(final Function<Symbol, Type> localVariableTypes) { + return FUNCTION_TYPE; } @Override - public Type getType() { + public Type getWidestOperationType() { return FUNCTION_TYPE; } @@ -867,27 +1173,28 @@ public final class FunctionNode extends LexicalContextExpression implements Flag //we never bother with object types narrower than objects, that will lead to byte code verification errors //as for instance even if we know we are returning a string from a method, the code generator will always //treat it as an object, at least for now - if (this.returnType == returnType) { + final Type type = returnType.isObject() ? Type.OBJECT : returnType; + if (this.returnType == type) { return this; } - final Type type = Type.widest(this.returnType, returnType.isObject() ? Type.OBJECT : returnType); return Node.replaceInLexicalContext( lc, this, new FunctionNode( this, lastToken, + endParserState, flags, - sourceURL, name, type, compileUnit, compilationState, - body.setReturnType(type), + body, parameters, - snapshot, - hints)); - } + thisProperties, + rootClass, source, namespace + )); + } /** * Check if the function is generated in strict mode @@ -902,6 +1209,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag * @see Compiler * @return the compile unit */ + @Override public CompileUnit getCompileUnit() { return compileUnit; } @@ -917,7 +1225,22 @@ public final class FunctionNode extends LexicalContextExpression implements Flag if (this.compileUnit == compileUnit) { return this; } - return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints)); + return Node.replaceInLexicalContext( + lc, + this, + new FunctionNode( + this, + lastToken, + endParserState, + flags, + name, + returnType, + compileUnit, + compilationState, + body, + parameters, + thisProperties, + rootClass, source, namespace)); } /** @@ -938,4 +1261,41 @@ public final class FunctionNode extends LexicalContextExpression implements Flag public Symbol compilerConstant(final CompilerConstants cc) { return body.getExistingSymbol(cc.symbolName()); } + + /** + * Get the root class that this function node compiles to + * @return root class + */ + public Class<?> getRootClass() { + return rootClass; + } + + /** + * Reset the root class that this function is compiled to + * @see Compiler + * @param lc lexical context + * @param rootClass root class + * @return function node or a new one if state was changed + */ + public FunctionNode setRootClass(final LexicalContext lc, final Class<?> rootClass) { + if (this.rootClass == rootClass) { + return this; + } + return Node.replaceInLexicalContext( + lc, + this, + new FunctionNode( + this, + lastToken, + endParserState, + flags, + name, + returnType, + compileUnit, + compilationState, + body, + parameters, + thisProperties, + rootClass, source, namespace)); + } } |