CompiledFunction.java revision 1211:367ac913fcb3
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 */ 25package jdk.nashorn.internal.runtime; 26 27import static jdk.nashorn.internal.lookup.Lookup.MH; 28import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; 29import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid; 30 31import java.lang.invoke.CallSite; 32import java.lang.invoke.MethodHandle; 33import java.lang.invoke.MethodHandles; 34import java.lang.invoke.MethodType; 35import java.lang.invoke.MutableCallSite; 36import java.lang.invoke.SwitchPoint; 37import java.util.ArrayList; 38import java.util.Collection; 39import java.util.Collections; 40import java.util.Iterator; 41import java.util.List; 42import java.util.Map; 43import java.util.TreeMap; 44import java.util.function.Supplier; 45import java.util.logging.Level; 46import jdk.internal.dynalink.linker.GuardedInvocation; 47import jdk.nashorn.internal.codegen.Compiler; 48import jdk.nashorn.internal.codegen.Compiler.CompilationPhases; 49import jdk.nashorn.internal.codegen.TypeMap; 50import jdk.nashorn.internal.codegen.types.ArrayType; 51import jdk.nashorn.internal.codegen.types.Type; 52import jdk.nashorn.internal.ir.FunctionNode; 53import jdk.nashorn.internal.objects.annotations.SpecializedFunction.LinkLogic; 54import jdk.nashorn.internal.runtime.events.RecompilationEvent; 55import jdk.nashorn.internal.runtime.linker.Bootstrap; 56import jdk.nashorn.internal.runtime.logging.DebugLogger; 57 58/** 59 * An version of a JavaScript function, native or JavaScript. 60 * Supports lazily generating a constructor version of the invocation. 61 */ 62final class CompiledFunction { 63 64 private static final MethodHandle NEWFILTER = findOwnMH("newFilter", Object.class, Object.class, Object.class); 65 private static final MethodHandle RELINK_COMPOSABLE_INVOKER = findOwnMH("relinkComposableInvoker", void.class, CallSite.class, CompiledFunction.class, boolean.class); 66 private static final MethodHandle HANDLE_REWRITE_EXCEPTION = findOwnMH("handleRewriteException", MethodHandle.class, CompiledFunction.class, OptimismInfo.class, RewriteException.class); 67 private static final MethodHandle RESTOF_INVOKER = MethodHandles.exactInvoker(MethodType.methodType(Object.class, RewriteException.class)); 68 69 private final DebugLogger log; 70 71 static final Collection<CompiledFunction> NO_FUNCTIONS = Collections.emptySet(); 72 73 /** 74 * The method type may be more specific than the invoker, if. e.g. 75 * the invoker is guarded, and a guard with a generic object only 76 * fallback, while the target is more specific, we still need the 77 * more specific type for sorting 78 */ 79 private MethodHandle invoker; 80 private MethodHandle constructor; 81 private OptimismInfo optimismInfo; 82 private final int flags; // from FunctionNode 83 private final MethodType callSiteType; 84 85 private final Specialization specialization; 86 87 CompiledFunction(final MethodHandle invoker) { 88 this(invoker, null, null); 89 } 90 91 static CompiledFunction createBuiltInConstructor(final MethodHandle invoker, final Specialization specialization) { 92 return new CompiledFunction(MH.insertArguments(invoker, 0, false), createConstructorFromInvoker(MH.insertArguments(invoker, 0, true)), specialization); 93 } 94 95 CompiledFunction(final MethodHandle invoker, final MethodHandle constructor, final Specialization specialization) { 96 this(invoker, constructor, 0, null, specialization, DebugLogger.DISABLED_LOGGER); 97 } 98 99 CompiledFunction(final MethodHandle invoker, final MethodHandle constructor, final int flags, final MethodType callSiteType, final Specialization specialization, final DebugLogger log) { 100 this.specialization = specialization; 101 if (specialization != null && specialization.isOptimistic()) { 102 /* 103 * An optimistic builtin with isOptimistic=true works like any optimistic generated function, i.e. it 104 * can throw unwarranted optimism exceptions. As native functions trivially can't have parts of them 105 * regenerated as restof methods, this only works if the methods are atomic/functional in their behavior 106 * and doesn't modify state before an UOE can be thrown. If they aren't, we can reexecute a wider version 107 * of the same builtin in a recompilation handler for FinalScriptFunctionData. There are several 108 * candidate methods in Native* that would benefit from this, but I haven't had time to implement any 109 * of them currently. In order to fit in with the relinking framework, the current thinking is 110 * that the methods still take a program point to fit in with other optimistic functions, but 111 * it is set to "first", which is the beginning of the method. The relinker can tell the difference 112 * between builtin and JavaScript functions. This might change. TODO 113 */ 114 this.invoker = MH.insertArguments(invoker, invoker.type().parameterCount() - 1, UnwarrantedOptimismException.FIRST_PROGRAM_POINT); 115 throw new AssertionError("Optimistic (UnwarrantedOptimismException throwing) builtin functions are currently not in use"); 116 } 117 this.invoker = invoker; 118 this.constructor = constructor; 119 this.flags = flags; 120 this.callSiteType = callSiteType; 121 this.log = log; 122 } 123 124 CompiledFunction(final MethodHandle invoker, final RecompilableScriptFunctionData functionData, 125 final Map<Integer, Type> invalidatedProgramPoints, final MethodType callSiteType, final int flags) { 126 this(invoker, null, flags, callSiteType, null, functionData.getLogger()); 127 if ((flags & FunctionNode.IS_DEOPTIMIZABLE) != 0) { 128 optimismInfo = new OptimismInfo(functionData, invalidatedProgramPoints); 129 } else { 130 optimismInfo = null; 131 } 132 } 133 134 static CompiledFunction createBuiltInConstructor(final MethodHandle invoker) { 135 return new CompiledFunction(MH.insertArguments(invoker, 0, false), createConstructorFromInvoker(MH.insertArguments(invoker, 0, true)), null); 136 } 137 138 boolean isSpecialization() { 139 return specialization != null; 140 } 141 142 boolean hasLinkLogic() { 143 return getLinkLogicClass() != null; 144 } 145 146 Class<? extends LinkLogic> getLinkLogicClass() { 147 if (isSpecialization()) { 148 final Class<? extends LinkLogic> linkLogicClass = specialization.getLinkLogicClass(); 149 assert !LinkLogic.isEmpty(linkLogicClass) : "empty link logic classes should have been removed by nasgen"; 150 return linkLogicClass; 151 } 152 return null; 153 } 154 155 int getFlags() { 156 return flags; 157 } 158 159 /** 160 * An optimistic specialization is one that can throw UnwarrantedOptimismException. 161 * This is allowed for native methods, as long as they are functional, i.e. don't change 162 * any state between entering and throwing the UOE. Then we can re-execute a wider version 163 * of the method in the continuation. Rest-of method generation for optimistic builtins is 164 * of course not possible, but this approach works and fits into the same relinking 165 * framework 166 * 167 * @return true if optimistic builtin 168 */ 169 boolean isOptimistic() { 170 return isSpecialization() ? specialization.isOptimistic() : false; 171 } 172 173 boolean isApplyToCall() { 174 return (flags & FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION) != 0; 175 } 176 177 boolean isVarArg() { 178 return isVarArgsType(invoker.type()); 179 } 180 181 @Override 182 public String toString() { 183 final StringBuilder sb = new StringBuilder(); 184 final Class<? extends LinkLogic> linkLogicClass = getLinkLogicClass(); 185 186 sb.append("[invokerType="). 187 append(invoker.type()). 188 append(" ctor="). 189 append(constructor). 190 append(" weight="). 191 append(weight()). 192 append(" linkLogic="). 193 append(linkLogicClass != null ? linkLogicClass.getSimpleName() : "none"); 194 195 return sb.toString(); 196 } 197 198 boolean needsCallee() { 199 return ScriptFunctionData.needsCallee(invoker); 200 } 201 202 /** 203 * Returns an invoker method handle for this function. Note that the handle is safely composable in 204 * the sense that you can compose it with other handles using any combinators even if you can't affect call site 205 * invalidation. If this compiled function is non-optimistic, then it returns the same value as 206 * {@link #getInvokerOrConstructor(boolean)}. However, if the function is optimistic, then this handle will 207 * incur an overhead as it will add an intermediate internal call site that can relink itself when the function 208 * needs to regenerate its code to always point at the latest generated code version. 209 * @return a guaranteed composable invoker method handle for this function. 210 */ 211 MethodHandle createComposableInvoker() { 212 return createComposableInvoker(false); 213 } 214 215 /** 216 * Returns an invoker method handle for this function when invoked as a constructor. Note that the handle should be 217 * considered non-composable in the sense that you can only compose it with other handles using any combinators if 218 * you can ensure that the composition is guarded by {@link #getOptimisticAssumptionsSwitchPoint()} if it's 219 * non-null, and that you can relink the call site it is set into as a target if the switch point is invalidated. In 220 * all other cases, use {@link #createComposableConstructor()}. 221 * @return a direct constructor method handle for this function. 222 */ 223 private MethodHandle getConstructor() { 224 if (constructor == null) { 225 constructor = createConstructorFromInvoker(createInvokerForPessimisticCaller()); 226 } 227 228 return constructor; 229 } 230 231 /** 232 * Creates a version of the invoker intended for a pessimistic caller (return type is Object, no caller optimistic 233 * program point available). 234 * @return a version of the invoker intended for a pessimistic caller. 235 */ 236 private MethodHandle createInvokerForPessimisticCaller() { 237 return createInvoker(Object.class, INVALID_PROGRAM_POINT); 238 } 239 240 /** 241 * Compose a constructor from an invoker. 242 * 243 * @param invoker invoker 244 * @return the composed constructor 245 */ 246 private static MethodHandle createConstructorFromInvoker(final MethodHandle invoker) { 247 final boolean needsCallee = ScriptFunctionData.needsCallee(invoker); 248 // If it was (callee, this, args...), permute it to (this, callee, args...). We're doing this because having 249 // "this" in the first argument position is what allows the elegant folded composition of 250 // (newFilter x constructor x allocator) further down below in the code. Also, ensure the composite constructor 251 // always returns Object. 252 final MethodHandle swapped = needsCallee ? swapCalleeAndThis(invoker) : invoker; 253 254 final MethodHandle returnsObject = MH.asType(swapped, swapped.type().changeReturnType(Object.class)); 255 256 final MethodType ctorType = returnsObject.type(); 257 258 // Construct a dropping type list for NEWFILTER, but don't include constructor "this" into it, so it's actually 259 // captured as "allocation" parameter of NEWFILTER after we fold the constructor into it. 260 // (this, [callee, ]args...) => ([callee, ]args...) 261 final Class<?>[] ctorArgs = ctorType.dropParameterTypes(0, 1).parameterArray(); 262 263 // Fold constructor into newFilter that replaces the return value from the constructor with the originally 264 // allocated value when the originally allocated value is a JS primitive (String, Boolean, Number). 265 // (result, this, [callee, ]args...) x (this, [callee, ]args...) => (this, [callee, ]args...) 266 final MethodHandle filtered = MH.foldArguments(MH.dropArguments(NEWFILTER, 2, ctorArgs), returnsObject); 267 268 // allocate() takes a ScriptFunction and returns a newly allocated ScriptObject... 269 if (needsCallee) { 270 // ...we either fold it into the previous composition, if we need both the ScriptFunction callee object and 271 // the newly allocated object in the arguments, so (this, callee, args...) x (callee) => (callee, args...), 272 // or... 273 return MH.foldArguments(filtered, ScriptFunction.ALLOCATE); 274 } 275 276 // ...replace the ScriptFunction argument with the newly allocated object, if it doesn't need the callee 277 // (this, args...) filter (callee) => (callee, args...) 278 return MH.filterArguments(filtered, 0, ScriptFunction.ALLOCATE); 279 } 280 281 /** 282 * Permutes the parameters in the method handle from {@code (callee, this, ...)} to {@code (this, callee, ...)}. 283 * Used when creating a constructor handle. 284 * @param mh a method handle with order of arguments {@code (callee, this, ...)} 285 * @return a method handle with order of arguments {@code (this, callee, ...)} 286 */ 287 private static MethodHandle swapCalleeAndThis(final MethodHandle mh) { 288 final MethodType type = mh.type(); 289 assert type.parameterType(0) == ScriptFunction.class : type; 290 assert type.parameterType(1) == Object.class : type; 291 final MethodType newType = type.changeParameterType(0, Object.class).changeParameterType(1, ScriptFunction.class); 292 final int[] reorder = new int[type.parameterCount()]; 293 reorder[0] = 1; 294 assert reorder[1] == 0; 295 for (int i = 2; i < reorder.length; ++i) { 296 reorder[i] = i; 297 } 298 return MethodHandles.permuteArguments(mh, newType, reorder); 299 } 300 301 /** 302 * Returns an invoker method handle for this function when invoked as a constructor. Note that the handle is safely 303 * composable in the sense that you can compose it with other handles using any combinators even if you can't affect 304 * call site invalidation. If this compiled function is non-optimistic, then it returns the same value as 305 * {@link #getConstructor()}. However, if the function is optimistic, then this handle will incur an overhead as it 306 * will add an intermediate internal call site that can relink itself when the function needs to regenerate its code 307 * to always point at the latest generated code version. 308 * @return a guaranteed composable constructor method handle for this function. 309 */ 310 MethodHandle createComposableConstructor() { 311 return createComposableInvoker(true); 312 } 313 314 boolean hasConstructor() { 315 return constructor != null; 316 } 317 318 MethodType type() { 319 return invoker.type(); 320 } 321 322 int weight() { 323 return weight(type()); 324 } 325 326 private static int weight(final MethodType type) { 327 if (isVarArgsType(type)) { 328 return Integer.MAX_VALUE; //if there is a varargs it should be the heavist and last fallback 329 } 330 331 int weight = Type.typeFor(type.returnType()).getWeight(); 332 for (int i = 0 ; i < type.parameterCount() ; i++) { 333 final Class<?> paramType = type.parameterType(i); 334 final int pweight = Type.typeFor(paramType).getWeight() * 2; //params are more important than call types as return values are always specialized 335 weight += pweight; 336 } 337 338 weight += type.parameterCount(); //more params outweigh few parameters 339 340 return weight; 341 } 342 343 static boolean isVarArgsType(final MethodType type) { 344 assert type.parameterCount() >= 1 : type; 345 return type.parameterType(type.parameterCount() - 1) == Object[].class; 346 } 347 348 static boolean moreGenericThan(final MethodType mt0, final MethodType mt1) { 349 return weight(mt0) > weight(mt1); 350 } 351 352 boolean betterThanFinal(final CompiledFunction other, final MethodType callSiteMethodType) { 353 // Prefer anything over nothing, as we can't compile new versions. 354 if (other == null) { 355 return true; 356 } 357 return betterThanFinal(this, other, callSiteMethodType); 358 } 359 360 private static boolean betterThanFinal(final CompiledFunction cf, final CompiledFunction other, final MethodType callSiteMethodType) { 361 final MethodType thisMethodType = cf.type(); 362 final MethodType otherMethodType = other.type(); 363 final int thisParamCount = getParamCount(thisMethodType); 364 final int otherParamCount = getParamCount(otherMethodType); 365 final int callSiteRawParamCount = getParamCount(callSiteMethodType); 366 final boolean csVarArg = callSiteRawParamCount == Integer.MAX_VALUE; 367 // Subtract 1 for callee for non-vararg call sites 368 final int callSiteParamCount = csVarArg ? callSiteRawParamCount : callSiteRawParamCount - 1; 369 370 // Prefer the function that discards less parameters 371 final int thisDiscardsParams = Math.max(callSiteParamCount - thisParamCount, 0); 372 final int otherDiscardsParams = Math.max(callSiteParamCount - otherParamCount, 0); 373 if(thisDiscardsParams < otherDiscardsParams) { 374 return true; 375 } 376 if(thisDiscardsParams > otherDiscardsParams) { 377 return false; 378 } 379 380 final boolean thisVarArg = thisParamCount == Integer.MAX_VALUE; 381 final boolean otherVarArg = otherParamCount == Integer.MAX_VALUE; 382 if(!(thisVarArg && otherVarArg && csVarArg)) { 383 // At least one of them isn't vararg 384 final Type[] thisType = toTypeWithoutCallee(thisMethodType, 0); // Never has callee 385 final Type[] otherType = toTypeWithoutCallee(otherMethodType, 0); // Never has callee 386 final Type[] callSiteType = toTypeWithoutCallee(callSiteMethodType, 1); // Always has callee 387 388 int narrowWeightDelta = 0; 389 int widenWeightDelta = 0; 390 final int minParamsCount = Math.min(Math.min(thisParamCount, otherParamCount), callSiteParamCount); 391 for(int i = 0; i < minParamsCount; ++i) { 392 final int callSiteParamWeight = getParamType(i, callSiteType, csVarArg).getWeight(); 393 // Delta is negative for narrowing, positive for widening 394 final int thisParamWeightDelta = getParamType(i, thisType, thisVarArg).getWeight() - callSiteParamWeight; 395 final int otherParamWeightDelta = getParamType(i, otherType, otherVarArg).getWeight() - callSiteParamWeight; 396 // Only count absolute values of narrowings 397 narrowWeightDelta += Math.max(-thisParamWeightDelta, 0) - Math.max(-otherParamWeightDelta, 0); 398 // Only count absolute values of widenings 399 widenWeightDelta += Math.max(thisParamWeightDelta, 0) - Math.max(otherParamWeightDelta, 0); 400 } 401 402 // If both functions accept more arguments than what is passed at the call site, account for ability 403 // to receive Undefined un-narrowed in the remaining arguments. 404 if(!thisVarArg) { 405 for(int i = callSiteParamCount; i < thisParamCount; ++i) { 406 narrowWeightDelta += Math.max(Type.OBJECT.getWeight() - thisType[i].getWeight(), 0); 407 } 408 } 409 if(!otherVarArg) { 410 for(int i = callSiteParamCount; i < otherParamCount; ++i) { 411 narrowWeightDelta -= Math.max(Type.OBJECT.getWeight() - otherType[i].getWeight(), 0); 412 } 413 } 414 415 // Prefer function that narrows less 416 if(narrowWeightDelta < 0) { 417 return true; 418 } 419 if(narrowWeightDelta > 0) { 420 return false; 421 } 422 423 // Prefer function that widens less 424 if(widenWeightDelta < 0) { 425 return true; 426 } 427 if(widenWeightDelta > 0) { 428 return false; 429 } 430 } 431 432 // Prefer the function that exactly matches the arity of the call site. 433 if(thisParamCount == callSiteParamCount && otherParamCount != callSiteParamCount) { 434 return true; 435 } 436 if(thisParamCount != callSiteParamCount && otherParamCount == callSiteParamCount) { 437 return false; 438 } 439 440 // Otherwise, neither function matches arity exactly. We also know that at this point, they both can receive 441 // more arguments than call site, otherwise we would've already chosen the one that discards less parameters. 442 // Note that variable arity methods are preferred, as they actually match the call site arity better, since they 443 // really have arbitrary arity. 444 if(thisVarArg) { 445 if(!otherVarArg) { 446 return true; // 447 } 448 } else if(otherVarArg) { 449 return false; 450 } 451 452 // Neither is variable arity; chose the one that has less extra parameters. 453 final int fnParamDelta = thisParamCount - otherParamCount; 454 if(fnParamDelta < 0) { 455 return true; 456 } 457 if(fnParamDelta > 0) { 458 return false; 459 } 460 461 final int callSiteRetWeight = Type.typeFor(callSiteMethodType.returnType()).getWeight(); 462 // Delta is negative for narrower return type, positive for wider return type 463 final int thisRetWeightDelta = Type.typeFor(thisMethodType.returnType()).getWeight() - callSiteRetWeight; 464 final int otherRetWeightDelta = Type.typeFor(otherMethodType.returnType()).getWeight() - callSiteRetWeight; 465 466 // Prefer function that returns a less wide return type 467 final int widenRetDelta = Math.max(thisRetWeightDelta, 0) - Math.max(otherRetWeightDelta, 0); 468 if(widenRetDelta < 0) { 469 return true; 470 } 471 if(widenRetDelta > 0) { 472 return false; 473 } 474 475 // Prefer function that returns a less narrow return type 476 final int narrowRetDelta = Math.max(-thisRetWeightDelta, 0) - Math.max(-otherRetWeightDelta, 0); 477 if(narrowRetDelta < 0) { 478 return true; 479 } 480 if(narrowRetDelta > 0) { 481 return false; 482 } 483 484 //if they are equal, pick the specialized one first 485 if (cf.isSpecialization() != other.isSpecialization()) { 486 return cf.isSpecialization(); //always pick the specialized version if we can 487 } 488 489 if (cf.isSpecialization() && other.isSpecialization()) { 490 return cf.getLinkLogicClass() != null; //pick link logic specialization above generic specializations 491 } 492 493 // Signatures are identical 494 throw new AssertionError(thisMethodType + " identically applicable to " + otherMethodType + " for " + callSiteMethodType); 495 } 496 497 private static Type[] toTypeWithoutCallee(final MethodType type, final int thisIndex) { 498 final int paramCount = type.parameterCount(); 499 final Type[] t = new Type[paramCount - thisIndex]; 500 for(int i = thisIndex; i < paramCount; ++i) { 501 t[i - thisIndex] = Type.typeFor(type.parameterType(i)); 502 } 503 return t; 504 } 505 506 private static Type getParamType(final int i, final Type[] paramTypes, final boolean isVarArg) { 507 final int fixParamCount = paramTypes.length - (isVarArg ? 1 : 0); 508 if(i < fixParamCount) { 509 return paramTypes[i]; 510 } 511 assert isVarArg; 512 return ((ArrayType)paramTypes[paramTypes.length - 1]).getElementType(); 513 } 514 515 boolean matchesCallSite(final MethodType other, final boolean pickVarArg) { 516 if (other.equals(this.callSiteType)) { 517 return true; 518 } 519 final MethodType type = type(); 520 final int fnParamCount = getParamCount(type); 521 final boolean isVarArg = fnParamCount == Integer.MAX_VALUE; 522 if (isVarArg) { 523 return pickVarArg; 524 } 525 526 final int csParamCount = getParamCount(other); 527 final boolean csIsVarArg = csParamCount == Integer.MAX_VALUE; 528 final int thisThisIndex = needsCallee() ? 1 : 0; // Index of "this" parameter in this function's type 529 530 final int fnParamCountNoCallee = fnParamCount - thisThisIndex; 531 final int minParams = Math.min(csParamCount - 1, fnParamCountNoCallee); // callSiteType always has callee, so subtract 1 532 // We must match all incoming parameters, except "this". Starting from 1 to skip "this". 533 for(int i = 1; i < minParams; ++i) { 534 final Type fnType = Type.typeFor(type.parameterType(i + thisThisIndex)); 535 final Type csType = csIsVarArg ? Type.OBJECT : Type.typeFor(other.parameterType(i + 1)); 536 if(!fnType.isEquivalentTo(csType)) { 537 return false; 538 } 539 } 540 541 // Must match any undefined parameters to Object type. 542 for(int i = minParams; i < fnParamCountNoCallee; ++i) { 543 if(!Type.typeFor(type.parameterType(i + thisThisIndex)).isEquivalentTo(Type.OBJECT)) { 544 return false; 545 } 546 } 547 548 return true; 549 } 550 551 private static int getParamCount(final MethodType type) { 552 final int paramCount = type.parameterCount(); 553 return type.parameterType(paramCount - 1).isArray() ? Integer.MAX_VALUE : paramCount; 554 } 555 556 private boolean canBeDeoptimized() { 557 return optimismInfo != null; 558 } 559 560 private MethodHandle createComposableInvoker(final boolean isConstructor) { 561 final MethodHandle handle = getInvokerOrConstructor(isConstructor); 562 563 // If compiled function is not optimistic, it can't ever change its invoker/constructor, so just return them 564 // directly. 565 if(!canBeDeoptimized()) { 566 return handle; 567 } 568 569 // Otherwise, we need a new level of indirection; need to introduce a mutable call site that can relink itslef 570 // to the compiled function's changed target whenever the optimistic assumptions are invalidated. 571 final CallSite cs = new MutableCallSite(handle.type()); 572 relinkComposableInvoker(cs, this, isConstructor); 573 return cs.dynamicInvoker(); 574 } 575 576 private static class HandleAndAssumptions { 577 final MethodHandle handle; 578 final SwitchPoint assumptions; 579 580 HandleAndAssumptions(final MethodHandle handle, final SwitchPoint assumptions) { 581 this.handle = handle; 582 this.assumptions = assumptions; 583 } 584 585 GuardedInvocation createInvocation() { 586 return new GuardedInvocation(handle, assumptions); 587 } 588 } 589 590 /** 591 * Returns a pair of an invocation created with a passed-in supplier and a non-invalidated switch point for 592 * optimistic assumptions (or null for the switch point if the function can not be deoptimized). While the method 593 * makes a best effort to return a non-invalidated switch point (compensating for possible deoptimizing 594 * recompilation happening on another thread) it is still possible that by the time this method returns the 595 * switchpoint has been invalidated by a {@code RewriteException} triggered on another thread for this function. 596 * This is not a problem, though, as these switch points are always used to produce call sites that fall back to 597 * relinking when they are invalidated, and in this case the execution will end up here again. What this method 598 * basically does is reduce such busy-loop relinking while the function is being recompiled on a different thread. 599 * @param invocationSupplier the supplier that constructs the actual invocation method handle; should use the 600 * {@code CompiledFunction} method itself in some capacity. 601 * @return a tuple object containing the method handle as created by the supplier and an optimistic assumptions 602 * switch point that is guaranteed to not have been invalidated before the call to this method (or null if the 603 * function can't be further deoptimized). 604 */ 605 private synchronized HandleAndAssumptions getValidOptimisticInvocation(final Supplier<MethodHandle> invocationSupplier) { 606 for(int i = 0; i < 2; ++i) { 607 final MethodHandle handle = invocationSupplier.get(); 608 final SwitchPoint assumptions = canBeDeoptimized() ? optimismInfo.optimisticAssumptions : null; 609 if(i == 0 && assumptions != null && assumptions.hasBeenInvalidated()) { 610 // We can be in a situation where one thread is in the middle of a deoptimizing compilation when we hit 611 // this and thus, it has invalidated the old switch point, but hasn't created the new one yet. Note that 612 // the behavior of invalidating the old switch point before recompilation, and only creating the new one 613 // after recompilation is by design. If we didn't wait here, we would be busy looping through the 614 // fallback path of the invalidated switch point, relinking the call site again with the same 615 // invalidated switch point, invoking the fallback, etc. stealing CPU cycles from the recompilation 616 // task we're dependent on. This can still happen if the switch point gets invalidated after we grabbed 617 // it here, in which case we'll indeed do one busy relink immediately. 618 // On the other hand, in order to avoid a rare livelock, we aren't doing an infinite loop, and we 619 // aren't wait()-ing indefinitely. We'll do at most one, at most 1000ms long wait after which we'll 620 // return the current handle even if it's invalidated (and which'll then trigger one loop through the 621 // relink mechanism). We therefore strike a balance between busy looping and a livelock risk by making 622 // sure that there's at most one iteration of busy loop per second. It is theoretically possible to 623 // correctly implement this without ever risking a livelock, but using this heuristic we eliminate the 624 // chance of the livelock, while still maintaining a good enough busy-looping prevention. 625 try { 626 wait(1000L); 627 } catch (final InterruptedException e) { 628 // Intentionally ignored. There's nothing meaningful we can do if we're interrupted 629 } 630 } else { 631 return new HandleAndAssumptions(handle, assumptions); 632 } 633 } 634 throw new AssertionError(); // never reached 635 } 636 637 private static void relinkComposableInvoker(final CallSite cs, final CompiledFunction inv, final boolean constructor) { 638 final HandleAndAssumptions handleAndAssumptions = inv.getValidOptimisticInvocation(new Supplier<MethodHandle>() { 639 @Override 640 public MethodHandle get() { 641 return inv.getInvokerOrConstructor(constructor); 642 } 643 }); 644 final MethodHandle handle = handleAndAssumptions.handle; 645 final SwitchPoint assumptions = handleAndAssumptions.assumptions; 646 final MethodHandle target; 647 if(assumptions == null) { 648 target = handle; 649 } else { 650 final MethodHandle relink = MethodHandles.insertArguments(RELINK_COMPOSABLE_INVOKER, 0, cs, inv, constructor); 651 target = assumptions.guardWithTest(handle, MethodHandles.foldArguments(cs.dynamicInvoker(), relink)); 652 } 653 cs.setTarget(target.asType(cs.type())); 654 } 655 656 private MethodHandle getInvokerOrConstructor(final boolean selectCtor) { 657 return selectCtor ? getConstructor() : createInvokerForPessimisticCaller(); 658 } 659 660 /** 661 * Returns a guarded invocation for this function when not invoked as a constructor. The guarded invocation has no 662 * guard but it potentially has an optimistic assumptions switch point. As such, it will probably not be used as a 663 * final guarded invocation, but rather as a holder for an invocation handle and switch point to be decomposed and 664 * reassembled into a different final invocation by the user of this method. Any recompositions should take care to 665 * continue to use the switch point. If that is not possible, use {@link #createComposableInvoker()} instead. 666 * @return a guarded invocation for an ordinary (non-constructor) invocation of this function. 667 */ 668 GuardedInvocation createFunctionInvocation(final Class<?> callSiteReturnType, final int callerProgramPoint) { 669 return getValidOptimisticInvocation(new Supplier<MethodHandle>() { 670 @Override 671 public MethodHandle get() { 672 return createInvoker(callSiteReturnType, callerProgramPoint); 673 } 674 }).createInvocation(); 675 } 676 677 /** 678 * Returns a guarded invocation for this function when invoked as a constructor. The guarded invocation has no guard 679 * but it potentially has an optimistic assumptions switch point. As such, it will probably not be used as a final 680 * guarded invocation, but rather as a holder for an invocation handle and switch point to be decomposed and 681 * reassembled into a different final invocation by the user of this method. Any recompositions should take care to 682 * continue to use the switch point. If that is not possible, use {@link #createComposableConstructor()} instead. 683 * @return a guarded invocation for invocation of this function as a constructor. 684 */ 685 GuardedInvocation createConstructorInvocation() { 686 return getValidOptimisticInvocation(new Supplier<MethodHandle>() { 687 @Override 688 public MethodHandle get() { 689 return getConstructor(); 690 } 691 }).createInvocation(); 692 } 693 694 private MethodHandle createInvoker(final Class<?> callSiteReturnType, final int callerProgramPoint) { 695 final boolean isOptimistic = canBeDeoptimized(); 696 MethodHandle handleRewriteException = isOptimistic ? createRewriteExceptionHandler() : null; 697 698 MethodHandle inv = invoker; 699 if(isValid(callerProgramPoint)) { 700 inv = OptimisticReturnFilters.filterOptimisticReturnValue(inv, callSiteReturnType, callerProgramPoint); 701 inv = changeReturnType(inv, callSiteReturnType); 702 if(callSiteReturnType.isPrimitive() && handleRewriteException != null) { 703 // because handleRewriteException always returns Object 704 handleRewriteException = OptimisticReturnFilters.filterOptimisticReturnValue(handleRewriteException, 705 callSiteReturnType, callerProgramPoint); 706 } 707 } else if(isOptimistic) { 708 // Required so that rewrite exception has the same return type. It'd be okay to do it even if we weren't 709 // optimistic, but it isn't necessary as the linker upstream will eventually convert the return type. 710 inv = changeReturnType(inv, callSiteReturnType); 711 } 712 713 if(isOptimistic) { 714 assert handleRewriteException != null; 715 final MethodHandle typedHandleRewriteException = changeReturnType(handleRewriteException, inv.type().returnType()); 716 return MH.catchException(inv, RewriteException.class, typedHandleRewriteException); 717 } 718 return inv; 719 } 720 721 private MethodHandle createRewriteExceptionHandler() { 722 return MH.foldArguments(RESTOF_INVOKER, MH.insertArguments(HANDLE_REWRITE_EXCEPTION, 0, this, optimismInfo)); 723 } 724 725 private static MethodHandle changeReturnType(final MethodHandle mh, final Class<?> newReturnType) { 726 return Bootstrap.getLinkerServices().asType(mh, mh.type().changeReturnType(newReturnType)); 727 } 728 729 @SuppressWarnings("unused") 730 private static MethodHandle handleRewriteException(final CompiledFunction function, final OptimismInfo oldOptimismInfo, final RewriteException re) { 731 return function.handleRewriteException(oldOptimismInfo, re); 732 } 733 734 /** 735 * Debug function for printing out all invalidated program points and their 736 * invalidation mapping to next type 737 * @param ipp 738 * @return string describing the ipp map 739 */ 740 private static List<String> toStringInvalidations(final Map<Integer, Type> ipp) { 741 if (ipp == null) { 742 return Collections.emptyList(); 743 } 744 745 final List<String> list = new ArrayList<>(); 746 747 for (final Iterator<Map.Entry<Integer, Type>> iter = ipp.entrySet().iterator(); iter.hasNext(); ) { 748 final Map.Entry<Integer, Type> entry = iter.next(); 749 final char bct = entry.getValue().getBytecodeStackType(); 750 final String type; 751 752 switch (entry.getValue().getBytecodeStackType()) { 753 case 'A': 754 type = "object"; 755 break; 756 case 'I': 757 type = "int"; 758 break; 759 case 'J': 760 type = "long"; 761 break; 762 case 'D': 763 type = "double"; 764 break; 765 default: 766 type = String.valueOf(bct); 767 break; 768 } 769 770 final StringBuilder sb = new StringBuilder(); 771 sb.append('['). 772 append("program point: "). 773 append(entry.getKey()). 774 append(" -> "). 775 append(type). 776 append(']'); 777 778 list.add(sb.toString()); 779 } 780 781 return list; 782 } 783 784 private void logRecompile(final String reason, final FunctionNode fn, final MethodType type, final Map<Integer, Type> ipp) { 785 if (log.isEnabled()) { 786 log.info(reason, DebugLogger.quote(fn.getName()), " signature: ", type); 787 log.indent(); 788 for (final String str : toStringInvalidations(ipp)) { 789 log.fine(str); 790 } 791 log.unindent(); 792 } 793 } 794 795 /** 796 * Handles a {@link RewriteException} raised during the execution of this function by recompiling (if needed) the 797 * function with an optimistic assumption invalidated at the program point indicated by the exception, and then 798 * executing a rest-of method to complete the execution with the deoptimized version. 799 * @param oldOptInfo the optimism info of this function. We must store it explicitly as a bound argument in the 800 * method handle, otherwise it could be null for handling a rewrite exception in an outer invocation of a recursive 801 * function when recursive invocations of the function have completely deoptimized it. 802 * @param re the rewrite exception that was raised 803 * @return the method handle for the rest-of method, for folding composition. 804 */ 805 private synchronized MethodHandle handleRewriteException(final OptimismInfo oldOptInfo, final RewriteException re) { 806 if (log.isEnabled()) { 807 log.info( 808 new RecompilationEvent( 809 Level.INFO, 810 re, 811 re.getReturnValueNonDestructive()), 812 "caught RewriteException ", 813 re.getMessageShort()); 814 log.indent(); 815 } 816 817 final MethodType type = type(); 818 819 // Compiler needs a call site type as its input, which always has a callee parameter, so we must add it if 820 // this function doesn't have a callee parameter. 821 final MethodType ct = type.parameterType(0) == ScriptFunction.class ? 822 type : 823 type.insertParameterTypes(0, ScriptFunction.class); 824 final OptimismInfo currentOptInfo = optimismInfo; 825 final boolean shouldRecompile = currentOptInfo != null && currentOptInfo.requestRecompile(re); 826 827 // Effective optimism info, for subsequent use. We'll normally try to use the current (latest) one, but if it 828 // isn't available, we'll use the old one bound into the call site. 829 final OptimismInfo effectiveOptInfo = currentOptInfo != null ? currentOptInfo : oldOptInfo; 830 FunctionNode fn = effectiveOptInfo.reparse(); 831 final boolean serialized = effectiveOptInfo.isSerialized(); 832 final Compiler compiler = effectiveOptInfo.getCompiler(fn, ct, re); //set to non rest-of 833 834 if (!shouldRecompile) { 835 // It didn't necessarily recompile, e.g. for an outer invocation of a recursive function if we already 836 // recompiled a deoptimized version for an inner invocation. 837 // We still need to do the rest of from the beginning 838 logRecompile("Rest-of compilation [STANDALONE] ", fn, ct, effectiveOptInfo.invalidatedProgramPoints); 839 return restOfHandle(effectiveOptInfo, compiler.compile(fn, serialized ? CompilationPhases.COMPILE_SERIALIZED_RESTOF : CompilationPhases.COMPILE_ALL_RESTOF), currentOptInfo != null); 840 } 841 842 logRecompile("Deoptimizing recompilation (up to bytecode) ", fn, ct, effectiveOptInfo.invalidatedProgramPoints); 843 fn = compiler.compile(fn, serialized ? CompilationPhases.RECOMPILE_SERIALIZED_UPTO_BYTECODE : CompilationPhases.COMPILE_UPTO_BYTECODE); 844 log.fine("Reusable IR generated"); 845 846 // compile the rest of the function, and install it 847 log.info("Generating and installing bytecode from reusable IR..."); 848 logRecompile("Rest-of compilation [CODE PIPELINE REUSE] ", fn, ct, effectiveOptInfo.invalidatedProgramPoints); 849 final FunctionNode normalFn = compiler.compile(fn, CompilationPhases.GENERATE_BYTECODE_AND_INSTALL); 850 851 if (effectiveOptInfo.data.usePersistentCodeCache()) { 852 final RecompilableScriptFunctionData data = effectiveOptInfo.data; 853 final int functionNodeId = data.getFunctionNodeId(); 854 final TypeMap typeMap = data.typeMap(ct); 855 final Type[] paramTypes = typeMap == null ? null : typeMap.getParameterTypes(functionNodeId); 856 final String cacheKey = CodeStore.getCacheKey(functionNodeId, paramTypes); 857 compiler.persistClassInfo(cacheKey, normalFn); 858 } 859 860 final boolean canBeDeoptimized = normalFn.canBeDeoptimized(); 861 862 if (log.isEnabled()) { 863 log.unindent(); 864 log.info("Done."); 865 866 log.info("Recompiled '", fn.getName(), "' (", Debug.id(this), ") ", canBeDeoptimized ? "can still be deoptimized." : " is completely deoptimized."); 867 log.finest("Looking up invoker..."); 868 } 869 870 final MethodHandle newInvoker = effectiveOptInfo.data.lookup(fn); 871 invoker = newInvoker.asType(type.changeReturnType(newInvoker.type().returnType())); 872 constructor = null; // Will be regenerated when needed 873 874 log.info("Done: ", invoker); 875 final MethodHandle restOf = restOfHandle(effectiveOptInfo, compiler.compile(fn, CompilationPhases.GENERATE_BYTECODE_AND_INSTALL_RESTOF), canBeDeoptimized); 876 877 // Note that we only adjust the switch point after we set the invoker/constructor. This is important. 878 if (canBeDeoptimized) { 879 effectiveOptInfo.newOptimisticAssumptions(); // Otherwise, set a new switch point. 880 } else { 881 optimismInfo = null; // If we got to a point where we no longer have optimistic assumptions, let the optimism info go. 882 } 883 notifyAll(); 884 885 return restOf; 886 } 887 888 private MethodHandle restOfHandle(final OptimismInfo info, final FunctionNode restOfFunction, final boolean canBeDeoptimized) { 889 assert info != null; 890 assert restOfFunction.getCompileUnit().getUnitClassName().contains("restOf"); 891 final MethodHandle restOf = 892 changeReturnType( 893 info.data.lookupCodeMethod( 894 restOfFunction.getCompileUnit().getCode(), 895 MH.type(restOfFunction.getReturnType().getTypeClass(), 896 RewriteException.class)), 897 Object.class); 898 899 if (!canBeDeoptimized) { 900 return restOf; 901 } 902 903 // If rest-of is itself optimistic, we must make sure that we can repeat a deoptimization if it, too hits an exception. 904 return MH.catchException(restOf, RewriteException.class, createRewriteExceptionHandler()); 905 906 } 907 908 private static class OptimismInfo { 909 // TODO: this is pointing to its owning ScriptFunctionData. Re-evaluate if that's okay. 910 private final RecompilableScriptFunctionData data; 911 private final Map<Integer, Type> invalidatedProgramPoints; 912 private SwitchPoint optimisticAssumptions; 913 private final DebugLogger log; 914 915 OptimismInfo(final RecompilableScriptFunctionData data, final Map<Integer, Type> invalidatedProgramPoints) { 916 this.data = data; 917 this.log = data.getLogger(); 918 this.invalidatedProgramPoints = invalidatedProgramPoints == null ? new TreeMap<Integer, Type>() : invalidatedProgramPoints; 919 newOptimisticAssumptions(); 920 } 921 922 private void newOptimisticAssumptions() { 923 optimisticAssumptions = new SwitchPoint(); 924 } 925 926 boolean requestRecompile(final RewriteException e) { 927 final Type retType = e.getReturnType(); 928 final Type previousFailedType = invalidatedProgramPoints.put(e.getProgramPoint(), retType); 929 930 if (previousFailedType != null && !previousFailedType.narrowerThan(retType)) { 931 final StackTraceElement[] stack = e.getStackTrace(); 932 final String functionId = stack.length == 0 ? 933 data.getName() : 934 stack[0].getClassName() + "." + stack[0].getMethodName(); 935 936 log.info("RewriteException for an already invalidated program point ", e.getProgramPoint(), " in ", functionId, ". This is okay for a recursive function invocation, but a bug otherwise."); 937 938 return false; 939 } 940 941 SwitchPoint.invalidateAll(new SwitchPoint[] { optimisticAssumptions }); 942 943 return true; 944 } 945 946 Compiler getCompiler(final FunctionNode fn, final MethodType actualCallSiteType, final RewriteException e) { 947 return data.getCompiler(fn, actualCallSiteType, e.getRuntimeScope(), invalidatedProgramPoints, getEntryPoints(e)); 948 } 949 950 private static int[] getEntryPoints(final RewriteException e) { 951 final int[] prevEntryPoints = e.getPreviousContinuationEntryPoints(); 952 final int[] entryPoints; 953 if (prevEntryPoints == null) { 954 entryPoints = new int[1]; 955 } else { 956 final int l = prevEntryPoints.length; 957 entryPoints = new int[l + 1]; 958 System.arraycopy(prevEntryPoints, 0, entryPoints, 1, l); 959 } 960 entryPoints[0] = e.getProgramPoint(); 961 return entryPoints; 962 } 963 964 FunctionNode reparse() { 965 return data.reparse(); 966 } 967 968 boolean isSerialized() { 969 return data.isSerialized(); 970 } 971 } 972 973 @SuppressWarnings("unused") 974 private static Object newFilter(final Object result, final Object allocation) { 975 return (result instanceof ScriptObject || !JSType.isPrimitive(result))? result : allocation; 976 } 977 978 private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) { 979 return MH.findStatic(MethodHandles.lookup(), CompiledFunction.class, name, MH.type(rtype, types)); 980 } 981} 982