Skip to content

Commit 4cc3bbf

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

File tree

5 files changed

+662
-54
lines changed

5 files changed

+662
-54
lines changed

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

Lines changed: 33 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,48 @@ 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) {
333+
// The PIC stores a sentinel to remember "do not relink this receiver shape";
334+
// execution still needs a real handle for the current invocation.
339335
mhw = fallbackSupplier.get();
340336
}
341337

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

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

356362
return mhw.getCachedMethodHandle();
357363
}
358364

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-
365365
/**
366366
* Core method for indy method selection using runtime types.
367367
* @deprecated Use the new bootHandle-based approach instead.
@@ -391,7 +391,9 @@ private static MethodHandle selectMethodHandle(CacheableCallSite callSite, Class
391391
// it is important but impacts the performance somehow when cache misses frequently
392392
Object receiver = arguments[0];
393393
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(key, mhw.isCanSetTarget() ? mhw : NULL_METHOD_HANDLE_WRAPPER);
395397
}
396398

397399
return mhw.getCachedMethodHandle();
@@ -404,6 +406,7 @@ private static MethodHandleWrapper fallback(CacheableCallSite callSite, Class<?>
404406
return new MethodHandleWrapper(
405407
selector.handle.asSpreader(Object[].class, arguments.length).asType(MethodType.methodType(Object.class, Object[].class)),
406408
selector.handle,
409+
selector.method,
407410
selector.cache
408411
);
409412
}

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)