condy and enums
Liam Miller-Cushon
cushon at google.com
Fri Oct 13 19:55:02 UTC 2023
Thanks for the comments, that is a very neat idea!
I was labouring under a misunderstanding of what the verifier constraints
are. My understanding is that at the Java language level, each static final
has to be definitely assigned at the end of the static initializer (JLS
8.3.1.2), but the VM has fewer restrictions. Is the only constraint that
putstatic is only allowed to write to static final fields inside clinit,
and there is no requirement that static finals are definitely assigned in
the clinit, so using Unsafe to initialize the fields is fair game?
I have a rough draft of your approach here:
https://github.com/openjdk/jdk/pull/16191
* It uses reflection and Unsafe.staticFieldOffset for the JVM method.
* I took a short-cut and disabled generation of values() array. It would be
possible to split the values() array across multiple methods, but that
would still require additional constant pool entries, and as you described
the constant pool size ends up being the limiting factor. I think the
values() array could still be a good use of indy or condy.
With that prototype, I was able to generate an enum with ~32K constants.
Each enum name currently uses two constant pool entries (a Utf8 and String
entry for the name). Perhaps there's a way to get that down to one entry,
and get to ~65k enums.
I collected some preliminary performance data: with 4000 constants, cold
start time is ~400ms with the new codegen and ~700ms without. With ~32K
constants, startup takes a full ~17s. From some initial investigation, most
of that time is spent in Class.getDeclaredFields and
Unsafe.staticFieldOffset.
This has been fun and educational. it also adds complexity to javac, and
may not be a clear improvement for non-pathological enums that don't have
many thousands of entries.
Do you think it could be worth pursuing?
On Wed, Oct 11, 2023 at 6:08 PM John Rose <john.r.rose at oracle.com> wrote:
> P.S. I trust it is clear how the single service method below would be used
> in the <clinit> of each client enum. One other thing occurred to me:
> Enums which have bootstrap entanglements with the MethodHandle class would
> need special treatment. For that reason, it might be better to not pass the
> argument MethodHandle enumMemberCreator but rather just call a private
> static method of a fixed name and signature, within the same enum. That can
> be easily done with method handles, if those are “on line”, but can also be
> done with core reflection, which boots up sooner.
>
> On 11 Oct 2023, at 18:01, John Rose wrote:
>
> ```
> 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/20231013/c5f2b04c/attachment.htm>
More information about the compiler-dev
mailing list