GROOVY-12227: GeneratedDispatcher: avoid runtime class definition so packed closures work in native images - #2765
Merged
paulk-asert merged 1 commit intoAug 10, 2026
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #2765 +/- ##
==================================================
+ Coverage 69.9972% 70.0025% +0.0053%
- Complexity 35557 35560 +3
==================================================
Files 1558 1558
Lines 131731 131751 +20
Branches 24178 24178
==================================================
+ Hits 92208 92229 +21
Misses 31176 31176
+ Partials 8347 8346 -1
🚀 New features to boost your workflow:
|
This comment has been minimized.
This comment has been minimized.
paulk-asert
force-pushed
the
packed-dispatcher-lmf-in-host
branch
from
August 10, 2026 07:04
b5b5692 to
0c7e5e7
Compare
packed closures work in native images Two layered changes to the GROOVY-12151 packed-closure machinery (GEP-27): 1. ClosureWriter now emits into each packed hosting class a private static $packedDispatchersFactory$ whose body adapts the class's three private static dispatch tables to their functional interfaces through ordinary bytecode-level LambdaMetafactory invokedynamic sites and returns them as one Bundle. The dispatcher accessor's bootstrap becomes the new four-argument IndyInterface.packedDispatchers, which receives that factory as a CONSTANT_MethodHandle bootstrap argument. 2. GeneratedDispatcher.bootstrap (four-argument form) just invokes the factory into a ConstantCallSite: linking needs neither a runtime Lookup.findStatic nor a programmatic LambdaMetafactory call — an undeclared reflective lookup and a run-time class definition, the two operations ahead-of-time runtimes restrict, so the linkage suits any such environment. Under GraalVM native image, the verified case, the factory's sites are pre-processed at image build time and its method references reach the class's own private tables without reflection metadata. On a regular JVM the linkage is equivalent: the VM spins the same three hidden classes when it links the factory's sites, with the cost moving from bootstrap-time programmatic LMF to first-invocation indy linkage (per packed class: one extra synthetic method, three indy sites, one bootstrap argument; the accessor shape is unchanged). The Bundle constructor is publicized because the compiler-emitted factory in the hosting class's own package constructs it directly. The previous three-argument bootstrap is retained verbatim for class files emitted by earlier 6.0 pre-releases, which keep linking through findStatic plus programmatic LambdaMetafactory. Conversely, class files in the new format need this runtime to link; both directions only concern unreleased 6.0 snapshots. Verified on GraalVM 25.2.4 (native-image 25.0.4): the packed repro that previously failed with 'Classes cannot be defined at runtime ... M$$Lambda...' now runs correctly (single emitted class, 30MB image, ~12ms total run time), and the tracing agent records zero packedDispatch entries for the new bytecode. PackedDispatcherFactoryTest covers every dispatch shape and the propagation of undeclared checked exceptions through both class-file formats -- the legacy format recreated by downgrading a freshly compiled accessor's bootstrap reference from the four-argument to the three-argument form, the only difference between the two formats. The test serializes its system-property mutation against other property-touching tests with @ResourceLock(SYSTEM_PROPERTIES).
paulk-asert
force-pushed
the
packed-dispatcher-lmf-in-host
branch
from
August 10, 2026 07:07
0c7e5e7 to
6fcc0a8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two layered changes to the GROOVY-12151 packed-closure machinery (GEP-27):
ClosureWriter now emits into each packed hosting class a private$packedDispatchersFactory$ whose body adapts the class's three
static
private static dispatch tables to their functional interfaces through
ordinary bytecode-level LambdaMetafactory invokedynamic sites and
returns them as one Bundle. The dispatcher accessor's bootstrap becomes
the new four-argument IndyInterface.packedDispatchers, which receives
that factory as a CONSTANT_MethodHandle bootstrap argument.
GeneratedDispatcher.bootstrap (four-argument form) just invokes the
factory into a ConstantCallSite: linking needs neither a runtime
Lookup.findStatic nor a programmatic LambdaMetafactory call — an
undeclared reflective lookup and a run-time class definition, the two
operations ahead-of-time runtimes restrict, so the linkage suits any
such environment. Under GraalVM native image, the verified case, the
factory's sites are pre-processed at image build time and its method
references reach the class's own private tables without reflection
metadata.
On a regular JVM the linkage is equivalent: the VM spins the same three
hidden classes when it links the factory's sites, with the cost moving
from bootstrap-time programmatic LMF to first-invocation indy linkage
(per packed class: one extra synthetic method, three indy sites, one
bootstrap argument; the accessor shape is unchanged). The Bundle
constructor is publicized because the compiler-emitted factory in the
hosting class's own package constructs it directly.
The previous three-argument bootstrap is retained verbatim for class
files emitted by earlier 6.0 pre-releases, which keep linking through
findStatic plus programmatic LambdaMetafactory. Conversely, class files
in the new format need this runtime to link; both directions only
concern unreleased 6.0 snapshots.
Verified on GraalVM 25.2.4 (native-image 25.0.4): the packed repro that
previously failed with 'Classes cannot be defined at runtime ...
M$$Lambda...' now runs correctly (single emitted class, 30MB image,
~12ms total run time), and the tracing agent records zero
packedDispatch entries for the new bytecode.
PackedDispatcherFactoryTest covers every dispatch shape and the
propagation of undeclared checked exceptions through both class-file
formats -- the legacy format recreated by downgrading a freshly
compiled accessor's bootstrap reference from the four-argument to the
three-argument form, the only difference between the two formats. The
test serializes its system-property mutation against other
property-touching tests with @ResourceLock(SYSTEM_PROPERTIES).