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