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

Tue, 15 Oct 2013 17:37:47 +0200

author
hannesw
date
Tue, 15 Oct 2013 17:37:47 +0200
changeset 623
aa452eb4a5d0
parent 611
a781ea074521
child 766
06ee95f094b4
permissions
-rw-r--r--

8026367: Add a sync keyword to mozilla_compat
Reviewed-by: sundar, attila, lagergren

     1 /*
     2  * Copyright (c) 2010, 2013, 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.lang.invoke.MethodHandle;
    31 import java.lang.invoke.MethodHandles;
    32 import java.lang.invoke.MethodType;
    33 import java.util.ArrayList;
    34 import java.util.Arrays;
    35 import java.util.LinkedList;
    36 import jdk.internal.dynalink.support.NameCodec;
    38 import jdk.nashorn.internal.codegen.Compiler;
    39 import jdk.nashorn.internal.codegen.CompilerConstants;
    40 import jdk.nashorn.internal.codegen.FunctionSignature;
    41 import jdk.nashorn.internal.codegen.types.Type;
    42 import jdk.nashorn.internal.ir.FunctionNode;
    43 import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
    44 import jdk.nashorn.internal.parser.Token;
    45 import jdk.nashorn.internal.parser.TokenType;
    47 /**
    48  * This is a subclass that represents a script function that may be regenerated,
    49  * for example with specialization based on call site types, or lazily generated.
    50  * The common denominator is that it can get new invokers during its lifespan,
    51  * unlike {@code FinalScriptFunctionData}
    52  */
    53 public final class RecompilableScriptFunctionData extends ScriptFunctionData {
    55     /** FunctionNode with the code for this ScriptFunction */
    56     private FunctionNode functionNode;
    58     /** Source from which FunctionNode was parsed. */
    59     private final Source source;
    61     /** Token of this function within the source. */
    62     private final long token;
    64     /** Allocator map from makeMap() */
    65     private final PropertyMap allocatorMap;
    67     /** Code installer used for all further recompilation/specialization of this ScriptFunction */
    68     private CodeInstaller<ScriptEnvironment> installer;
    70     /** Name of class where allocator function resides */
    71     private final String allocatorClassName;
    73     /** lazily generated allocator */
    74     private MethodHandle allocator;
    76     private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
    78     /**
    79      * Used for specialization based on runtime arguments. Whenever we specialize on
    80      * callsite parameter types at runtime, we need to use a parameter type guard to
    81      * ensure that the specialized version of the script function continues to be
    82      * applicable for a particular callsite *
    83      */
    84     private static final MethodHandle PARAM_TYPE_GUARD = findOwnMH("paramTypeGuard", boolean.class, Type[].class,  Object[].class);
    86     /**
    87      * It is usually a good gamble whever we detect a runtime callsite with a double
    88      * (or java.lang.Number instance) to specialize the parameter to an integer, if the
    89      * parameter in question can be represented as one. The double typically only exists
    90      * because the compiler doesn't know any better than "a number type" and conservatively
    91      * picks doubles when it can't prove that an integer addition wouldn't overflow
    92      */
    93     private static final MethodHandle ENSURE_INT = findOwnMH("ensureInt", int.class, Object.class);
    95     /**
    96      * Constructor - public as scripts use it
    97      *
    98      * @param functionNode       functionNode that represents this function code
    99      * @param installer          installer for code regeneration versions of this function
   100      * @param allocatorClassName name of our allocator class, will be looked up dynamically if used as a constructor
   101      * @param allocatorMap       allocator map to seed instances with, when constructing
   102      */
   103     public RecompilableScriptFunctionData(final FunctionNode functionNode, final CodeInstaller<ScriptEnvironment> installer, final String allocatorClassName, final PropertyMap allocatorMap) {
   104         super(functionName(functionNode),
   105               functionNode.getParameters().size(),
   106               functionNode.isStrict(),
   107               false,
   108               true);
   110         this.functionNode       = functionNode;
   111         this.source             = functionNode.getSource();
   112         this.token              = tokenFor(functionNode);
   113         this.installer          = installer;
   114         this.allocatorClassName = allocatorClassName;
   115         this.allocatorMap       = allocatorMap;
   116     }
   118     @Override
   119     String toSource() {
   120         if (source != null && token != 0) {
   121             return source.getString(Token.descPosition(token), Token.descLength(token));
   122         }
   124         return "function " + (name == null ? "" : name) + "() { [native code] }";
   125     }
   127     @Override
   128     public String toString() {
   129         final StringBuilder sb = new StringBuilder();
   131         if (source != null) {
   132             sb.append(source.getName())
   133                 .append(':')
   134                 .append(functionNode.getLineNumber())
   135                 .append(' ');
   136         }
   138         return sb.toString() + super.toString();
   139     }
   141     private static String functionName(final FunctionNode fn) {
   142         if (fn.isAnonymous()) {
   143             return "";
   144         } else {
   145             final FunctionNode.Kind kind = fn.getKind();
   146             if (kind == FunctionNode.Kind.GETTER || kind == FunctionNode.Kind.SETTER) {
   147                 final String name = NameCodec.decode(fn.getIdent().getName());
   148                 return name.substring(4); // 4 is "get " or "set "
   149             } else {
   150                 return fn.getIdent().getName();
   151             }
   152         }
   153     }
   155     private static long tokenFor(final FunctionNode fn) {
   156         final int  position   = Token.descPosition(fn.getFirstToken());
   157         final int  length     = Token.descPosition(fn.getLastToken()) - position + Token.descLength(fn.getLastToken());
   159         return Token.toDesc(TokenType.FUNCTION, position, length);
   160     }
   162     @Override
   163     ScriptObject allocate() {
   164         try {
   165             ensureHasAllocator(); //if allocatorClass name is set to null (e.g. for bound functions) we don't even try
   166             return allocator == null ? null : (ScriptObject)allocator.invokeExact(allocatorMap);
   167         } catch (final RuntimeException | Error e) {
   168             throw e;
   169         } catch (final Throwable t) {
   170             throw new RuntimeException(t);
   171         }
   172     }
   174     private void ensureHasAllocator() throws ClassNotFoundException {
   175         if (allocator == null && allocatorClassName != null) {
   176             this.allocator = MH.findStatic(LOOKUP, Context.forStructureClass(allocatorClassName), CompilerConstants.ALLOCATE.symbolName(), MH.type(ScriptObject.class, PropertyMap.class));
   177         }
   178     }
   180     @Override
   181     protected synchronized void ensureCodeGenerated() {
   182          if (!code.isEmpty()) {
   183              return; // nothing to do, we have code, at least some.
   184          }
   186          if (functionNode.isLazy()) {
   187              Compiler.LOG.info("Trampoline hit: need to do lazy compilation of '", functionNode.getName(), "'");
   188              final Compiler compiler = new Compiler(installer);
   189              functionNode = compiler.compile(functionNode);
   190              assert !functionNode.isLazy();
   191              compiler.install(functionNode);
   193              /*
   194               * We don't need to update any flags - varArgs and needsCallee are instrincic
   195               * in the function world we need to get a destination node from the compile instead
   196               * and replace it with our function node. TODO
   197               */
   198          }
   200          /*
   201           * We can't get to this program point unless we have bytecode, either from
   202           * eager compilation or from running a lazy compile on the lines above
   203           */
   205          assert functionNode.hasState(CompilationState.EMITTED) : functionNode.getName() + " " + functionNode.getState() + " " + Debug.id(functionNode);
   207          // code exists - look it up and add it into the automatically sorted invoker list
   208          addCode(functionNode);
   210          if (! functionNode.canSpecialize()) {
   211              // allow GC to claim IR stuff that is not needed anymore
   212              functionNode = null;
   213              installer = null;
   214          }
   215     }
   217     private MethodHandle addCode(final FunctionNode fn) {
   218         return addCode(fn, null, null, null);
   219     }
   221     private MethodHandle addCode(final FunctionNode fn, final MethodType runtimeType, final MethodHandle guard, final MethodHandle fallback) {
   222         final MethodType targetType = new FunctionSignature(fn).getMethodType();
   223         MethodHandle target =
   224             MH.findStatic(
   225                     LOOKUP,
   226                     fn.getCompileUnit().getCode(),
   227                     fn.getName(),
   228                     targetType);
   230         /*
   231          * For any integer argument. a double that is representable as an integer is OK.
   232          * otherwise the guard would have failed. in that case introduce a filter that
   233          * casts the double to an integer, which we know will preserve all precision.
   234          */
   235         for (int i = 0; i < targetType.parameterCount(); i++) {
   236             if (targetType.parameterType(i) == int.class) {
   237                 //representable as int
   238                 target = MH.filterArguments(target, i, ENSURE_INT);
   239             }
   240         }
   242         MethodHandle mh = target;
   243         if (guard != null) {
   244             mh = MH.guardWithTest(MH.asCollector(guard, Object[].class, target.type().parameterCount()), MH.asType(target, fallback.type()), fallback);
   245         }
   247         final CompiledFunction cf = new CompiledFunction(runtimeType == null ? targetType : runtimeType, mh);
   248         code.add(cf);
   250         return cf.getInvoker();
   251     }
   253     private static Type runtimeType(final Object arg) {
   254         if (arg == null) {
   255             return Type.OBJECT;
   256         }
   258         final Class<?> clazz = arg.getClass();
   259         assert !clazz.isPrimitive() : "always boxed";
   260         if (clazz == Double.class) {
   261             return JSType.isRepresentableAsInt((double)arg) ? Type.INT : Type.NUMBER;
   262         } else if (clazz == Integer.class) {
   263             return Type.INT;
   264         } else if (clazz == Long.class) {
   265             return Type.LONG;
   266         } else if (clazz == String.class) {
   267             return Type.STRING;
   268         }
   269         return Type.OBJECT;
   270     }
   272     private static boolean canCoerce(final Object arg, final Type type) {
   273         Type argType = runtimeType(arg);
   274         if (Type.widest(argType, type) == type || arg == ScriptRuntime.UNDEFINED) {
   275             return true;
   276         }
   277         System.err.println(arg + " does not fit in "+ argType + " " + type + " " + arg.getClass());
   278         new Throwable().printStackTrace();
   279         return false;
   280     }
   282     @SuppressWarnings("unused")
   283     private static boolean paramTypeGuard(final Type[] paramTypes, final Object... args) {
   284         final int length = args.length;
   285         assert args.length >= paramTypes.length;
   287         //i==start, skip the this, callee params etc
   288         int start = args.length - paramTypes.length;
   289         for (int i = start; i < args.length; i++) {
   290             final Object arg = args[i];
   291             if (!canCoerce(arg, paramTypes[i - start])) {
   292                 return false;
   293             }
   294         }
   295         return true;
   296     }
   298     @SuppressWarnings("unused")
   299     private static int ensureInt(final Object arg) {
   300         if (arg instanceof Number) {
   301             return ((Number)arg).intValue();
   302         } else if (arg instanceof Undefined) {
   303             return 0;
   304         }
   305         throw new AssertionError(arg);
   306     }
   308     /**
   309      * Given the runtime callsite args, compute a method type that is equivalent to what
   310      * was passed - this is typically a lot more specific that what the compiler has been
   311      * able to deduce
   312      * @param callSiteType callsite type for the compiled callsite target
   313      * @param args runtime arguments to the compiled callsite target
   314      * @return adjusted method type, narrowed as to conform to runtime callsite type instead
   315      */
   316     private static MethodType runtimeType(final MethodType callSiteType, final Object[] args) {
   317         if (args == null) {
   318             //for example bound, or otherwise runtime arguments to callsite unavailable, then
   319             //do not change the type
   320             return callSiteType;
   321         }
   322         final Class<?>[] paramTypes = new Class<?>[callSiteType.parameterCount()];
   323         final int        start      = args.length - callSiteType.parameterCount();
   324         for (int i = start; i < args.length; i++) {
   325             paramTypes[i - start] = runtimeType(args[i]).getTypeClass();
   326         }
   327         return MH.type(callSiteType.returnType(), paramTypes);
   328     }
   330     private static ArrayList<Type> runtimeType(final MethodType mt) {
   331         final ArrayList<Type> type = new ArrayList<>();
   332         for (int i = 0; i < mt.parameterCount(); i++) {
   333             type.add(Type.typeFor(mt.parameterType(i)));
   334         }
   335         return type;
   336     }
   338     @Override
   339     synchronized MethodHandle getBestInvoker(final MethodType callSiteType, final Object[] args) {
   340         final MethodType runtimeType = runtimeType(callSiteType, args);
   341         assert runtimeType.parameterCount() == callSiteType.parameterCount();
   343         final MethodHandle mh = super.getBestInvoker(runtimeType, args);
   345         /*
   346          * Not all functions can be specialized, for example, if we deemed memory
   347          * footprint too large to store a parse snapshot, or if it is meaningless
   348          * to do so, such as e.g. for runScript
   349          */
   350         if (functionNode == null || !functionNode.canSpecialize()) {
   351             return mh;
   352         }
   354         /*
   355          * Check if best invoker is equally specific or more specific than runtime
   356          * type. In that case, we don't need further specialization, but can use
   357          * whatever we have already. We know that it will match callSiteType, or it
   358          * would not have been returned from getBestInvoker
   359          */
   360         if (!code.isLessSpecificThan(runtimeType)) {
   361             return mh;
   362         }
   364         int i;
   365         final FunctionNode snapshot = functionNode.getSnapshot();
   366         assert snapshot != null;
   368         /*
   369          * Create a list of the arg types that the compiler knows about
   370          * typically, the runtime args are a lot more specific, and we should aggressively
   371          * try to use those whenever possible
   372          * We WILL try to make an aggressive guess as possible, and add guards if needed.
   373          * For example, if the compiler can deduce that we have a number type, but the runtime
   374          * passes and int, we might still want to keep it an int, and the gamble to
   375          * check that whatever is passed is int representable usually pays off
   376          * If the compiler only knows that a parameter is an "Object", it is still worth
   377          * it to try to specialize it by looking at the runtime arg.
   378          */
   379         final LinkedList<Type> compileTimeArgs = new LinkedList<>();
   380         for (i = callSiteType.parameterCount() - 1; i >= 0 && compileTimeArgs.size() < snapshot.getParameters().size(); i--) {
   381             compileTimeArgs.addFirst(Type.typeFor(callSiteType.parameterType(i)));
   382         }
   384         /*
   385          * The classes known at compile time are a safe to generate as primitives without parameter guards
   386          * But the classes known at runtime (if more specific than compile time types) are safe to generate as primitives
   387          * IFF there are parameter guards
   388          */
   389         MethodHandle guard = null;
   390         final ArrayList<Type> runtimeParamTypes = runtimeType(runtimeType);
   391         while (runtimeParamTypes.size() > functionNode.getParameters().size()) {
   392             runtimeParamTypes.remove(0);
   393         }
   394         for (i = 0; i < compileTimeArgs.size(); i++) {
   395             final Type rparam = Type.typeFor(runtimeType.parameterType(i));
   396             final Type cparam = compileTimeArgs.get(i);
   398             if (cparam.isObject() && !rparam.isObject()) {
   399                 //check that the runtime object is still coercible to the runtime type, because compiler can't prove it's always primitive
   400                 if (guard == null) {
   401                     guard = MH.insertArguments(PARAM_TYPE_GUARD, 0, (Object)runtimeParamTypes.toArray(new Type[runtimeParamTypes.size()]));
   402                 }
   403             }
   404         }
   406         Compiler.LOG.info("Callsite specialized ", name, " runtimeType=", runtimeType, " parameters=", snapshot.getParameters(), " args=", Arrays.asList(args));
   408         assert snapshot != null;
   409         assert snapshot != functionNode;
   411         final Compiler compiler = new Compiler(installer);
   413         final FunctionNode compiledSnapshot = compiler.compile(
   414             snapshot.setHints(
   415                 null,
   416                 new Compiler.Hints(runtimeParamTypes.toArray(new Type[runtimeParamTypes.size()]))));
   418         /*
   419          * No matter how narrow your types were, they can never be narrower than Attr during recompile made them. I.e. you
   420          * can put an int into the function here, if you see it as a runtime type, but if the function uses a multiplication
   421          * on it, it will still need to be a double. At least until we have overflow checks. Similarly, if an int is
   422          * passed but it is used as a string, it makes no sense to make the parameter narrower than Object. At least until
   423          * the "different types for one symbol in difference places" work is done
   424          */
   425         compiler.install(compiledSnapshot);
   427         return addCode(compiledSnapshot, runtimeType, guard, mh);
   428     }
   430     private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
   431         return MH.findStatic(MethodHandles.lookup(), RecompilableScriptFunctionData.class, name, MH.type(rtype, types));
   432     }
   434 }

mercurial