The new DynamicConstantValue attribute will have the same
format as ConstantValue with similar semantics. Its constant
pool reference will point to a DynamicConstant constant.
The containing field must be static and final.
At javac time, clients will ignore this attribute, treating the
field as a normal lazily resolved static variable. At class
load time, the field will be left uninitialized, but internally
marked as unresolved.
When a getstatic instruction is resolved against this
field, and it is in the unresolved state, the JVM will
resolve the constant pool entry. After successful
resolution, the getstatic instruction will be linked
to point at the constant pool entry (or a copy thereof,
perhaps the otherwise-unused static field storage).
The resolution of such a getstatic instruction will
also entail the execution of the <clinit> method of
the enclosing class. (Alternatively, the resolution
will not entail the execution of the <clinit>; any
such initialization action will take place independently.
Either decision leads to a good user model.)
Reflection of such a field will work by referring to
the bytecode behavior of the corresponding getstatic.
In no case will reflection be able to assign a new value.
The static compiler will accept an annotation or keyword
on a field declaration (static, final, and with an initializer
expression) that will ensure that the field will be given
a DynamicConstantValue attribute. Possibly, the initializer
expression will be required to be a special form, such as
the derivation of the initialization value from a Constable
of the correct type. (This would make the translation to
DynamicConstant more explicit.)
A suggested keyword for these fields is "lazy". (This evaluation
mode may also be useful in other contexts, such as instance fields.)
(Alternatively, we could consider opting into the lazy behavior
automatically on recompile; in that case the opt-out would consist
of refactoring the initialization into an assignment in the <clinit> method.
Such a change would potentially change initialization order in ways
that could introduce bugs, but may be safe enough to be worth the
gains in startup time. This requires empirical study.)
Whether this behavior is enabled by an annotation or keyword,
the Java Language Specification must take account of such
constants, since their initialization takes place later than
(or perhaps earlier than) the sequence of effects produced
by the <clinit> method. The semantics of such fields is as
if the fields were defined individually, in separate classes
nested inside the target class, and as if the DynamicConstant
expression were executed by an ldc instruction in the separate
class initializer.
By contrast, the ConstantValue attribute of a static final field can point
to one of five constant pool types: String, Integer, Long, Float, Double.
A reference to such a field is usually satisfied at static compile
type, by folding the initializer value. Alternatively, it can be
satisfied by executing a getstatic instruction against the field.
In that case, the JVM arranges to install the constant in
the field variable at class preparation time (and before the
static initializer <clinit> is run).
The DCV attribute interacts with "getstatic" to produce a fully
resolved value later than the <clinit> block execution. (In fact
it allows <clinit> blocks to become very rare.) A reflective
API should be added also which permits one class to request
(given suitable permissions) the constant pool structure behind
a dynamic constant defined by that second class. The API
for expressing such constant pool structures is out of scope
for this RFE, but it would presumably include nodes which
could represent not only primitive CP entries (string, int)
but also DynamicConstant applications (including name,
field type, BSM, and static argument constants). Such a
reflective API would allow classes to serve as libraries not
only of live values but also symbolic recipes for such values.
As such, these recipes could not only be resolved under
program control, but also modified and customized.
Apart from the lazy-evaluation semantics, the DCV attribute
also provides a way for a separately compiled classfile to
export named references into its constant pool, which can
be used later by other separately compiled classifies,
by mentioning a field-ref to the named DCV. This suggests
that metaprogramming on CP constants could make use
of DCVs as a way for separately compiled classfiles to
cooperatively build up complex constants (including
behavioral ones).
It may be useful to add to the DynamicConstantValue
attribute a bit mask of mode bits, to individually select
optional access modes:
1. can this DCV be resolved via "getfield" and Field::get?
2. can this DCV be reflected as a non-resolved symbolic constant in the CP?
3. can this DCV be evaluated *after* the <clinit> of its classfile?
4. can this DCV be evaluated (safely) *before* the <clinit> (including at compile time)?
5. can this DCV be resolved, and then (safely) back-mapped from a live value to a symbolic reference?
6. do any of the above modes (except 1) require additional access privileges, besides simple field-ref resolution (entailed by 1)?
As noted by Remi Forax (comment section), a related use
case for DCV could be *eager* symbolic evaluation at AOT
time. This would seem to be the exact opposite of lazy
evaluation, but the two can co-exist if the symbolic expression
(a condy constant) of a lazy constant can be determined
to be eagerly evaluable. This would be the case if all the
constants and operations of the symbolic expression were
either constant pool constants, or applications of methods
that were suitably marked as "pure" (or "trackable", etc.).
In that case, the AOT could confidently pre-evaluate the
constant, and use the result, knowing that the result was
not sensitive to evaluation ordering (neither dependent
on nor producing side effects). Doing this correctly
obviously requires additional attributes or annotations
for the involved functions, but it would mesh well with
DCV. Relative to AOT support, even if a DCV fails
to be side-effect free, separating such a constant
from another class's <clinit> is likely to enable some
optimizations significant at AOT time. In particular,
if a class exports 100 constants and an AOT compilation
unit only uses 3 of them, the AOT code can trigger
evaluation of just the 3 it needs, leaving the other 997
untriggered. The AOT team has run into this use case
already, and avoiding the 997 has made it difficult for
them to optimize the 3.