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