<!DOCTYPE html><html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body><div style="font-family: sans-serif;"><div class="markdown" style="white-space: normal;">
<p dir="auto">On 9 Oct 2023, at 14:45, Liam Miller-Cushon wrote:</p>
</div><div class="plaintext" style="white-space: normal;"><blockquote style="margin: 0 0 5px; padding-left: 5px; border-left: 2px solid #777777; color: #777777;"><p dir="auto"> I'd like to discuss the idea of using constant dynamic in the generated code for enums.</p>
</blockquote></div>
<div class="markdown" style="white-space: normal;">
<p dir="auto">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.</p>
<p dir="auto">I would consider using this code shape for the <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;"><clinit></code>:</p>
<pre style="margin-left: 15px; margin-right: 15px; padding: 5px; background-color: #F7F7F7; border-radius: 5px 5px 5px 5px; overflow-x: auto; max-width: 90vw;"><code style="margin: 0; border-radius: 3px; background-color: #F7F7F7; padding: 0px;">… for each member:
iconst {nextenumordinal}
invokestatic $make$(I)L{thisenumclass};
putstatic {nextenumfield}
…
return
</code></pre>
<p dir="auto">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.</p>
<p dir="auto">The single helper method <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">$make$</code> would in many cases be very simple, but might be a large <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">tableswitch</code> 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 (<code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">$make$</code>) to decide which sub-helper to call. All straightforward and also annoyingly unproductive, since large enums are very rare.</p>
<p dir="auto">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 (<code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;"><clinit></code>) 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).</p>
<p dir="auto">If you want to do decisively better, and support tens of thousands of enum members, you can do so by bolding making <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;"><clinit></code> 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.</p>
<p dir="auto">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:</p>
<pre style="margin-left: 15px; margin-right: 15px; padding: 5px; background-color: #F7F7F7; border-radius: 5px 5px 5px 5px; overflow-x: auto; max-width: 90vw;"><code style="margin: 0; border-radius: 3px; background-color: #F7F7F7; padding: 0px;">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);
}
}
}
</code></pre>
<p dir="auto">The creation of the values array should be done in <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;"><clinit></code>, as well, or as a condy (yes, that’s a good usage of condy!) and cloned as a fresh copy for each call to <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">values()</code>. And it can be done reflectively as well. Just iterate over all the fields and store them into the array. (Use the <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">ordinal()</code> as the array index, or just assert that the fields are in the correct order already.)</p>
<p dir="auto">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).</p>
</div></div></body>
</html>