[
https://issues.apache.org/jira/browse/GROOVY-12240?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Paul King updated GROOVY-12240:
-------------------------------
Description:
{{EnumVisitor}} creates every enum constant through a synthetic helper:
{code:groovy}
def $INIT(Object[] para) {
return this(*para)
}
{code}
{{this(*para)}} is a spread constructor call, so
{{InvocationWriter.makeDirectConstructorCall}} refuses it — it bails on
{{SpreadExpression}}, and again on {{!controller.isConstructor()}} — and the
body compiles to {{ScriptBytecodeAdapter.despreadList}} plus
{{selectConstructorAndTransformArguments}}. The meta class then picks the
constructor at run time by reflecting over {{getDeclaredConstructors()}}. The
static initializer reaches {{$INIT}} itself through a dynamic call site.
For {{enum Colors}} \{ {{RED, GREEN, BLUE}} \} that is the entire
constant-creation path, even though the only arguments are the
compiler-supplied name and ordinal, both known at compile time.
Where reflection over the enum is not available, the class cannot initialize at
all. In a GraalVM native image built without reachability metadata for the
enum, {{getDeclaredConstructors()}} returns nothing and class initialization
throws:
{noformat}
groovy.lang.GroovyRuntimeException: Could not find matching constructor for:
com.example.MyEnum(String, Integer)
{noformat}
(note the boxed {{Integer}} — the ordinal has been through {{Object[]}}). This
kills the application in a static initializer before any user code runs.
{{@CompileStatic}} does not help. Groovy already compiles the *call site*
statically (StaticTypeCheckingVisitor, GROOVY-10845); it is {{$INIT}}'s own
body that is necessarily dynamic.
h3. Proposal
When every constant of an enum is a plain identifier, the arguments are
provably {{[name, ordinal]}}, and the static initializer can call the enum's
{{(String, int)}} constructor directly:
{noformat}
static {}; static {};
0: ldc // class Colors 0: new // class Colors
2: ldc // String RED 3: dup
4: iconst_0 4: ldc // String RED
5: invokestatic Integer.valueOf 6: iconst_0
8: invokedynamic invoke:(Class;String; 7: invokespecial
"<init>":(Ljava/lang/String;I)V
Integer;)Object; 10: putstatic Field
RED:LColors;
13: invokedynamic cast:(Object;)LColors;
18: putstatic Field RED:LColors;
{noformat}
The same shape javac emits for a Java enum: 21 bytes and two invokedynamic call
sites per constant become 13 bytes and none.
Anything else — constants with arguments, named-argument form, anonymous
constant bodies, or a mixture — keeps the existing {{$INIT}} path, as does any
enum lacking a {{(String, int)}} constructor (checked at bytecode generation,
after {{Verifier}} has run). {{$INIT}} itself is unchanged and still generated
for every enum.
h3. Scope, stated honestly
This is a *compile-time* change. It only helps code compiled by a Groovy that
carries the fix; bytecode already compiled by an earlier Groovy keeps its
{{$INIT}} path whichever Groovy runs it. Confirmed by building a GraalVM native
image of an application against a patched Groovy: the framework's own enums,
compiled by an earlier Groovy, still failed with {{Could not find matching
constructor}} until reachability metadata was restored.
Enums whose constants take arguments ({{RED(255, 0, 0)}}) are not addressed and
still require reachability metadata in a native image. Fixing those would mean
relaxing {{InvocationWriter.makeDirectConstructorCall}} to work outside a
constructor, which is a much wider change.
Related to GROOVY-12234.
I have a patch with tests ({{./gradlew :test}} passes in full: 16,548 tests, 0
failures) and will open a pull request against this issue.
was:
{{EnumVisitor}} creates every enum constant through a synthetic helper:
{code:groovy}
def $INIT(Object[] para) {
return this(*para)
}
{code}
{{this(*para)}} is a spread constructor call, so
{{InvocationWriter.makeDirectConstructorCall}} refuses it — it bails on
{{SpreadExpression}}, and again on {{!controller.isConstructor()}} — and the
body compiles to {{ScriptBytecodeAdapter.despreadList}} plus
{{selectConstructorAndTransformArguments}}. The meta class then picks the
constructor at run time by reflecting over {{getDeclaredConstructors()}}. The
static initializer reaches {{$INIT}} itself through a dynamic call site.
For {{enum Colors { RED, GREEN, BLUE }}} that is the entire constant-creation
path, even though the only arguments are the compiler-supplied name and
ordinal, both known at compile time.
Where reflection over the enum is not available, the class cannot initialize at
all. In a GraalVM native image built without reachability metadata for the
enum, {{getDeclaredConstructors()}} returns nothing and class initialization
throws:
{noformat}
groovy.lang.GroovyRuntimeException: Could not find matching constructor for:
com.example.MyEnum(String, Integer)
{noformat}
(note the boxed {{Integer}} — the ordinal has been through {{Object[]}}). This
kills the application in a static initializer before any user code runs.
{{@CompileStatic}} does not help. Groovy already compiles the *call site*
statically (StaticTypeCheckingVisitor, GROOVY-10845); it is {{$INIT}}'s own
body that is necessarily dynamic.
h3. Proposal
When every constant of an enum is a plain identifier, the arguments are
provably {{[name, ordinal]}}, and the static initializer can call the enum's
{{(String, int)}} constructor directly:
{noformat}
static {}; static {};
0: ldc // class Colors 0: new // class Colors
2: ldc // String RED 3: dup
4: iconst_0 4: ldc // String RED
5: invokestatic Integer.valueOf 6: iconst_0
8: invokedynamic invoke:(Class;String; 7: invokespecial
"<init>":(Ljava/lang/String;I)V
Integer;)Object; 10: putstatic Field
RED:LColors;
13: invokedynamic cast:(Object;)LColors;
18: putstatic Field RED:LColors;
{noformat}
The same shape javac emits for a Java enum: 21 bytes and two invokedynamic call
sites per constant become 13 bytes and none.
Anything else — constants with arguments, named-argument form, anonymous
constant bodies, or a mixture — keeps the existing {{$INIT}} path, as does any
enum lacking a {{(String, int)}} constructor (checked at bytecode generation,
after {{Verifier}} has run). {{$INIT}} itself is unchanged and still generated
for every enum.
h3. Scope, stated honestly
This is a *compile-time* change. It only helps code compiled by a Groovy that
carries the fix; bytecode already compiled by an earlier Groovy keeps its
{{$INIT}} path whichever Groovy runs it. Confirmed by building a GraalVM native
image of an application against a patched Groovy: the framework's own enums,
compiled by an earlier Groovy, still failed with {{Could not find matching
constructor}} until reachability metadata was restored.
Enums whose constants take arguments ({{RED(255, 0, 0)}}) are not addressed and
still require reachability metadata in a native image. Fixing those would mean
relaxing {{InvocationWriter.makeDirectConstructorCall}} to work outside a
constructor, which is a much wider change.
Related to GROOVY-12234.
I have a patch with tests ({{./gradlew :test}} passes in full: 16,548 tests, 0
failures) and will open a pull request against this issue.
> Initialize argument-less enum constants with a direct constructor call
> ----------------------------------------------------------------------
>
> Key: GROOVY-12240
> URL: https://issues.apache.org/jira/browse/GROOVY-12240
> Project: Groovy
> Issue Type: Improvement
> Components: Compiler
> Affects Versions: 5.0.8
> Reporter: Scott Murphy Heiberg
> Assignee: Paul King
> Priority: Minor
> Fix For: 6.0.0-beta-2
>
>
> {{EnumVisitor}} creates every enum constant through a synthetic helper:
> {code:groovy}
> def $INIT(Object[] para) {
> return this(*para)
> }
> {code}
> {{this(*para)}} is a spread constructor call, so
> {{InvocationWriter.makeDirectConstructorCall}} refuses it — it bails on
> {{SpreadExpression}}, and again on {{!controller.isConstructor()}} — and the
> body compiles to {{ScriptBytecodeAdapter.despreadList}} plus
> {{selectConstructorAndTransformArguments}}. The meta class then picks the
> constructor at run time by reflecting over {{getDeclaredConstructors()}}. The
> static initializer reaches {{$INIT}} itself through a dynamic call site.
> For {{enum Colors}} \{ {{RED, GREEN, BLUE}} \} that is the entire
> constant-creation path, even though the only arguments are the
> compiler-supplied name and ordinal, both known at compile time.
> Where reflection over the enum is not available, the class cannot initialize
> at all. In a GraalVM native image built without reachability metadata for the
> enum, {{getDeclaredConstructors()}} returns nothing and class initialization
> throws:
> {noformat}
> groovy.lang.GroovyRuntimeException: Could not find matching constructor for:
> com.example.MyEnum(String, Integer)
> {noformat}
> (note the boxed {{Integer}} — the ordinal has been through {{Object[]}}).
> This kills the application in a static initializer before any user code runs.
> {{@CompileStatic}} does not help. Groovy already compiles the *call site*
> statically (StaticTypeCheckingVisitor, GROOVY-10845); it is {{$INIT}}'s own
> body that is necessarily dynamic.
> h3. Proposal
> When every constant of an enum is a plain identifier, the arguments are
> provably {{[name, ordinal]}}, and the static initializer can call the enum's
> {{(String, int)}} constructor directly:
> {noformat}
> static {}; static {};
> 0: ldc // class Colors 0: new // class Colors
> 2: ldc // String RED 3: dup
> 4: iconst_0 4: ldc // String RED
> 5: invokestatic Integer.valueOf 6: iconst_0
> 8: invokedynamic invoke:(Class;String; 7: invokespecial
> "<init>":(Ljava/lang/String;I)V
> Integer;)Object; 10: putstatic Field
> RED:LColors;
> 13: invokedynamic cast:(Object;)LColors;
> 18: putstatic Field RED:LColors;
> {noformat}
> The same shape javac emits for a Java enum: 21 bytes and two invokedynamic
> call sites per constant become 13 bytes and none.
> Anything else — constants with arguments, named-argument form, anonymous
> constant bodies, or a mixture — keeps the existing {{$INIT}} path, as does
> any enum lacking a {{(String, int)}} constructor (checked at bytecode
> generation, after {{Verifier}} has run). {{$INIT}} itself is unchanged and
> still generated for every enum.
> h3. Scope, stated honestly
> This is a *compile-time* change. It only helps code compiled by a Groovy that
> carries the fix; bytecode already compiled by an earlier Groovy keeps its
> {{$INIT}} path whichever Groovy runs it. Confirmed by building a GraalVM
> native image of an application against a patched Groovy: the framework's own
> enums, compiled by an earlier Groovy, still failed with {{Could not find
> matching constructor}} until reachability metadata was restored.
> Enums whose constants take arguments ({{RED(255, 0, 0)}}) are not addressed
> and still require reachability metadata in a native image. Fixing those would
> mean relaxing {{InvocationWriter.makeDirectConstructorCall}} to work outside
> a constructor, which is a much wider change.
> Related to GROOVY-12234.
> I have a patch with tests ({{./gradlew :test}} passes in full: 16,548 tests,
> 0 failures) and will open a pull request against this issue.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)