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.lang.invoke.MethodHandle; 32 import java.lang.invoke.MethodHandles; 33 import java.lang.invoke.MethodType; 34 import java.util.Collections; 35 import java.util.HashMap; 36 import java.util.HashSet; 37 import java.util.Map; 38 import java.util.Set; 39 import java.util.TreeMap; 40 import jdk.internal.dynalink.support.NameCodec; 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.ObjectClassGenerator.AllocatorDescriptor; 46 import jdk.nashorn.internal.codegen.OptimisticTypesPersistence; 47 import jdk.nashorn.internal.codegen.TypeMap; 48 import jdk.nashorn.internal.codegen.types.Type; 49 import jdk.nashorn.internal.ir.FunctionNode; 50 import jdk.nashorn.internal.ir.LexicalContext; 51 import jdk.nashorn.internal.ir.visitor.NodeVisitor; 52 import jdk.nashorn.internal.objects.Global; 53 import jdk.nashorn.internal.parser.Parser; 54 import jdk.nashorn.internal.parser.Token; 55 import jdk.nashorn.internal.parser.TokenType; 56 import jdk.nashorn.internal.runtime.logging.DebugLogger; 57 import jdk.nashorn.internal.runtime.logging.Loggable; 58 import 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") 66 public 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("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 String mainClassName = script.getMainClassName(); 495 final byte[] mainClassBytes = script.getClassBytes().get(mainClassName); 496 497 final Class<?> mainClass = installer.install(mainClassName, mainClassBytes); 498 499 installedClasses.put(mainClassName, mainClass); 500 501 for (final Map.Entry<String, byte[]> entry : script.getClassBytes().entrySet()) { 502 final String className = entry.getKey(); 503 final byte[] code = entry.getValue(); 504 505 if (className.equals(mainClassName)) { 506 continue; 507 } 508 509 installedClasses.put(className, installer.install(className, code)); 510 } 511 512 final Map<Integer, FunctionInitializer> initializers = script.getInitializers(); 513 assert initializers != null; 514 assert initializers.size() == 1; 515 final FunctionInitializer initializer = initializers.values().iterator().next(); 516 517 final Object[] constants = script.getConstants(); 518 for (int i = 0; i < constants.length; i++) { 519 if (constants[i] instanceof RecompilableScriptFunctionData) { 520 // replace deserialized function data with the ones we already have 521 constants[i] = getScriptFunctionData(((RecompilableScriptFunctionData) constants[i]).getFunctionNodeId()); 522 } 523 } 524 525 installer.initialize(installedClasses.values(), source, constants); 526 initializer.setCode(installedClasses.get(initializer.getClassName())); 527 return initializer; 528 } 529 530 boolean usePersistentCodeCache() { 531 final ScriptEnvironment env = installer.getOwner(); 532 return env._persistent_cache && env._optimistic_types; 533 } 534 535 private MethodType explicitParams(final MethodType callSiteType) { 536 if (CompiledFunction.isVarArgsType(callSiteType)) { 537 return null; 538 } 539 540 final MethodType noCalleeThisType = callSiteType.dropParameterTypes(0, 2); // (callee, this) is always in call site type 541 final int callSiteParamCount = noCalleeThisType.parameterCount(); 542 543 // Widen parameters of reference types to Object as we currently don't care for specialization among reference 544 // types. E.g. call site saying (ScriptFunction, Object, String) should still link to (ScriptFunction, Object, Object) 545 final Class<?>[] paramTypes = noCalleeThisType.parameterArray(); 546 boolean changed = false; 547 for (int i = 0; i < paramTypes.length; ++i) { 548 final Class<?> paramType = paramTypes[i]; 549 if (!(paramType.isPrimitive() || paramType == Object.class)) { 550 paramTypes[i] = Object.class; 551 changed = true; 552 } 553 } 554 final MethodType generalized = changed ? MethodType.methodType(noCalleeThisType.returnType(), paramTypes) : noCalleeThisType; 555 556 if (callSiteParamCount < getArity()) { 557 return generalized.appendParameterTypes(Collections.<Class<?>>nCopies(getArity() - callSiteParamCount, Object.class)); 558 } 559 return generalized; 560 } 561 562 private FunctionNode extractFunctionFromScript(final FunctionNode script) { 563 final Set<FunctionNode> fns = new HashSet<>(); 564 script.getBody().accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { 565 @Override 566 public boolean enterFunctionNode(final FunctionNode fn) { 567 fns.add(fn); 568 return false; 569 } 570 }); 571 assert fns.size() == 1 : "got back more than one method in recompilation"; 572 final FunctionNode f = fns.iterator().next(); 573 assert f.getId() == functionNodeId; 574 if (!getFunctionFlag(FunctionNode.IS_DECLARED) && f.isDeclared()) { 575 return f.clearFlag(null, FunctionNode.IS_DECLARED); 576 } 577 return f; 578 } 579 580 MethodHandle lookup(final FunctionInitializer fnInit) { 581 final MethodType type = fnInit.getMethodType(); 582 return lookupCodeMethod(fnInit.getCode(), type); 583 } 584 585 MethodHandle lookup(final FunctionNode fn) { 586 final MethodType type = new FunctionSignature(fn).getMethodType(); 587 return lookupCodeMethod(fn.getCompileUnit().getCode(), type); 588 } 589 590 MethodHandle lookupCodeMethod(final Class<?> code, final MethodType targetType) { 591 log.info("Looking up ", DebugLogger.quote(name), " type=", targetType); 592 return MH.findStatic(LOOKUP, code, functionName, targetType); 593 } 594 595 /** 596 * Initializes this function data with the eagerly generated version of the code. This method can only be invoked 597 * by the compiler internals in Nashorn and is public for implementation reasons only. Attempting to invoke it 598 * externally will result in an exception. 599 */ 600 public void initializeCode(final FunctionInitializer initializer) { 601 // Since the method is public, we double-check that we aren't invoked with an inappropriate compile unit. 602 if(!code.isEmpty()) { 603 throw new IllegalStateException(name); 604 } 605 addCode(lookup(initializer), null, null, initializer.getFlags()); 606 } 607 608 private CompiledFunction addCode(final MethodHandle target, final Map<Integer, Type> invalidatedProgramPoints, 609 final MethodType callSiteType, final int fnFlags) { 610 final CompiledFunction cfn = new CompiledFunction(target, this, invalidatedProgramPoints, callSiteType, fnFlags); 611 code.add(cfn); 612 return cfn; 613 } 614 615 /** 616 * Add code with specific call site type. It will adapt the type of the looked up method handle to fit the call site 617 * type. This is necessary because even if we request a specialization that takes an "int" parameter, we might end 618 * up getting one that takes a "double" etc. because of internal function logic causes widening (e.g. assignment of 619 * a wider value to the parameter variable). However, we use the method handle type for matching subsequent lookups 620 * for the same specialization, so we must adapt the handle to the expected type. 621 * @param fnInit the function 622 * @param callSiteType the call site type 623 * @return the compiled function object, with its type matching that of the call site type. 624 */ 625 private CompiledFunction addCode(final FunctionInitializer fnInit, final MethodType callSiteType) { 626 if (isVariableArity()) { 627 return addCode(lookup(fnInit), fnInit.getInvalidatedProgramPoints(), callSiteType, fnInit.getFlags()); 628 } 629 630 final MethodHandle handle = lookup(fnInit); 631 final MethodType fromType = handle.type(); 632 MethodType toType = needsCallee(fromType) ? callSiteType.changeParameterType(0, ScriptFunction.class) : callSiteType.dropParameterTypes(0, 1); 633 toType = toType.changeReturnType(fromType.returnType()); 634 635 final int toCount = toType.parameterCount(); 636 final int fromCount = fromType.parameterCount(); 637 final int minCount = Math.min(fromCount, toCount); 638 for(int i = 0; i < minCount; ++i) { 639 final Class<?> fromParam = fromType.parameterType(i); 640 final Class<?> toParam = toType.parameterType(i); 641 // If method has an Object parameter, but call site had String, preserve it as Object. No need to narrow it 642 // artificially. Note that this is related to how CompiledFunction.matchesCallSite() works, specifically 643 // the fact that various reference types compare to equal (see "fnType.isEquivalentTo(csType)" there). 644 if (fromParam != toParam && !fromParam.isPrimitive() && !toParam.isPrimitive()) { 645 assert fromParam.isAssignableFrom(toParam); 646 toType = toType.changeParameterType(i, fromParam); 647 } 648 } 649 if (fromCount > toCount) { 650 toType = toType.appendParameterTypes(fromType.parameterList().subList(toCount, fromCount)); 651 } else if (fromCount < toCount) { 652 toType = toType.dropParameterTypes(fromCount, toCount); 653 } 654 655 return addCode(lookup(fnInit).asType(toType), fnInit.getInvalidatedProgramPoints(), callSiteType, fnInit.getFlags()); 656 } 657 658 659 @Override 660 synchronized CompiledFunction getBest(final MethodType callSiteType, final ScriptObject runtimeScope) { 661 CompiledFunction existingBest = super.getBest(callSiteType, runtimeScope); 662 if (existingBest == null) { 663 existingBest = addCode(compileTypeSpecialization(callSiteType, runtimeScope, true), callSiteType); 664 } 665 666 assert existingBest != null; 667 //we are calling a vararg method with real args 668 boolean applyToCall = existingBest.isVarArg() && !CompiledFunction.isVarArgsType(callSiteType); 669 670 //if the best one is an apply to call, it has to match the callsite exactly 671 //or we need to regenerate 672 if (existingBest.isApplyToCall()) { 673 final CompiledFunction best = lookupExactApplyToCall(callSiteType); 674 if (best != null) { 675 return best; 676 } 677 applyToCall = true; 678 } 679 680 if (applyToCall) { 681 final FunctionInitializer fnInit = compileTypeSpecialization(callSiteType, runtimeScope, false); 682 if ((fnInit.getFlags() & FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION) != 0) { //did the specialization work 683 existingBest = addCode(fnInit, callSiteType); 684 } 685 } 686 687 return existingBest; 688 } 689 690 @Override 691 boolean isRecompilable() { 692 return true; 693 } 694 695 @Override 696 public boolean needsCallee() { 697 return getFunctionFlag(FunctionNode.NEEDS_CALLEE); 698 } 699 700 /** 701 * Returns the {@link FunctionNode} flags associated with this function data. 702 * @return the {@link FunctionNode} flags associated with this function data. 703 */ 704 public int getFunctionFlags() { 705 return functionFlags; 706 } 707 708 @Override 709 MethodType getGenericType() { 710 // 2 is for (callee, this) 711 if (isVariableArity()) { 712 return MethodType.genericMethodType(2, true); 713 } 714 return MethodType.genericMethodType(2 + getArity()); 715 } 716 717 /** 718 * Return the function node id. 719 * @return the function node id 720 */ 721 public int getFunctionNodeId() { 722 return functionNodeId; 723 } 724 725 public Source getSource() { 726 return source; 727 } 728 729 /** 730 * Return a script function data based on a function id, either this function if 731 * the id matches or a nested function based on functionId. This goes down into 732 * nested functions until all leaves are exhausted. 733 * 734 * @param functionId function id 735 * @return script function data or null if invalid id 736 */ 737 public RecompilableScriptFunctionData getScriptFunctionData(final int functionId) { 738 if (functionId == functionNodeId) { 739 return this; 740 } 741 RecompilableScriptFunctionData data; 742 743 data = nestedFunctions == null ? null : nestedFunctions.get(functionId); 744 if (data != null) { 745 return data; 746 } 747 for (final RecompilableScriptFunctionData ndata : nestedFunctions.values()) { 748 data = ndata.getScriptFunctionData(functionId); 749 if (data != null) { 750 return data; 751 } 752 } 753 return null; 754 } 755 756 /** 757 * Check whether a certain name is a global symbol, i.e. only exists as defined 758 * in outermost scope and not shadowed by being parameter or assignment in inner 759 * scopes 760 * 761 * @param functionNode function node to check 762 * @param symbolName symbol name 763 * @return true if global symbol 764 */ 765 public boolean isGlobalSymbol(final FunctionNode functionNode, final String symbolName) { 766 RecompilableScriptFunctionData data = getScriptFunctionData(functionNode.getId()); 767 assert data != null; 768 769 do { 770 if (data.hasInternalSymbol(symbolName)) { 771 return false; 772 } 773 data = data.getParent(); 774 } while(data != null); 775 776 return true; 777 } 778 779 private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { 780 in.defaultReadObject(); 781 createLogger(); 782 } 783 784 private void createLogger() { 785 log = initLogger(Context.getContextTrusted()); 786 } 787 }