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