[ 
https://issues.apache.org/jira/browse/GROOVY-12151?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18095706#comment-18095706
 ] 

ASF GitHub Bot commented on GROOVY-12151:
-----------------------------------------

Copilot commented on code in PR #2692:
URL: https://github.com/apache/groovy/pull/2692#discussion_r3566423724


##########
src/main/java/org/codehaus/groovy/runtime/PackedClosure.java:
##########
@@ -0,0 +1,224 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.codehaus.groovy.runtime;
+
+import groovy.lang.Closure;
+
+import java.lang.invoke.MethodHandle;
+
+/**
+ * A single, shared {@link Closure} adapter for {@code @PackedClosures} 
compact closure compilation.
+ * <p>
+ * Normally the Groovy compiler generates one inner class per closure literal
+ * (e.g. {@code Owner$_method_closure1}). Under {@code @PackedClosures} an 
<em>eligible</em>
+ * closure body is instead hoisted into a synthetic method on the enclosing 
("owner")
+ * class, and the closure literal is replaced by an instance of this one 
adapter class,
+ * which dispatches back to that method. This removes the per-closure 
generated class
+ * (and the deeply-nested {@code $_closure1$_closure2$_closure3} name 
explosion) while
+ * still yielding a real {@code groovy.lang.Closure} instance, so features 
that operate
+ * through {@code call()} (iteration, {@code curry}, {@code memoize}, {@code 
trampoline})
+ * continue to work.
+ * <p>
+ * Captured values are stored at construction and prepended to the call 
arguments before
+ * dispatch, so the hoisted method has the shape {@code method(captured..., 
closureParams...)}.
+ * Dispatch goes through a {@link java.lang.invoke.MethodHandle} constant to 
the private hoisted
+ * method (bound to the receiver for an instance method), so there is no 
reflection.
+ */
+public final class PackedClosure extends Closure<Object> {
+
+    private static final long serialVersionUID = 1L;
+    private static final Object[] EMPTY = new Object[0];
+
+    private final MethodHandle handle;
+    private final String method;
+    private final Object[] captured;
+    private final boolean strict;
+    private final org.codehaus.groovy.reflection.ParameterTypes paramInfo;
+
+    /**
+     * @param owner        the enclosing instance the hoisted method lives on 
(also used as thisObject)
+     * @param handle       an invoke handle to the private hoisted method (a 
constant in the hosting
+     *                     class): dispatch needs no reflection and binds the 
exact method, so an
+     *                     inherited packed closure can never misdispatch to a 
subclass's method
+     * @param method       the name of the hoisted synthetic method (kept for 
diagnostics only)
+     * @param captured     values captured from the enclosing scope, prepended 
to each call
+     * @param visibleTypes the closure's declared parameter types, so callers 
that key behaviour on
+     *                     {@code getParameterTypes()} (DGM arity/type 
decisions) and argument
+     *                     adaptation (vararg collection into a trailing array 
parameter) behave
+     *                     exactly as with a generated closure class
+     * @param strict       whether the delegate guard throws. {@code true} for 
the dynamic trust path
+     *                     (an unverifiable {@code @PackedClosures} assertion 
must fail fast on misuse);
+     *                     {@code false} when the type checker PROVED every 
free name owner-resolved.
+     */
+    public PackedClosure(final Object owner, final MethodHandle handle, final 
String method, final Object[] captured, final Class[] visibleTypes, final 
boolean strict) {
+        super(owner, owner);
+        // Bind the receiver for an instance method so the handle takes 
(captured..., args...); a static
+        // hoisted method passes the class as owner and its handle already 
takes no receiver. Then adapt
+        // the typed handle ONCE to a canonical (Object[])->Object shape so 
each call is a single
+        // invokeExact (JIT-inlinable) rather than a per-call, arg-boxing 
invokeWithArguments.
+        MethodHandle bound = (owner instanceof Class) ? handle : 
handle.bindTo(owner);
+        this.handle = 
bound.asType(bound.type().generic()).asSpreader(Object[].class, 
bound.type().parameterCount());
+        this.method = method;
+        this.captured = (captured != null) ? captured : EMPTY;
+        this.strict = strict;
+        this.maximumNumberOfParameters = visibleTypes.length;
+        this.parameterTypes = visibleTypes;
+        this.paramInfo = new 
org.codehaus.groovy.reflection.ParameterTypes(visibleTypes);
+    }
+
+    // GEP-27 runtime guard: a hoisted body binds free names to the owner at 
compile time, so it
+    // cannot honour a caller-set delegate. Rather than silently mis-resolve 
(returning the owner's
+    // member where a normal closure would reach the delegate), fail fast at 
the point of misuse.
+    // Setting the delegate to the owner is the harmless default and stays a 
no-op.
+    @Override
+    public void setDelegate(final Object delegate) {
+        if (strict && delegate != null && delegate != getOwner()) {
+            throw new UnsupportedOperationException(
+                    "Cannot set a delegate on a @PackedClosures closure 
(hoisted method '" + method
+                    + "'): its free names were bound to the owner at compile 
time, so an external "
+                    + "delegate cannot be honoured. Remove @PackedClosures 
from this scope, or "
+                    + "exclude this closure, if it relies on delegate-based 
resolution.");
+        }
+        super.setDelegate(delegate);
+    }
+
+    @Override
+    public void setResolveStrategy(final int resolveStrategy) {
+        // DELEGATE_FIRST/DELEGATE_ONLY need a delegate the hoisted body 
cannot consult; TO_SELF
+        // would resolve names against this shared adapter, which has no 
user-defined members.
+        // Only owner-based resolution (OWNER_FIRST/OWNER_ONLY) is 
reproducible by a hoisted body.
+        if (strict && resolveStrategy != OWNER_FIRST && resolveStrategy != 
OWNER_ONLY) {
+            throw new UnsupportedOperationException(
+                    "Cannot set resolveStrategy=" + 
strategyName(resolveStrategy)
+                    + " on a @PackedClosures closure (hoisted method '" + 
method + "'): a hoisted "
+                    + "body resolves free names against the owner at compile 
time, so only "
+                    + "OWNER_FIRST/OWNER_ONLY are supported. Remove 
@PackedClosures from this "
+                    + "scope, or exclude this closure.");
+        }
+        super.setResolveStrategy(resolveStrategy);
+    }
+
+    private static String strategyName(final int s) {
+        switch (s) {
+            case DELEGATE_FIRST: return "DELEGATE_FIRST";
+            case DELEGATE_ONLY:  return "DELEGATE_ONLY";
+            case TO_SELF:        return "TO_SELF";
+            default:             return String.valueOf(s);
+        }
+    }
+
+    // Dispatch call() straight to doCall: the generic varargs doCall is 
otherwise vararg-ambiguous
+    // through the metaclass when a single argument is itself an Object[] (the 
metaclass would treat
+    // the element as the whole argument list and spread it). Unwrap invoker 
exceptions exactly as
+    // Closure.call does, so user exceptions surface unwrapped.
+    @Override
+    public Object call(final Object... args) {
+        try {
+            return doCall(args);
+        } catch (InvokerInvocationException e) {
+            
org.apache.groovy.internal.util.UncheckedThrow.rethrow(e.getCause());
+            return null; // unreachable
+        }
+    }
+
+    public Object doCall(final Object... args) {
+        Object[] provided = (args != null) ? args : EMPTY;
+        int arity = getMaximumNumberOfParameters();
+        // Fast path: the caller supplied exactly the declared number of 
arguments (the common
+        // each()/collect() call). Skip the List-destructuring, 
correctArguments and per-arg coercion
+        // adaptation 

> hoist eligible closure bodies to a shared adapter @PackedClosures/flag 
> (GEP-27 S1)
> ----------------------------------------------------------------------------------
>
>                 Key: GROOVY-12151
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12151
>             Project: Groovy
>          Issue Type: Improvement
>            Reporter: Paul King
>            Assignee: Paul King
>            Priority: Major
>




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to