This is an automated email from the ASF dual-hosted git repository. blackdrag pushed a commit to branch feature/Cache_changes in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 1acf9b981592cced38ea4941bc760ff712034a1c Author: Jochen Theodorou <[email protected]> AuthorDate: Wed May 20 10:18:08 2026 +0200 GROOVY-12023: add a PIC with configurable size for indy in fron of the existing cache --- .../groovy/vmplugin/v8/CacheableCallSite.java | 34 +++++++++++++++++- .../codehaus/groovy/vmplugin/v8/IndyInterface.java | 28 +++++++++++---- .../org/codehaus/groovy/vmplugin/v8/Selector.java | 40 ++++++++++++++-------- 3 files changed, 81 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/codehaus/groovy/vmplugin/v8/CacheableCallSite.java b/src/main/java/org/codehaus/groovy/vmplugin/v8/CacheableCallSite.java index ff57d8b979..87ba5573f1 100644 --- a/src/main/java/org/codehaus/groovy/vmplugin/v8/CacheableCallSite.java +++ b/src/main/java/org/codehaus/groovy/vmplugin/v8/CacheableCallSite.java @@ -51,6 +51,9 @@ public class CacheableCallSite extends MutableCallSite { private final AtomicLong fallbackRound = new AtomicLong(); private MethodHandle defaultTarget; private MethodHandle fallbackTarget; + private volatile MethodHandle picChain; + private Object[] picKeys; + private int picCount; private final Map<Object, SoftReference<MethodHandleWrapper>> lruCache = new LinkedHashMap<Object, SoftReference<MethodHandleWrapper>>(INITIAL_CAPACITY, LOAD_FACTOR, true) { @Serial private static final long serialVersionUID = 7785958879964294463L; @@ -164,6 +167,33 @@ public class CacheableCallSite extends MutableCallSite { } } + public void recordInPic(Object key, int maxPicSize) { + if (picKeys == null) picKeys = new Object[maxPicSize]; + if (picCount < picKeys.length) { + picKeys[picCount++] = key; + } + } + + public boolean picIncludes(Object key) { + if (picKeys == null) return false; + for (int i = 0; i < picCount; i++) { + if (picKeys[i] == key) return true; + } + return false; + } + + public MethodHandle getPicChain() { + return picChain; + } + + public void setPicChain(MethodHandle picChain) { + this.picChain = picChain; + } + + public int getPicCount() { + return picCount; + } + private static final class MRUEntry { final Object key; final MethodHandleWrapper wrapper; @@ -195,7 +225,9 @@ public class CacheableCallSite extends MutableCallSite { */ public void resetFallbackCount() { fallbackCount.set(0); - fallbackRound.incrementAndGet(); + picCount = 0; + picChain = null; + picKeys = null; } /** diff --git a/src/main/java/org/codehaus/groovy/vmplugin/v8/IndyInterface.java b/src/main/java/org/codehaus/groovy/vmplugin/v8/IndyInterface.java index 91ef98d680..6943533faa 100644 --- a/src/main/java/org/codehaus/groovy/vmplugin/v8/IndyInterface.java +++ b/src/main/java/org/codehaus/groovy/vmplugin/v8/IndyInterface.java @@ -49,6 +49,7 @@ public class IndyInterface { private static final long INDY_OPTIMIZE_THRESHOLD = SystemUtil.getLongSafe("groovy.indy.optimize.threshold", 1_000L); private static final long INDY_FALLBACK_THRESHOLD = SystemUtil.getLongSafe("groovy.indy.fallback.threshold", 1_000L); private static final long INDY_FALLBACK_CUTOFF = SystemUtil.getLongSafe("groovy.indy.fallback.cutoff", 100L); + private static final int INDY_PIC_SIZE = SystemUtil.getIntegerSafe("groovy.indy.pic.size", 4); /** * Flags for method and property calls. @@ -371,7 +372,7 @@ public class IndyInterface { mhw.incrementLatestHitCount(); if (mhw.isCanSetTarget() && (callSite.getTarget() != mhw.getTargetMethodHandle())) { if (mhw.getLatestHitCount() > INDY_OPTIMIZE_THRESHOLD) { - optimizeCallSite(callSite, mhw); + optimizeCallSite(callSite, sender, methodName, callID, safeNavigation, thisCall, spreadCall, arguments, mhw); } } return mhw.getCachedMethodHandle(); @@ -400,22 +401,37 @@ public class IndyInterface { } if (mhw.getLatestHitCount() > INDY_OPTIMIZE_THRESHOLD) { - optimizeCallSite(callSite, mhw); + optimizeCallSite(callSite, sender, methodName, callID, safeNavigation, thisCall, spreadCall, arguments, mhw); } } return mhw.getCachedMethodHandle(); } - private static void optimizeCallSite(CacheableCallSite callSite, MethodHandleWrapper mhw) { + private static void optimizeCallSite(CacheableCallSite callSite, Class<?> sender, String methodName, int callID, boolean safeNavigation, boolean thisCall, boolean spreadCall, Object[] arguments, MethodHandleWrapper mhw) { if (callSite.getFallbackRound().get() > INDY_FALLBACK_CUTOFF) { if (callSite.getTarget() != callSite.getDefaultTarget()) { callSite.setTarget(callSite.getDefaultTarget()); } } else { - if (callSite.getTarget() != mhw.getTargetMethodHandle()) { - callSite.setTarget(mhw.getTargetMethodHandle()); - if (LOG_ENABLED) LOG.info("call site target set, preparing outside invocation"); + Object receiverKey = receiverCacheKey(arguments[0]); + if (!callSite.picIncludes(receiverKey) && callSite.getPicCount() < INDY_PIC_SIZE) { + Selector selector = Selector.getSelector(callSite, sender, methodName, callID, safeNavigation, thisCall, spreadCall, arguments); + selector.skipSwitchPoint = true; + MethodHandle picChain = callSite.getPicChain(); + if (picChain == null) picChain = callSite.getDefaultTarget(); + selector.fallback = picChain; + selector.setCallSiteTarget(); + + MethodHandle newChain = selector.handle; + callSite.setPicChain(newChain); + + // wrap with top-level SwitchPoint guard + MethodHandle target = switchPoint.guardWithTest(newChain, callSite.getDefaultTarget()); + callSite.setTarget(target); + callSite.recordInPic(receiverKey, INDY_PIC_SIZE); + + if (LOG_ENABLED) LOG.info("call site target updated with PIC link, pic size: " + callSite.getPicCount()); } } mhw.resetLatestHitCount(); diff --git a/src/main/java/org/codehaus/groovy/vmplugin/v8/Selector.java b/src/main/java/org/codehaus/groovy/vmplugin/v8/Selector.java index 12ce177122..44b9ee61c4 100644 --- a/src/main/java/org/codehaus/groovy/vmplugin/v8/Selector.java +++ b/src/main/java/org/codehaus/groovy/vmplugin/v8/Selector.java @@ -161,11 +161,30 @@ public abstract class Selector { * Controls whether Groovy runtime exceptions are unwrapped around the target. */ public boolean catchException = true; + /** + * Indicates whether the global SwitchPoint should be skipped for this selector. + */ + public boolean skipSwitchPoint = false; + /** + * Custom fallback method handle to use during re-linking or PIC building. + */ + public MethodHandle fallback; /** * Call-site category associated with this selector. */ public CallType callType; + public static MethodHandle maybeWrapWithExceptionHandler(MethodHandle handle, boolean catchException) { + if (handle == null || !catchException) return handle; + Class<?> returnType = handle.type().returnType(); + if (returnType != Object.class) { + MethodType mtype = MethodType.methodType(returnType, GroovyRuntimeException.class); + return MethodHandles.catchException(handle, GroovyRuntimeException.class, UNWRAP_EXCEPTION.asType(mtype)); + } else { + return MethodHandles.catchException(handle, GroovyRuntimeException.class, UNWRAP_EXCEPTION); + } + } + /** * Cache values for read-only access */ @@ -1033,17 +1052,8 @@ public abstract class Selector { * Adds the standard exception handler. */ public void addExceptionHandler() { - //TODO: if we would know exactly which paths require the exceptions - // and which paths not, we can sometimes save this guard - if (handle == null || !catchException) return; - Class<?> returnType = handle.type().returnType(); - if (returnType != Object.class) { - MethodType mtype = MethodType.methodType(returnType, GroovyRuntimeException.class); - handle = MethodHandles.catchException(handle, GroovyRuntimeException.class, UNWRAP_EXCEPTION.asType(mtype)); - } else { - handle = MethodHandles.catchException(handle, GroovyRuntimeException.class, UNWRAP_EXCEPTION); - } - if (LOG_ENABLED) LOG.info("added GroovyRuntimeException unwrapper"); + handle = maybeWrapWithExceptionHandler(handle, catchException); + if (handle != null && LOG_ENABLED) LOG.info("added GroovyRuntimeException unwrapper"); } /** @@ -1052,7 +1062,7 @@ public abstract class Selector { public void setGuards(Object receiver) { if (!cache || handle == null) return; - MethodHandle fallback = callSite.getFallbackTarget(); + MethodHandle fallback = this.fallback != null ? this.fallback : callSite.getFallbackTarget(); // special guards for receiver if (receiver instanceof GroovyObject go) { @@ -1087,8 +1097,10 @@ public abstract class Selector { } // handle constant metaclass and category changes - handle = switchPoint.guardWithTest(handle, fallback); - if (LOG_ENABLED) LOG.info("added switch point guard"); + if (!skipSwitchPoint) { + handle = switchPoint.guardWithTest(handle, fallback); + if (LOG_ENABLED) LOG.info("added switch point guard"); + } java.util.function.Predicate<Class<?>> nonFinalOrNullUnsafe = (t) -> { return !Modifier.isFinal(t.getModifiers())
