RecompilableScriptFunctionData.java revision 1105:3d7f49505033
1/*
2 * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package jdk.nashorn.internal.runtime;
27
28import static jdk.nashorn.internal.lookup.Lookup.MH;
29import java.io.IOException;
30import java.lang.invoke.MethodHandle;
31import java.lang.invoke.MethodHandles;
32import java.lang.invoke.MethodType;
33import java.util.Collection;
34import java.util.Collections;
35import java.util.HashMap;
36import java.util.HashSet;
37import java.util.Map;
38import java.util.Set;
39import java.util.TreeMap;
40import jdk.internal.dynalink.support.NameCodec;
41import jdk.nashorn.internal.codegen.Compiler;
42import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
43import jdk.nashorn.internal.codegen.CompilerConstants;
44import jdk.nashorn.internal.codegen.FunctionSignature;
45import jdk.nashorn.internal.codegen.Namespace;
46import jdk.nashorn.internal.codegen.ObjectClassGenerator.AllocatorDescriptor;
47import jdk.nashorn.internal.codegen.OptimisticTypesPersistence;
48import jdk.nashorn.internal.codegen.TypeMap;
49import jdk.nashorn.internal.codegen.types.Type;
50import jdk.nashorn.internal.ir.FunctionNode;
51import jdk.nashorn.internal.ir.LexicalContext;
52import jdk.nashorn.internal.ir.visitor.NodeVisitor;
53import jdk.nashorn.internal.objects.Global;
54import jdk.nashorn.internal.parser.Parser;
55import jdk.nashorn.internal.parser.Token;
56import jdk.nashorn.internal.parser.TokenType;
57import jdk.nashorn.internal.runtime.logging.DebugLogger;
58import jdk.nashorn.internal.runtime.logging.Loggable;
59import jdk.nashorn.internal.runtime.logging.Logger;
60/**
61 * This is a subclass that represents a script function that may be regenerated,
62 * for example with specialization based on call site types, or lazily generated.
63 * The common denominator is that it can get new invokers during its lifespan,
64 * unlike {@code FinalScriptFunctionData}
65 */
66@Logger(name="recompile")
67public final class RecompilableScriptFunctionData extends ScriptFunctionData implements Loggable {
68    /** Prefix used for all recompiled script classes */
69    public static final String RECOMPILATION_PREFIX = "Recompilation$";
70
71    /** Unique function node id for this function node */
72    private final int functionNodeId;
73
74    private final String functionName;
75
76    /** The line number where this function begins. */
77    private final int lineNumber;
78
79    /** Source from which FunctionNode was parsed. */
80    private transient Source source;
81
82    /** Serialized, compressed form of the AST. Used by split functions as they can't be reparsed from source. */
83    private final byte[] serializedAst;
84
85    /** Token of this function within the source. */
86    private final long token;
87
88    /**
89     * Represents the allocation strategy (property map, script object class, and method handle) for when
90     * this function is used as a constructor. Note that majority of functions (those not setting any this.*
91     * properties) will share a single canonical "default strategy" instance.
92     */
93    private final AllocationStrategy allocationStrategy;
94
95    /**
96     * Opaque object representing parser state at the end of the function. Used when reparsing outer function
97     * to help with skipping parsing inner functions.
98     */
99    private final Object endParserState;
100
101    /** Code installer used for all further recompilation/specialization of this ScriptFunction */
102    private transient CodeInstaller<ScriptEnvironment> installer;
103
104    private final Map<Integer, RecompilableScriptFunctionData> nestedFunctions;
105
106    /** Id to parent function if one exists */
107    private RecompilableScriptFunctionData parent;
108
109    /** Copy of the {@link FunctionNode} flags. */
110    private final int functionFlags;
111
112    private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
113
114    private transient DebugLogger log;
115
116    private final Map<String, Integer> externalScopeDepths;
117
118    private final Set<String> internalSymbols;
119
120    private static final int GET_SET_PREFIX_LENGTH = "*et ".length();
121
122    private static final long serialVersionUID = 4914839316174633726L;
123
124    /**
125     * Constructor - public as scripts use it
126     *
127     * @param functionNode        functionNode that represents this function code
128     * @param installer           installer for code regeneration versions of this function
129     * @param allocationDescriptor descriptor for the allocation behavior when this function is used as a constructor
130     * @param nestedFunctions     nested function map
131     * @param externalScopeDepths external scope depths
132     * @param internalSymbols     internal symbols to method, defined in its scope
133     * @param serializedAst       a serialized AST representation. Normally only used for split functions.
134     */
135    public RecompilableScriptFunctionData(
136        final FunctionNode functionNode,
137        final CodeInstaller<ScriptEnvironment> installer,
138        final AllocatorDescriptor allocationDescriptor,
139        final Map<Integer, RecompilableScriptFunctionData> nestedFunctions,
140        final Map<String, Integer> externalScopeDepths,
141        final Set<String> internalSymbols,
142        final byte[] serializedAst) {
143
144        super(functionName(functionNode),
145              Math.min(functionNode.getParameters().size(), MAX_ARITY),
146              getDataFlags(functionNode));
147
148        this.functionName        = functionNode.getName();
149        this.lineNumber          = functionNode.getLineNumber();
150        this.functionFlags       = functionNode.getFlags() | (functionNode.needsCallee() ? FunctionNode.NEEDS_CALLEE : 0);
151        this.functionNodeId      = functionNode.getId();
152        this.source              = functionNode.getSource();
153        this.endParserState      = functionNode.getEndParserState();
154        this.token               = tokenFor(functionNode);
155        this.installer           = installer;
156        this.allocationStrategy  = AllocationStrategy.get(allocationDescriptor);
157        this.nestedFunctions     = smallMap(nestedFunctions);
158        this.externalScopeDepths = smallMap(externalScopeDepths);
159        this.internalSymbols     = smallSet(new HashSet<>(internalSymbols));
160
161        for (final RecompilableScriptFunctionData nfn : nestedFunctions.values()) {
162            assert nfn.getParent() == null;
163            nfn.setParent(this);
164        }
165
166        this.serializedAst = serializedAst;
167        createLogger();
168    }
169
170    private static <K, V> Map<K, V> smallMap(final Map<K, V> map) {
171        if (map == null || map.isEmpty()) {
172            return Collections.emptyMap();
173        } else if (map.size() == 1) {
174            final Map.Entry<K, V> entry = map.entrySet().iterator().next();
175            return Collections.singletonMap(entry.getKey(), entry.getValue());
176        } else {
177            return map;
178        }
179    }
180
181    private static <T> Set<T> smallSet(final Set<T> set) {
182        if (set == null || set.isEmpty()) {
183            return Collections.emptySet();
184        } else if (set.size() == 1) {
185            return Collections.singleton(set.iterator().next());
186        } else {
187            return set;
188        }
189    }
190
191    @Override
192    public DebugLogger getLogger() {
193        return log;
194    }
195
196    @Override
197    public DebugLogger initLogger(final Context ctxt) {
198        return ctxt.getLogger(this.getClass());
199    }
200
201    /**
202     * Check if a symbol is internally defined in a function. For example
203     * if "undefined" is internally defined in the outermost program function,
204     * it has not been reassigned or overridden and can be optimized
205     *
206     * @param symbolName symbol name
207     * @return true if symbol is internal to this ScriptFunction
208     */
209
210    public boolean hasInternalSymbol(final String symbolName) {
211        return internalSymbols.contains(symbolName);
212    }
213
214    /**
215     * Return the external symbol table
216     * @param symbolName symbol name
217     * @return the external symbol table with proto depths
218     */
219    public int getExternalSymbolDepth(final String symbolName) {
220        final Integer depth = externalScopeDepths.get(symbolName);
221        return depth == null ? -1 : depth;
222    }
223
224    /**
225     * Returns the names of all external symbols this function uses.
226     * @return the names of all external symbols this function uses.
227     */
228    public Set<String> getExternalSymbolNames() {
229        return Collections.unmodifiableSet(externalScopeDepths.keySet());
230    }
231
232    /**
233     * Returns the opaque object representing the parser state at the end of this function's body, used to
234     * skip parsing this function when reparsing its containing outer function.
235     * @return the object representing the end parser state
236     */
237    public Object getEndParserState() {
238        return endParserState;
239    }
240
241    /**
242     * Get the parent of this RecompilableScriptFunctionData. If we are
243     * a nested function, we have a parent. Note that "null" return value
244     * can also mean that we have a parent but it is unknown, so this can
245     * only be used for conservative assumptions.
246     * @return parent data, or null if non exists and also null IF UNKNOWN.
247     */
248    public RecompilableScriptFunctionData getParent() {
249       return parent;
250    }
251
252    void setParent(final RecompilableScriptFunctionData parent) {
253        this.parent = parent;
254    }
255
256    @Override
257    String toSource() {
258        if (source != null && token != 0) {
259            return source.getString(Token.descPosition(token), Token.descLength(token));
260        }
261
262        return "function " + (name == null ? "" : name) + "() { [native code] }";
263    }
264
265    /**
266     * Initialize transient fields on deserialized instances
267     *
268     * @param src source
269     * @param inst code installer
270     */
271    public void initTransients(final Source src, final CodeInstaller<ScriptEnvironment> inst) {
272        if (this.source == null && this.installer == null) {
273            this.source    = src;
274            this.installer = inst;
275        } else if (this.source != src || !this.installer.isCompatibleWith(inst)) {
276            // Existing values must be same as those passed as parameters
277            throw new IllegalArgumentException();
278        }
279    }
280
281    @Override
282    public String toString() {
283        return super.toString() + '@' + functionNodeId;
284    }
285
286    @Override
287    public String toStringVerbose() {
288        final StringBuilder sb = new StringBuilder();
289
290        sb.append("fnId=").append(functionNodeId).append(' ');
291
292        if (source != null) {
293            sb.append(source.getName())
294                .append(':')
295                .append(lineNumber)
296                .append(' ');
297        }
298
299        return sb.toString() + super.toString();
300    }
301
302    @Override
303    public String getFunctionName() {
304        return functionName;
305    }
306
307    @Override
308    public boolean inDynamicContext() {
309        return getFunctionFlag(FunctionNode.IN_DYNAMIC_CONTEXT);
310    }
311
312    private static String functionName(final FunctionNode fn) {
313        if (fn.isAnonymous()) {
314            return "";
315        }
316        final FunctionNode.Kind kind = fn.getKind();
317        if (kind == FunctionNode.Kind.GETTER || kind == FunctionNode.Kind.SETTER) {
318            final String name = NameCodec.decode(fn.getIdent().getName());
319            return name.substring(GET_SET_PREFIX_LENGTH);
320        }
321        return fn.getIdent().getName();
322    }
323
324    private static long tokenFor(final FunctionNode fn) {
325        final int  position  = Token.descPosition(fn.getFirstToken());
326        final long lastToken = Token.withDelimiter(fn.getLastToken());
327        // EOL uses length field to store the line number
328        final int  length    = Token.descPosition(lastToken) - position + (Token.descType(lastToken) == TokenType.EOL ? 0 : Token.descLength(lastToken));
329
330        return Token.toDesc(TokenType.FUNCTION, position, length);
331    }
332
333    private static int getDataFlags(final FunctionNode functionNode) {
334        int flags = IS_CONSTRUCTOR;
335        if (functionNode.isStrict()) {
336            flags |= IS_STRICT;
337        }
338        if (functionNode.needsCallee()) {
339            flags |= NEEDS_CALLEE;
340        }
341        if (functionNode.usesThis() || functionNode.hasEval()) {
342            flags |= USES_THIS;
343        }
344        if (functionNode.isVarArg()) {
345            flags |= IS_VARIABLE_ARITY;
346        }
347        return flags;
348    }
349
350    @Override
351    PropertyMap getAllocatorMap() {
352        return allocationStrategy.getAllocatorMap();
353    }
354
355    @Override
356    ScriptObject allocate(final PropertyMap map) {
357        return allocationStrategy.allocate(map);
358    }
359
360    boolean isSerialized() {
361        return serializedAst != null;
362    }
363
364    FunctionNode reparse() {
365        if (isSerialized()) {
366            return deserialize();
367        }
368
369        final int descPosition = Token.descPosition(token);
370        final Context context = Context.getContextTrusted();
371        final Parser parser = new Parser(
372            context.getEnv(),
373            source,
374            new Context.ThrowErrorManager(),
375            isStrict(),
376            // source starts at line 0, so even though lineNumber is the correct declaration line, back off
377            // one to make it exclusive
378            lineNumber - 1,
379            context.getLogger(Parser.class));
380
381        if (getFunctionFlag(FunctionNode.IS_ANONYMOUS)) {
382            parser.setFunctionName(functionName);
383        }
384        parser.setReparsedFunction(this);
385
386        final FunctionNode program = parser.parse(CompilerConstants.PROGRAM.symbolName(), descPosition,
387                Token.descLength(token), true);
388        // Parser generates a program AST even if we're recompiling a single function, so when we are only
389        // recompiling a single function, extract it from the program.
390        return (isProgram() ? program : extractFunctionFromScript(program)).setName(null, functionName);
391    }
392
393    private FunctionNode deserialize() {
394        final ScriptEnvironment env = installer.getOwner();
395        final Timing timing = env._timing;
396        final long t1 = System.nanoTime();
397        try {
398            return AstDeserializer.deserialize(serializedAst).initializeDeserialized(source, new Namespace(env.getNamespace()));
399        } finally {
400            timing.accumulateTime("'Deserialize'", System.nanoTime() - t1);
401        }
402    }
403
404    private boolean getFunctionFlag(final int flag) {
405        return (functionFlags & flag) != 0;
406    }
407
408    private boolean isProgram() {
409        return getFunctionFlag(FunctionNode.IS_PROGRAM);
410    }
411
412    TypeMap typeMap(final MethodType fnCallSiteType) {
413        if (fnCallSiteType == null) {
414            return null;
415        }
416
417        if (CompiledFunction.isVarArgsType(fnCallSiteType)) {
418            return null;
419        }
420
421        return new TypeMap(functionNodeId, explicitParams(fnCallSiteType), needsCallee());
422    }
423
424    private static ScriptObject newLocals(final ScriptObject runtimeScope) {
425        final ScriptObject locals = Global.newEmptyInstance();
426        locals.setProto(runtimeScope);
427        return locals;
428    }
429
430    private Compiler getCompiler(final FunctionNode fn, final MethodType actualCallSiteType, final ScriptObject runtimeScope) {
431        return getCompiler(fn, actualCallSiteType, newLocals(runtimeScope), null, null);
432    }
433
434    /**
435     * Returns a code installer for installing new code. If we're using either optimistic typing or loader-per-compile,
436     * then asks for a code installer with a new class loader; otherwise just uses the current installer. We use
437     * a new class loader with optimistic typing so that deoptimized code can get reclaimed by GC.
438     * @return a code installer for installing new code.
439     */
440    private CodeInstaller<ScriptEnvironment> getInstallerForNewCode() {
441        final ScriptEnvironment env = installer.getOwner();
442        return env._optimistic_types || env._loader_per_compile ? installer.withNewLoader() : installer;
443    }
444
445    Compiler getCompiler(final FunctionNode functionNode, final MethodType actualCallSiteType,
446            final ScriptObject runtimeScope, final Map<Integer, Type> invalidatedProgramPoints,
447            final int[] continuationEntryPoints) {
448        final TypeMap typeMap = typeMap(actualCallSiteType);
449        final Type[] paramTypes = typeMap == null ? null : typeMap.getParameterTypes(functionNodeId);
450        final Object typeInformationFile = OptimisticTypesPersistence.getLocationDescriptor(source, functionNodeId, paramTypes);
451        final Context context = Context.getContextTrusted();
452        return new Compiler(
453                context,
454                context.getEnv(),
455                getInstallerForNewCode(),
456                functionNode.getSource(),  // source
457                context.getErrorManager(),
458                isStrict() | functionNode.isStrict(), // is strict
459                true,       // is on demand
460                this,       // compiledFunction, i.e. this RecompilableScriptFunctionData
461                typeMap,    // type map
462                getEffectiveInvalidatedProgramPoints(invalidatedProgramPoints, typeInformationFile), // invalidated program points
463                typeInformationFile,
464                continuationEntryPoints, // continuation entry points
465                runtimeScope); // runtime scope
466    }
467
468    /**
469     * If the function being compiled already has its own invalidated program points map, use it. Otherwise, attempt to
470     * load invalidated program points map from the persistent type info cache.
471     * @param invalidatedProgramPoints the function's current invalidated program points map. Null if the function
472     * doesn't have it.
473     * @param typeInformationFile the object describing the location of the persisted type information.
474     * @return either the existing map, or a loaded map from the persistent type info cache, or a new empty map if
475     * neither an existing map or a persistent cached type info is available.
476     */
477    @SuppressWarnings("unused")
478    private static Map<Integer, Type> getEffectiveInvalidatedProgramPoints(
479            final Map<Integer, Type> invalidatedProgramPoints, final Object typeInformationFile) {
480        if(invalidatedProgramPoints != null) {
481            return invalidatedProgramPoints;
482        }
483        final Map<Integer, Type> loadedProgramPoints = OptimisticTypesPersistence.load(typeInformationFile);
484        return loadedProgramPoints != null ? loadedProgramPoints : new TreeMap<Integer, Type>();
485    }
486
487    private FunctionInitializer compileTypeSpecialization(final MethodType actualCallSiteType, final ScriptObject runtimeScope, final boolean persist) {
488        // We're creating an empty script object for holding local variables. AssignSymbols will populate it with
489        // explicit Undefined values for undefined local variables (see AssignSymbols#defineSymbol() and
490        // CompilationEnvironment#declareLocalSymbol()).
491
492        if (log.isEnabled()) {
493            log.info("Parameter type specialization of '", functionName, "' signature: ", actualCallSiteType);
494        }
495
496        final boolean persistentCache = usePersistentCodeCache() && persist;
497        String cacheKey = null;
498        if (persistentCache) {
499            final TypeMap typeMap = typeMap(actualCallSiteType);
500            final Type[] paramTypes = typeMap == null ? null : typeMap.getParameterTypes(functionNodeId);
501            cacheKey = CodeStore.getCacheKey(functionNodeId, paramTypes);
502            final CodeInstaller<ScriptEnvironment> newInstaller = getInstallerForNewCode();
503            final StoredScript script = newInstaller.loadScript(source, cacheKey);
504
505            if (script != null) {
506                Compiler.updateCompilationId(script.getCompilationId());
507                return installStoredScript(script, newInstaller);
508            }
509        }
510
511        final FunctionNode fn = reparse();
512        final Compiler compiler = getCompiler(fn, actualCallSiteType, runtimeScope);
513        final FunctionNode compiledFn = compiler.compile(fn,
514                isSerialized() ? CompilationPhases.COMPILE_ALL_SERIALIZED : CompilationPhases.COMPILE_ALL);
515
516        if (persist && !compiledFn.getFlag(FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION)) {
517            compiler.persistClassInfo(cacheKey, compiledFn);
518        }
519        return new FunctionInitializer(compiledFn, compiler.getInvalidatedProgramPoints());
520    }
521
522    private static Map<String, Class<?>> installStoredScriptClasses(final StoredScript script, final CodeInstaller<ScriptEnvironment> installer) {
523        final Map<String, Class<?>> installedClasses = new HashMap<>();
524        final Map<String, byte[]>   classBytes       = script.getClassBytes();
525        final String   mainClassName   = script.getMainClassName();
526        final byte[]   mainClassBytes  = classBytes.get(mainClassName);
527
528        final Class<?> mainClass       = installer.install(mainClassName, mainClassBytes);
529
530        installedClasses.put(mainClassName, mainClass);
531
532        for (final Map.Entry<String, byte[]> entry : classBytes.entrySet()) {
533            final String className = entry.getKey();
534            final byte[] bytecode = entry.getValue();
535
536            if (className.equals(mainClassName)) {
537                continue;
538            }
539
540            installedClasses.put(className, installer.install(className, bytecode));
541        }
542        return installedClasses;
543    }
544
545    /**
546     * Install this script using the given {@code installer}.
547     *
548     * @param script the compiled script
549     * @return the function initializer
550     */
551    private FunctionInitializer installStoredScript(final StoredScript script, final CodeInstaller<ScriptEnvironment> newInstaller) {
552        final Map<String, Class<?>> installedClasses = installStoredScriptClasses(script, newInstaller);
553
554        final Map<Integer, FunctionInitializer> initializers = script.getInitializers();
555        assert initializers != null;
556        assert initializers.size() == 1;
557        final FunctionInitializer initializer = initializers.values().iterator().next();
558
559        final Object[] constants = script.getConstants();
560        for (int i = 0; i < constants.length; i++) {
561            if (constants[i] instanceof RecompilableScriptFunctionData) {
562                // replace deserialized function data with the ones we already have
563                constants[i] = getScriptFunctionData(((RecompilableScriptFunctionData) constants[i]).getFunctionNodeId());
564            }
565        }
566
567        newInstaller.initialize(installedClasses.values(), source, constants);
568        initializer.setCode(installedClasses.get(initializer.getClassName()));
569        return initializer;
570    }
571
572    boolean usePersistentCodeCache() {
573        final ScriptEnvironment env = installer.getOwner();
574        return env._persistent_cache && env._optimistic_types;
575    }
576
577    private MethodType explicitParams(final MethodType callSiteType) {
578        if (CompiledFunction.isVarArgsType(callSiteType)) {
579            return null;
580        }
581
582        final MethodType noCalleeThisType = callSiteType.dropParameterTypes(0, 2); // (callee, this) is always in call site type
583        final int callSiteParamCount = noCalleeThisType.parameterCount();
584
585        // Widen parameters of reference types to Object as we currently don't care for specialization among reference
586        // types. E.g. call site saying (ScriptFunction, Object, String) should still link to (ScriptFunction, Object, Object)
587        final Class<?>[] paramTypes = noCalleeThisType.parameterArray();
588        boolean changed = false;
589        for (int i = 0; i < paramTypes.length; ++i) {
590            final Class<?> paramType = paramTypes[i];
591            if (!(paramType.isPrimitive() || paramType == Object.class)) {
592                paramTypes[i] = Object.class;
593                changed = true;
594            }
595        }
596        final MethodType generalized = changed ? MethodType.methodType(noCalleeThisType.returnType(), paramTypes) : noCalleeThisType;
597
598        if (callSiteParamCount < getArity()) {
599            return generalized.appendParameterTypes(Collections.<Class<?>>nCopies(getArity() - callSiteParamCount, Object.class));
600        }
601        return generalized;
602    }
603
604    private FunctionNode extractFunctionFromScript(final FunctionNode script) {
605        final Set<FunctionNode> fns = new HashSet<>();
606        script.getBody().accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
607            @Override
608            public boolean enterFunctionNode(final FunctionNode fn) {
609                fns.add(fn);
610                return false;
611            }
612        });
613        assert fns.size() == 1 : "got back more than one method in recompilation";
614        final FunctionNode f = fns.iterator().next();
615        assert f.getId() == functionNodeId;
616        if (!getFunctionFlag(FunctionNode.IS_DECLARED) && f.isDeclared()) {
617            return f.clearFlag(null, FunctionNode.IS_DECLARED);
618        }
619        return f;
620    }
621
622    private void logLookup(final boolean shouldLog, final MethodType targetType) {
623        if (shouldLog && log.isEnabled()) {
624            log.info("Looking up ", DebugLogger.quote(functionName), " type=", targetType);
625        }
626    }
627
628    private MethodHandle lookup(final FunctionInitializer fnInit, final boolean shouldLog) {
629        final MethodType type = fnInit.getMethodType();
630        logLookup(shouldLog, type);
631        return lookupCodeMethod(fnInit.getCode(), type);
632    }
633
634    MethodHandle lookup(final FunctionNode fn) {
635        final MethodType type = new FunctionSignature(fn).getMethodType();
636        logLookup(true, type);
637        return lookupCodeMethod(fn.getCompileUnit().getCode(), type);
638    }
639
640    MethodHandle lookupCodeMethod(final Class<?> codeClass, final MethodType targetType) {
641        return MH.findStatic(LOOKUP, codeClass, functionName, targetType);
642    }
643
644    /**
645     * Initializes this function data with the eagerly generated version of the code. This method can only be invoked
646     * by the compiler internals in Nashorn and is public for implementation reasons only. Attempting to invoke it
647     * externally will result in an exception.
648     *
649     * @param initializer FunctionInitializer for this data
650     */
651    public void initializeCode(final FunctionInitializer initializer) {
652        // Since the method is public, we double-check that we aren't invoked with an inappropriate compile unit.
653        if(!code.isEmpty()) {
654            throw new IllegalStateException(name);
655        }
656        addCode(lookup(initializer, true), null, null, initializer.getFlags());
657    }
658
659    private CompiledFunction addCode(final MethodHandle target, final Map<Integer, Type> invalidatedProgramPoints,
660                                     final MethodType callSiteType, final int fnFlags) {
661        final CompiledFunction cfn = new CompiledFunction(target, this, invalidatedProgramPoints, callSiteType, fnFlags);
662        code.add(cfn);
663        return cfn;
664    }
665
666    /**
667     * Add code with specific call site type. It will adapt the type of the looked up method handle to fit the call site
668     * type. This is necessary because even if we request a specialization that takes an "int" parameter, we might end
669     * up getting one that takes a "double" etc. because of internal function logic causes widening (e.g. assignment of
670     * a wider value to the parameter variable). However, we use the method handle type for matching subsequent lookups
671     * for the same specialization, so we must adapt the handle to the expected type.
672     * @param fnInit the function
673     * @param callSiteType the call site type
674     * @return the compiled function object, with its type matching that of the call site type.
675     */
676    private CompiledFunction addCode(final FunctionInitializer fnInit, final MethodType callSiteType) {
677        if (isVariableArity()) {
678            return addCode(lookup(fnInit, true), fnInit.getInvalidatedProgramPoints(), callSiteType, fnInit.getFlags());
679        }
680
681        final MethodHandle handle = lookup(fnInit, true);
682        final MethodType fromType = handle.type();
683        MethodType toType = needsCallee(fromType) ? callSiteType.changeParameterType(0, ScriptFunction.class) : callSiteType.dropParameterTypes(0, 1);
684        toType = toType.changeReturnType(fromType.returnType());
685
686        final int toCount = toType.parameterCount();
687        final int fromCount = fromType.parameterCount();
688        final int minCount = Math.min(fromCount, toCount);
689        for(int i = 0; i < minCount; ++i) {
690            final Class<?> fromParam = fromType.parameterType(i);
691            final Class<?>   toParam =   toType.parameterType(i);
692            // If method has an Object parameter, but call site had String, preserve it as Object. No need to narrow it
693            // artificially. Note that this is related to how CompiledFunction.matchesCallSite() works, specifically
694            // the fact that various reference types compare to equal (see "fnType.isEquivalentTo(csType)" there).
695            if (fromParam != toParam && !fromParam.isPrimitive() && !toParam.isPrimitive()) {
696                assert fromParam.isAssignableFrom(toParam);
697                toType = toType.changeParameterType(i, fromParam);
698            }
699        }
700        if (fromCount > toCount) {
701            toType = toType.appendParameterTypes(fromType.parameterList().subList(toCount, fromCount));
702        } else if (fromCount < toCount) {
703            toType = toType.dropParameterTypes(fromCount, toCount);
704        }
705
706        return addCode(lookup(fnInit, false).asType(toType), fnInit.getInvalidatedProgramPoints(), callSiteType, fnInit.getFlags());
707    }
708
709    /**
710     * Returns the return type of a function specialization for particular parameter types.<br>
711     * <b>Be aware that the way this is implemented, it forces full materialization (compilation and installation) of
712     * code for that specialization.</b>
713     * @param callSiteType the parameter types at the call site. It must include the mandatory {@code callee} and
714     * {@code this} parameters, so it needs to start with at least {@code ScriptFunction.class} and
715     * {@code Object.class} class. Since the return type of the function is calculated from the code itself, it is
716     * irrelevant and should be set to {@code Object.class}.
717     * @param runtimeScope a current runtime scope. Can be null but when it's present it will be used as a source of
718     * current runtime values that can improve the compiler's type speculations (and thus reduce the need for later
719     * recompilations) if the specialization is not already present and thus needs to be freshly compiled.
720     * @return the return type of the function specialization.
721     */
722    public Class<?> getReturnType(final MethodType callSiteType, final ScriptObject runtimeScope) {
723        return getBest(callSiteType, runtimeScope, CompiledFunction.NO_FUNCTIONS).type().returnType();
724    }
725
726    @Override
727    synchronized CompiledFunction getBest(final MethodType callSiteType, final ScriptObject runtimeScope, final Collection<CompiledFunction> forbidden) {
728        CompiledFunction existingBest = super.getBest(callSiteType, runtimeScope, forbidden);
729        if (existingBest == null) {
730            existingBest = addCode(compileTypeSpecialization(callSiteType, runtimeScope, true), callSiteType);
731        }
732
733        assert existingBest != null;
734        //we are calling a vararg method with real args
735        boolean varArgWithRealArgs = existingBest.isVarArg() && !CompiledFunction.isVarArgsType(callSiteType);
736
737        //if the best one is an apply to call, it has to match the callsite exactly
738        //or we need to regenerate
739        if (existingBest.isApplyToCall()) {
740            final CompiledFunction best = lookupExactApplyToCall(callSiteType);
741            if (best != null) {
742                return best;
743            }
744            varArgWithRealArgs = true;
745        }
746
747        if (varArgWithRealArgs) {
748            // special case: we had an apply to call, but we failed to make it fit.
749            // Try to generate a specialized one for this callsite. It may
750            // be another apply to call specialization, or it may not, but whatever
751            // it is, it is a specialization that is guaranteed to fit
752            final FunctionInitializer fnInit = compileTypeSpecialization(callSiteType, runtimeScope, false);
753            existingBest = addCode(fnInit, callSiteType);
754        }
755
756        return existingBest;
757    }
758
759    @Override
760    boolean isRecompilable() {
761        return true;
762    }
763
764    @Override
765    public boolean needsCallee() {
766        return getFunctionFlag(FunctionNode.NEEDS_CALLEE);
767    }
768
769    /**
770     * Returns the {@link FunctionNode} flags associated with this function data.
771     * @return the {@link FunctionNode} flags associated with this function data.
772     */
773    public int getFunctionFlags() {
774        return functionFlags;
775    }
776
777    @Override
778    MethodType getGenericType() {
779        // 2 is for (callee, this)
780        if (isVariableArity()) {
781            return MethodType.genericMethodType(2, true);
782        }
783        return MethodType.genericMethodType(2 + getArity());
784    }
785
786    /**
787     * Return the function node id.
788     * @return the function node id
789     */
790    public int getFunctionNodeId() {
791        return functionNodeId;
792    }
793
794    /**
795     * Get the source for the script
796     * @return source
797     */
798    public Source getSource() {
799        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