Paul King created GROOVY-12750:
----------------------------------
Summary: Closure packing: dispatch fixed-arity calls without an
argument array, and let a direct call propagate an InvokerInvocationException
the body throws
Key: GROOVY-12750
URL: https://issues.apache.org/jira/browse/GROOVY-12750
Project: Groovy
Issue Type: Bug
Reporter: Paul King
Follow-up fixes to closure packing (GROOVY-12151), which shipped in 6.0.0.
h3. A direct call unwrapped an InvokerInvocationException thrown by the closure
body
The {{call(Object)}} and {{call(Object...)}} paths of a packed closure reach
the hoisted body through its dispatch table, with no reflective step in
between, so there is no invoker wrapping to undo. They nevertheless caught
{{InvokerInvocationException}} and rethrew its cause, so an
{{InvokerInvocationException}} thrown by the body itself was unwrapped, where a
closure compiled to its own class lets it through:
{code:groovy}
// with closure packing enabled
Closure one = { Object x -> throw new InvokerInvocationException(new
IOException('one')) }
one.call((Object) 1) // closure class: InvokerInvocationException; packed
closure: IOException
{code}
The catch is removed from both paths. {{mopCall}}, which goes through the
metaclass, keeps it.
h3. Fixed-arity calls allocated the closure on every creation
A dynamic call site invokes a packed closure through its fixed-arity
{{doCall}}, which wrapped the arguments in an array only for {{dispatchAll}} to
check the length against the class's constant arity and unwrap them again.
Escape analysis removes that array, but the {{doCall}} -> {{dispatchAll}} chain
was too large to inline at the call site, so the closure object itself escaped
and was allocated each time it was created. The one-parameter and
implicit-parameter forms now go straight to {{dispatchOne}}, and the
two-parameter form to a new {{dispatchTwo}}, the branches {{dispatchAll}} takes
for these shapes anyway.
A new JMH benchmark, {{ClosureDoCallBench}} (dynamic {{c(x)}} call sites,
{{-prof gc}}), shows the monomorphic one- and two-parameter cases going from
0.6-0.7x to about 1.0x of a closure compiled to its own class, and from 128 to
32 B/op. The implicit-parameter case improves in most forks, and the
megamorphic case is unchanged.
h3. The packing runtime link was missing from the @GroovyABI registry
A class compiled with closure packing links against parts of the runtime that
the {{@GroovyABI}} registry did not record: the {{MethodHandle}}-taking
{{IndyInterface.packedDispatchers}} bootstrap, the
{{GeneratedDispatcher$Bundle}} constructor its dispatcher factory calls, and
the {{dispatch}}/{{dispatch1}}/{{dispatch2}} methods its {{LambdaMetafactory}}
sites implement. They are now annotated {{@GroovyABI(since = "6.0.0")}} and
registered, so that {{checkGroovyABISync}} catches a change that would break
compiled code. Comments claiming that {{Bundle}} and {{GeneratedDispatcher}}
never appear in generated class files are corrected.
h3. Backport
The first two fixes apply to 6.0.x. The registry change concerns the
compatibility tracking on master.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)