Skip to content

Commit 98ef860

Browse files
committed
GROOVY-11935: Set invokedynamic call site target immediately for static method calls to enable earlier JIT inlining
1 parent ac6fe49 commit 98ef860

File tree

5 files changed

+658
-54
lines changed

5 files changed

+658
-54
lines changed

src/main/java/org/codehaus/groovy/vmplugin/v8/IndyInterface.java

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import groovy.lang.GroovySystem;
2222
import org.apache.groovy.util.SystemUtil;
2323
import org.codehaus.groovy.GroovyBugError;
24-
import org.codehaus.groovy.reflection.ClassInfo;
2524
import org.codehaus.groovy.runtime.GeneratedClosure;
2625

2726
import java.lang.invoke.CallSite;
@@ -31,6 +30,7 @@
3130
import java.lang.invoke.MethodType;
3231
import java.lang.invoke.MutableCallSite;
3332
import java.lang.invoke.SwitchPoint;
33+
import java.lang.reflect.Modifier;
3434
import java.util.Map;
3535
import java.util.function.Function;
3636
import java.util.logging.Level;
@@ -320,48 +320,46 @@ public static Object fromCache(CacheableCallSite callSite, Class<?> sender, Stri
320320
*/
321321
private static MethodHandle fromCacheHandle(CacheableCallSite callSite, Class<?> sender, String methodName, int callID, Boolean safeNavigation, Boolean thisCall, Boolean spreadCall, Object dummyReceiver, Object[] arguments) throws Throwable {
322322
FallbackSupplier fallbackSupplier = new FallbackSupplier(callSite, sender, methodName, callID, safeNavigation, thisCall, spreadCall, dummyReceiver, arguments);
323+
final Object receiver = arguments[0];
323324

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-
}
325+
String receiverClassName = receiver != null ? receiver.getClass().getName() : NULL_OBJECT_CLASS_NAME;
326+
MethodHandleWrapper mhw = callSite.getAndPut(receiverClassName, (theName) -> {
327+
MethodHandleWrapper fallback = fallbackSupplier.get();
328+
if (fallback.isCanSetTarget()) return fallback;
329+
return NULL_METHOD_HANDLE_WRAPPER;
330+
});
337331

338332
if (mhw == NULL_METHOD_HANDLE_WRAPPER) {
339333
mhw = fallbackSupplier.get();
340334
}
341335

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());
336+
if (mhw.isCanSetTarget() && (callSite.getTarget() != mhw.getTargetMethodHandle())) {
337+
// GROOVY-11935: Set invokedynamic call site target immediately for static method calls to enable earlier JIT inlining
338+
if (receiver instanceof Class) {
339+
var method = mhw.getMethod();
340+
if (method != null && Modifier.isStatic(method.getModifiers())) {
341+
callSite.setTarget(mhw.getTargetMethodHandle());
347342
}
348-
} else {
349-
callSite.setTarget(mhw.getTargetMethodHandle());
350-
if (LOG_ENABLED) LOG.info("call site target set, preparing outside invocation");
351343
}
352344

353-
mhw.resetLatestHitCount();
345+
if (mhw.getLatestHitCount() > INDY_OPTIMIZE_THRESHOLD) {
346+
if (callSite.getFallbackRound().get() > INDY_FALLBACK_CUTOFF) {
347+
if (callSite.getTarget() != callSite.getDefaultTarget()) {
348+
// reset the call site target to default forever to avoid JIT deoptimization storm further
349+
callSite.setTarget(callSite.getDefaultTarget());
350+
}
351+
} else {
352+
callSite.setTarget(mhw.getTargetMethodHandle());
353+
if (LOG_ENABLED) LOG.info("call site target set, preparing outside invocation");
354+
}
355+
356+
mhw.resetLatestHitCount();
357+
}
354358
}
355359

356360
return mhw.getCachedMethodHandle();
357361
}
358362

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-
365363
/**
366364
* Core method for indy method selection using runtime types.
367365
* @deprecated Use the new bootHandle-based approach instead.
@@ -391,7 +389,9 @@ private static MethodHandle selectMethodHandle(CacheableCallSite callSite, Class
391389
// it is important but impacts the performance somehow when cache misses frequently
392390
Object receiver = arguments[0];
393391
String key = receiver != null ? receiver.getClass().getName() : NULL_OBJECT_CLASS_NAME;
394-
callSite.put(key, mhw);
392+
393+
// Avoid PIC pollution: don't write back uncached wrappers, e.g. for instance-level metaClass dispatches.
394+
callSite.put(key, mhw.isCanSetTarget() ? mhw : NULL_METHOD_HANDLE_WRAPPER);
395395
}
396396

397397
return mhw.getCachedMethodHandle();
@@ -404,6 +404,7 @@ private static MethodHandleWrapper fallback(CacheableCallSite callSite, Class<?>
404404
return new MethodHandleWrapper(
405405
selector.handle.asSpreader(Object[].class, arguments.length).asType(MethodType.methodType(Object.class, Object[].class)),
406406
selector.handle,
407+
selector.method,
407408
selector.cache
408409
);
409410
}

src/main/java/org/codehaus/groovy/vmplugin/v8/MethodHandleWrapper.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
*/
1919
package org.codehaus.groovy.vmplugin.v8;
2020

21+
import groovy.lang.MetaMethod;
22+
2123
import java.lang.invoke.MethodHandle;
2224
import java.util.concurrent.atomic.AtomicLong;
2325

@@ -29,12 +31,14 @@
2931
class MethodHandleWrapper {
3032
private final MethodHandle cachedMethodHandle;
3133
private final MethodHandle targetMethodHandle;
34+
private final MetaMethod method;
3235
private final boolean canSetTarget;
3336
private final AtomicLong latestHitCount = new AtomicLong(0);
3437

35-
public MethodHandleWrapper(MethodHandle cachedMethodHandle, MethodHandle targetMethodHandle, boolean canSetTarget) {
38+
public MethodHandleWrapper(MethodHandle cachedMethodHandle, MethodHandle targetMethodHandle, MetaMethod method, boolean canSetTarget) {
3639
this.cachedMethodHandle = cachedMethodHandle;
3740
this.targetMethodHandle = targetMethodHandle;
41+
this.method = method;
3842
this.canSetTarget = canSetTarget;
3943
}
4044

@@ -46,6 +50,10 @@ public MethodHandle getTargetMethodHandle() {
4650
return targetMethodHandle;
4751
}
4852

53+
public MetaMethod getMethod() {
54+
return method;
55+
}
56+
4957
public boolean isCanSetTarget() {
5058
return canSetTarget;
5159
}
@@ -67,10 +75,10 @@ public static MethodHandleWrapper getNullMethodHandleWrapper() {
6775
}
6876

6977
private static class NullMethodHandleWrapper extends MethodHandleWrapper {
70-
public static final NullMethodHandleWrapper INSTANCE = new NullMethodHandleWrapper(null, null, false);
78+
public static final NullMethodHandleWrapper INSTANCE = new NullMethodHandleWrapper();
7179

72-
private NullMethodHandleWrapper(MethodHandle cachedMethodHandle, MethodHandle targetMethodHandle, boolean canSetTarget) {
73-
super(cachedMethodHandle, targetMethodHandle, canSetTarget);
80+
private NullMethodHandleWrapper() {
81+
super(null, null, null, false);
7482
}
7583
}
7684
}

0 commit comments

Comments
 (0)