condy and enums

John Rose john.r.rose at oracle.com
Thu Oct 12 01:01:45 UTC 2023


On 9 Oct 2023, at 14:45, Liam Miller-Cushon wrote:

>  I'd like to discuss the idea of using constant dynamic in the 
> generated code for enums.

Although I’m fond of condy, I don’t think this is in its sweet spot. 
  Condy is good for fine-grained lazy evaluation, at the granularity of 
bytecode instructions.  But for setting up an enum, the granularity is 
really the whole class, and all you need per enum member is a 
stereotyped action to build the enum instance, followed by putstatic.

I would consider using this code shape for the `<clinit>`:

```
… for each member:
iconst {nextenumordinal}
invokestatic $make$(I)L{thisenumclass};
putstatic {nextenumfield}
…
return
```

That would get you to 7k enums, by factoring the mess of creation into a 
single helper method, or 8k enums with a helper method for each block of 
256.

The single helper method `$make$` would in many cases be very simple, 
but might be a large `tableswitch` with varying code on each branch.  
The single helper can be factored into multiple sub-helpers, if code 
size grows beyond the bytecode size limit.  (It will do that, if each 
branch has to contain different code.)  So instead you’d have to 
generate a sub-helper function for each block of (say) 1000 enums, and a 
decision tree at the top level (`$make$`) to decide which sub-helper to 
call.  All straightforward and also annoyingly unproductive, since large 
enums are very rare.

Replacing the invokestatic by ldc/condy gives you marginally more enums, 
but at the cost of exploding the size (and complexity and runtime 
effort) of your constant pool.  When calculating complexity, don’t 
just count bytecodes in one method (`<clinit>`) but rather estimate the 
classfile size.  That means taking into account the number and size of 
constant pool entries, as well as the number and size of bytecodes (also 
method declarations).

If you want to do decisively better, and support tens of thousands of 
enum members, you can do so by bolding making `<clinit>` be constant in 
size.  That can be accomplished by using a reflective computation to 
build the enums and patch them into the static finals.  You could use 
varhandles or old reflection, but I’d just go straight to Unsafe and 
make this a JDK service function, to avoid bootstrapping issues.

The service function would iterate over the enum class’s fields, 
detect which ones were enum elements, and call a helper function created 
by the enum class:

```
public static void initializeEnumClass(Lookup enumClassLU, MethodHandle 
enumMemberCreator) {
   int ordinal = 0;
   if (!enumClassLU.hasPrivateAccess())  throw (IAE);
   Class<? extends Enum> ec = 
enumClassLU.lookupClass().asSubClass(Enum.class);
   for (Field f : ec.getDeclaredFields()) {  //order significant here
     if (f is an enum member) {
       Object e = enumMemberCreator.invokeExact(f, ordinal++);
       // next stuff can be done more directly by Unsafe
       assert(f.get(null) == null);  //caller resp.
       f.setAccessible(true);
       f.set(null, e);
     }
   }
}
```

The creation of the values array should be done in `<clinit>`, as well, 
or as a condy (yes, that’s a good usage of condy!) and cloned as a 
fresh copy for each call to `values()`.  And it can be done reflectively 
as well.  Just iterate over all the fields and store them into the 
array.  (Use the `ordinal()` as the array index, or just assert that the 
fields are in the correct order already.)

With those two adjustments, to bind enums and build the values array 
reflectively, your enum would be limited only by the maximum size of the 
constant pool.  That is, you could have up to about 65k enums (but not 
the whole 2^16).

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/compiler-dev/attachments/20231011/def0e064/attachment.htm>


More information about the compiler-dev mailing list