src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java

Wed, 05 Nov 2014 12:34:06 +0100

author
lagergren
date
Wed, 05 Nov 2014 12:34:06 +0100
changeset 1086
d0b26e6f602c
parent 1082
e1e27c4262be
child 1096
568ec2feb228
permissions
-rw-r--r--

8057825: Bug in apply specialization - if an apply specialization that is available doesn't fit, a new one wouldn't be installed, if the new code generated as a specialization didn't manage to do the apply specialization. Basically changing a conditional to an unconditional.
Reviewed-by: attila, hannesw

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

mercurial