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