/* * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.nashorn.internal.runtime; import static jdk.nashorn.internal.lookup.Lookup.MH; import java.io.IOException; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.ref.Reference; import java.lang.ref.SoftReference; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import jdk.nashorn.internal.codegen.Compiler; import jdk.nashorn.internal.codegen.Compiler.CompilationPhases; import jdk.nashorn.internal.codegen.CompilerConstants; import jdk.nashorn.internal.codegen.FunctionSignature; import jdk.nashorn.internal.codegen.Namespace; import jdk.nashorn.internal.codegen.OptimisticTypesPersistence; import jdk.nashorn.internal.codegen.TypeMap; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.Block; import jdk.nashorn.internal.ir.ForNode; import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.ir.IdentNode; import jdk.nashorn.internal.ir.LexicalContext; import jdk.nashorn.internal.ir.Node; import jdk.nashorn.internal.ir.SwitchNode; import jdk.nashorn.internal.ir.Symbol; import jdk.nashorn.internal.ir.TryNode; import jdk.nashorn.internal.ir.visitor.SimpleNodeVisitor; import jdk.nashorn.internal.objects.Global; import jdk.nashorn.internal.parser.Parser; import jdk.nashorn.internal.parser.Token; import jdk.nashorn.internal.parser.TokenType; import jdk.nashorn.internal.runtime.linker.NameCodec; import jdk.nashorn.internal.runtime.logging.DebugLogger; import jdk.nashorn.internal.runtime.logging.Loggable; import jdk.nashorn.internal.runtime.logging.Logger; import jdk.nashorn.internal.runtime.options.Options; /** * This is a subclass that represents a script function that may be regenerated, * for example with specialization based on call site types, or lazily generated. * The common denominator is that it can get new invokers during its lifespan, * unlike {@code FinalScriptFunctionData} */ @Logger(name="recompile") public final class RecompilableScriptFunctionData extends ScriptFunctionData implements Loggable { /** Prefix used for all recompiled script classes */ public static final String RECOMPILATION_PREFIX = "Recompilation$"; private static final ExecutorService astSerializerExecutorService = createAstSerializerExecutorService(); /** Unique function node id for this function node */ private final int functionNodeId; private final String functionName; /** The line number where this function begins. */ private final int lineNumber; /** Source from which FunctionNode was parsed. */ private transient Source source; /** * Cached form of the AST. Either a {@code SerializedAst} object used by split functions as they can't be * reparsed from source, or a soft reference to a {@code FunctionNode} for other functions (it is safe * to be cleared as they can be reparsed). */ private volatile Object cachedAst; /** Token of this function within the source. */ private final long token; /** * Represents the allocation strategy (property map, script object class, and method handle) for when * this function is used as a constructor. Note that majority of functions (those not setting any this.* * properties) will share a single canonical "default strategy" instance. */ private final AllocationStrategy allocationStrategy; /** * Opaque object representing parser state at the end of the function. Used when reparsing outer function * to help with skipping parsing inner functions. */ private final Object endParserState; /** Code installer used for all further recompilation/specialization of this ScriptFunction */ private transient CodeInstaller installer; private final Map<Integer, RecompilableScriptFunctionData> nestedFunctions; /** Id to parent function if one exists */ private RecompilableScriptFunctionData parent; /** Copy of the {@link FunctionNode} flags. */ private final int functionFlags; private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); private transient DebugLogger log; private final Map<String, Integer> externalScopeDepths; private final Set<String> internalSymbols; private static final int GET_SET_PREFIX_LENGTH = "*et ".length(); private static final long serialVersionUID = 4914839316174633726L; /** * Constructor - public as scripts use it * * @param functionNode functionNode that represents this function code * @param installer installer for code regeneration versions of this function * @param allocationStrategy strategy for the allocation behavior when this function is used as a constructor * @param nestedFunctions nested function map * @param externalScopeDepths external scope depths * @param internalSymbols internal symbols to method, defined in its scope */ public RecompilableScriptFunctionData( final FunctionNode functionNode, final CodeInstaller installer, final AllocationStrategy allocationStrategy, final Map<Integer, RecompilableScriptFunctionData> nestedFunctions, final Map<String, Integer> externalScopeDepths, final Set<String> internalSymbols) { super(functionName(functionNode), Math.min(functionNode.getParameters().size(), MAX_ARITY), getDataFlags(functionNode)); this.functionName = functionNode.getName(); this.lineNumber = functionNode.getLineNumber(); this.functionFlags = functionNode.getFlags() | (functionNode.needsCallee() ? FunctionNode.NEEDS_CALLEE : 0); this.functionNodeId = functionNode.getId(); this.source = functionNode.getSource(); this.endParserState = functionNode.getEndParserState(); this.token = tokenFor(functionNode); this.installer = installer; this.allocationStrategy = allocationStrategy; this.nestedFunctions = smallMap(nestedFunctions); this.externalScopeDepths = smallMap(externalScopeDepths); this.internalSymbols = smallSet(new HashSet<>(internalSymbols)); for (final RecompilableScriptFunctionData nfn : nestedFunctions.values()) { assert nfn.getParent() == null; nfn.setParent(this); } createLogger(); } private static <K, V> Map<K, V> smallMap(final Map<K, V> map) { if (map == null || map.isEmpty()) { return Collections.emptyMap(); } else if (map.size() == 1) { final Map.Entry<K, V> entry = map.entrySet().iterator().next(); return Collections.singletonMap(entry.getKey(), entry.getValue()); } else { return map; } } private static <T> Set<T> smallSet(final Set<T> set) { if (set == null || set.isEmpty()) { return Collections.emptySet(); } else if (set.size() == 1) { return Collections.singleton(set.iterator().next()); } else { return set; } } @Override public DebugLogger getLogger() { return log; } @Override public DebugLogger initLogger(final Context ctxt) { return ctxt.getLogger(this.getClass()); } /** * Check if a symbol is internally defined in a function. For example * if "undefined" is internally defined in the outermost program function, * it has not been reassigned or overridden and can be optimized * * @param symbolName symbol name * @return true if symbol is internal to this ScriptFunction */ public boolean hasInternalSymbol(final String symbolName) { return internalSymbols.contains(symbolName); } /** * Return the external symbol table * @param symbolName symbol name * @return the external symbol table with proto depths */ public int getExternalSymbolDepth(final String symbolName) { final Integer depth = externalScopeDepths.get(symbolName); return depth == null ? -1 : depth; } /** * Returns the names of all external symbols this function uses. * @return the names of all external symbols this function uses. */ public Set<String> getExternalSymbolNames() { return Collections.unmodifiableSet(externalScopeDepths.keySet()); } /** * Returns the opaque object representing the parser state at the end of this function's body, used to * skip parsing this function when reparsing its containing outer function. * @return the object representing the end parser state */ public Object getEndParserState() { return endParserState; } /** * Get the parent of this RecompilableScriptFunctionData. If we are * a nested function, we have a parent. Note that "null" return value * can also mean that we have a parent but it is unknown, so this can * only be used for conservative assumptions. * @return parent data, or null if non exists and also null IF UNKNOWN. */ public RecompilableScriptFunctionData getParent() { return parent; } void setParent(final RecompilableScriptFunctionData parent) { this.parent = parent; } @Override String toSource() { if (source != null && token != 0) { return source.getString(Token.descPosition(token), Token.descLength(token)); } return "function " + (name == null ? "" : name) + "() { [native code] }"; } /** * Initialize transient fields on deserialized instances * * @param src source * @param inst code installer */ public void initTransients(final Source src, final CodeInstaller inst) { if (this.source == null && this.installer == null) { this.source = src; this.installer = inst; } else if (this.source != src || !this.installer.isCompatibleWith(inst)) { // Existing values must be same as those passed as parameters throw new IllegalArgumentException(); } } @Override public String toString() { return super.toString() + '@' + functionNodeId; } @Override public String toStringVerbose() { final StringBuilder sb = new StringBuilder(); sb.append("fnId=").append(functionNodeId).append(' '); if (source != null) { sb.append(source.getName()) .append(':') .append(lineNumber) .append(' '); } return sb.toString() + super.toString(); } @Override public String getFunctionName() { return functionName; } @Override public boolean inDynamicContext() { return getFunctionFlag(FunctionNode.IN_DYNAMIC_CONTEXT); } private static String functionName(final FunctionNode fn) { if (fn.isAnonymous()) { return ""; } final FunctionNode.Kind kind = fn.getKind(); if (kind == FunctionNode.Kind.GETTER || kind == FunctionNode.Kind.SETTER) { final String name = NameCodec.decode(fn.getIdent().getName()); return name.substring(GET_SET_PREFIX_LENGTH); } return fn.getIdent().getName(); } private static long tokenFor(final FunctionNode fn) { final int position = Token.descPosition(fn.getFirstToken()); final long lastToken = Token.withDelimiter(fn.getLastToken()); // EOL uses length field to store the line number final int length = Token.descPosition(lastToken) - position + (Token.descType(lastToken) == TokenType.EOL ? 0 : Token.descLength(lastToken)); return Token.toDesc(TokenType.FUNCTION, position, length); } private static int getDataFlags(final FunctionNode functionNode) { int flags = IS_CONSTRUCTOR; if (functionNode.isStrict()) { flags |= IS_STRICT; } if (functionNode.needsCallee()) { flags |= NEEDS_CALLEE; } if (functionNode.usesThis() || functionNode.hasEval()) { flags |= USES_THIS; } if (functionNode.isVarArg()) { flags |= IS_VARIABLE_ARITY; } if (functionNode.getKind() == FunctionNode.Kind.GETTER || functionNode.getKind() == FunctionNode.Kind.SETTER) { flags |= IS_PROPERTY_ACCESSOR; } if (functionNode.isMethod() || functionNode.isClassConstructor()) { flags |= IS_ES6_METHOD; } return flags; } @Override PropertyMap getAllocatorMap(final ScriptObject prototype) { return allocationStrategy.getAllocatorMap(prototype); } @Override ScriptObject allocate(final PropertyMap map) { return allocationStrategy.allocate(map); } FunctionNode reparse() { final FunctionNode cachedFunction = getCachedAst(); if (cachedFunction != null) { assert cachedFunction.isCached(); return cachedFunction; } final int descPosition = Token.descPosition(token); final Context context = Context.getContextTrusted(); final Parser parser = new Parser( context.getEnv(), source, new Context.ThrowErrorManager(), isStrict(), // source starts at line 0, so even though lineNumber is the correct declaration line, back off // one to make it exclusive lineNumber - 1, context.getLogger(Parser.class)); if (getFunctionFlag(FunctionNode.IS_ANONYMOUS)) { parser.setFunctionName(functionName); } parser.setReparsedFunction(this); final FunctionNode program = parser.parse(CompilerConstants.PROGRAM.symbolName(), descPosition, Token.descLength(token), flags); // Parser generates a program AST even if we're recompiling a single function, so when we are only // recompiling a single function, extract it from the program. return (isProgram() ? program : extractFunctionFromScript(program)).setName(null, functionName); } private FunctionNode getCachedAst() { final Object lCachedAst = cachedAst; // Are we softly caching the AST? if (lCachedAst instanceof Reference<?>) { final FunctionNode fn = (FunctionNode)((Reference<?>)lCachedAst).get(); if (fn != null) { // Yes we are - this is fast return cloneSymbols(fn); } // Are we strongly caching a serialized AST (for split functions only)? } else if (lCachedAst instanceof SerializedAst) { final SerializedAst serializedAst = (SerializedAst)lCachedAst; // Even so, are we also softly caching the AST? final FunctionNode cachedFn = serializedAst.cachedAst.get(); if (cachedFn != null) { // Yes we are - this is fast return cloneSymbols(cachedFn); } final FunctionNode deserializedFn = deserialize(serializedAst.serializedAst); // Softly cache after deserialization, maybe next time we won't need to deserialize serializedAst.cachedAst = new SoftReference<>(deserializedFn); return deserializedFn; } // No cached representation; return null for reparsing return null; } /** * Sets the AST to cache in this function * @param astToCache the new AST to cache */ public void setCachedAst(final FunctionNode astToCache) { assert astToCache.getId() == functionNodeId; // same function assert !(cachedAst instanceof SerializedAst); // Can't overwrite serialized AST final boolean isSplit = astToCache.isSplit(); // If we're caching a split function, we're doing it in the eager pass, hence there can be no other // cached representation already. In other words, isSplit implies cachedAst == null. assert !isSplit || cachedAst == null; // final FunctionNode symbolClonedAst = cloneSymbols(astToCache); final Reference<FunctionNode> ref = new SoftReference<>(symbolClonedAst); cachedAst = ref; // Asynchronously serialize split functions. if (isSplit) { astSerializerExecutorService.execute(() -> { cachedAst = new SerializedAst(symbolClonedAst, ref); }); } } /** * Creates the AST serializer executor service used for in-memory serialization of split functions' ASTs. * It is created with an unbounded queue (so it can queue any number of pending tasks). Its core and max * threads is the same, but they are all allowed to time out so when there's no work, they can all go * away. The threads will be daemons, and they will time out if idle for a minute. Their priority is also * slightly lower than normal priority as we'd prefer the CPU to keep running the program; serializing * split function is a memory conservation measure (it allows us to release the AST), it can wait a bit. * @return an executor service with above described characteristics. */ private static ExecutorService createAstSerializerExecutorService() { final int threads = Math.max(1, Options.getIntProperty("nashorn.serialize.threads", Runtime.getRuntime().availableProcessors() / 2)); final ThreadPoolExecutor service = new ThreadPoolExecutor(threads, threads, 1, TimeUnit.MINUTES, new LinkedBlockingDeque<>(), (r) -> { final Thread t = new Thread(r, "Nashorn AST Serializer"); t.setDaemon(true); t.setPriority(Thread.NORM_PRIORITY - 1); return t; }); service.allowCoreThreadTimeOut(true); return service; } /** * A tuple of a serialized AST and a soft reference to a deserialized AST. This is used to cache split * functions. Since split functions are altered from their source form, they can't be reparsed from * source. While we could just use the {@code byte[]} representation in {@link RecompilableScriptFunctionData#cachedAst} * we're using this tuple instead to also keep a deserialized AST around in memory to cut down on * deserialization costs. */ private static class SerializedAst { private final byte[] serializedAst; private volatile Reference<FunctionNode> cachedAst; SerializedAst(final FunctionNode fn, final Reference<FunctionNode> cachedAst) { this.serializedAst = AstSerializer.serialize(fn); this.cachedAst = cachedAst; } } private FunctionNode deserialize(final byte[] serializedAst) { final ScriptEnvironment env = installer.getContext().getEnv(); final Timing timing = env._timing; final long t1 = System.nanoTime(); try { return AstDeserializer.deserialize(serializedAst).initializeDeserialized(source, new Namespace(env.getNamespace())); } finally { timing.accumulateTime("'Deserialize'", System.nanoTime() - t1); } } private FunctionNode cloneSymbols(final FunctionNode fn) { final IdentityHashMap<Symbol, Symbol> symbolReplacements = new IdentityHashMap<>(); final boolean cached = fn.isCached(); // blockDefinedSymbols is used to re-mark symbols defined outside the function as global. We only // need to do this when we cache an eagerly parsed function (which currently means a split one, as we // don't cache non-split functions from the eager pass); those already cached, or those not split // don't need this step. final Set<Symbol> blockDefinedSymbols = fn.isSplit() && !cached ? Collections.newSetFromMap(new IdentityHashMap<>()) : null; FunctionNode newFn = (FunctionNode)fn.accept(new SimpleNodeVisitor() { private Symbol getReplacement(final Symbol original) { if (original == null) { return null; } final Symbol existingReplacement = symbolReplacements.get(original); if (existingReplacement != null) { return existingReplacement; } final Symbol newReplacement = original.clone(); symbolReplacements.put(original, newReplacement); return newReplacement; } @Override public Node leaveIdentNode(final IdentNode identNode) { final Symbol oldSymbol = identNode.getSymbol(); if (oldSymbol != null) { final Symbol replacement = getReplacement(oldSymbol); return identNode.setSymbol(replacement); } return identNode; } @Override public Node leaveForNode(final ForNode forNode) { return ensureUniqueLabels(forNode.setIterator(lc, getReplacement(forNode.getIterator()))); } @Override public Node leaveSwitchNode(final SwitchNode switchNode) { return ensureUniqueLabels(switchNode.setTag(lc, getReplacement(switchNode.getTag()))); } @Override public Node leaveTryNode(final TryNode tryNode) { return ensureUniqueLabels(tryNode.setException(lc, getReplacement(tryNode.getException()))); } @Override public boolean enterBlock(final Block block) { for(final Symbol symbol: block.getSymbols()) { final Symbol replacement = getReplacement(symbol); if (blockDefinedSymbols != null) { blockDefinedSymbols.add(replacement); } } return true; } @Override public Node leaveBlock(final Block block) { return ensureUniqueLabels(block.replaceSymbols(lc, symbolReplacements)); } @Override public Node leaveFunctionNode(final FunctionNode functionNode) { return functionNode.setParameters(lc, functionNode.visitParameters(this)); } @Override protected Node leaveDefault(final Node node) { return ensureUniqueLabels(node); }; private Node ensureUniqueLabels(final Node node) { // If we're returning a cached AST, we must also ensure unique labels return cached ? node.ensureUniqueLabels(lc) : node; } }); if (blockDefinedSymbols != null) { // Mark all symbols not defined in blocks as globals Block newBody = null; for(final Symbol symbol: symbolReplacements.values()) { if(!blockDefinedSymbols.contains(symbol)) { assert symbol.isScope(); // must be scope assert externalScopeDepths.containsKey(symbol.getName()); // must be known to us as an external // Register it in the function body symbol table as a new global symbol symbol.setFlags((symbol.getFlags() & ~Symbol.KINDMASK) | Symbol.IS_GLOBAL); if (newBody == null) { newBody = newFn.getBody().copyWithNewSymbols(); newFn = newFn.setBody(null, newBody); } assert newBody.getExistingSymbol(symbol.getName()) == null; // must not be defined in the body already newBody.putSymbol(symbol); } } } return newFn.setCached(null); } private boolean getFunctionFlag(final int flag) { return (functionFlags & flag) != 0; } private boolean isProgram() { return getFunctionFlag(FunctionNode.IS_PROGRAM); } TypeMap typeMap(final MethodType fnCallSiteType) { if (fnCallSiteType == null) { return null; } if (CompiledFunction.isVarArgsType(fnCallSiteType)) { return null; } return new TypeMap(functionNodeId, explicitParams(fnCallSiteType), needsCallee()); } private static ScriptObject newLocals(final ScriptObject runtimeScope) { final ScriptObject locals = Global.newEmptyInstance(); locals.setProto(runtimeScope); return locals; } private Compiler getCompiler(final FunctionNode fn, final MethodType actualCallSiteType, final ScriptObject runtimeScope) { return getCompiler(fn, actualCallSiteType, newLocals(runtimeScope), null, null); } /** * Returns a code installer for installing new code. If we're using either optimistic typing or loader-per-compile, * then asks for a code installer with a new class loader; otherwise just uses the current installer. We use * a new class loader with optimistic typing so that deoptimized code can get reclaimed by GC. * @return a code installer for installing new code. */ private CodeInstaller getInstallerForNewCode() { final ScriptEnvironment env = installer.getContext().getEnv(); return env._optimistic_types || env._loader_per_compile ? installer.getOnDemandCompilationInstaller() : installer; } Compiler getCompiler(final FunctionNode functionNode, final MethodType actualCallSiteType, final ScriptObject runtimeScope, final Map<Integer, Type> invalidatedProgramPoints, final int[] continuationEntryPoints) { final TypeMap typeMap = typeMap(actualCallSiteType); final Type[] paramTypes = typeMap == null ? null : typeMap.getParameterTypes(functionNodeId); final Object typeInformationFile = OptimisticTypesPersistence.getLocationDescriptor(source, functionNodeId, paramTypes); return Compiler.forOnDemandCompilation( getInstallerForNewCode(), functionNode.getSource(), // source isStrict() | functionNode.isStrict(), // is strict this, // compiledFunction, i.e. this RecompilableScriptFunctionData typeMap, // type map getEffectiveInvalidatedProgramPoints(invalidatedProgramPoints, typeInformationFile), // invalidated program points typeInformationFile, continuationEntryPoints, // continuation entry points runtimeScope); // runtime scope } /** * If the function being compiled already has its own invalidated program points map, use it. Otherwise, attempt to * load invalidated program points map from the persistent type info cache. * @param invalidatedProgramPoints the function's current invalidated program points map. Null if the function * doesn't have it. * @param typeInformationFile the object describing the location of the persisted type information. * @return either the existing map, or a loaded map from the persistent type info cache, or a new empty map if * neither an existing map or a persistent cached type info is available. */ @SuppressWarnings("unused") private static Map<Integer, Type> getEffectiveInvalidatedProgramPoints( final Map<Integer, Type> invalidatedProgramPoints, final Object typeInformationFile) { if(invalidatedProgramPoints != null) { return invalidatedProgramPoints; } final Map<Integer, Type> loadedProgramPoints = OptimisticTypesPersistence.load(typeInformationFile); return loadedProgramPoints != null ? loadedProgramPoints : new TreeMap<Integer, Type>(); } private FunctionInitializer compileTypeSpecialization(final MethodType actualCallSiteType, final ScriptObject runtimeScope, final boolean persist) { // We're creating an empty script object for holding local variables. AssignSymbols will populate it with // explicit Undefined values for undefined local variables (see AssignSymbols#defineSymbol() and // CompilationEnvironment#declareLocalSymbol()). if (log.isEnabled()) { log.info("Parameter type specialization of '", functionName, "' signature: ", actualCallSiteType); } final boolean persistentCache = persist && usePersistentCodeCache(); String cacheKey = null; if (persistentCache) { final TypeMap typeMap = typeMap(actualCallSiteType); final Type[] paramTypes = typeMap == null ? null : typeMap.getParameterTypes(functionNodeId); cacheKey = CodeStore.getCacheKey(functionNodeId, paramTypes); final CodeInstaller newInstaller = getInstallerForNewCode(); final StoredScript script = newInstaller.loadScript(source, cacheKey); if (script != null) { Compiler.updateCompilationId(script.getCompilationId()); return script.installFunction(this, newInstaller); } } final FunctionNode fn = reparse(); final Compiler compiler = getCompiler(fn, actualCallSiteType, runtimeScope); final FunctionNode compiledFn = compiler.compile(fn, fn.isCached() ? CompilationPhases.COMPILE_ALL_CACHED : CompilationPhases.COMPILE_ALL); if (persist && !compiledFn.hasApplyToCallSpecialization()) { compiler.persistClassInfo(cacheKey, compiledFn); } return new FunctionInitializer(compiledFn, compiler.getInvalidatedProgramPoints()); } boolean usePersistentCodeCache() { return installer != null && installer.getContext().getEnv()._persistent_cache; } private MethodType explicitParams(final MethodType callSiteType) { if (CompiledFunction.isVarArgsType(callSiteType)) { return null; } final MethodType noCalleeThisType = callSiteType.dropParameterTypes(0, 2); // (callee, this) is always in call site type final int callSiteParamCount = noCalleeThisType.parameterCount(); // Widen parameters of reference types to Object as we currently don't care for specialization among reference // types. E.g. call site saying (ScriptFunction, Object, String) should still link to (ScriptFunction, Object, Object) final Class<?>[] paramTypes = noCalleeThisType.parameterArray(); boolean changed = false; for (int i = 0; i < paramTypes.length; ++i) { final Class<?> paramType = paramTypes[i]; if (!(paramType.isPrimitive() || paramType == Object.class)) { paramTypes[i] = Object.class; changed = true; } } final MethodType generalized = changed ? MethodType.methodType(noCalleeThisType.returnType(), paramTypes) : noCalleeThisType; if (callSiteParamCount < getArity()) { return generalized.appendParameterTypes(Collections.<Class<?>>nCopies(getArity() - callSiteParamCount, Object.class)); } return generalized; } private FunctionNode extractFunctionFromScript(final FunctionNode script) { final Set<FunctionNode> fns = new HashSet<>(); script.getBody().accept(new SimpleNodeVisitor() { @Override public boolean enterFunctionNode(final FunctionNode fn) { fns.add(fn); return false; } }); assert fns.size() == 1 : "got back more than one method in recompilation"; final FunctionNode f = fns.iterator().next(); assert f.getId() == functionNodeId; if (!getFunctionFlag(FunctionNode.IS_DECLARED) && f.isDeclared()) { return f.clearFlag(null, FunctionNode.IS_DECLARED); } return f; } private void logLookup(final boolean shouldLog, final MethodType targetType) { if (shouldLog && log.isEnabled()) { log.info("Looking up ", DebugLogger.quote(functionName), " type=", targetType); } } private MethodHandle lookup(final FunctionInitializer fnInit, final boolean shouldLog) { final MethodType type = fnInit.getMethodType(); logLookup(shouldLog, type); return lookupCodeMethod(fnInit.getCode(), type); } MethodHandle lookup(final FunctionNode fn) { final MethodType type = new FunctionSignature(fn).getMethodType(); logLookup(true, type); return lookupCodeMethod(fn.getCompileUnit().getCode(), type); } MethodHandle lookupCodeMethod(final Class<?> codeClass, final MethodType targetType) { return MH.findStatic(LOOKUP, codeClass, functionName, targetType); } /** * Initializes this function data with the eagerly generated version of the code. This method can only be invoked * by the compiler internals in Nashorn and is public for implementation reasons only. Attempting to invoke it * externally will result in an exception. * * @param functionNode FunctionNode for this data */ public void initializeCode(final FunctionNode functionNode) { // Since the method is public, we double-check that we aren't invoked with an inappropriate compile unit. if (!code.isEmpty() || functionNode.getId() != functionNodeId || !functionNode.getCompileUnit().isInitializing(this, functionNode)) { throw new IllegalStateException(name); } addCode(lookup(functionNode), null, null, functionNode.getFlags()); } /** * Initializes this function with the given function code initializer. * @param initializer function code initializer */ void initializeCode(final FunctionInitializer initializer) { addCode(lookup(initializer, true), null, null, initializer.getFlags()); } private CompiledFunction addCode(final MethodHandle target, final Map<Integer, Type> invalidatedProgramPoints, final MethodType callSiteType, final int fnFlags) { final CompiledFunction cfn = new CompiledFunction(target, this, invalidatedProgramPoints, callSiteType, fnFlags); assert noDuplicateCode(cfn) : "duplicate code"; code.add(cfn); return cfn; } /** * Add code with specific call site type. It will adapt the type of the looked up method handle to fit the call site * type. This is necessary because even if we request a specialization that takes an "int" parameter, we might end * up getting one that takes a "double" etc. because of internal function logic causes widening (e.g. assignment of * a wider value to the parameter variable). However, we use the method handle type for matching subsequent lookups * for the same specialization, so we must adapt the handle to the expected type. * @param fnInit the function * @param callSiteType the call site type * @return the compiled function object, with its type matching that of the call site type. */ private CompiledFunction addCode(final FunctionInitializer fnInit, final MethodType callSiteType) { if (isVariableArity()) { return addCode(lookup(fnInit, true), fnInit.getInvalidatedProgramPoints(), callSiteType, fnInit.getFlags()); } final MethodHandle handle = lookup(fnInit, true); final MethodType fromType = handle.type(); MethodType toType = needsCallee(fromType) ? callSiteType.changeParameterType(0, ScriptFunction.class) : callSiteType.dropParameterTypes(0, 1); toType = toType.changeReturnType(fromType.returnType()); final int toCount = toType.parameterCount(); final int fromCount = fromType.parameterCount(); final int minCount = Math.min(fromCount, toCount); for(int i = 0; i < minCount; ++i) { final Class<?> fromParam = fromType.parameterType(i); final Class<?> toParam = toType.parameterType(i); // If method has an Object parameter, but call site had String, preserve it as Object. No need to narrow it // artificially. Note that this is related to how CompiledFunction.matchesCallSite() works, specifically // the fact that various reference types compare to equal (see "fnType.isEquivalentTo(csType)" there). if (fromParam != toParam && !fromParam.isPrimitive() && !toParam.isPrimitive()) { assert fromParam.isAssignableFrom(toParam); toType = toType.changeParameterType(i, fromParam); } } if (fromCount > toCount) { toType = toType.appendParameterTypes(fromType.parameterList().subList(toCount, fromCount)); } else if (fromCount < toCount) { toType = toType.dropParameterTypes(fromCount, toCount); } return addCode(lookup(fnInit, false).asType(toType), fnInit.getInvalidatedProgramPoints(), callSiteType, fnInit.getFlags()); } /** * Returns the return type of a function specialization for particular parameter types.<br> * <b>Be aware that the way this is implemented, it forces full materialization (compilation and installation) of * code for that specialization.</b> * @param callSiteType the parameter types at the call site. It must include the mandatory {@code callee} and * {@code this} parameters, so it needs to start with at least {@code ScriptFunction.class} and * {@code Object.class} class. Since the return type of the function is calculated from the code itself, it is * irrelevant and should be set to {@code Object.class}. * @param runtimeScope a current runtime scope. Can be null but when it's present it will be used as a source of * current runtime values that can improve the compiler's type speculations (and thus reduce the need for later * recompilations) if the specialization is not already present and thus needs to be freshly compiled. * @return the return type of the function specialization. */ public Class<?> getReturnType(final MethodType callSiteType, final ScriptObject runtimeScope) { return getBest(callSiteType, runtimeScope, CompiledFunction.NO_FUNCTIONS).type().returnType(); } @Override synchronized CompiledFunction getBest(final MethodType callSiteType, final ScriptObject runtimeScope, final Collection<CompiledFunction> forbidden, final boolean linkLogicOkay) { assert isValidCallSite(callSiteType) : callSiteType; CompiledFunction existingBest = pickFunction(callSiteType, false); if (existingBest == null) { existingBest = pickFunction(callSiteType, true); // try vararg last } if (existingBest == null) { existingBest = addCode(compileTypeSpecialization(callSiteType, runtimeScope, true), callSiteType); } assert existingBest != null; //if the best one is an apply to call, it has to match the callsite exactly //or we need to regenerate if (existingBest.isApplyToCall()) { final CompiledFunction best = lookupExactApplyToCall(callSiteType); if (best != null) { return best; } // special case: we had an apply to call, but we failed to make it fit. // Try to generate a specialized one for this callsite. It may // be another apply to call specialization, or it may not, but whatever // it is, it is a specialization that is guaranteed to fit existingBest = addCode(compileTypeSpecialization(callSiteType, runtimeScope, false), callSiteType); } return existingBest; } @Override public boolean needsCallee() { return getFunctionFlag(FunctionNode.NEEDS_CALLEE); } /** * Returns the {@link FunctionNode} flags associated with this function data. * @return the {@link FunctionNode} flags associated with this function data. */ public int getFunctionFlags() { return functionFlags; } @Override MethodType getGenericType() { // 2 is for (callee, this) if (isVariableArity()) { return MethodType.genericMethodType(2, true); } return MethodType.genericMethodType(2 + getArity()); } /** * Return the function node id. * @return the function node id */ public int getFunctionNodeId() { return functionNodeId; } /** * Get the source for the script * @return source */ public Source getSource() { return source; } /** * Return a script function data based on a function id, either this function if * the id matches or a nested function based on functionId. This goes down into * nested functions until all leaves are exhausted. * * @param functionId function id * @return script function data or null if invalid id */ public RecompilableScriptFunctionData getScriptFunctionData(final int functionId) { if (functionId == functionNodeId) { return this; } RecompilableScriptFunctionData data; data = nestedFunctions == null ? null : nestedFunctions.get(functionId); if (data != null) { return data; } for (final RecompilableScriptFunctionData ndata : nestedFunctions.values()) { data = ndata.getScriptFunctionData(functionId); if (data != null) { return data; } } return null; } /** * Check whether a certain name is a global symbol, i.e. only exists as defined * in outermost scope and not shadowed by being parameter or assignment in inner * scopes * * @param functionNode function node to check * @param symbolName symbol name * @return true if global symbol */ public boolean isGlobalSymbol(final FunctionNode functionNode, final String symbolName) { RecompilableScriptFunctionData data = getScriptFunctionData(functionNode.getId()); assert data != null; do { if (data.hasInternalSymbol(symbolName)) { return false; } data = data.getParent(); } while(data != null); return true; } /** * Restores the {@link #getFunctionFlags()} flags to a function node. During on-demand compilation, we might need * to restore flags to a function node that was otherwise not subjected to a full compile pipeline (e.g. its parse * was skipped, or it's a nested function of a deserialized function. * @param lc current lexical context * @param fn the function node to restore flags onto * @return the transformed function node */ public FunctionNode restoreFlags(final LexicalContext lc, final FunctionNode fn) { assert fn.getId() == functionNodeId; FunctionNode newFn = fn.setFlags(lc, functionFlags); // This compensates for missing markEval() in case the function contains an inner function // that contains eval(), that now we didn't discover since we skipped the inner function. if (newFn.hasNestedEval()) { assert newFn.hasScopeBlock(); newFn = newFn.setBody(lc, newFn.getBody().setNeedsScope(null)); } return newFn; } // Make sure code does not contain a compiled function with the same signature as compiledFunction private boolean noDuplicateCode(final CompiledFunction compiledFunction) { for (final CompiledFunction cf : code) { if (cf.type().equals(compiledFunction.type())) { return false; } } return true; } private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); createLogger(); } private void createLogger() { log = initLogger(Context.getContextTrusted()); } }