Daniel Sun created GROOVY-12325:
-----------------------------------

             Summary: Speed up CachedMethod.invoke with a generated 
JIT-constant trampoline
                 Key: GROOVY-12325
                 URL: https://issues.apache.org/jira/browse/GROOVY-12325
             Project: Groovy
          Issue Type: Improvement
            Reporter: Daniel Sun


h2. Problem

{{CachedMethod.invoke}} is the MOP/Java fallback used by {{MetaClassImpl}}, 
classic uncompiled call sites, and the default indy cold tier 
({{invokeColdReflective}} -> {{doMethodInvoke}}).

That path still calls {{java.lang.reflect.Method.invoke}}. A {{MethodHandle}} 
held in an instance field is in the same performance band. After C2, only a 
JIT-constant callee (direct {{invokevirtual}} / {{invokestatic}} / 
{{invokeinterface}} in generated bytecode, or {{invokeExact}} of a {{static 
final}} / classData handle, or a linked {{invokedynamic}} CallSite) runs like a 
Java direct call.

Hot monomorphic indy and {{@CompileStatic}} already have that shape. 
{{CachedMethod.invoke}} does not.

h2. Approach

After {{groovy.cachedmethod.invoker.threshold}} hits (default 100, below 
{{groovy.indy.optimize.threshold}} of 1000 so cold indy is still on 
{{doMethodInvoke}} when the trampoline appears), install a generated 
{{DirectInvoker}} behind {{CachedMethod.invoke}} only.

Internal types live in {{org.apache.groovy.internal.runtime.invoke}} 
(japicmp-excluded). Definition reuses {{HiddenClassDefiner}} (GROOVY-12223) and 
{{ClassLoaderForClassArtifacts}}.

Define order:

# InvokerFactory nestmate + direct invoke when the member is publicly invocable 
from that class ({{String.startsWith}}).
# Declaring-class nestmate + direct invoke when {{privateLookupIn}} is 
possible. Private class methods use {{invokevirtual}}; private interface 
methods use {{invokeinterface}} (hidden nestmates do not subclass the host, so 
{{invokespecial}} fails verification).
# InvokerFactory nestmate + classData {{MethodHandle}} + {{invokeExact}} when 
types are still resolvable from the runtime loader.
# {{ClassLoaderForClassArtifacts}} when the host loader can resolve 
{{DirectInvoker}} — never for bootstrap types.

Failures sticky-return {{null}}; {{CachedMethod.invoke}} keeps 
{{Method.invoke}}. Generation is skipped for caller-sensitive and abstract 
methods, Android, native image, and when hidden classes are disabled.

This is the MOP "Groovy as caller" path ({{makeAccessible}}). Indy continues to 
{{unreflect}} with the call-site {{Lookup}} and must not be fed the trampoline.

h2. Configuration

{noformat}
-Dgroovy.cachedmethod.invoker.threshold=100
-Dgroovy.cachedmethod.invoker.disable=true
{noformat}

The existing {{-Dgroovy.hidden.classes.disable=true}} also turns generation off.

h2. Compatibility

* No change to the {{MetaMethod.invoke}} / {{CachedMethod.invoke}} signatures.
* Selection (categories, EMC, interceptable, per-instance MetaClass) is 
unchanged; the trampoline is bound to the Java {{Method}}, not to a 
{{MetaMethod}} wrapper.
* Wrong-argument type on the generated path is {{ClassCastException}} 
(rethrown), matching DGM / {{CallSiteGenerator}}. The reflective path still 
wraps {{IllegalArgumentException}} in {{InvokerInvocationException}}.
* Opt-out: {{-Dgroovy.cachedmethod.invoker.disable=true}}.




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to