RecompilableScriptFunctionData.java revision 1105:3d7f49505033
1283766Sgrembo/* 2283766Sgrembo * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved. 3283766Sgrembo * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4283766Sgrembo * 5283766Sgrembo * This code is free software; you can redistribute it and/or modify it 6283766Sgrembo * under the terms of the GNU General Public License version 2 only, as 7283766Sgrembo * published by the Free Software Foundation. Oracle designates this 8283766Sgrembo * particular file as subject to the "Classpath" exception as provided 9283766Sgrembo * by Oracle in the LICENSE file that accompanied this code. 10283766Sgrembo * 11283766Sgrembo * This code is distributed in the hope that it will be useful, but WITHOUT 12283766Sgrembo * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13283766Sgrembo * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14283766Sgrembo * version 2 for more details (a copy is included in the LICENSE file that 15283766Sgrembo * accompanied this code). 16283766Sgrembo * 17283766Sgrembo * You should have received a copy of the GNU General Public License version 18283766Sgrembo * 2 along with this work; if not, write to the Free Software Foundation, 19283766Sgrembo * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20283766Sgrembo * 21283766Sgrembo * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22283766Sgrembo * or visit www.oracle.com if you need additional information or have any 23283766Sgrembo * questions. 24283766Sgrembo */ 25283766Sgrembo 26283766Sgrembopackage jdk.nashorn.internal.runtime; 27283766Sgrembo 28283766Sgremboimport static jdk.nashorn.internal.lookup.Lookup.MH; 29283766Sgremboimport java.io.IOException; 30283766Sgremboimport java.lang.invoke.MethodHandle; 31283766Sgremboimport java.lang.invoke.MethodHandles; 32283766Sgremboimport java.lang.invoke.MethodType; 33283766Sgremboimport java.util.Collection; 34283766Sgremboimport java.util.Collections; 35283766Sgremboimport java.util.HashMap; 36283766Sgremboimport java.util.HashSet; 37283766Sgremboimport java.util.Map; 38283766Sgremboimport java.util.Set; 39283766Sgremboimport java.util.TreeMap; 40310519Savgimport jdk.internal.dynalink.support.NameCodec; 41283766Sgremboimport jdk.nashorn.internal.codegen.Compiler; 42283766Sgremboimport jdk.nashorn.internal.codegen.Compiler.CompilationPhases; 43284803Sgremboimport jdk.nashorn.internal.codegen.CompilerConstants; 44283766Sgremboimport jdk.nashorn.internal.codegen.FunctionSignature; 45283766Sgremboimport jdk.nashorn.internal.codegen.Namespace; 46283766Sgremboimport jdk.nashorn.internal.codegen.ObjectClassGenerator.AllocatorDescriptor; 47283766Sgremboimport jdk.nashorn.internal.codegen.OptimisticTypesPersistence; 48283766Sgremboimport jdk.nashorn.internal.codegen.TypeMap; 49283766Sgremboimport jdk.nashorn.internal.codegen.types.Type; 50283766Sgremboimport jdk.nashorn.internal.ir.FunctionNode; 51283766Sgremboimport jdk.nashorn.internal.ir.LexicalContext; 52283766Sgremboimport jdk.nashorn.internal.ir.visitor.NodeVisitor; 53284803Sgremboimport jdk.nashorn.internal.objects.Global; 54283766Sgremboimport jdk.nashorn.internal.parser.Parser; 55283766Sgremboimport jdk.nashorn.internal.parser.Token; 56283766Sgremboimport jdk.nashorn.internal.parser.TokenType; 57283766Sgremboimport jdk.nashorn.internal.runtime.logging.DebugLogger; 58283766Sgremboimport jdk.nashorn.internal.runtime.logging.Loggable; 59283766Sgremboimport jdk.nashorn.internal.runtime.logging.Logger; 60283766Sgrembo/** 61283766Sgrembo * This is a subclass that represents a script function that may be regenerated, 62283766Sgrembo * for example with specialization based on call site types, or lazily generated. 63310072Savg * The common denominator is that it can get new invokers during its lifespan, 64310072Savg * unlike {@code FinalScriptFunctionData} 65283766Sgrembo */ 66283766Sgrembo@Logger(name="recompile") 67283766Sgrembopublic final class RecompilableScriptFunctionData extends ScriptFunctionData implements Loggable { 68283766Sgrembo /** Prefix used for all recompiled script classes */ 69283766Sgrembo public static final String RECOMPILATION_PREFIX = "Recompilation$"; 70283766Sgrembo 71283766Sgrembo /** Unique function node id for this function node */ 72283766Sgrembo private final int functionNodeId; 73283766Sgrembo 74283766Sgrembo private final String functionName; 75283766Sgrembo 76283766Sgrembo /** The line number where this function begins. */ 77283766Sgrembo private final int lineNumber; 78286918Sgrembo 79286918Sgrembo /** Source from which FunctionNode was parsed. */ 80283766Sgrembo private transient Source source; 81283766Sgrembo 82283766Sgrembo /** Serialized, compressed form of the AST. Used by split functions as they can't be reparsed from source. */ 83283766Sgrembo private final byte[] serializedAst; 84283766Sgrembo 85283766Sgrembo /** Token of this function within the source. */ 86283766Sgrembo private final long token; 87283766Sgrembo 88283766Sgrembo /** 89283766Sgrembo * Represents the allocation strategy (property map, script object class, and method handle) for when 90283766Sgrembo * this function is used as a constructor. Note that majority of functions (those not setting any this.* 91283766Sgrembo * properties) will share a single canonical "default strategy" instance. 92283766Sgrembo */ 93283766Sgrembo private final AllocationStrategy allocationStrategy; 94283766Sgrembo 95283766Sgrembo /** 96283766Sgrembo * Opaque object representing parser state at the end of the function. Used when reparsing outer function 97283766Sgrembo * to help with skipping parsing inner functions. 98283766Sgrembo */ 99283766Sgrembo private final Object endParserState; 100283766Sgrembo 101283766Sgrembo /** Code installer used for all further recompilation/specialization of this ScriptFunction */ 102283766Sgrembo private transient CodeInstaller<ScriptEnvironment> installer; 103283766Sgrembo 104283766Sgrembo private final Map<Integer, RecompilableScriptFunctionData> nestedFunctions; 105283766Sgrembo 106283766Sgrembo /** Id to parent function if one exists */ 107283766Sgrembo private RecompilableScriptFunctionData parent; 108283766Sgrembo 109283766Sgrembo /** Copy of the {@link FunctionNode} flags. */ 110283766Sgrembo private final int functionFlags; 111283766Sgrembo 112300990Sgrembo private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); 113300990Sgrembo 114300990Sgrembo private transient DebugLogger log; 115300990Sgrembo 116300990Sgrembo private final Map<String, Integer> externalScopeDepths; 117300990Sgrembo 118300990Sgrembo private final Set<String> internalSymbols; 119300990Sgrembo 120300990Sgrembo private static final int GET_SET_PREFIX_LENGTH = "*et ".length(); 121300990Sgrembo 122300990Sgrembo private static final long serialVersionUID = 4914839316174633726L; 123283766Sgrembo 124310072Savg /** 125283766Sgrembo * Constructor - public as scripts use it 126283766Sgrembo * 127283766Sgrembo * @param functionNode functionNode that represents this function code 128283766Sgrembo * @param installer installer for code regeneration versions of this function 129283766Sgrembo * @param allocationDescriptor descriptor for the allocation behavior when this function is used as a constructor 130283766Sgrembo * @param nestedFunctions nested function map 131283766Sgrembo * @param externalScopeDepths external scope depths 132297762Sjhb * @param internalSymbols internal symbols to method, defined in its scope 133297762Sjhb * @param serializedAst a serialized AST representation. Normally only used for split functions. 134297762Sjhb */ 135297762Sjhb public RecompilableScriptFunctionData( 136283766Sgrembo final FunctionNode functionNode, 137283766Sgrembo final CodeInstaller<ScriptEnvironment> installer, 138283766Sgrembo final AllocatorDescriptor allocationDescriptor, 139283766Sgrembo final Map<Integer, RecompilableScriptFunctionData> nestedFunctions, 140283766Sgrembo final Map<String, Integer> externalScopeDepths, 141283766Sgrembo final Set<String> internalSymbols, 142283766Sgrembo final byte[] serializedAst) { 143283766Sgrembo 144283766Sgrembo super(functionName(functionNode), 145283766Sgrembo Math.min(functionNode.getParameters().size(), MAX_ARITY), 146283766Sgrembo getDataFlags(functionNode)); 147283766Sgrembo 148283766Sgrembo this.functionName = functionNode.getName(); 149283766Sgrembo this.lineNumber = functionNode.getLineNumber(); 150283766Sgrembo this.functionFlags = functionNode.getFlags() | (functionNode.needsCallee() ? FunctionNode.NEEDS_CALLEE : 0); 151283766Sgrembo this.functionNodeId = functionNode.getId(); 152310072Savg this.source = functionNode.getSource(); 153283766Sgrembo this.endParserState = functionNode.getEndParserState(); 154283766Sgrembo this.token = tokenFor(functionNode); 155283766Sgrembo this.installer = installer; 156283766Sgrembo this.allocationStrategy = AllocationStrategy.get(allocationDescriptor); 157283766Sgrembo this.nestedFunctions = smallMap(nestedFunctions); 158283766Sgrembo this.externalScopeDepths = smallMap(externalScopeDepths); 159283766Sgrembo this.internalSymbols = smallSet(new HashSet<>(internalSymbols)); 160283766Sgrembo 161283766Sgrembo for (final RecompilableScriptFunctionData nfn : nestedFunctions.values()) { 162283766Sgrembo assert nfn.getParent() == null; 163283766Sgrembo nfn.setParent(this); 164283766Sgrembo } 165283766Sgrembo 166283766Sgrembo this.serializedAst = serializedAst; 167283766Sgrembo createLogger(); 168283766Sgrembo } 169283766Sgrembo 170283766Sgrembo private static <K, V> Map<K, V> smallMap(final Map<K, V> map) { 171283766Sgrembo if (map == null || map.isEmpty()) { 172283766Sgrembo return Collections.emptyMap(); 173283766Sgrembo } else if (map.size() == 1) { 174283766Sgrembo final Map.Entry<K, V> entry = map.entrySet().iterator().next(); 175283766Sgrembo return Collections.singletonMap(entry.getKey(), entry.getValue()); 176283766Sgrembo } else { 177283766Sgrembo return map; 178283766Sgrembo } 179283766Sgrembo } 180283766Sgrembo 181283766Sgrembo private static <T> Set<T> smallSet(final Set<T> set) { 182283766Sgrembo if (set == null || set.isEmpty()) { 183283766Sgrembo return Collections.emptySet(); 184283766Sgrembo } else if (set.size() == 1) { 185283766Sgrembo return Collections.singleton(set.iterator().next()); 186283766Sgrembo } else { 187283766Sgrembo return set; 188283766Sgrembo } 189283766Sgrembo } 190283766Sgrembo 191283766Sgrembo @Override 192283766Sgrembo public DebugLogger getLogger() { 193283766Sgrembo return log; 194283766Sgrembo } 195283766Sgrembo 196283766Sgrembo @Override 197283766Sgrembo public DebugLogger initLogger(final Context ctxt) { 198283766Sgrembo return ctxt.getLogger(this.getClass()); 199284803Sgrembo } 200283766Sgrembo 201283766Sgrembo /** 202283766Sgrembo * Check if a symbol is internally defined in a function. For example 203283766Sgrembo * if "undefined" is internally defined in the outermost program function, 204283766Sgrembo * it has not been reassigned or overridden and can be optimized 205283766Sgrembo * 206283766Sgrembo * @param symbolName symbol name 207283766Sgrembo * @return true if symbol is internal to this ScriptFunction 208283766Sgrembo */ 209283766Sgrembo 210283766Sgrembo public boolean hasInternalSymbol(final String symbolName) { 211283766Sgrembo return internalSymbols.contains(symbolName); 212283766Sgrembo } 213283766Sgrembo 214283766Sgrembo /** 215283766Sgrembo * Return the external symbol table 216283766Sgrembo * @param symbolName symbol name 217283766Sgrembo * @return the external symbol table with proto depths 218283766Sgrembo */ 219283766Sgrembo public int getExternalSymbolDepth(final String symbolName) { 220283766Sgrembo final Integer depth = externalScopeDepths.get(symbolName); 221283766Sgrembo return depth == null ? -1 : depth; 222283766Sgrembo } 223283766Sgrembo 224283766Sgrembo /** 225283766Sgrembo * Returns the names of all external symbols this function uses. 226283766Sgrembo * @return the names of all external symbols this function uses. 227283766Sgrembo */ 228283766Sgrembo public Set<String> getExternalSymbolNames() { 229283766Sgrembo return Collections.unmodifiableSet(externalScopeDepths.keySet()); 230283766Sgrembo } 231283766Sgrembo 232283766Sgrembo /** 233283766Sgrembo * Returns the opaque object representing the parser state at the end of this function's body, used to 234283766Sgrembo * skip parsing this function when reparsing its containing outer function. 235283766Sgrembo * @return the object representing the end parser state 236283766Sgrembo */ 237283766Sgrembo public Object getEndParserState() { 238310517Savg return endParserState; 239283766Sgrembo } 240283766Sgrembo 241283766Sgrembo /** 242283766Sgrembo * Get the parent of this RecompilableScriptFunctionData. If we are 243283766Sgrembo * a nested function, we have a parent. Note that "null" return value 244310517Savg * can also mean that we have a parent but it is unknown, so this can 245283766Sgrembo * only be used for conservative assumptions. 246283766Sgrembo * @return parent data, or null if non exists and also null IF UNKNOWN. 247283766Sgrembo */ 248283766Sgrembo public RecompilableScriptFunctionData getParent() { 249283766Sgrembo return parent; 250283766Sgrembo } 251283766Sgrembo 252283766Sgrembo void setParent(final RecompilableScriptFunctionData parent) { 253283766Sgrembo this.parent = parent; 254283766Sgrembo } 255283766Sgrembo 256283766Sgrembo @Override 257283766Sgrembo String toSource() { 258283766Sgrembo if (source != null && token != 0) { 259283766Sgrembo return source.getString(Token.descPosition(token), Token.descLength(token)); 260283766Sgrembo } 261283766Sgrembo 262283766Sgrembo return "function " + (name == null ? "" : name) + "() { [native code] }"; 263283766Sgrembo } 264283766Sgrembo 265283766Sgrembo /** 266283766Sgrembo * Initialize transient fields on deserialized instances 267283766Sgrembo * 268283766Sgrembo * @param src source 269283766Sgrembo * @param inst code installer 270283766Sgrembo */ 271283766Sgrembo public void initTransients(final Source src, final CodeInstaller<ScriptEnvironment> inst) { 272283766Sgrembo if (this.source == null && this.installer == null) { 273283766Sgrembo this.source = src; 274283766Sgrembo this.installer = inst; 275283766Sgrembo } else if (this.source != src || !this.installer.isCompatibleWith(inst)) { 276283766Sgrembo // Existing values must be same as those passed as parameters 277283766Sgrembo throw new IllegalArgumentException(); 278283766Sgrembo } 279283766Sgrembo } 280283766Sgrembo 281283766Sgrembo @Override 282283766Sgrembo public String toString() { 283283766Sgrembo return super.toString() + '@' + functionNodeId; 284283766Sgrembo } 285283766Sgrembo 286283766Sgrembo @Override 287283766Sgrembo public String toStringVerbose() { 288310072Savg final StringBuilder sb = new StringBuilder(); 289310072Savg 290310072Savg sb.append("fnId=").append(functionNodeId).append(' '); 291310072Savg 292310072Savg if (source != null) { 293310517Savg sb.append(source.getName()) 294310072Savg .append(':') 295310072Savg .append(lineNumber) 296310072Savg .append(' '); 297310072Savg } 298310072Savg 299310072Savg return sb.toString() + super.toString(); 300310072Savg } 301310072Savg 302310072Savg @Override 303310072Savg public String getFunctionName() { 304310072Savg return functionName; 305310072Savg } 306310072Savg 307310072Savg @Override 308310072Savg public boolean inDynamicContext() { 309310072Savg return getFunctionFlag(FunctionNode.IN_DYNAMIC_CONTEXT); 310310072Savg } 311310072Savg 312310072Savg private static String functionName(final FunctionNode fn) { 313310072Savg if (fn.isAnonymous()) { 314310072Savg return ""; 315310072Savg } 316310072Savg final FunctionNode.Kind kind = fn.getKind(); 317310072Savg if (kind == FunctionNode.Kind.GETTER || kind == FunctionNode.Kind.SETTER) { 318310072Savg final String name = NameCodec.decode(fn.getIdent().getName()); 319310072Savg return name.substring(GET_SET_PREFIX_LENGTH); 320310072Savg } 321310072Savg return fn.getIdent().getName(); 322310072Savg } 323310072Savg 324310072Savg private static long tokenFor(final FunctionNode fn) { 325310072Savg final int position = Token.descPosition(fn.getFirstToken()); 326310072Savg final long lastToken = Token.withDelimiter(fn.getLastToken()); 327310072Savg // EOL uses length field to store the line number 328310072Savg final int length = Token.descPosition(lastToken) - position + (Token.descType(lastToken) == TokenType.EOL ? 0 : Token.descLength(lastToken)); 329310072Savg 330310072Savg return Token.toDesc(TokenType.FUNCTION, position, length); 331310072Savg } 332310072Savg 333310072Savg private static int getDataFlags(final FunctionNode functionNode) { 334310072Savg int flags = IS_CONSTRUCTOR; 335310072Savg if (functionNode.isStrict()) { 336310072Savg flags |= IS_STRICT; 337310072Savg } 338310072Savg if (functionNode.needsCallee()) { 339310072Savg flags |= NEEDS_CALLEE; 340310072Savg } 341310072Savg if (functionNode.usesThis() || functionNode.hasEval()) { 342310072Savg flags |= USES_THIS; 343310072Savg } 344310072Savg if (functionNode.isVarArg()) { 345310072Savg flags |= IS_VARIABLE_ARITY; 346310072Savg } 347310072Savg return flags; 348310072Savg } 349310072Savg 350310072Savg @Override 351310072Savg PropertyMap getAllocatorMap() { 352310072Savg return allocationStrategy.getAllocatorMap(); 353310072Savg } 354310072Savg 355310072Savg @Override 356310072Savg ScriptObject allocate(final PropertyMap map) { 357310072Savg return allocationStrategy.allocate(map); 358310072Savg } 359310072Savg 360310072Savg boolean isSerialized() { 361310072Savg return serializedAst != null; 362310072Savg } 363310072Savg 364310072Savg FunctionNode reparse() { 365310072Savg if (isSerialized()) { 366310072Savg return deserialize(); 367310072Savg } 368310072Savg 369310072Savg final int descPosition = Token.descPosition(token); 370310072Savg final Context context = Context.getContextTrusted(); 371310072Savg final Parser parser = new Parser( 372310072Savg context.getEnv(), 373310072Savg source, 374310072Savg new Context.ThrowErrorManager(), 375310072Savg isStrict(), 376310072Savg // source starts at line 0, so even though lineNumber is the correct declaration line, back off 377310072Savg // one to make it exclusive 378310072Savg lineNumber - 1, 379310072Savg context.getLogger(Parser.class)); 380310072Savg 381310072Savg if (getFunctionFlag(FunctionNode.IS_ANONYMOUS)) { 382310072Savg parser.setFunctionName(functionName); 383310072Savg } 384310072Savg parser.setReparsedFunction(this); 385310072Savg 386310072Savg final FunctionNode program = parser.parse(CompilerConstants.PROGRAM.symbolName(), descPosition, 387310072Savg Token.descLength(token), true); 388310072Savg // Parser generates a program AST even if we're recompiling a single function, so when we are only 389310072Savg // recompiling a single function, extract it from the program. 390310072Savg return (isProgram() ? program : extractFunctionFromScript(program)).setName(null, functionName); 391310072Savg } 392310072Savg 393310072Savg private FunctionNode deserialize() { 394310072Savg final ScriptEnvironment env = installer.getOwner(); 395310072Savg final Timing timing = env._timing; 396310072Savg final long t1 = System.nanoTime(); 397310072Savg try { 398310072Savg return AstDeserializer.deserialize(serializedAst).initializeDeserialized(source, new Namespace(env.getNamespace())); 399310072Savg } finally { 400310072Savg timing.accumulateTime("'Deserialize'", System.nanoTime() - t1); 401310072Savg } 402310072Savg } 403310072Savg 404310072Savg private boolean getFunctionFlag(final int flag) { 405310072Savg return (functionFlags & flag) != 0; 406310072Savg } 407310072Savg 408310072Savg private boolean isProgram() { 409310072Savg return getFunctionFlag(FunctionNode.IS_PROGRAM); 410310072Savg } 411310072Savg 412310072Savg TypeMap typeMap(final MethodType fnCallSiteType) { 413310072Savg if (fnCallSiteType == null) { 414310072Savg return null; 415310072Savg } 416310072Savg 417310072Savg if (CompiledFunction.isVarArgsType(fnCallSiteType)) { 418310072Savg return null; 419310072Savg } 420310072Savg 421310072Savg return new TypeMap(functionNodeId, explicitParams(fnCallSiteType), needsCallee()); 422310072Savg } 423310072Savg 424310072Savg private static ScriptObject newLocals(final ScriptObject runtimeScope) { 425310072Savg final ScriptObject locals = Global.newEmptyInstance(); 426310072Savg locals.setProto(runtimeScope); 427310072Savg return locals; 428310072Savg } 429310072Savg 430310072Savg private Compiler getCompiler(final FunctionNode fn, final MethodType actualCallSiteType, final ScriptObject runtimeScope) { 431310072Savg return getCompiler(fn, actualCallSiteType, newLocals(runtimeScope), null, null); 432310072Savg } 433310072Savg 434310072Savg /** 435310072Savg * Returns a code installer for installing new code. If we're using either optimistic typing or loader-per-compile, 436310072Savg * then asks for a code installer with a new class loader; otherwise just uses the current installer. We use 437310072Savg * a new class loader with optimistic typing so that deoptimized code can get reclaimed by GC. 438310072Savg * @return a code installer for installing new code. 439310072Savg */ 440310072Savg private CodeInstaller<ScriptEnvironment> getInstallerForNewCode() { 441310072Savg final ScriptEnvironment env = installer.getOwner(); 442310072Savg return env._optimistic_types || env._loader_per_compile ? installer.withNewLoader() : installer; 443310072Savg } 444310072Savg 445310072Savg Compiler getCompiler(final FunctionNode functionNode, final MethodType actualCallSiteType, 446310072Savg final ScriptObject runtimeScope, final Map<Integer, Type> invalidatedProgramPoints, 447310072Savg final int[] continuationEntryPoints) { 448310072Savg final TypeMap typeMap = typeMap(actualCallSiteType); 449310072Savg final Type[] paramTypes = typeMap == null ? null : typeMap.getParameterTypes(functionNodeId); 450310072Savg final Object typeInformationFile = OptimisticTypesPersistence.getLocationDescriptor(source, functionNodeId, paramTypes); 451310072Savg final Context context = Context.getContextTrusted(); 452310072Savg return new Compiler( 453310072Savg context, 454310072Savg context.getEnv(), 455310072Savg getInstallerForNewCode(), 456310072Savg functionNode.getSource(), // source 457310072Savg context.getErrorManager(), 458310072Savg isStrict() | functionNode.isStrict(), // is strict 459310072Savg true, // is on demand 460310072Savg this, // compiledFunction, i.e. this RecompilableScriptFunctionData 461310072Savg typeMap, // type map 462310072Savg getEffectiveInvalidatedProgramPoints(invalidatedProgramPoints, typeInformationFile), // invalidated program points 463310072Savg typeInformationFile, 464310072Savg continuationEntryPoints, // continuation entry points 465310072Savg runtimeScope); // runtime scope 466310072Savg } 467310072Savg 468310072Savg /** 469310072Savg * If the function being compiled already has its own invalidated program points map, use it. Otherwise, attempt to 470310072Savg * load invalidated program points map from the persistent type info cache. 471310072Savg * @param invalidatedProgramPoints the function's current invalidated program points map. Null if the function 472310072Savg * doesn't have it. 473310072Savg * @param typeInformationFile the object describing the location of the persisted type information. 474310072Savg * @return either the existing map, or a loaded map from the persistent type info cache, or a new empty map if 475310072Savg * neither an existing map or a persistent cached type info is available. 476310072Savg */ 477310072Savg @SuppressWarnings("unused") 478310072Savg private static Map<Integer, Type> getEffectiveInvalidatedProgramPoints( 479310072Savg final Map<Integer, Type> invalidatedProgramPoints, final Object typeInformationFile) { 480310072Savg if(invalidatedProgramPoints != null) { 481310072Savg return invalidatedProgramPoints; 482310072Savg } 483310072Savg final Map<Integer, Type> loadedProgramPoints = OptimisticTypesPersistence.load(typeInformationFile); 484310072Savg return loadedProgramPoints != null ? loadedProgramPoints : new TreeMap<Integer, Type>(); 485310072Savg } 486310072Savg 487310072Savg private FunctionInitializer compileTypeSpecialization(final MethodType actualCallSiteType, final ScriptObject runtimeScope, final boolean persist) { 488310072Savg // We're creating an empty script object for holding local variables. AssignSymbols will populate it with 489310072Savg // explicit Undefined values for undefined local variables (see AssignSymbols#defineSymbol() and 490310072Savg // CompilationEnvironment#declareLocalSymbol()). 491310072Savg 492310072Savg if (log.isEnabled()) { 493310072Savg log.info("Parameter type specialization of '", functionName, "' signature: ", actualCallSiteType); 494310072Savg } 495310072Savg 496310072Savg final boolean persistentCache = usePersistentCodeCache() && persist; 497310072Savg String cacheKey = null; 498310072Savg if (persistentCache) { 499310072Savg final TypeMap typeMap = typeMap(actualCallSiteType); 500310072Savg final Type[] paramTypes = typeMap == null ? null : typeMap.getParameterTypes(functionNodeId); 501310072Savg cacheKey = CodeStore.getCacheKey(functionNodeId, paramTypes); 502310072Savg final CodeInstaller<ScriptEnvironment> newInstaller = getInstallerForNewCode(); 503310072Savg final StoredScript script = newInstaller.loadScript(source, cacheKey); 504310072Savg 505310072Savg if (script != null) { 506310072Savg Compiler.updateCompilationId(script.getCompilationId()); 507310517Savg return installStoredScript(script, newInstaller); 508310072Savg } 509310072Savg } 510310072Savg 511310072Savg final FunctionNode fn = reparse(); 512310072Savg final Compiler compiler = getCompiler(fn, actualCallSiteType, runtimeScope); 513310072Savg final FunctionNode compiledFn = compiler.compile(fn, 514310072Savg isSerialized() ? CompilationPhases.COMPILE_ALL_SERIALIZED : CompilationPhases.COMPILE_ALL); 515310072Savg 516310072Savg if (persist && !compiledFn.getFlag(FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION)) { 517283766Sgrembo compiler.persistClassInfo(cacheKey, compiledFn); 518283766Sgrembo } 519283766Sgrembo return new FunctionInitializer(compiledFn, compiler.getInvalidatedProgramPoints()); 520283766Sgrembo } 521283766Sgrembo 522283766Sgrembo private static Map<String, Class<?>> installStoredScriptClasses(final StoredScript script, final CodeInstaller<ScriptEnvironment> installer) { 523283766Sgrembo final Map<String, Class<?>> installedClasses = new HashMap<>(); 524283766Sgrembo final Map<String, byte[]> classBytes = script.getClassBytes(); 525311809Sgonzo final String mainClassName = script.getMainClassName(); 526311809Sgonzo final byte[] mainClassBytes = classBytes.get(mainClassName); 527311809Sgonzo 528339029Sgonzo final Class<?> mainClass = installer.install(mainClassName, mainClassBytes); 529339029Sgonzo 530339029Sgonzo installedClasses.put(mainClassName, mainClass); 531339029Sgonzo 532339029Sgonzo for (final Map.Entry<String, byte[]> entry : classBytes.entrySet()) { 533339029Sgonzo final String className = entry.getKey(); 534339029Sgonzo final byte[] bytecode = entry.getValue(); 535339029Sgonzo 536339029Sgonzo if (className.equals(mainClassName)) { 537339029Sgonzo continue; 538331834Sgonzo } 539331834Sgonzo 540331834Sgonzo installedClasses.put(className, installer.install(className, bytecode)); 541331834Sgonzo } 542331834Sgonzo return installedClasses; 543283766Sgrembo } 544331834Sgonzo 545331834Sgonzo /** 546331834Sgonzo * Install this script using the given {@code installer}. 547331834Sgonzo * 548331834Sgonzo * @param script the compiled script 549331834Sgonzo * @return the function initializer 550331834Sgonzo */ 551331834Sgonzo private FunctionInitializer installStoredScript(final StoredScript script, final CodeInstaller<ScriptEnvironment> newInstaller) { 552331834Sgonzo final Map<String, Class<?>> installedClasses = installStoredScriptClasses(script, newInstaller); 553331834Sgonzo 554283766Sgrembo final Map<Integer, FunctionInitializer> initializers = script.getInitializers(); 555283766Sgrembo assert initializers != null; 556331834Sgonzo assert initializers.size() == 1; 557331834Sgonzo final FunctionInitializer initializer = initializers.values().iterator().next(); 558331834Sgonzo 559331834Sgonzo final Object[] constants = script.getConstants(); 560331834Sgonzo for (int i = 0; i < constants.length; i++) { 561331834Sgonzo if (constants[i] instanceof RecompilableScriptFunctionData) { 562331834Sgonzo // replace deserialized function data with the ones we already have 563283766Sgrembo constants[i] = getScriptFunctionData(((RecompilableScriptFunctionData) constants[i]).getFunctionNodeId()); 564331834Sgonzo } 565331834Sgonzo } 566339030Sgonzo 567331834Sgonzo newInstaller.initialize(installedClasses.values(), source, constants); 568331834Sgonzo initializer.setCode(installedClasses.get(initializer.getClassName())); 569331834Sgonzo return initializer; 570283766Sgrembo } 571283766Sgrembo 572283766Sgrembo boolean usePersistentCodeCache() { 573283766Sgrembo final ScriptEnvironment env = installer.getOwner(); 574283766Sgrembo return env._persistent_cache && env._optimistic_types; 575283766Sgrembo } 576283766Sgrembo 577283766Sgrembo private MethodType explicitParams(final MethodType callSiteType) { 578283766Sgrembo if (CompiledFunction.isVarArgsType(callSiteType)) { 579283766Sgrembo return null; 580283766Sgrembo } 581283766Sgrembo 582283766Sgrembo final MethodType noCalleeThisType = callSiteType.dropParameterTypes(0, 2); // (callee, this) is always in call site type 583283766Sgrembo final int callSiteParamCount = noCalleeThisType.parameterCount(); 584283766Sgrembo 585283766Sgrembo // Widen parameters of reference types to Object as we currently don't care for specialization among reference 586283766Sgrembo // types. E.g. call site saying (ScriptFunction, Object, String) should still link to (ScriptFunction, Object, Object) 587283766Sgrembo final Class<?>[] paramTypes = noCalleeThisType.parameterArray(); 588283766Sgrembo boolean changed = false; 589283766Sgrembo for (int i = 0; i < paramTypes.length; ++i) { 590283766Sgrembo final Class<?> paramType = paramTypes[i]; 591283766Sgrembo if (!(paramType.isPrimitive() || paramType == Object.class)) { 592283766Sgrembo paramTypes[i] = Object.class; 593283766Sgrembo changed = true; 594283766Sgrembo } 595283766Sgrembo } 596283766Sgrembo final MethodType generalized = changed ? MethodType.methodType(noCalleeThisType.returnType(), paramTypes) : noCalleeThisType; 597283766Sgrembo 598284803Sgrembo if (callSiteParamCount < getArity()) { 599284803Sgrembo return generalized.appendParameterTypes(Collections.<Class<?>>nCopies(getArity() - callSiteParamCount, Object.class)); 600283766Sgrembo } 601283766Sgrembo return generalized; 602283766Sgrembo } 603283766Sgrembo 604283766Sgrembo private FunctionNode extractFunctionFromScript(final FunctionNode script) { 605283766Sgrembo final Set<FunctionNode> fns = new HashSet<>(); 606283766Sgrembo script.getBody().accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { 607283766Sgrembo @Override 608283766Sgrembo public boolean enterFunctionNode(final FunctionNode fn) { 609310072Savg fns.add(fn); 610310072Savg return false; 611310072Savg } 612283766Sgrembo }); 613283766Sgrembo assert fns.size() == 1 : "got back more than one method in recompilation"; 614283766Sgrembo final FunctionNode f = fns.iterator().next(); 615283766Sgrembo assert f.getId() == functionNodeId; 616283766Sgrembo if (!getFunctionFlag(FunctionNode.IS_DECLARED) && f.isDeclared()) { 617283766Sgrembo return f.clearFlag(null, FunctionNode.IS_DECLARED); 618283766Sgrembo } 619283766Sgrembo return f; 620331834Sgonzo } 621331834Sgonzo 622331834Sgonzo private void logLookup(final boolean shouldLog, final MethodType targetType) { 623331834Sgonzo if (shouldLog && log.isEnabled()) { 624331834Sgonzo log.info("Looking up ", DebugLogger.quote(functionName), " type=", targetType); 625331834Sgonzo } 626331834Sgonzo } 627283766Sgrembo 628283766Sgrembo private MethodHandle lookup(final FunctionInitializer fnInit, final boolean shouldLog) { 629284803Sgrembo final MethodType type = fnInit.getMethodType(); 630283766Sgrembo logLookup(shouldLog, type); 631283766Sgrembo return lookupCodeMethod(fnInit.getCode(), type); 632283766Sgrembo } 633283766Sgrembo 634284803Sgrembo MethodHandle lookup(final FunctionNode fn) { 635283766Sgrembo final MethodType type = new FunctionSignature(fn).getMethodType(); 636283766Sgrembo logLookup(true, type); 637283766Sgrembo return lookupCodeMethod(fn.getCompileUnit().getCode(), type); 638283766Sgrembo } 639283766Sgrembo 640283766Sgrembo MethodHandle lookupCodeMethod(final Class<?> codeClass, final MethodType targetType) { 641283766Sgrembo return MH.findStatic(LOOKUP, codeClass, functionName, targetType); 642283766Sgrembo } 643283766Sgrembo 644283766Sgrembo /** 645300990Sgrembo * Initializes this function data with the eagerly generated version of the code. This method can only be invoked 646300990Sgrembo * by the compiler internals in Nashorn and is public for implementation reasons only. Attempting to invoke it 647283766Sgrembo * externally will result in an exception. 648283766Sgrembo * 649283766Sgrembo * @param initializer FunctionInitializer for this data 650283766Sgrembo */ 651283766Sgrembo public void initializeCode(final FunctionInitializer initializer) { 652283766Sgrembo // Since the method is public, we double-check that we aren't invoked with an inappropriate compile unit. 653283766Sgrembo if(!code.isEmpty()) { 654283766Sgrembo throw new IllegalStateException(name); 655283766Sgrembo } 656283766Sgrembo addCode(lookup(initializer, true), null, null, initializer.getFlags()); 657283766Sgrembo } 658283766Sgrembo 659283766Sgrembo private CompiledFunction addCode(final MethodHandle target, final Map<Integer, Type> invalidatedProgramPoints, 660283766Sgrembo final MethodType callSiteType, final int fnFlags) { 661283766Sgrembo final CompiledFunction cfn = new CompiledFunction(target, this, invalidatedProgramPoints, callSiteType, fnFlags); 662283766Sgrembo code.add(cfn); 663283766Sgrembo return cfn; 664283766Sgrembo } 665283766Sgrembo 666283766Sgrembo /** 667283766Sgrembo * Add code with specific call site type. It will adapt the type of the looked up method handle to fit the call site 668283766Sgrembo * type. This is necessary because even if we request a specialization that takes an "int" parameter, we might end 669283766Sgrembo * up getting one that takes a "double" etc. because of internal function logic causes widening (e.g. assignment of 670283766Sgrembo * a wider value to the parameter variable). However, we use the method handle type for matching subsequent lookups 671283766Sgrembo * for the same specialization, so we must adapt the handle to the expected type. 672283766Sgrembo * @param fnInit the function 673283766Sgrembo * @param callSiteType the call site type 674283766Sgrembo * @return the compiled function object, with its type matching that of the call site type. 675283766Sgrembo */ 676283766Sgrembo private CompiledFunction addCode(final FunctionInitializer fnInit, final MethodType callSiteType) { 677283766Sgrembo if (isVariableArity()) { 678283766Sgrembo return addCode(lookup(fnInit, true), fnInit.getInvalidatedProgramPoints(), callSiteType, fnInit.getFlags()); 679283766Sgrembo } 680283766Sgrembo 681283766Sgrembo final MethodHandle handle = lookup(fnInit, true); 682283766Sgrembo final MethodType fromType = handle.type(); 683283766Sgrembo MethodType toType = needsCallee(fromType) ? callSiteType.changeParameterType(0, ScriptFunction.class) : callSiteType.dropParameterTypes(0, 1); 684283766Sgrembo toType = toType.changeReturnType(fromType.returnType()); 685283766Sgrembo 686310072Savg final int toCount = toType.parameterCount(); 687310072Savg final int fromCount = fromType.parameterCount(); 688283766Sgrembo final int minCount = Math.min(fromCount, toCount); 689283766Sgrembo for(int i = 0; i < minCount; ++i) { 690283766Sgrembo final Class<?> fromParam = fromType.parameterType(i); 691284803Sgrembo final Class<?> toParam = toType.parameterType(i); 692284803Sgrembo // If method has an Object parameter, but call site had String, preserve it as Object. No need to narrow it 693283766Sgrembo // artificially. Note that this is related to how CompiledFunction.matchesCallSite() works, specifically 694310072Savg // the fact that various reference types compare to equal (see "fnType.isEquivalentTo(csType)" there). 695283766Sgrembo if (fromParam != toParam && !fromParam.isPrimitive() && !toParam.isPrimitive()) { 696283766Sgrembo assert fromParam.isAssignableFrom(toParam); 697283766Sgrembo toType = toType.changeParameterType(i, fromParam); 698283766Sgrembo } 699284803Sgrembo } 700284803Sgrembo if (fromCount > toCount) { 701311809Sgonzo toType = toType.appendParameterTypes(fromType.parameterList().subList(toCount, fromCount)); 702311809Sgonzo } else if (fromCount < toCount) { 703311809Sgonzo toType = toType.dropParameterTypes(fromCount, toCount); 704311809Sgonzo } 705283766Sgrembo 706283766Sgrembo return addCode(lookup(fnInit, false).asType(toType), fnInit.getInvalidatedProgramPoints(), callSiteType, fnInit.getFlags()); 707283766Sgrembo } 708283766Sgrembo 709284803Sgrembo /** 710283766Sgrembo * Returns the return type of a function specialization for particular parameter types.<br> 711283766Sgrembo * <b>Be aware that the way this is implemented, it forces full materialization (compilation and installation) of 712283766Sgrembo * code for that specialization.</b> 713283766Sgrembo * @param callSiteType the parameter types at the call site. It must include the mandatory {@code callee} and 714283766Sgrembo * {@code this} parameters, so it needs to start with at least {@code ScriptFunction.class} and 715283766Sgrembo * {@code Object.class} class. Since the return type of the function is calculated from the code itself, it is 716283766Sgrembo * irrelevant and should be set to {@code Object.class}. 717284803Sgrembo * @param runtimeScope a current runtime scope. Can be null but when it's present it will be used as a source of 718283766Sgrembo * current runtime values that can improve the compiler's type speculations (and thus reduce the need for later 719300990Sgrembo * recompilations) if the specialization is not already present and thus needs to be freshly compiled. 720283766Sgrembo * @return the return type of the function specialization. 721283766Sgrembo */ 722283766Sgrembo public Class<?> getReturnType(final MethodType callSiteType, final ScriptObject runtimeScope) { 723283766Sgrembo return getBest(callSiteType, runtimeScope, CompiledFunction.NO_FUNCTIONS).type().returnType(); 724283766Sgrembo } 725283766Sgrembo 726283766Sgrembo @Override 727339030Sgonzo synchronized CompiledFunction getBest(final MethodType callSiteType, final ScriptObject runtimeScope, final Collection<CompiledFunction> forbidden) { 728339030Sgonzo CompiledFunction existingBest = super.getBest(callSiteType, runtimeScope, forbidden); 729339030Sgonzo if (existingBest == null) { 730339030Sgonzo existingBest = addCode(compileTypeSpecialization(callSiteType, runtimeScope, true), callSiteType); 731339030Sgonzo } 732339030Sgonzo 733339030Sgonzo assert existingBest != null; 734339030Sgonzo //we are calling a vararg method with real args 735339030Sgonzo boolean varArgWithRealArgs = existingBest.isVarArg() && !CompiledFunction.isVarArgsType(callSiteType); 736339030Sgonzo 737339030Sgonzo //if the best one is an apply to call, it has to match the callsite exactly 738339030Sgonzo //or we need to regenerate 739339030Sgonzo if (existingBest.isApplyToCall()) { 740283766Sgrembo final CompiledFunction best = lookupExactApplyToCall(callSiteType); 741284803Sgrembo if (best != null) { 742283766Sgrembo return best; 743283766Sgrembo } 744283766Sgrembo varArgWithRealArgs = true; 745283766Sgrembo } 746283766Sgrembo 747283766Sgrembo if (varArgWithRealArgs) { 748283766Sgrembo // special case: we had an apply to call, but we failed to make it fit. 749283766Sgrembo // Try to generate a specialized one for this callsite. It may 750283766Sgrembo // be another apply to call specialization, or it may not, but whatever 751283766Sgrembo // it is, it is a specialization that is guaranteed to fit 752283766Sgrembo final FunctionInitializer fnInit = compileTypeSpecialization(callSiteType, runtimeScope, false); 753283766Sgrembo existingBest = addCode(fnInit, callSiteType); 754283766Sgrembo } 755283766Sgrembo 756283766Sgrembo return existingBest; 757283766Sgrembo } 758283766Sgrembo 759283766Sgrembo @Override 760283766Sgrembo boolean isRecompilable() { 761283766Sgrembo return true; 762283766Sgrembo } 763283766Sgrembo 764283766Sgrembo @Override 765283766Sgrembo public boolean needsCallee() { 766283766Sgrembo return getFunctionFlag(FunctionNode.NEEDS_CALLEE); 767283766Sgrembo } 768283766Sgrembo 769283766Sgrembo /** 770283766Sgrembo * Returns the {@link FunctionNode} flags associated with this function data. 771283766Sgrembo * @return the {@link FunctionNode} flags associated with this function data. 772283766Sgrembo */ 773283766Sgrembo public int getFunctionFlags() { 774331834Sgonzo return functionFlags; 775331834Sgonzo } 776331834Sgonzo 777331834Sgonzo @Override 778331834Sgonzo MethodType getGenericType() { 779331834Sgonzo // 2 is for (callee, this) 780331834Sgonzo if (isVariableArity()) { 781331834Sgonzo return MethodType.genericMethodType(2, true); 782331834Sgonzo } 783331834Sgonzo return MethodType.genericMethodType(2 + getArity()); 784331834Sgonzo } 785331834Sgonzo 786331834Sgonzo /** 787331834Sgonzo * Return the function node id. 788331834Sgonzo * @return the function node id 789331834Sgonzo */ 790331834Sgonzo public int getFunctionNodeId() { 791331834Sgonzo return functionNodeId; 792331834Sgonzo } 793331834Sgonzo 794331834Sgonzo /** 795283766Sgrembo * Get the source for the script 796283766Sgrembo * @return source 797283766Sgrembo */ 798311809Sgonzo public Source getSource() { 799311809Sgonzo return source; 800 } 801 802 /** 803 * Return a script function data based on a function id, either this function if 804 * the id matches or a nested function based on functionId. This goes down into 805 * nested functions until all leaves are exhausted. 806 * 807 * @param functionId function id 808 * @return script function data or null if invalid id 809 */ 810 public RecompilableScriptFunctionData getScriptFunctionData(final int functionId) { 811 if (functionId == functionNodeId) { 812 return this; 813 } 814 RecompilableScriptFunctionData data; 815 816 data = nestedFunctions == null ? null : nestedFunctions.get(functionId); 817 if (data != null) { 818 return data; 819 } 820 for (final RecompilableScriptFunctionData ndata : nestedFunctions.values()) { 821 data = ndata.getScriptFunctionData(functionId); 822 if (data != null) { 823 return data; 824 } 825 } 826 return null; 827 } 828 829 /** 830 * Check whether a certain name is a global symbol, i.e. only exists as defined 831 * in outermost scope and not shadowed by being parameter or assignment in inner 832 * scopes 833 * 834 * @param functionNode function node to check 835 * @param symbolName symbol name 836 * @return true if global symbol 837 */ 838 public boolean isGlobalSymbol(final FunctionNode functionNode, final String symbolName) { 839 RecompilableScriptFunctionData data = getScriptFunctionData(functionNode.getId()); 840 assert data != null; 841 842 do { 843 if (data.hasInternalSymbol(symbolName)) { 844 return false; 845 } 846 data = data.getParent(); 847 } while(data != null); 848 849 return true; 850 } 851 852 /** 853 * Restores the {@link #getFunctionFlags()} flags to a function node. During on-demand compilation, we might need 854 * to restore flags to a function node that was otherwise not subjected to a full compile pipeline (e.g. its parse 855 * was skipped, or it's a nested function of a deserialized function. 856 * @param lc current lexical context 857 * @param fn the function node to restore flags onto 858 * @return the transformed function node 859 */ 860 public FunctionNode restoreFlags(final LexicalContext lc, final FunctionNode fn) { 861 assert fn.getId() == functionNodeId; 862 FunctionNode newFn = fn.setFlags(lc, functionFlags); 863 // This compensates for missing markEval() in case the function contains an inner function 864 // that contains eval(), that now we didn't discover since we skipped the inner function. 865 if (newFn.hasNestedEval()) { 866 assert newFn.hasScopeBlock(); 867 newFn = newFn.setBody(lc, newFn.getBody().setNeedsScope(null)); 868 } 869 return newFn; 870 } 871 872 private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { 873 in.defaultReadObject(); 874 createLogger(); 875 } 876 877 private void createLogger() { 878 log = initLogger(Context.getContextTrusted()); 879 } 880} 881