RecompilableScriptFunctionData.java revision 1105:3d7f49505033
1283766Sgrembo/*
2283766Sgrembo * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
3283766Sgrembo * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4283766Sgrembo *
5283766Sgrembo * This code is free software; you can redistribute it and/or modify it
6283766Sgrembo * under the terms of the GNU General Public License version 2 only, as
7283766Sgrembo * published by the Free Software Foundation.  Oracle designates this
8283766Sgrembo * particular file as subject to the "Classpath" exception as provided
9283766Sgrembo * by Oracle in the LICENSE file that accompanied this code.
10283766Sgrembo *
11283766Sgrembo * This code is distributed in the hope that it will be useful, but WITHOUT
12283766Sgrembo * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13283766Sgrembo * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14283766Sgrembo * version 2 for more details (a copy is included in the LICENSE file that
15283766Sgrembo * accompanied this code).
16283766Sgrembo *
17283766Sgrembo * You should have received a copy of the GNU General Public License version
18283766Sgrembo * 2 along with this work; if not, write to the Free Software Foundation,
19283766Sgrembo * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20283766Sgrembo *
21283766Sgrembo * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22283766Sgrembo * or visit www.oracle.com if you need additional information or have any
23283766Sgrembo * questions.
24283766Sgrembo */
25283766Sgrembo
26283766Sgrembopackage jdk.nashorn.internal.runtime;
27283766Sgrembo
28283766Sgremboimport static jdk.nashorn.internal.lookup.Lookup.MH;
29283766Sgremboimport java.io.IOException;
30283766Sgremboimport java.lang.invoke.MethodHandle;
31283766Sgremboimport java.lang.invoke.MethodHandles;
32283766Sgremboimport java.lang.invoke.MethodType;
33283766Sgremboimport java.util.Collection;
34283766Sgremboimport java.util.Collections;
35283766Sgremboimport java.util.HashMap;
36283766Sgremboimport java.util.HashSet;
37283766Sgremboimport java.util.Map;
38283766Sgremboimport java.util.Set;
39283766Sgremboimport java.util.TreeMap;
40310519Savgimport jdk.internal.dynalink.support.NameCodec;
41283766Sgremboimport jdk.nashorn.internal.codegen.Compiler;
42283766Sgremboimport jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
43284803Sgremboimport jdk.nashorn.internal.codegen.CompilerConstants;
44283766Sgremboimport jdk.nashorn.internal.codegen.FunctionSignature;
45283766Sgremboimport jdk.nashorn.internal.codegen.Namespace;
46283766Sgremboimport jdk.nashorn.internal.codegen.ObjectClassGenerator.AllocatorDescriptor;
47283766Sgremboimport jdk.nashorn.internal.codegen.OptimisticTypesPersistence;
48283766Sgremboimport jdk.nashorn.internal.codegen.TypeMap;
49283766Sgremboimport jdk.nashorn.internal.codegen.types.Type;
50283766Sgremboimport jdk.nashorn.internal.ir.FunctionNode;
51283766Sgremboimport jdk.nashorn.internal.ir.LexicalContext;
52283766Sgremboimport jdk.nashorn.internal.ir.visitor.NodeVisitor;
53284803Sgremboimport jdk.nashorn.internal.objects.Global;
54283766Sgremboimport jdk.nashorn.internal.parser.Parser;
55283766Sgremboimport jdk.nashorn.internal.parser.Token;
56283766Sgremboimport jdk.nashorn.internal.parser.TokenType;
57283766Sgremboimport jdk.nashorn.internal.runtime.logging.DebugLogger;
58283766Sgremboimport jdk.nashorn.internal.runtime.logging.Loggable;
59283766Sgremboimport jdk.nashorn.internal.runtime.logging.Logger;
60283766Sgrembo/**
61283766Sgrembo * This is a subclass that represents a script function that may be regenerated,
62283766Sgrembo * for example with specialization based on call site types, or lazily generated.
63310072Savg * The common denominator is that it can get new invokers during its lifespan,
64310072Savg * unlike {@code FinalScriptFunctionData}
65283766Sgrembo */
66283766Sgrembo@Logger(name="recompile")
67283766Sgrembopublic final class RecompilableScriptFunctionData extends ScriptFunctionData implements Loggable {
68283766Sgrembo    /** Prefix used for all recompiled script classes */
69283766Sgrembo    public static final String RECOMPILATION_PREFIX = "Recompilation$";
70283766Sgrembo
71283766Sgrembo    /** Unique function node id for this function node */
72283766Sgrembo    private final int functionNodeId;
73283766Sgrembo
74283766Sgrembo    private final String functionName;
75283766Sgrembo
76283766Sgrembo    /** The line number where this function begins. */
77283766Sgrembo    private final int lineNumber;
78286918Sgrembo
79286918Sgrembo    /** Source from which FunctionNode was parsed. */
80283766Sgrembo    private transient Source source;
81283766Sgrembo
82283766Sgrembo    /** Serialized, compressed form of the AST. Used by split functions as they can't be reparsed from source. */
83283766Sgrembo    private final byte[] serializedAst;
84283766Sgrembo
85283766Sgrembo    /** Token of this function within the source. */
86283766Sgrembo    private final long token;
87283766Sgrembo
88283766Sgrembo    /**
89283766Sgrembo     * Represents the allocation strategy (property map, script object class, and method handle) for when
90283766Sgrembo     * this function is used as a constructor. Note that majority of functions (those not setting any this.*
91283766Sgrembo     * properties) will share a single canonical "default strategy" instance.
92283766Sgrembo     */
93283766Sgrembo    private final AllocationStrategy allocationStrategy;
94283766Sgrembo
95283766Sgrembo    /**
96283766Sgrembo     * Opaque object representing parser state at the end of the function. Used when reparsing outer function
97283766Sgrembo     * to help with skipping parsing inner functions.
98283766Sgrembo     */
99283766Sgrembo    private final Object endParserState;
100283766Sgrembo
101283766Sgrembo    /** Code installer used for all further recompilation/specialization of this ScriptFunction */
102283766Sgrembo    private transient CodeInstaller<ScriptEnvironment> installer;
103283766Sgrembo
104283766Sgrembo    private final Map<Integer, RecompilableScriptFunctionData> nestedFunctions;
105283766Sgrembo
106283766Sgrembo    /** Id to parent function if one exists */
107283766Sgrembo    private RecompilableScriptFunctionData parent;
108283766Sgrembo
109283766Sgrembo    /** Copy of the {@link FunctionNode} flags. */
110283766Sgrembo    private final int functionFlags;
111283766Sgrembo
112300990Sgrembo    private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
113300990Sgrembo
114300990Sgrembo    private transient DebugLogger log;
115300990Sgrembo
116300990Sgrembo    private final Map<String, Integer> externalScopeDepths;
117300990Sgrembo
118300990Sgrembo    private final Set<String> internalSymbols;
119300990Sgrembo
120300990Sgrembo    private static final int GET_SET_PREFIX_LENGTH = "*et ".length();
121300990Sgrembo
122300990Sgrembo    private static final long serialVersionUID = 4914839316174633726L;
123283766Sgrembo
124310072Savg    /**
125283766Sgrembo     * Constructor - public as scripts use it
126283766Sgrembo     *
127283766Sgrembo     * @param functionNode        functionNode that represents this function code
128283766Sgrembo     * @param installer           installer for code regeneration versions of this function
129283766Sgrembo     * @param allocationDescriptor descriptor for the allocation behavior when this function is used as a constructor
130283766Sgrembo     * @param nestedFunctions     nested function map
131283766Sgrembo     * @param externalScopeDepths external scope depths
132297762Sjhb     * @param internalSymbols     internal symbols to method, defined in its scope
133297762Sjhb     * @param serializedAst       a serialized AST representation. Normally only used for split functions.
134297762Sjhb     */
135297762Sjhb    public RecompilableScriptFunctionData(
136283766Sgrembo        final FunctionNode functionNode,
137283766Sgrembo        final CodeInstaller<ScriptEnvironment> installer,
138283766Sgrembo        final AllocatorDescriptor allocationDescriptor,
139283766Sgrembo        final Map<Integer, RecompilableScriptFunctionData> nestedFunctions,
140283766Sgrembo        final Map<String, Integer> externalScopeDepths,
141283766Sgrembo        final Set<String> internalSymbols,
142283766Sgrembo        final byte[] serializedAst) {
143283766Sgrembo
144283766Sgrembo        super(functionName(functionNode),
145283766Sgrembo              Math.min(functionNode.getParameters().size(), MAX_ARITY),
146283766Sgrembo              getDataFlags(functionNode));
147283766Sgrembo
148283766Sgrembo        this.functionName        = functionNode.getName();
149283766Sgrembo        this.lineNumber          = functionNode.getLineNumber();
150283766Sgrembo        this.functionFlags       = functionNode.getFlags() | (functionNode.needsCallee() ? FunctionNode.NEEDS_CALLEE : 0);
151283766Sgrembo        this.functionNodeId      = functionNode.getId();
152310072Savg        this.source              = functionNode.getSource();
153283766Sgrembo        this.endParserState      = functionNode.getEndParserState();
154283766Sgrembo        this.token               = tokenFor(functionNode);
155283766Sgrembo        this.installer           = installer;
156283766Sgrembo        this.allocationStrategy  = AllocationStrategy.get(allocationDescriptor);
157283766Sgrembo        this.nestedFunctions     = smallMap(nestedFunctions);
158283766Sgrembo        this.externalScopeDepths = smallMap(externalScopeDepths);
159283766Sgrembo        this.internalSymbols     = smallSet(new HashSet<>(internalSymbols));
160283766Sgrembo
161283766Sgrembo        for (final RecompilableScriptFunctionData nfn : nestedFunctions.values()) {
162283766Sgrembo            assert nfn.getParent() == null;
163283766Sgrembo            nfn.setParent(this);
164283766Sgrembo        }
165283766Sgrembo
166283766Sgrembo        this.serializedAst = serializedAst;
167283766Sgrembo        createLogger();
168283766Sgrembo    }
169283766Sgrembo
170283766Sgrembo    private static <K, V> Map<K, V> smallMap(final Map<K, V> map) {
171283766Sgrembo        if (map == null || map.isEmpty()) {
172283766Sgrembo            return Collections.emptyMap();
173283766Sgrembo        } else if (map.size() == 1) {
174283766Sgrembo            final Map.Entry<K, V> entry = map.entrySet().iterator().next();
175283766Sgrembo            return Collections.singletonMap(entry.getKey(), entry.getValue());
176283766Sgrembo        } else {
177283766Sgrembo            return map;
178283766Sgrembo        }
179283766Sgrembo    }
180283766Sgrembo
181283766Sgrembo    private static <T> Set<T> smallSet(final Set<T> set) {
182283766Sgrembo        if (set == null || set.isEmpty()) {
183283766Sgrembo            return Collections.emptySet();
184283766Sgrembo        } else if (set.size() == 1) {
185283766Sgrembo            return Collections.singleton(set.iterator().next());
186283766Sgrembo        } else {
187283766Sgrembo            return set;
188283766Sgrembo        }
189283766Sgrembo    }
190283766Sgrembo
191283766Sgrembo    @Override
192283766Sgrembo    public DebugLogger getLogger() {
193283766Sgrembo        return log;
194283766Sgrembo    }
195283766Sgrembo
196283766Sgrembo    @Override
197283766Sgrembo    public DebugLogger initLogger(final Context ctxt) {
198283766Sgrembo        return ctxt.getLogger(this.getClass());
199284803Sgrembo    }
200283766Sgrembo
201283766Sgrembo    /**
202283766Sgrembo     * Check if a symbol is internally defined in a function. For example
203283766Sgrembo     * if "undefined" is internally defined in the outermost program function,
204283766Sgrembo     * it has not been reassigned or overridden and can be optimized
205283766Sgrembo     *
206283766Sgrembo     * @param symbolName symbol name
207283766Sgrembo     * @return true if symbol is internal to this ScriptFunction
208283766Sgrembo     */
209283766Sgrembo
210283766Sgrembo    public boolean hasInternalSymbol(final String symbolName) {
211283766Sgrembo        return internalSymbols.contains(symbolName);
212283766Sgrembo    }
213283766Sgrembo
214283766Sgrembo    /**
215283766Sgrembo     * Return the external symbol table
216283766Sgrembo     * @param symbolName symbol name
217283766Sgrembo     * @return the external symbol table with proto depths
218283766Sgrembo     */
219283766Sgrembo    public int getExternalSymbolDepth(final String symbolName) {
220283766Sgrembo        final Integer depth = externalScopeDepths.get(symbolName);
221283766Sgrembo        return depth == null ? -1 : depth;
222283766Sgrembo    }
223283766Sgrembo
224283766Sgrembo    /**
225283766Sgrembo     * Returns the names of all external symbols this function uses.
226283766Sgrembo     * @return the names of all external symbols this function uses.
227283766Sgrembo     */
228283766Sgrembo    public Set<String> getExternalSymbolNames() {
229283766Sgrembo        return Collections.unmodifiableSet(externalScopeDepths.keySet());
230283766Sgrembo    }
231283766Sgrembo
232283766Sgrembo    /**
233283766Sgrembo     * Returns the opaque object representing the parser state at the end of this function's body, used to
234283766Sgrembo     * skip parsing this function when reparsing its containing outer function.
235283766Sgrembo     * @return the object representing the end parser state
236283766Sgrembo     */
237283766Sgrembo    public Object getEndParserState() {
238310517Savg        return endParserState;
239283766Sgrembo    }
240283766Sgrembo
241283766Sgrembo    /**
242283766Sgrembo     * Get the parent of this RecompilableScriptFunctionData. If we are
243283766Sgrembo     * a nested function, we have a parent. Note that "null" return value
244310517Savg     * can also mean that we have a parent but it is unknown, so this can
245283766Sgrembo     * only be used for conservative assumptions.
246283766Sgrembo     * @return parent data, or null if non exists and also null IF UNKNOWN.
247283766Sgrembo     */
248283766Sgrembo    public RecompilableScriptFunctionData getParent() {
249283766Sgrembo       return parent;
250283766Sgrembo    }
251283766Sgrembo
252283766Sgrembo    void setParent(final RecompilableScriptFunctionData parent) {
253283766Sgrembo        this.parent = parent;
254283766Sgrembo    }
255283766Sgrembo
256283766Sgrembo    @Override
257283766Sgrembo    String toSource() {
258283766Sgrembo        if (source != null && token != 0) {
259283766Sgrembo            return source.getString(Token.descPosition(token), Token.descLength(token));
260283766Sgrembo        }
261283766Sgrembo
262283766Sgrembo        return "function " + (name == null ? "" : name) + "() { [native code] }";
263283766Sgrembo    }
264283766Sgrembo
265283766Sgrembo    /**
266283766Sgrembo     * Initialize transient fields on deserialized instances
267283766Sgrembo     *
268283766Sgrembo     * @param src source
269283766Sgrembo     * @param inst code installer
270283766Sgrembo     */
271283766Sgrembo    public void initTransients(final Source src, final CodeInstaller<ScriptEnvironment> inst) {
272283766Sgrembo        if (this.source == null && this.installer == null) {
273283766Sgrembo            this.source    = src;
274283766Sgrembo            this.installer = inst;
275283766Sgrembo        } else if (this.source != src || !this.installer.isCompatibleWith(inst)) {
276283766Sgrembo            // Existing values must be same as those passed as parameters
277283766Sgrembo            throw new IllegalArgumentException();
278283766Sgrembo        }
279283766Sgrembo    }
280283766Sgrembo
281283766Sgrembo    @Override
282283766Sgrembo    public String toString() {
283283766Sgrembo        return super.toString() + '@' + functionNodeId;
284283766Sgrembo    }
285283766Sgrembo
286283766Sgrembo    @Override
287283766Sgrembo    public String toStringVerbose() {
288310072Savg        final StringBuilder sb = new StringBuilder();
289310072Savg
290310072Savg        sb.append("fnId=").append(functionNodeId).append(' ');
291310072Savg
292310072Savg        if (source != null) {
293310517Savg            sb.append(source.getName())
294310072Savg                .append(':')
295310072Savg                .append(lineNumber)
296310072Savg                .append(' ');
297310072Savg        }
298310072Savg
299310072Savg        return sb.toString() + super.toString();
300310072Savg    }
301310072Savg
302310072Savg    @Override
303310072Savg    public String getFunctionName() {
304310072Savg        return functionName;
305310072Savg    }
306310072Savg
307310072Savg    @Override
308310072Savg    public boolean inDynamicContext() {
309310072Savg        return getFunctionFlag(FunctionNode.IN_DYNAMIC_CONTEXT);
310310072Savg    }
311310072Savg
312310072Savg    private static String functionName(final FunctionNode fn) {
313310072Savg        if (fn.isAnonymous()) {
314310072Savg            return "";
315310072Savg        }
316310072Savg        final FunctionNode.Kind kind = fn.getKind();
317310072Savg        if (kind == FunctionNode.Kind.GETTER || kind == FunctionNode.Kind.SETTER) {
318310072Savg            final String name = NameCodec.decode(fn.getIdent().getName());
319310072Savg            return name.substring(GET_SET_PREFIX_LENGTH);
320310072Savg        }
321310072Savg        return fn.getIdent().getName();
322310072Savg    }
323310072Savg
324310072Savg    private static long tokenFor(final FunctionNode fn) {
325310072Savg        final int  position  = Token.descPosition(fn.getFirstToken());
326310072Savg        final long lastToken = Token.withDelimiter(fn.getLastToken());
327310072Savg        // EOL uses length field to store the line number
328310072Savg        final int  length    = Token.descPosition(lastToken) - position + (Token.descType(lastToken) == TokenType.EOL ? 0 : Token.descLength(lastToken));
329310072Savg
330310072Savg        return Token.toDesc(TokenType.FUNCTION, position, length);
331310072Savg    }
332310072Savg
333310072Savg    private static int getDataFlags(final FunctionNode functionNode) {
334310072Savg        int flags = IS_CONSTRUCTOR;
335310072Savg        if (functionNode.isStrict()) {
336310072Savg            flags |= IS_STRICT;
337310072Savg        }
338310072Savg        if (functionNode.needsCallee()) {
339310072Savg            flags |= NEEDS_CALLEE;
340310072Savg        }
341310072Savg        if (functionNode.usesThis() || functionNode.hasEval()) {
342310072Savg            flags |= USES_THIS;
343310072Savg        }
344310072Savg        if (functionNode.isVarArg()) {
345310072Savg            flags |= IS_VARIABLE_ARITY;
346310072Savg        }
347310072Savg        return flags;
348310072Savg    }
349310072Savg
350310072Savg    @Override
351310072Savg    PropertyMap getAllocatorMap() {
352310072Savg        return allocationStrategy.getAllocatorMap();
353310072Savg    }
354310072Savg
355310072Savg    @Override
356310072Savg    ScriptObject allocate(final PropertyMap map) {
357310072Savg        return allocationStrategy.allocate(map);
358310072Savg    }
359310072Savg
360310072Savg    boolean isSerialized() {
361310072Savg        return serializedAst != null;
362310072Savg    }
363310072Savg
364310072Savg    FunctionNode reparse() {
365310072Savg        if (isSerialized()) {
366310072Savg            return deserialize();
367310072Savg        }
368310072Savg
369310072Savg        final int descPosition = Token.descPosition(token);
370310072Savg        final Context context = Context.getContextTrusted();
371310072Savg        final Parser parser = new Parser(
372310072Savg            context.getEnv(),
373310072Savg            source,
374310072Savg            new Context.ThrowErrorManager(),
375310072Savg            isStrict(),
376310072Savg            // source starts at line 0, so even though lineNumber is the correct declaration line, back off
377310072Savg            // one to make it exclusive
378310072Savg            lineNumber - 1,
379310072Savg            context.getLogger(Parser.class));
380310072Savg
381310072Savg        if (getFunctionFlag(FunctionNode.IS_ANONYMOUS)) {
382310072Savg            parser.setFunctionName(functionName);
383310072Savg        }
384310072Savg        parser.setReparsedFunction(this);
385310072Savg
386310072Savg        final FunctionNode program = parser.parse(CompilerConstants.PROGRAM.symbolName(), descPosition,
387310072Savg                Token.descLength(token), true);
388310072Savg        // Parser generates a program AST even if we're recompiling a single function, so when we are only
389310072Savg        // recompiling a single function, extract it from the program.
390310072Savg        return (isProgram() ? program : extractFunctionFromScript(program)).setName(null, functionName);
391310072Savg    }
392310072Savg
393310072Savg    private FunctionNode deserialize() {
394310072Savg        final ScriptEnvironment env = installer.getOwner();
395310072Savg        final Timing timing = env._timing;
396310072Savg        final long t1 = System.nanoTime();
397310072Savg        try {
398310072Savg            return AstDeserializer.deserialize(serializedAst).initializeDeserialized(source, new Namespace(env.getNamespace()));
399310072Savg        } finally {
400310072Savg            timing.accumulateTime("'Deserialize'", System.nanoTime() - t1);
401310072Savg        }
402310072Savg    }
403310072Savg
404310072Savg    private boolean getFunctionFlag(final int flag) {
405310072Savg        return (functionFlags & flag) != 0;
406310072Savg    }
407310072Savg
408310072Savg    private boolean isProgram() {
409310072Savg        return getFunctionFlag(FunctionNode.IS_PROGRAM);
410310072Savg    }
411310072Savg
412310072Savg    TypeMap typeMap(final MethodType fnCallSiteType) {
413310072Savg        if (fnCallSiteType == null) {
414310072Savg            return null;
415310072Savg        }
416310072Savg
417310072Savg        if (CompiledFunction.isVarArgsType(fnCallSiteType)) {
418310072Savg            return null;
419310072Savg        }
420310072Savg
421310072Savg        return new TypeMap(functionNodeId, explicitParams(fnCallSiteType), needsCallee());
422310072Savg    }
423310072Savg
424310072Savg    private static ScriptObject newLocals(final ScriptObject runtimeScope) {
425310072Savg        final ScriptObject locals = Global.newEmptyInstance();
426310072Savg        locals.setProto(runtimeScope);
427310072Savg        return locals;
428310072Savg    }
429310072Savg
430310072Savg    private Compiler getCompiler(final FunctionNode fn, final MethodType actualCallSiteType, final ScriptObject runtimeScope) {
431310072Savg        return getCompiler(fn, actualCallSiteType, newLocals(runtimeScope), null, null);
432310072Savg    }
433310072Savg
434310072Savg    /**
435310072Savg     * Returns a code installer for installing new code. If we're using either optimistic typing or loader-per-compile,
436310072Savg     * then asks for a code installer with a new class loader; otherwise just uses the current installer. We use
437310072Savg     * a new class loader with optimistic typing so that deoptimized code can get reclaimed by GC.
438310072Savg     * @return a code installer for installing new code.
439310072Savg     */
440310072Savg    private CodeInstaller<ScriptEnvironment> getInstallerForNewCode() {
441310072Savg        final ScriptEnvironment env = installer.getOwner();
442310072Savg        return env._optimistic_types || env._loader_per_compile ? installer.withNewLoader() : installer;
443310072Savg    }
444310072Savg
445310072Savg    Compiler getCompiler(final FunctionNode functionNode, final MethodType actualCallSiteType,
446310072Savg            final ScriptObject runtimeScope, final Map<Integer, Type> invalidatedProgramPoints,
447310072Savg            final int[] continuationEntryPoints) {
448310072Savg        final TypeMap typeMap = typeMap(actualCallSiteType);
449310072Savg        final Type[] paramTypes = typeMap == null ? null : typeMap.getParameterTypes(functionNodeId);
450310072Savg        final Object typeInformationFile = OptimisticTypesPersistence.getLocationDescriptor(source, functionNodeId, paramTypes);
451310072Savg        final Context context = Context.getContextTrusted();
452310072Savg        return new Compiler(
453310072Savg                context,
454310072Savg                context.getEnv(),
455310072Savg                getInstallerForNewCode(),
456310072Savg                functionNode.getSource(),  // source
457310072Savg                context.getErrorManager(),
458310072Savg                isStrict() | functionNode.isStrict(), // is strict
459310072Savg                true,       // is on demand
460310072Savg                this,       // compiledFunction, i.e. this RecompilableScriptFunctionData
461310072Savg                typeMap,    // type map
462310072Savg                getEffectiveInvalidatedProgramPoints(invalidatedProgramPoints, typeInformationFile), // invalidated program points
463310072Savg                typeInformationFile,
464310072Savg                continuationEntryPoints, // continuation entry points
465310072Savg                runtimeScope); // runtime scope
466310072Savg    }
467310072Savg
468310072Savg    /**
469310072Savg     * If the function being compiled already has its own invalidated program points map, use it. Otherwise, attempt to
470310072Savg     * load invalidated program points map from the persistent type info cache.
471310072Savg     * @param invalidatedProgramPoints the function's current invalidated program points map. Null if the function
472310072Savg     * doesn't have it.
473310072Savg     * @param typeInformationFile the object describing the location of the persisted type information.
474310072Savg     * @return either the existing map, or a loaded map from the persistent type info cache, or a new empty map if
475310072Savg     * neither an existing map or a persistent cached type info is available.
476310072Savg     */
477310072Savg    @SuppressWarnings("unused")
478310072Savg    private static Map<Integer, Type> getEffectiveInvalidatedProgramPoints(
479310072Savg            final Map<Integer, Type> invalidatedProgramPoints, final Object typeInformationFile) {
480310072Savg        if(invalidatedProgramPoints != null) {
481310072Savg            return invalidatedProgramPoints;
482310072Savg        }
483310072Savg        final Map<Integer, Type> loadedProgramPoints = OptimisticTypesPersistence.load(typeInformationFile);
484310072Savg        return loadedProgramPoints != null ? loadedProgramPoints : new TreeMap<Integer, Type>();
485310072Savg    }
486310072Savg
487310072Savg    private FunctionInitializer compileTypeSpecialization(final MethodType actualCallSiteType, final ScriptObject runtimeScope, final boolean persist) {
488310072Savg        // We're creating an empty script object for holding local variables. AssignSymbols will populate it with
489310072Savg        // explicit Undefined values for undefined local variables (see AssignSymbols#defineSymbol() and
490310072Savg        // CompilationEnvironment#declareLocalSymbol()).
491310072Savg
492310072Savg        if (log.isEnabled()) {
493310072Savg            log.info("Parameter type specialization of '", functionName, "' signature: ", actualCallSiteType);
494310072Savg        }
495310072Savg
496310072Savg        final boolean persistentCache = usePersistentCodeCache() && persist;
497310072Savg        String cacheKey = null;
498310072Savg        if (persistentCache) {
499310072Savg            final TypeMap typeMap = typeMap(actualCallSiteType);
500310072Savg            final Type[] paramTypes = typeMap == null ? null : typeMap.getParameterTypes(functionNodeId);
501310072Savg            cacheKey = CodeStore.getCacheKey(functionNodeId, paramTypes);
502310072Savg            final CodeInstaller<ScriptEnvironment> newInstaller = getInstallerForNewCode();
503310072Savg            final StoredScript script = newInstaller.loadScript(source, cacheKey);
504310072Savg
505310072Savg            if (script != null) {
506310072Savg                Compiler.updateCompilationId(script.getCompilationId());
507310517Savg                return installStoredScript(script, newInstaller);
508310072Savg            }
509310072Savg        }
510310072Savg
511310072Savg        final FunctionNode fn = reparse();
512310072Savg        final Compiler compiler = getCompiler(fn, actualCallSiteType, runtimeScope);
513310072Savg        final FunctionNode compiledFn = compiler.compile(fn,
514310072Savg                isSerialized() ? CompilationPhases.COMPILE_ALL_SERIALIZED : CompilationPhases.COMPILE_ALL);
515310072Savg
516310072Savg        if (persist && !compiledFn.getFlag(FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION)) {
517283766Sgrembo            compiler.persistClassInfo(cacheKey, compiledFn);
518283766Sgrembo        }
519283766Sgrembo        return new FunctionInitializer(compiledFn, compiler.getInvalidatedProgramPoints());
520283766Sgrembo    }
521283766Sgrembo
522283766Sgrembo    private static Map<String, Class<?>> installStoredScriptClasses(final StoredScript script, final CodeInstaller<ScriptEnvironment> installer) {
523283766Sgrembo        final Map<String, Class<?>> installedClasses = new HashMap<>();
524283766Sgrembo        final Map<String, byte[]>   classBytes       = script.getClassBytes();
525311809Sgonzo        final String   mainClassName   = script.getMainClassName();
526311809Sgonzo        final byte[]   mainClassBytes  = classBytes.get(mainClassName);
527311809Sgonzo
528339029Sgonzo        final Class<?> mainClass       = installer.install(mainClassName, mainClassBytes);
529339029Sgonzo
530339029Sgonzo        installedClasses.put(mainClassName, mainClass);
531339029Sgonzo
532339029Sgonzo        for (final Map.Entry<String, byte[]> entry : classBytes.entrySet()) {
533339029Sgonzo            final String className = entry.getKey();
534339029Sgonzo            final byte[] bytecode = entry.getValue();
535339029Sgonzo
536339029Sgonzo            if (className.equals(mainClassName)) {
537339029Sgonzo                continue;
538331834Sgonzo            }
539331834Sgonzo
540331834Sgonzo            installedClasses.put(className, installer.install(className, bytecode));
541331834Sgonzo        }
542331834Sgonzo        return installedClasses;
543283766Sgrembo    }
544331834Sgonzo
545331834Sgonzo    /**
546331834Sgonzo     * Install this script using the given {@code installer}.
547331834Sgonzo     *
548331834Sgonzo     * @param script the compiled script
549331834Sgonzo     * @return the function initializer
550331834Sgonzo     */
551331834Sgonzo    private FunctionInitializer installStoredScript(final StoredScript script, final CodeInstaller<ScriptEnvironment> newInstaller) {
552331834Sgonzo        final Map<String, Class<?>> installedClasses = installStoredScriptClasses(script, newInstaller);
553331834Sgonzo
554283766Sgrembo        final Map<Integer, FunctionInitializer> initializers = script.getInitializers();
555283766Sgrembo        assert initializers != null;
556331834Sgonzo        assert initializers.size() == 1;
557331834Sgonzo        final FunctionInitializer initializer = initializers.values().iterator().next();
558331834Sgonzo
559331834Sgonzo        final Object[] constants = script.getConstants();
560331834Sgonzo        for (int i = 0; i < constants.length; i++) {
561331834Sgonzo            if (constants[i] instanceof RecompilableScriptFunctionData) {
562331834Sgonzo                // replace deserialized function data with the ones we already have
563283766Sgrembo                constants[i] = getScriptFunctionData(((RecompilableScriptFunctionData) constants[i]).getFunctionNodeId());
564331834Sgonzo            }
565331834Sgonzo        }
566339030Sgonzo
567331834Sgonzo        newInstaller.initialize(installedClasses.values(), source, constants);
568331834Sgonzo        initializer.setCode(installedClasses.get(initializer.getClassName()));
569331834Sgonzo        return initializer;
570283766Sgrembo    }
571283766Sgrembo
572283766Sgrembo    boolean usePersistentCodeCache() {
573283766Sgrembo        final ScriptEnvironment env = installer.getOwner();
574283766Sgrembo        return env._persistent_cache && env._optimistic_types;
575283766Sgrembo    }
576283766Sgrembo
577283766Sgrembo    private MethodType explicitParams(final MethodType callSiteType) {
578283766Sgrembo        if (CompiledFunction.isVarArgsType(callSiteType)) {
579283766Sgrembo            return null;
580283766Sgrembo        }
581283766Sgrembo
582283766Sgrembo        final MethodType noCalleeThisType = callSiteType.dropParameterTypes(0, 2); // (callee, this) is always in call site type
583283766Sgrembo        final int callSiteParamCount = noCalleeThisType.parameterCount();
584283766Sgrembo
585283766Sgrembo        // Widen parameters of reference types to Object as we currently don't care for specialization among reference
586283766Sgrembo        // types. E.g. call site saying (ScriptFunction, Object, String) should still link to (ScriptFunction, Object, Object)
587283766Sgrembo        final Class<?>[] paramTypes = noCalleeThisType.parameterArray();
588283766Sgrembo        boolean changed = false;
589283766Sgrembo        for (int i = 0; i < paramTypes.length; ++i) {
590283766Sgrembo            final Class<?> paramType = paramTypes[i];
591283766Sgrembo            if (!(paramType.isPrimitive() || paramType == Object.class)) {
592283766Sgrembo                paramTypes[i] = Object.class;
593283766Sgrembo                changed = true;
594283766Sgrembo            }
595283766Sgrembo        }
596283766Sgrembo        final MethodType generalized = changed ? MethodType.methodType(noCalleeThisType.returnType(), paramTypes) : noCalleeThisType;
597283766Sgrembo
598284803Sgrembo        if (callSiteParamCount < getArity()) {
599284803Sgrembo            return generalized.appendParameterTypes(Collections.<Class<?>>nCopies(getArity() - callSiteParamCount, Object.class));
600283766Sgrembo        }
601283766Sgrembo        return generalized;
602283766Sgrembo    }
603283766Sgrembo
604283766Sgrembo    private FunctionNode extractFunctionFromScript(final FunctionNode script) {
605283766Sgrembo        final Set<FunctionNode> fns = new HashSet<>();
606283766Sgrembo        script.getBody().accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
607283766Sgrembo            @Override
608283766Sgrembo            public boolean enterFunctionNode(final FunctionNode fn) {
609310072Savg                fns.add(fn);
610310072Savg                return false;
611310072Savg            }
612283766Sgrembo        });
613283766Sgrembo        assert fns.size() == 1 : "got back more than one method in recompilation";
614283766Sgrembo        final FunctionNode f = fns.iterator().next();
615283766Sgrembo        assert f.getId() == functionNodeId;
616283766Sgrembo        if (!getFunctionFlag(FunctionNode.IS_DECLARED) && f.isDeclared()) {
617283766Sgrembo            return f.clearFlag(null, FunctionNode.IS_DECLARED);
618283766Sgrembo        }
619283766Sgrembo        return f;
620331834Sgonzo    }
621331834Sgonzo
622331834Sgonzo    private void logLookup(final boolean shouldLog, final MethodType targetType) {
623331834Sgonzo        if (shouldLog && log.isEnabled()) {
624331834Sgonzo            log.info("Looking up ", DebugLogger.quote(functionName), " type=", targetType);
625331834Sgonzo        }
626331834Sgonzo    }
627283766Sgrembo
628283766Sgrembo    private MethodHandle lookup(final FunctionInitializer fnInit, final boolean shouldLog) {
629284803Sgrembo        final MethodType type = fnInit.getMethodType();
630283766Sgrembo        logLookup(shouldLog, type);
631283766Sgrembo        return lookupCodeMethod(fnInit.getCode(), type);
632283766Sgrembo    }
633283766Sgrembo
634284803Sgrembo    MethodHandle lookup(final FunctionNode fn) {
635283766Sgrembo        final MethodType type = new FunctionSignature(fn).getMethodType();
636283766Sgrembo        logLookup(true, type);
637283766Sgrembo        return lookupCodeMethod(fn.getCompileUnit().getCode(), type);
638283766Sgrembo    }
639283766Sgrembo
640283766Sgrembo    MethodHandle lookupCodeMethod(final Class<?> codeClass, final MethodType targetType) {
641283766Sgrembo        return MH.findStatic(LOOKUP, codeClass, functionName, targetType);
642283766Sgrembo    }
643283766Sgrembo
644283766Sgrembo    /**
645300990Sgrembo     * Initializes this function data with the eagerly generated version of the code. This method can only be invoked
646300990Sgrembo     * by the compiler internals in Nashorn and is public for implementation reasons only. Attempting to invoke it
647283766Sgrembo     * externally will result in an exception.
648283766Sgrembo     *
649283766Sgrembo     * @param initializer FunctionInitializer for this data
650283766Sgrembo     */
651283766Sgrembo    public void initializeCode(final FunctionInitializer initializer) {
652283766Sgrembo        // Since the method is public, we double-check that we aren't invoked with an inappropriate compile unit.
653283766Sgrembo        if(!code.isEmpty()) {
654283766Sgrembo            throw new IllegalStateException(name);
655283766Sgrembo        }
656283766Sgrembo        addCode(lookup(initializer, true), null, null, initializer.getFlags());
657283766Sgrembo    }
658283766Sgrembo
659283766Sgrembo    private CompiledFunction addCode(final MethodHandle target, final Map<Integer, Type> invalidatedProgramPoints,
660283766Sgrembo                                     final MethodType callSiteType, final int fnFlags) {
661283766Sgrembo        final CompiledFunction cfn = new CompiledFunction(target, this, invalidatedProgramPoints, callSiteType, fnFlags);
662283766Sgrembo        code.add(cfn);
663283766Sgrembo        return cfn;
664283766Sgrembo    }
665283766Sgrembo
666283766Sgrembo    /**
667283766Sgrembo     * Add code with specific call site type. It will adapt the type of the looked up method handle to fit the call site
668283766Sgrembo     * type. This is necessary because even if we request a specialization that takes an "int" parameter, we might end
669283766Sgrembo     * up getting one that takes a "double" etc. because of internal function logic causes widening (e.g. assignment of
670283766Sgrembo     * a wider value to the parameter variable). However, we use the method handle type for matching subsequent lookups
671283766Sgrembo     * for the same specialization, so we must adapt the handle to the expected type.
672283766Sgrembo     * @param fnInit the function
673283766Sgrembo     * @param callSiteType the call site type
674283766Sgrembo     * @return the compiled function object, with its type matching that of the call site type.
675283766Sgrembo     */
676283766Sgrembo    private CompiledFunction addCode(final FunctionInitializer fnInit, final MethodType callSiteType) {
677283766Sgrembo        if (isVariableArity()) {
678283766Sgrembo            return addCode(lookup(fnInit, true), fnInit.getInvalidatedProgramPoints(), callSiteType, fnInit.getFlags());
679283766Sgrembo        }
680283766Sgrembo
681283766Sgrembo        final MethodHandle handle = lookup(fnInit, true);
682283766Sgrembo        final MethodType fromType = handle.type();
683283766Sgrembo        MethodType toType = needsCallee(fromType) ? callSiteType.changeParameterType(0, ScriptFunction.class) : callSiteType.dropParameterTypes(0, 1);
684283766Sgrembo        toType = toType.changeReturnType(fromType.returnType());
685283766Sgrembo
686310072Savg        final int toCount = toType.parameterCount();
687310072Savg        final int fromCount = fromType.parameterCount();
688283766Sgrembo        final int minCount = Math.min(fromCount, toCount);
689283766Sgrembo        for(int i = 0; i < minCount; ++i) {
690283766Sgrembo            final Class<?> fromParam = fromType.parameterType(i);
691284803Sgrembo            final Class<?>   toParam =   toType.parameterType(i);
692284803Sgrembo            // If method has an Object parameter, but call site had String, preserve it as Object. No need to narrow it
693283766Sgrembo            // artificially. Note that this is related to how CompiledFunction.matchesCallSite() works, specifically
694310072Savg            // the fact that various reference types compare to equal (see "fnType.isEquivalentTo(csType)" there).
695283766Sgrembo            if (fromParam != toParam && !fromParam.isPrimitive() && !toParam.isPrimitive()) {
696283766Sgrembo                assert fromParam.isAssignableFrom(toParam);
697283766Sgrembo                toType = toType.changeParameterType(i, fromParam);
698283766Sgrembo            }
699284803Sgrembo        }
700284803Sgrembo        if (fromCount > toCount) {
701311809Sgonzo            toType = toType.appendParameterTypes(fromType.parameterList().subList(toCount, fromCount));
702311809Sgonzo        } else if (fromCount < toCount) {
703311809Sgonzo            toType = toType.dropParameterTypes(fromCount, toCount);
704311809Sgonzo        }
705283766Sgrembo
706283766Sgrembo        return addCode(lookup(fnInit, false).asType(toType), fnInit.getInvalidatedProgramPoints(), callSiteType, fnInit.getFlags());
707283766Sgrembo    }
708283766Sgrembo
709284803Sgrembo    /**
710283766Sgrembo     * Returns the return type of a function specialization for particular parameter types.<br>
711283766Sgrembo     * <b>Be aware that the way this is implemented, it forces full materialization (compilation and installation) of
712283766Sgrembo     * code for that specialization.</b>
713283766Sgrembo     * @param callSiteType the parameter types at the call site. It must include the mandatory {@code callee} and
714283766Sgrembo     * {@code this} parameters, so it needs to start with at least {@code ScriptFunction.class} and
715283766Sgrembo     * {@code Object.class} class. Since the return type of the function is calculated from the code itself, it is
716283766Sgrembo     * irrelevant and should be set to {@code Object.class}.
717284803Sgrembo     * @param runtimeScope a current runtime scope. Can be null but when it's present it will be used as a source of
718283766Sgrembo     * current runtime values that can improve the compiler's type speculations (and thus reduce the need for later
719300990Sgrembo     * recompilations) if the specialization is not already present and thus needs to be freshly compiled.
720283766Sgrembo     * @return the return type of the function specialization.
721283766Sgrembo     */
722283766Sgrembo    public Class<?> getReturnType(final MethodType callSiteType, final ScriptObject runtimeScope) {
723283766Sgrembo        return getBest(callSiteType, runtimeScope, CompiledFunction.NO_FUNCTIONS).type().returnType();
724283766Sgrembo    }
725283766Sgrembo
726283766Sgrembo    @Override
727339030Sgonzo    synchronized CompiledFunction getBest(final MethodType callSiteType, final ScriptObject runtimeScope, final Collection<CompiledFunction> forbidden) {
728339030Sgonzo        CompiledFunction existingBest = super.getBest(callSiteType, runtimeScope, forbidden);
729339030Sgonzo        if (existingBest == null) {
730339030Sgonzo            existingBest = addCode(compileTypeSpecialization(callSiteType, runtimeScope, true), callSiteType);
731339030Sgonzo        }
732339030Sgonzo
733339030Sgonzo        assert existingBest != null;
734339030Sgonzo        //we are calling a vararg method with real args
735339030Sgonzo        boolean varArgWithRealArgs = existingBest.isVarArg() && !CompiledFunction.isVarArgsType(callSiteType);
736339030Sgonzo
737339030Sgonzo        //if the best one is an apply to call, it has to match the callsite exactly
738339030Sgonzo        //or we need to regenerate
739339030Sgonzo        if (existingBest.isApplyToCall()) {
740283766Sgrembo            final CompiledFunction best = lookupExactApplyToCall(callSiteType);
741284803Sgrembo            if (best != null) {
742283766Sgrembo                return best;
743283766Sgrembo            }
744283766Sgrembo            varArgWithRealArgs = true;
745283766Sgrembo        }
746283766Sgrembo
747283766Sgrembo        if (varArgWithRealArgs) {
748283766Sgrembo            // special case: we had an apply to call, but we failed to make it fit.
749283766Sgrembo            // Try to generate a specialized one for this callsite. It may
750283766Sgrembo            // be another apply to call specialization, or it may not, but whatever
751283766Sgrembo            // it is, it is a specialization that is guaranteed to fit
752283766Sgrembo            final FunctionInitializer fnInit = compileTypeSpecialization(callSiteType, runtimeScope, false);
753283766Sgrembo            existingBest = addCode(fnInit, callSiteType);
754283766Sgrembo        }
755283766Sgrembo
756283766Sgrembo        return existingBest;
757283766Sgrembo    }
758283766Sgrembo
759283766Sgrembo    @Override
760283766Sgrembo    boolean isRecompilable() {
761283766Sgrembo        return true;
762283766Sgrembo    }
763283766Sgrembo
764283766Sgrembo    @Override
765283766Sgrembo    public boolean needsCallee() {
766283766Sgrembo        return getFunctionFlag(FunctionNode.NEEDS_CALLEE);
767283766Sgrembo    }
768283766Sgrembo
769283766Sgrembo    /**
770283766Sgrembo     * Returns the {@link FunctionNode} flags associated with this function data.
771283766Sgrembo     * @return the {@link FunctionNode} flags associated with this function data.
772283766Sgrembo     */
773283766Sgrembo    public int getFunctionFlags() {
774331834Sgonzo        return functionFlags;
775331834Sgonzo    }
776331834Sgonzo
777331834Sgonzo    @Override
778331834Sgonzo    MethodType getGenericType() {
779331834Sgonzo        // 2 is for (callee, this)
780331834Sgonzo        if (isVariableArity()) {
781331834Sgonzo            return MethodType.genericMethodType(2, true);
782331834Sgonzo        }
783331834Sgonzo        return MethodType.genericMethodType(2 + getArity());
784331834Sgonzo    }
785331834Sgonzo
786331834Sgonzo    /**
787331834Sgonzo     * Return the function node id.
788331834Sgonzo     * @return the function node id
789331834Sgonzo     */
790331834Sgonzo    public int getFunctionNodeId() {
791331834Sgonzo        return functionNodeId;
792331834Sgonzo    }
793331834Sgonzo
794331834Sgonzo    /**
795283766Sgrembo     * Get the source for the script
796283766Sgrembo     * @return source
797283766Sgrembo     */
798311809Sgonzo    public Source getSource() {
799311809Sgonzo        return source;
800    }
801
802    /**
803     * Return a script function data based on a function id, either this function if
804     * the id matches or a nested function based on functionId. This goes down into
805     * nested functions until all leaves are exhausted.
806     *
807     * @param functionId function id
808     * @return script function data or null if invalid id
809     */
810    public RecompilableScriptFunctionData getScriptFunctionData(final int functionId) {
811        if (functionId == functionNodeId) {
812            return this;
813        }
814        RecompilableScriptFunctionData data;
815
816        data = nestedFunctions == null ? null : nestedFunctions.get(functionId);
817        if (data != null) {
818            return data;
819        }
820        for (final RecompilableScriptFunctionData ndata : nestedFunctions.values()) {
821            data = ndata.getScriptFunctionData(functionId);
822            if (data != null) {
823                return data;
824            }
825        }
826        return null;
827    }
828
829    /**
830     * Check whether a certain name is a global symbol, i.e. only exists as defined
831     * in outermost scope and not shadowed by being parameter or assignment in inner
832     * scopes
833     *
834     * @param functionNode function node to check
835     * @param symbolName symbol name
836     * @return true if global symbol
837     */
838    public boolean isGlobalSymbol(final FunctionNode functionNode, final String symbolName) {
839        RecompilableScriptFunctionData data = getScriptFunctionData(functionNode.getId());
840        assert data != null;
841
842        do {
843            if (data.hasInternalSymbol(symbolName)) {
844                return false;
845            }
846            data = data.getParent();
847        } while(data != null);
848
849        return true;
850    }
851
852    /**
853     * Restores the {@link #getFunctionFlags()} flags to a function node. During on-demand compilation, we might need
854     * to restore flags to a function node that was otherwise not subjected to a full compile pipeline (e.g. its parse
855     * was skipped, or it's a nested function of a deserialized function.
856     * @param lc current lexical context
857     * @param fn the function node to restore flags onto
858     * @return the transformed function node
859     */
860    public FunctionNode restoreFlags(final LexicalContext lc, final FunctionNode fn) {
861        assert fn.getId() == functionNodeId;
862        FunctionNode newFn = fn.setFlags(lc, functionFlags);
863        // This compensates for missing markEval() in case the function contains an inner function
864        // that contains eval(), that now we didn't discover since we skipped the inner function.
865        if (newFn.hasNestedEval()) {
866            assert newFn.hasScopeBlock();
867            newFn = newFn.setBody(lc, newFn.getBody().setNeedsScope(null));
868        }
869        return newFn;
870    }
871
872    private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
873        in.defaultReadObject();
874        createLogger();
875    }
876
877    private void createLogger() {
878        log = initLogger(Context.getContextTrusted());
879    }
880}
881