Static value fields initialization
john.r.rose at oracle.com
Mon May 28 23:12:42 UTC 2018
On May 25, 2018, at 8:18 AM, Frederic Parain <frederic.parain at oracle.com> wrote:
> … Let’s consider the model for minimal L-world 1, where all value
> fields are flattenable. Static value fields must be initialized to their
> default value at preparation time, which implies the initialization
> of their value class. Here’s the contradiction: the value class
> initialization requires code execution, when previous statement
> in the preparation specification says there’s no code execution.
Currently, preparation is a very early phase which can only assume
that supers are loaded. With value types, we also load the non-static
field types. So far, neither of those (supers or instance fields) will tolerate
circular dependencies, and the recursive loading of supers (and
presumably instance fields) doesn't cause code to execute either.
(Exception: Class loader code can run, at the "meta" level to the
class. We usually disregard that kind of execution. We are
concerned here with the phased execution of user-written
bytecodes in loaded classes.)
Executing code during preparation would greatly constrain the
JVM's execution order, both creating new dependencies that might
not be resolvable, because of circularities, and also preventing
optimizations (such as CDS or AOT) which might perform early
phases like preparation in a special way, but cannot tolerate
general code execution.
The good news is I don't think we need to complicate preparation
to that extent; some small rule tweaks will get us where we want.
When supers and instance field types are loaded, it is not yet possible
to run <clinit> but it *is* possible to compute the bits of a default value,
because we have defined the default value without reference to any
computation: It is pure structure (the nesting of the value down to its
primitive and reference components) plus the zero bits for each leaf
of the structure.
The early determination of a default value for a value type V lets us
compute (and allocate and initialize) static default values for V very
early, before V begins to initialize.
A class C might have some "static V v;" and V is mentioned in C's
ValueTypes attribute. This means that during C's preparation, storage
must be prepared for C.v. This also requires that V be loaded enough
to determine V's default value. A reasonable implementation might
allocate static storage for *both* a reference to V (as if V were a
reference type) and *also* a writable buffer (or one-elemenet array)
holding the default bits (all zeroes) for V; it would initialize the reference
C.v to point to the buffer for C.v and also copy the bits of V.default (previously
computed) into the buffer for C.v; further "putfield" ops on C.v would
overwrite the bits in the writable buffer for C.v, but leave the reference
for C.v unchanged. Another reasonable implementation (and a simpler
one) might just allocate static storage a reference C.v and patch it
with a read-only copy of V.default; further "putfield" ops would box
the new value and overwrite the reference for C.v. Both of these
implementations could leverage existing static-field preparation logic
that applies to references, to set up the reference part of C.v.
(In either of the above implementation strategies, A JVM should
probably allocate, as a standard feature of every value class V,
a reference to a read-only copy of V's default value, and place
that reference in the same table as the user-defined static fields
of V. It is as if the JVM adds a synthetic static field V.$default
of synthetic type "reference to V", which points to a read-only
buffered default (all-zero) value of type V. Such a thing can
be easily created during preparation time, when the JVM is
already concerned with creating the machinery for V's statics.
After that point, V or any of its client classes can easily obtain
the pointer to the canonical copy of V's default as V.$default.
I don't suggest literally naming it "$default" but rather having
an injected "extra" static reference slot, wherever the static
field references are kept by the JVM.)
Does any of this lead to a paradox where the default value C.v of type
V can become visible before V.<clinit> is run? We certainly want to
avoid such a thing; no value of V.default should enter the JVM stack
until V.<clinit> is triggered. Does this mean that we need to execute
V.<clinit> in order to prepare C.v? That brings in additional circular
dependencies which I think we will find intractable.
The answer is simple, when you think about it: The preparation
phase allocates a static variable C.v initialized to a value V.default,
but neither the variable nor its value ever accessed until the static
variable is loaded. This means that we can *prepare* C.v and
V.default before V.<clinit> runs, as long as we ensure that the
value doesn't escape before V.<clinit> is triggered.
Doing this seems simple to me. It is already the case that C.<clinit>
must be triggered before C.v becomes available. What about V.<clinit>?
I think that is easy to handle with a new rule in the JVMS, that makes
sure the V.<clinit> triggers before C.<clinit>.
There's a simple way to phrase this rule: The initialization of a class
C must recursively trigger the initialization of every value type that
occurs as the type of a field (whether static field or instance field).
Thus, just before C.<clinit> is run, the JVM must first run V.<clinit>,
because the type of C.v is V.
A simpler, broader rule would be: Just before a class C is initialized,
for each type V in its ValueTypes attribute, the type V is initialized
(recursively, with the usual short circuit logic if the initialization of V
has already started in the same thread). It is tempting to play this
game at every step: Each V in C.ValueTypes is loaded before
C is loaded, and so on for the other phases (prepared and/or linked,
initialized). Perhaps that gains us simplicity without loss of function.
(Notes: I say C.<clinit> and V.<clinit> for concreteness; what I really
mean is the initialization phase of C and V. The logic above should
be adjusted to reflect this, because it still must apply even if C or V
lacks an actual <clinit> block. I say "trigger" above because, as we
know, the <clinit> has to *start* before anything in the class can be
executed, but it doesn't have to *finish* because of the corner case
of circular dependencies, which are resolved in the first thread that
needs the initialization to execute.)
So let's resolve this contradiction by creating the default value
at preparation time but ensuring that it never appears as the
result of a bytecode execution (vdefault, getstatic, aaload, etc.)
until the value's <clinit> has been triggered.
One final note: You might be thinking that we at least need to
execute the code of the value type's nullary constructor, in order
to compute the default value. That's where we profit from laying
down a hard rule, that the VM owns the default value, not the user.
So the nullary constructor is not definable by the user, and (as
noted above) its effect is defined purely in terms of the structure
of the value type, and the default (all-zero) values of the leaf
components of the value. Put another way, the JVM doesn't
need *any* constructor to create the unique default value of
a given value type (and forbidding nullary constructors is just
a way of avoiding misunderstandings). In any case, there is
never any need to execute code to derive the default.
More information about the valhalla-spec-observers