Skip to content

GROOVY-12137: experimental reflective cold tier for indy dispatch (sp…#2673

Open
paulk-asert wants to merge 1 commit into
apache:masterfrom
paulk-asert:groovy12137
Open

GROOVY-12137: experimental reflective cold tier for indy dispatch (sp…#2673
paulk-asert wants to merge 1 commit into
apache:masterfrom
paulk-asert:groovy12137

Conversation

@paulk-asert

Copy link
Copy Markdown
Contributor

…ike)

Behind the groovy.indy.cold.reflection flag (default: off), plain method calls dispatch reflectively while a call site is cold, deferring all MethodHandle chain construction — and its one-time LambdaForm creation cost — to hit-count promotion. The shared cold dispatcher has a single (Object[])Object shape for every call site, arity, and primitive pattern; promotion installs the unchanged full guarded chain, so the hot path is identical to master.

Plain calls are public, non-static, non-category CachedMethods on public classes with a real receiver and no spread/safe-null/interceptor semantics; caller-sensitive targets (including unannotated caller-context-sensitive cases like object serialization, and interface selections resolving to sensitive implementations) stay on the full path. Selection reuses Selector's existing logic via a new selection-only hook; validity is re-checked per call with plain-Java equivalents of the chain's guards. A per-wrapper cumulative counter promotes polymorphic receiver shapes that the consecutive-hit counter never catches.

Measured (deterministic class-load counters + fresh-JVM timing): ~2/3 of per-shape LambdaForm cost removed on cold dispatch workloads, 1.34-1.40x faster cold dispatch with many cold sites, steady state unchanged after promotion, MOP-heavy test subset (3437 tests) green with the flag on.

@codecov-commenter

codecov-commenter commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 80 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.6328%. Comparing base (23780cf) to head (750878e).
⚠️ Report is 6 commits behind head on master.

Files with missing lines Patch % Lines
...vmplugin/v8/ColdReflectiveMethodHandleWrapper.java 0.0000% 49 Missing ⚠️
...a/org/codehaus/groovy/reflection/CachedMethod.java 0.0000% 17 Missing ⚠️
...java/org/codehaus/groovy/vmplugin/v8/Selector.java 0.0000% 14 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##               master      #2673        +/-   ##
==================================================
- Coverage     68.7019%   68.6328%   -0.0691%     
+ Complexity      34123      34119         -4     
==================================================
  Files            1536       1537         +1     
  Lines          128912     129033       +121     
  Branches        23369      23406        +37     
==================================================
- Hits            88565      88559         -6     
- Misses          32517      32639       +122     
- Partials         7830       7835         +5     
Files with missing lines Coverage Δ
...org/codehaus/groovy/vmplugin/v8/IndyInterface.java 72.8324% <ø> (-15.5734%) ⬇️
...java/org/codehaus/groovy/vmplugin/v8/Selector.java 78.7645% <0.0000%> (-2.1879%) ⬇️
...a/org/codehaus/groovy/reflection/CachedMethod.java 55.7692% <0.0000%> (-6.8207%) ⬇️
...vmplugin/v8/ColdReflectiveMethodHandleWrapper.java 0.0000% <0.0000%> (ø)

... and 11 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread src/main/java/org/codehaus/groovy/vmplugin/v8/IndyInterface.java
Comment thread src/main/java/org/codehaus/groovy/vmplugin/v8/Selector.java
@paulk-asert paulk-asert force-pushed the groovy12137 branch 2 times, most recently from e1d35a3 to fec99f7 Compare July 8, 2026 02:38
@paulk-asert

Copy link
Copy Markdown
Contributor Author

@blackdrag updated - please reassess

Comment thread src/main/java/org/codehaus/groovy/vmplugin/v8/IndyInterface.java
Comment thread src/main/java/org/codehaus/groovy/vmplugin/v8/Selector.java Outdated
Comment thread src/main/java/org/codehaus/groovy/vmplugin/v8/Selector.java Outdated
…ike)

Behind the groovy.indy.cold.reflection flag (default: off), plain method
calls dispatch reflectively while a call site is cold, deferring all
MethodHandle chain construction — and its one-time LambdaForm creation
cost — to hit-count promotion. The shared cold dispatcher has a single
(Object[])Object shape for every call site, arity, and primitive
pattern; promotion installs the unchanged full guarded chain, so the
hot path is identical to master.

Plain calls are public, non-static, non-category CachedMethods on
public classes with a real receiver and no spread/safe-null/interceptor
semantics; caller-sensitive targets (including unannotated
caller-context-sensitive cases like object serialization, and interface
selections resolving to sensitive implementations) stay on the full
path. Selection reuses Selector's existing logic via a new
selection-only hook; validity is re-checked per call with plain-Java
equivalents of the chain's guards. A per-wrapper cumulative counter
promotes polymorphic receiver shapes that the consecutive-hit counter
never catches.

Measured (deterministic class-load counters + fresh-JVM timing):
~2/3 of per-shape LambdaForm cost removed on cold dispatch workloads,
1.34-1.40x faster cold dispatch with many cold sites, steady state
unchanged after promotion, MOP-heavy test subset (3437 tests) green
with the flag on.

Changes from review feedback: consolidate caller-sensitivity, harden invariants

    - selectForColdReflection now throws GroovyBugError for non-METHOD call
      types instead of silently returning null, so a broken fallback gate
      cannot hide behind the full handle path
    - the NullObject exclusion is marked TODO: its methods are ordinary
      methods on a singleton receiver and could be served reflectively
    - all method-determined caller-sensitivity — the annotation probe plus
      declaring-class cases like object serialization's stack-walking loader
      lookup — now lives behind CachedMethod#isCallerSensitive, computed
      once per method; the wrapper keeps only the receiver-dependent case
    - implementation resolution now applies to abstract selections only
      (e.g. the metaclass picking ObjectInput.readObject() for a call on an
      ObjectInputStream): interface default methods carry their own
      annotations, are probed directly, and take the reflective tier
@testlens-app

testlens-app Bot commented Jul 9, 2026

Copy link
Copy Markdown

✅ All tests passed ✅

🏷️ Commit: 750878e
▶️ Tests: 103231 executed
⚪️ Checks: 31/31 completed


Learn more about TestLens at testlens.app.

@paulk-asert

Copy link
Copy Markdown
Contributor Author

@blackdrag Updated - ready for another assessment if you have time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants