Skip to content

GROOVY-12227: GeneratedDispatcher: avoid runtime class definition so … - #2765

Open
paulk-asert wants to merge 1 commit into
apache:masterfrom
paulk-asert:packed-dispatcher-lmf-in-host
Open

GROOVY-12227: GeneratedDispatcher: avoid runtime class definition so …#2765
paulk-asert wants to merge 1 commit into
apache:masterfrom
paulk-asert:packed-dispatcher-lmf-in-host

Conversation

@paulk-asert

Copy link
Copy Markdown
Contributor

…packed closures work in native images

Two layered changes to the GROOVY-12151 packed-closure machinery (GEP-27):

  1. ClosureWriter now passes the three dispatch tables as constant bootstrap arguments (CONSTANT_MethodHandle), resolved by the VM's constant pool rather than a runtime Lookup.findStatic -- which under GraalVM native image demanded per-class reflection metadata. Verified: the tracing agent records zero packedDispatch entries for the new bytecode.

  2. GeneratedDispatcher.bootstrap keeps the LambdaMetafactory hidden-class adapters on a regular JVM (the JIT-inlining rationale in the class javadoc), but where classes cannot be defined at run time -- native image, detected per link so build-time class init cannot bake in the wrong answer -- it adapts the tables with method-handle-invoking wrappers instead: ordinary bytecode of this class, AOT-compiled into the image. A catch-based fallback covers AOT runtimes the property probe misses. -Dgroovy.packed.dispatch.handles=true forces the wrapper path on a JVM, for parity testing.

Permanent behaviour, no user-facing flag: JVM semantics are unchanged and the fallback engages only where the hidden-class path cannot work. The old 3-arg bootstrap remains for class files from earlier 6.0 snapshots.

Before/after on GraalVM 25.2.4 (native-image 25.0.4): the packed repro previously failed with 'Classes cannot be defined at runtime ... M$$Lambda...'; it now runs correctly (single emitted class, 30MB image, ~12ms total run time). Parity + undeclared-checked-exception propagation covered by PackedDispatcherHandleBundleTest; all existing packed-closure suites green.

…packed closures work in native images

Two layered changes to the GROOVY-12151 packed-closure machinery (GEP-27):

1. ClosureWriter now passes the three dispatch tables as constant bootstrap
   arguments (CONSTANT_MethodHandle), resolved by the VM's constant pool
   rather than a runtime Lookup.findStatic -- which under GraalVM native
   image demanded per-class reflection metadata. Verified: the tracing
   agent records zero packedDispatch entries for the new bytecode.

2. GeneratedDispatcher.bootstrap keeps the LambdaMetafactory hidden-class
   adapters on a regular JVM (the JIT-inlining rationale in the class
   javadoc), but where classes cannot be defined at run time -- native
   image, detected per link so build-time class init cannot bake in the
   wrong answer -- it adapts the tables with method-handle-invoking
   wrappers instead: ordinary bytecode of this class, AOT-compiled into
   the image. A catch-based fallback covers AOT runtimes the property
   probe misses. -Dgroovy.packed.dispatch.handles=true forces the wrapper
   path on a JVM, for parity testing.

Permanent behaviour, no user-facing flag: JVM semantics are unchanged and
the fallback engages only where the hidden-class path cannot work. The
old 3-arg bootstrap remains for class files from earlier 6.0 snapshots.

Before/after on GraalVM 25.2.4 (native-image 25.0.4): the packed repro
previously failed with 'Classes cannot be defined at runtime ...
M$$Lambda...'; it now runs correctly (single emitted class, 30MB image,
~12ms total run time). Parity + undeclared-checked-exception propagation
covered by PackedDispatcherHandleBundleTest; all existing packed-closure
suites green.
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 69.9778%. Comparing base (7a34d45) to head (b5b5692).
⚠️ Report is 6 commits behind head on master.

Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##               master      #2765        +/-   ##
==================================================
+ Coverage     69.9738%   69.9778%   +0.0040%     
- Complexity      35505      35528        +23     
==================================================
  Files            1557       1557                
  Lines          131675     131706        +31     
  Branches        24169      24174         +5     
==================================================
+ Hits            92138      92165        +27     
- Misses          31193      31205        +12     
+ Partials         8344       8336         -8     
Files with missing lines Coverage Δ
...rg/codehaus/groovy/classgen/asm/ClosureWriter.java 90.0404% <100.0000%> (+0.2473%) ⬆️
...g/codehaus/groovy/runtime/GeneratedDispatcher.java 29.1667% <100.0000%> (-57.7899%) ⬇️
...org/codehaus/groovy/vmplugin/v8/IndyInterface.java 83.5165% <100.0000%> (-0.4614%) ⬇️

... and 10 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.

@testlens-app

testlens-app Bot commented Aug 4, 2026

Copy link
Copy Markdown

✅ All tests passed ✅

🏷️ Commit: b5b5692
▶️ Tests: 108745 executed
⚪️ Checks: 31/31 completed


Learn more about TestLens at testlens.app.

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.

2 participants