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