<div dir="ltr">Here is another experience report, although in the more pedestrian<br>setting of a compiler with Clojure as input.  Over the past 4 weeks I<br>moved an incomplete project of mine from a homegrown bytecode emitter<br>to Classfile.  I have now reached feature parity with the old<br>codebase, and classfile generation is essentially complete.<br><br>### Reading<br><br>Sole users of Classfile.parse() are the unit tests.  They take a<br>sequence of byte arrays and wrangle them into the data format expected<br>by the existing test cases.  In case of an expected/actual mismatch,<br>the nested data is diffed and visualised side by side.<br><br>Some minor work was involved in assigning nice names to labels, and to<br>slide LocalVariable and ExceptionCatch to their prior places in the<br>instruction sequences.  The rest of the mapping essentially wrote<br>itself by following the API's guidance.  Because of the high density<br>of instanceof checks on instruction types, this was the one place<br>where I wished for shorter class names.<br><br>### Writing<br><br>Manually writing parts of a class or a method's Code attribute is a<br>pleasure.  Concise, easy to write, and easy to read.  Similarly, going<br>from the intermediate representation to Classfile handlers is a<br>breeze.<br><br>I find the ability to represent bytecode instructions as immutable<br>data especially useful.  It allows me to stash away "simple" opcodes<br>in a uniform way in the IR early while parsing the source code, and<br>have them emit themselves when writing out the class.  A single node<br>class of the intermediate representation is sufficient to cover the<br>bulk of opcodes (currently with a single exception; see below).<br><br>Another huge boon is `block()`.  I call it just once in my code base,<br>but it reduces the work of managing local slots, their lifetimes, and<br>their debug information by an enormous amount.<br><br>With regard to performance, I can only say that it is fast.  For<br>example, turnaround time for the unit tests is usually below 150ms and<br>it is the best I have managed so far.  These tests encompass 2.3k<br>build() and 1.5k parse() of class files on virtual threads, with file<br>size ranging from tiny to small.<br><br>### Lower than I like<br><br>The bulk of API usage stays on a single level of abstraction, for<br>example working with *Desc entry points instead of *Entry.  There are<br>rare places where this slipped at little.<br><br>The New*Array family of instructions gave me a hard time.  I had<br>forgotten that there are three of those, and there was some bumping<br>around involved while re-learning this fact.  (Btw, anewarray() accepts<br>a primitive ClassDesc and converts it into a reference type, e.g. "I"<br>to "LI;".)  Two of the instructions have no ClassDesc factory, which<br>meant I had to wrap the family into a intermediate representation node<br>to carry essentially (ClassDesc,int) downstream to the CodeBuilder<br>instance.  I wonder if it is worthwhile to move the three under an<br>umbrella interface, similar to what ConstantInstruction is doing.<br><br>The one place where I use labelToBci() is try/catch/finally.  There is<br>the special case of exceptionCatch() failing for an empty region, a<br>condition that in turn can lead to handler blocks becoming<br>unreachable.  For me, the only robust way to deal with this to a)<br>guard against an empty region by inspecting the bcis and b)<br>subsequently omitting the invalid/unreachable parts.<br><br>Another single use only is constantPool(), to go from a DMHD instance<br>to CodeBuilder's (field|invoke)Instruction.  This was a consequence of<br>DMHD only providing the lookupDescriptor() as String and not as an MTD<br>as well.  With hindsight, it may have been better for me to recover<br>the MTD from the String regardless, and to stay on the level of *Desc<br>throughout.<br><br>Finally, is there a way to decide between tableswitch and<br>lookupswitch?  Lacking something better, I'm trying to emulate this<br>code here:<br><a href="https://github.com/openjdk/jdk-sandbox/blob/master/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java#L1320">https://github.com/openjdk/jdk-sandbox/blob/master/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java#L1320</a><br><br>### Lost in translation<br><br>One feature I cannot duplicate with Classfile is try/catch/finally in<br>expression position when the operand stack is not empty.  The old<br>bytecode generator dealt with this case by unwinding the operand stack<br>into locals, evaluating the t/c/f, and then rebuilding the operand<br>stack with the result on top.  But to do this, one needs to know what<br>the operand stack looks like at the point of the `try`.<br><br>Echoing Dan's sentiment, I'm also looking forward to Classfile being<br>part of the JDK.<div><br>-- mva<br></div><div><br></div></div>