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