aboutsummaryrefslogtreecommitdiff
path: root/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java')
-rw-r--r--src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java1054
1 files changed, 0 insertions, 1054 deletions
diff --git a/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java b/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java
deleted file mode 100644
index 41be82a4..00000000
--- a/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java
+++ /dev/null
@@ -1,1054 +0,0 @@
-/*
- * 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.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import jdk.internal.dynalink.support.NameCodec;
-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.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;
- }
- 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), isPropertyAccessor());
- // 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(new Runnable() {
- @Override
- public void run() {
- 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, 1L, TimeUnit.MINUTES, new LinkedBlockingDeque<Runnable>(),
- new ThreadFactory() {
- @Override
- public Thread newThread(final Runnable 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<Symbol, Boolean>()) : 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.withNewLoader() : 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());
- }
-}