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