[lworld] RFR: 8244313: [lworld] Evolve javac's code generation to match scheme documented in SoV

Maurizio Cimadamore mcimadamore at openjdk.java.net
Mon Apr 19 10:26:59 UTC 2021

On Mon, 19 Apr 2021 10:18:44 GMT, Maurizio Cimadamore <mcimadamore at openjdk.org> wrote:

>> Obviously, different approaches possible.
>> I am documenting my chain of thoughts/observations here:
>>     - Each invocation of the javac compiler, gets a single ClassWriter instance and a single instance of the byte code generator Gen.
>>     - The single instance of the code generator Gen instantiates a single instance of the PoolWriter
>>     - The Pool writer manages all the state associated with the constant pool
>>     - Given a single invocation of the compiler may call for compiling numerous classes and generating numerous class files, and since each class file has its own constant pool, as each JCClassDecl advances through Gen and gets written out into a class file, the ClassWriter calls PoolWriter.reset() after finished with a writing out a class file. This is so as to allow for the slate to be wiped clean in preparation for the next JCClassDecl being fed to Gen.
>> It is conceivable that we could have bifurcated the the primitive class source declaration into two completely different ASTs, one rooted at a JCClassDecl for the value projection class and another rooted at a at a JCClassDecl for the reference projection class, each with its own members according the members sorting algorithm outlined in SoV4 and inject them into the pipeline at the suitable stage. Each JCClassDecl advances through the byte code generator Gen and the ClassWriter on its own, gets its own pool and gets written out on its own in this block in com.sun.tools.javac.main.JavaCompiler#genCode
>> JavaFileObject genCode(Env<AttrContext> env, JCClassDecl cdef) throws IOException {
>>         try {
>>             if (gen.genClass(env, cdef) && (errorCount() == 0))
>>                 return writer.writeClass(cdef.sym);
>>         } catch (ClassWriter.PoolOverflow ex) {
>>             log.error(cdef.pos(), Errors.LimitPool);
>>         } catch (ClassWriter.StringOverflow ex) {
>>             log.error(cdef.pos(),
>>                       Errors.LimitStringOverflow(ex.value.substring(0, 20)));
>>         } catch (CompletionFailure ex) {
>>             chk.completionError(cdef.pos(), ex);
>>         }
>>         return null;
>>     }
>> The three abstractions involved i.e Gen, PoolWriter and ClassWriter each is "aware" of the fact that a single JCClassDecl that is advancing through the pipeline is going to be bifurcated down the road at the deep end and handshake with each other to achieve this end goal. 
>> Alternate implementations where one or more these abstractions are completely oblivious to the bifurcation taking place are possible. 
>> Which approach is better may boil down to a matter of taste. I found it extremely simple and straightforward to enhance the PoolWriter to operate on bipartite pool by having each of these three abstractions be "clued into the goings on".
> Trying to understand - is switchPool called only once?
> If so, can't we replace switchPool by simply creating a new PoolWriter? I know that, up to now, we had only one instance of PW, but of the various abstractions, PW seems the one that is more amenable in terms of spawning new instances?

Ok, I see - switch pool is also called from ClassWriter whenever we need to add a new symbol to the pool and we see that the symbol belongs to a different projection.

I guess the approach that seems intuitively more natural to me (as it requires less modifiable state) is one where we create _two_ ClassWriter instances for the same symbol/AST - but one on the reference projection polarity, the other on the inline polarity. Symbols that do not belong to the correct polarity are just skipped.

I agree that splitting JCClassDecl seems like overkill for this.


PR: https://git.openjdk.java.net/valhalla/pull/386

More information about the valhalla-dev mailing list