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