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())

Reply via email to