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