Static value fields initialization
karen.kinnear at oracle.com
Wed May 30 20:14:25 UTC 2018
Very much like the direction of your proposal, which I summarize as:
1. preparation: continue no code execution
VM determines default value via size information from loaded class
(flattened fields are pre-loaded, so we have that information at preparation time)
2. pre-<clinit>: <clinit> of the containing class requires <clinit> for all entries
in the ValueTypes attribute prior to completing its own <clinit>
I think we could optimize this to pre-<clinit> for entries in the ValueTypes attribute that
are referenced by local flattenable fields, static or instance.
Specifically: we have entries in the ValueTypes attribute for:
1. local fields
2. local method signatures
3. remote fields
4. remote method signatures
I think we only need the pre-<clinit> for #1
3. I think we also need to ensure that other bytecodes that can return a default value instance
of a value type would also require <clinit> of the value type:
defaultvalue, anewarray, multianewarray (JVMS draft 4d already includes defaultvalue, but not yet array bytecodes)
Still exploring to see if there are any holes in these assumptions, appreciate additional eyes on this.
So far so good.
p.s. The circularity issue relative to flattenable static fields is the same as the one relative to flattenable instance fields.
When you pre-load the value type you risk circularity if it is defined to contain directly or indirectly a flattenable field
of the type of itself.
For LW1, with all value type fields flattenable, users will not be able to have a static field containing an instance of itself.
If we want to support that capability in a future LWX, we could do that with a non-flattenable static field containing a value type.
> On May 28, 2018, at 7:12 PM, John Rose <john.r.rose at oracle.com> wrote:
> 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.
> — John
More information about the valhalla-spec-experts