Classfile artifacts to support compilation/reflection
daniel.smith at oracle.com
Mon May 3 22:29:32 UTC 2021
> On May 3, 2021, at 3:58 PM, Dan Smith <daniel.smith at oracle.com> wrote:
> To review the story for the JVM, it goes like this: there are two channels for subclass instance creation. The first channel, for identity subclasses, is the mutation-based '<init>' route, with subclasses required by the verifier to call up the '<init>' chain (and supply appropriate arguments) to get usable class instances. '<init>' declarations can limit access. The second channel, for primitive subclasses, is an opt-in flag in the parent that says "I'll allow primitive children without performing any initialization", and then 'defaultvalue' and 'withfield' can just ignore the superclass. This route also needs accessibility restrictions.
> Here's how the *language behavior* of abstract classes is defined in JEP 401:
> "An interface can explicitly extend either IdentityObject or PrimitiveObject if the author determines that all implementing objects are expected to have or not have identity. It is an error if a class ends up implementing both interfaces implicitly, explicitly, or by inheritance. By default, an interface extends neither of these interfaces and can be implemented by both kinds of concrete classes."
> "An abstract class can similarly be declared to implement either IdentityObject or PrimitiveObject; or, if it declares a field, an instance initializer, a non-empty constructor, or a synchronized method, it implicitly implements IdentityObject. Otherwise, it extends neither interface and can be extended by both kinds of concrete classes."
> We decided that detecting a "non-empty constructor" is not a job the JVM should be expected to perform, and so the JVM needs an explicit signal for the second-channel flavor of instance creation. (And note, BTW, that the first channel *also* has an explicit opt-in in the JVM, even though there's an implicit constructor in the language. You can declare a JVM class without instance creation support.) javac is responsible for generating that opt-in signal; legacy classes don't get it until recompilation.
> Given that signal, the JVM can do some error checks (again quoting JEP 401):
> "An abstract class that allows primitive subclasses declares this capability in its classfile (details TBD). At class load time, an error occurs if the class is not abstract, declares an instance field, declares a synchronized method, or implements—directly or indirectly—IdentityObject."
> Concretely, the natural fit for encoding is a class attribute ('PrimitiveInstantiation', say) that carries an access flag.
BTW, I'd suggest updating the State of Valhalla, Section 3 document to better reflect the above details. Here's what it currently says:
>> An abstract class is a primitive superclass candidate if it has no fields, its no-arg constructor is empty (marked ACC_ABSTRACT), it has no other <init> methods, and its superclass is either Object or a primitive superclass candidate.
We should validate no fields, etc., but "and" conjunctions in definitions are dangerous. Let's not fall into the trap of "it's a legal class, it just doesn't mean what it appears to mean." There should be one clear opt-in signal, and then anything inconsistent with that is an error.
I'm pretty down on the ACC_ABSTRACT route for expressing the opt-in, because it intertwines the identity and primitive instance creation channels. There's no particular reason for the JVM to prevent, say, a primitive-friendly class from including some logging code in its identity channel. (Whether a *language* wants to adopt the associated complexity is a separate decision.)
>> (The static factory of the primitive class does not call the superclass constructor. This can be safe only if the constructor is empty anyway.)
More fundamentally at the JVM level, the 'defaultvalue' and 'withfield' instructions do not call any superclass instance initialization logic. Whether identity-only logic in <init> methods is "safe" is a problem for the class's author/language to work out.
>> The properties of being a primitive superclass candidate are "contagious" up the superclass chain. That is, if some abstract class has an empty no-arg constructor, its immediate superclass must also have an empty no-arg constructor.
IdentityObject is the mechanism for this check. Per the JEP text quoted above, if you've got the opt-in flag, you're not allowed to implement IdentityObject. (And if you don't, you implicitly implement IdentityObject.)
>> It would seem to follow that even Objectmust have a empty (ACC_ABSTRACT) constructor, but Object is special, because it alone (of all classes) is allowed to have a truly empty concrete constructor method. Thus, Object is exempt from the "contagion" of the requirement of empty constructors.
Object can, and should, have the primitive subclasses opt-in. And, of course, it doesn't implement IdentityObject.
More information about the valhalla-spec-observers