paulk-asert opened a new pull request, #2692:
URL: https://github.com/apache/groovy/pull/2692

   …edClosures/flag off by default (GEP-27 S1)
   
   Add closure packing on top of the SAM-lambda hoisting: an eligible closure 
literal's body is hoisted into a synthetic method on the enclosing class and 
the literal is replaced by a shared PackedClosure adapter, removing the 
per-closure generated class (and the nested $_closure1$_closure2 name 
explosion) while the value stays a real groovy.lang.Closure.
   
   Capability analysis (PackStrategy, ClosureWriter.chooseStrategy):
   - a single decision point selects FULL_CLASS (S0) vs PACKED_ADAPTER (S1);
   - triggered by the @PackedClosures annotation (trust path, dynamic or 
static), or automatically under @CompileStatic when the type checker PROVES the 
closure delegate-independent -- every implicitly-received name resolved against 
the owner, read from STC's IMPLICIT_RECEIVER paths (a path ending in "owner" is 
safe; "delegate", from @DelegatesTo/with, is not). Behind 
groovy.target.closure.pack.
   
   Typed static dispatch (phase 4): the hoisted body reuses the expressions 
StaticCompilationVisitor already annotated, so under @CompileStatic it compiles 
statically -- read-only captures pass as typed leading parameters, inferred 
types (@ClosureParams, implicit it) arrive via metadata, and the method takes 
the closure's inferred return type. Packed bodies are statically dispatched 
with zero invokedynamic, often fewer indy sites than a closure class (which 
Reference-boxes captures as Object).
   
   Written captures ride the compiler's holder machinery: the shared Reference 
is passed as a parameter (originType/closure-shared/UseExistingReference), so 
the body is not rewritten, compiles statically, and supports every write form 
(compound ops beyond +=, postfix value semantics).
   
   Soundness for the dynamic trust path:
   - PackedClosure runtime guard fails fast if a delegate / delegate-consulting 
resolveStrategy is set on a trust-packed closure; a statically proven closure 
tolerates it as a no-op (as a @CompileStatic closure class does today);
   - compile-time escape decline keeps closures that visibly escape (stored to 
a field/property, returned, appended, in a collection literal) as classes.
   
   Mechanism fidelity so a packed closure behaves like a generated closure 
class: correct owner/static dispatch in static methods; getParameterTypes() 
carries the real declared types; arguments are adapted as reflective dispatch 
would (vararg collection, single List/Tuple destructuring, per-arg coercion 
Closure->SAM and GString->String); call() dispatches straight to doCall with 
invoker-exception unwrapping; dispatch via a cached reflective Method 
(metaclass coercion would auto-dereference Reference holder arguments).
   
   ClassNode.visitMethods: dedup newly-added methods by identity via an 
IdentityHashMap set (O(1)) instead of ArrayList.removeAll (O(n^2)). Visiting a 
method during code generation can add more (constructor-ref synthetic, lambda 
$deserializeLambda$, a hoisted body that hoists a nested one); the O(1) 
fixpoint visits them all and fixes a compile-time freeze the O(n^2) scan caused 
against method-generating visitors (e.g. groovy-contracts).
   
   Tests: ClosurePackCapabilityTest and PackedClosuresTransformTest (proof, 
declines, auto-pack, zero-indy typed bodies, write forms, destructuring). 
Flag-on stress net 0 failures across LambdaTest + all *Closure*/*DelegatesTo* 
static-compile suites + ReproducibleBytecodeBugs; flag-off byte-identical (778 
green); groovy-contracts compiles and tests clean.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to