|
21 | 21 | import groovy.lang.GroovySystem; |
22 | 22 | import org.apache.groovy.util.SystemUtil; |
23 | 23 | import org.codehaus.groovy.GroovyBugError; |
24 | | -import org.codehaus.groovy.reflection.ClassInfo; |
25 | 24 | import org.codehaus.groovy.runtime.GeneratedClosure; |
26 | 25 |
|
27 | 26 | import java.lang.invoke.CallSite; |
|
31 | 30 | import java.lang.invoke.MethodType; |
32 | 31 | import java.lang.invoke.MutableCallSite; |
33 | 32 | import java.lang.invoke.SwitchPoint; |
| 33 | +import java.lang.reflect.Modifier; |
34 | 34 | import java.util.Map; |
35 | 35 | import java.util.function.Function; |
36 | 36 | import java.util.logging.Level; |
@@ -320,48 +320,49 @@ public static Object fromCache(CacheableCallSite callSite, Class<?> sender, Stri |
320 | 320 | */ |
321 | 321 | private static MethodHandle fromCacheHandle(CacheableCallSite callSite, Class<?> sender, String methodName, int callID, Boolean safeNavigation, Boolean thisCall, Boolean spreadCall, Object dummyReceiver, Object[] arguments) throws Throwable { |
322 | 322 | FallbackSupplier fallbackSupplier = new FallbackSupplier(callSite, sender, methodName, callID, safeNavigation, thisCall, spreadCall, dummyReceiver, arguments); |
323 | | - |
324 | | - MethodHandleWrapper mhw; |
325 | | - if (bypassCache(spreadCall, arguments)) { |
326 | | - mhw = NULL_METHOD_HANDLE_WRAPPER; |
327 | | - } else { |
328 | | - Object receiver = arguments[0]; |
329 | | - String receiverClassName = receiver != null ? receiver.getClass().getName() : NULL_OBJECT_CLASS_NAME; |
330 | | - |
331 | | - mhw = callSite.getAndPut(receiverClassName, (theName) -> { |
332 | | - MethodHandleWrapper fallback = fallbackSupplier.get(); |
333 | | - if (fallback.isCanSetTarget()) return fallback; |
334 | | - return NULL_METHOD_HANDLE_WRAPPER; |
335 | | - }); |
336 | | - } |
| 323 | + Object receiver = arguments[0]; |
| 324 | + String receiverClassName = receiverCacheKey(receiver); |
| 325 | + MethodHandleWrapper mhw = callSite.getAndPut(receiverClassName, (theName) -> { |
| 326 | + MethodHandleWrapper fallback = fallbackSupplier.get(); |
| 327 | + if (fallback.isCanSetTarget()) return fallback; |
| 328 | + return NULL_METHOD_HANDLE_WRAPPER; |
| 329 | + }); |
337 | 330 |
|
338 | 331 | if (mhw == NULL_METHOD_HANDLE_WRAPPER) { |
| 332 | + // The PIC stores a sentinel to remember "do not relink this receiver shape"; |
| 333 | + // execution still needs a real handle for the current invocation. |
339 | 334 | mhw = fallbackSupplier.get(); |
340 | 335 | } |
341 | 336 |
|
342 | | - if (mhw.isCanSetTarget() && (callSite.getTarget() != mhw.getTargetMethodHandle()) && (mhw.getLatestHitCount() > INDY_OPTIMIZE_THRESHOLD)) { |
343 | | - if (callSite.getFallbackRound().get() > INDY_FALLBACK_CUTOFF) { |
344 | | - if (callSite.getTarget() != callSite.getDefaultTarget()) { |
345 | | - // reset the call site target to default forever to avoid JIT deoptimization storm further |
346 | | - callSite.setTarget(callSite.getDefaultTarget()); |
| 337 | + if (mhw.isCanSetTarget() && (callSite.getTarget() != mhw.getTargetMethodHandle())) { |
| 338 | + // GROOVY-11935: Set invokedynamic call site target immediately to enable earlier JIT inlining. |
| 339 | + if (callSite.type().parameterType(0) == Class.class) { |
| 340 | + var method = mhw.getMethod(); |
| 341 | + if (method != null && Modifier.isStatic(method.getModifiers())) { |
| 342 | + callSite.setTarget(mhw.getTargetMethodHandle()); |
347 | 343 | } |
348 | | - } else { |
349 | | - callSite.setTarget(mhw.getTargetMethodHandle()); |
350 | | - if (LOG_ENABLED) LOG.info("call site target set, preparing outside invocation"); |
351 | 344 | } |
352 | 345 |
|
353 | | - mhw.resetLatestHitCount(); |
| 346 | + if (mhw.getLatestHitCount() > INDY_OPTIMIZE_THRESHOLD) { |
| 347 | + if (callSite.getFallbackRound().get() > INDY_FALLBACK_CUTOFF) { |
| 348 | + if (callSite.getTarget() != callSite.getDefaultTarget()) { |
| 349 | + // reset the call site target to default forever to avoid JIT deoptimization storm further |
| 350 | + callSite.setTarget(callSite.getDefaultTarget()); |
| 351 | + } |
| 352 | + } else { |
| 353 | + if (callSite.getTarget() != mhw.getTargetMethodHandle()) { |
| 354 | + callSite.setTarget(mhw.getTargetMethodHandle()); |
| 355 | + if (LOG_ENABLED) LOG.info("call site target set, preparing outside invocation"); |
| 356 | + } |
| 357 | + } |
| 358 | + |
| 359 | + mhw.resetLatestHitCount(); |
| 360 | + } |
354 | 361 | } |
355 | 362 |
|
356 | 363 | return mhw.getCachedMethodHandle(); |
357 | 364 | } |
358 | 365 |
|
359 | | - private static boolean bypassCache(Boolean spreadCall, Object[] arguments) { |
360 | | - if (spreadCall) return true; |
361 | | - Object receiver = arguments[0]; |
362 | | - return receiver != null && ClassInfo.getClassInfo(receiver.getClass()).hasPerInstanceMetaClasses(); |
363 | | - } |
364 | | - |
365 | 366 | /** |
366 | 367 | * Core method for indy method selection using runtime types. |
367 | 368 | * @deprecated Use the new bootHandle-based approach instead. |
@@ -390,20 +391,34 @@ private static MethodHandle selectMethodHandle(CacheableCallSite callSite, Class |
390 | 391 | // correct the stale methodHandle in the inline cache of callsite |
391 | 392 | // it is important but impacts the performance somehow when cache misses frequently |
392 | 393 | Object receiver = arguments[0]; |
393 | | - String key = receiver != null ? receiver.getClass().getName() : NULL_OBJECT_CLASS_NAME; |
394 | | - callSite.put(key, mhw); |
| 394 | + |
| 395 | + // Avoid PIC pollution: don't write back uncached wrappers, e.g. for instance-level metaClass dispatches. |
| 396 | + callSite.put(receiverCacheKey(receiver), mhw.isCanSetTarget() ? mhw : NULL_METHOD_HANDLE_WRAPPER); |
395 | 397 | } |
396 | 398 |
|
397 | 399 | return mhw.getCachedMethodHandle(); |
398 | 400 | } |
399 | 401 |
|
| 402 | + /** |
| 403 | + * Computes the PIC cache key for the given receiver. |
| 404 | + * Different {@code Class} objects (e.g. {@code A} vs {@code B}) share the same runtime class |
| 405 | + * ({@code java.lang.Class}) but dispatch to different methods. Including the represented class |
| 406 | + * name avoids PIC cache collisions for static-method call sites. |
| 407 | + */ |
| 408 | + private static String receiverCacheKey(Object receiver) { |
| 409 | + if (receiver == null) return NULL_OBJECT_CLASS_NAME; |
| 410 | + if (receiver instanceof Class<?> c) return "java.lang.Class:" + c.getName(); |
| 411 | + return receiver.getClass().getName(); |
| 412 | + } |
| 413 | + |
400 | 414 | private static MethodHandleWrapper fallback(CacheableCallSite callSite, Class<?> sender, String methodName, int callID, Boolean safeNavigation, Boolean thisCall, Boolean spreadCall, Object dummyReceiver, Object[] arguments) { |
401 | 415 | Selector selector = Selector.getSelector(callSite, sender, methodName, callID, safeNavigation, thisCall, spreadCall, arguments); |
402 | 416 | selector.setCallSiteTarget(); |
403 | 417 |
|
404 | 418 | return new MethodHandleWrapper( |
405 | 419 | selector.handle.asSpreader(Object[].class, arguments.length).asType(MethodType.methodType(Object.class, Object[].class)), |
406 | 420 | selector.handle, |
| 421 | + selector.method, |
407 | 422 | selector.cache |
408 | 423 | ); |
409 | 424 | } |
|
0 commit comments