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