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