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