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)