Lazy statics (was: Feedback / query on jextract for Windows 10)

forax at univ-mlv.fr forax at univ-mlv.fr
Wed Feb 3 21:58:04 UTC 2021


> De: "Brian Goetz" <brian.goetz at oracle.com>
> À: "Maurizio Cimadamore" <maurizio.cimadamore at oracle.com>, "Remi Forax"
> <forax at univ-mlv.fr>, "John Rose" <john.r.rose at oracle.com>
> Cc: "duncan gittins" <duncan.gittins at gmail.com>, "panama-dev at openjdk.java.net'"
> <panama-dev at openjdk.java.net>, "Claes Redestad" <claes.redestad at oracle.com>
> Envoyé: Mercredi 3 Février 2021 19:35:11
> Objet: Lazy statics (was: Feedback / query on jextract for Windows 10)

> Obviously this is ground that has been at least partially trodden, and past
> attempts to traverse this territory has not yet yielded a clear roadmap, hence
> it's still on the shelf, despite its obvious attractiveness. Let me try and
> enumerate some of the loose ends.

> We seek laziness because we want to defer a calculation into the future, where
> it might not need to happen. This applies both to statics and cached properties
> of objects. Computing a static final field now must happen in <clinit>, which
> happens before any other useful work does; computing an instance field in the
> constructor happens before anyone can do anything useful with the object.
> Laziness gives us a chance to avoid the computation until we need it. It always
> comes with a tradeoff, which might be complexity in the programming model, or
> coordination overhead (CASes and such.)

> Programmers want laziness built into their model because it lets them express
> what they want without having to imperatively implement it. Platform creators
> like laziness because the author gives us more information about program
> invariants, which the platform can then usefully exploit. The key new
> information is "write arity." Today, Java offers us two write-arity options;
> exactly once (final) or "anything goes". Laziness expresses a zero-or-one write
> arity, along with tacit permission to delay side-effects until the value is
> needed.

> From a programmer perspective, both the instance and static paths are
> attractive; we have all done more work than we'd like (and made more mistakes
> than we should) implementing lazy initialization in both instance and static
> state. But from a platform perspective, the static case is more compelling,
> because there's more that the runtime can do to optimize lazy statics -- it can
> treat them as trustable statics that are grist for the inlining mill. There are
> fewer options here for instances.

> Without painting the bikeshed, let me point out that there are two possible user
> models for capturing laziness: lazy fields and cached methods. Lazy fields are
> the more obvious:

> __lazy static final String s = computeABigString();

> but we could frame this equivalently as

> __cached static String computeABigString() { ... }

> There are pros and cons to each, but this is largely a user-model question about
> the story we tell. Saying this is a field, vs a cached method, sends a slightly
> different message.

> Separately, there's a hidden possible requirement we should try to be explicit
> about: whether we care about binary compatible migration for _public_ lazy
> static fields. If we have today:

> public static final String s = ...

> and some *other* class accesses the field, there will be `getstatic`
> instructions in the wild that expect the field to be there. But, this would
> constrain the ability to translate a read of a lazy static as invoking a method
> whose body does an LDC, which might be a preferable translation. I am not
> expressing an opinion on whether this is a requirement or not, as much as
> calling it out as something we could _choose_ to take on, or not, with full
> awareness of the tradeoffs.

> Reframing as caching makes it more obvious that we have to be clear about what
> guarantees we are offering, especially in the instance case; are we
> guaranteeing to execute the method no more than once, or is it OK for multiple
> threads to race to initialize the cached value? What visibility guarantees do
> we make?

> Finally, there's exceptions. One of the pain points we have today is
> initializing method handles in static finals; we can't say

> static final MethodHandle mh = ... lookup ...

> because of the exceptions. THis is a mere annoyance with finals, where we'd
> catch and rethrow the exception in the <clinit> and the client would get an
> ExceptionInInitializerError, but worse with lazies, since the timing of the
> exception can be any time and any place.

> I'm not writing all this so that we can post our pet solutions now; I'm trying
> to capture everything in one place. I'm sure I forgot something; let's try to
> get a complete picture of the problem space.
It's a nice review of the issues. I'll add two things. 

The problem with checked exceptions is not new, they are a hindrance to function composition since Java 1.0, but because we are leveraging more and more forms of function composition, lambda, stream, now lazy constants initialized by a function, so it's now an usual road block. Here the problem of checked exceptions is the same as with a static block, you have to introduce a block of code, it can be done by asking the user to write itself the block inside a method or like a lambda have a syntax to open a block directly after the assignment (maybe reusing yield ??). 
For the runtime exceptions, they should be wrapped into an ExceptionInInitializerError (which is a LinkageError), so we have the same behavior if the constant is initialized in the static bloc or using the lazy constant mechanism. 

The initialization semantics is both harder and simpler, we currently have the VM semantics inherited from ldc + condy, the initializer (the bootstrap method) can be called by multiple threads, the first thread to write into the constant pool win and all others threads return the winning value. 
We can not do better without tweaking the VM, so either we decide another semantics and it means adding a new mechanism into the VM or we are piggybacking on what already exists in the VM and just copy the VM semantics into the JLS. 

Rémi 

> On 2/3/2021 7:46 AM, Maurizio Cimadamore wrote:

>> On Mon, 2021-02-01 at 09:31 +0100, Remi Forax wrote:

>>> Another intermediary solution is to implement the lazy keyword in the
>>> compiler (javac) but only enable it under a flag. So that flag can be
>>> used when compiling java.base and jextract generated classes.

>> I'd avoid such hacky solutions :-)
>> In my experience, people look at those "hidden" flag, and then before
>> you know it, you start seeing code in the wild that depends on them
>> (few years ago I saw code still using -jsr14).

>>> For me the non-static lazy is a non problem, Java already considers
>>> separately final field and static final field, so lazy can be only
>>> allowed on static final fields, but neither on static fields nor on
>>> non-static fields.

>> I tend to agree - allowing dynamic constants in the constant value
>> attribute seems to be a straightforward generalization of what we have
>> now.

>> Exploring the space of non-static lazy final is a possibility, but I
>> don't think that exploration should block what would seem like a
>> generally useful strategy (being forced to use classfile generation
>> just because there's no language support for lazy static constants
>> seems like a suboptimal situation).

>> My intuition also tells me that non-static constant will likely not be
>> as interesting; at least in the method handle/var handle case you want
>> these things to be static - so that C2 can see their constant-ness -
>> so, if a "Constable" instance is likely what we'd like to use the lazy
>> support for, then I'd say that tying that to "static" seems a
>> relatively forced move (at least given the VM we have today).

>> Maurizio


More information about the panama-dev mailing list