lazy statics design notes

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Fri Mar 1 01:30:36 UTC 2019


Question: in which category of badness does this belong to?

class Foo {
    static int I = Foo.J;
    static int J = 2;

    public static void main(String[] args) {
       System.out.println(I); //prints 0
       System.out.println(J); //prints 2
    }
}

The language allows forward references to static fields, assuming you 
use a _qualified_ name (don't ask me why :-)).

But I guess this is similar to having a static init calling a static 
method which returns the value of a non-yet initialized static. In any 
case, it feels that, if we condify these, we would change semantics, as 
suddenly "I" will be initialized to 2 too?

Also, beware of cycles!

class Foo {
    static int I = Foo.J;
    static int J = I;

    public static void main(String[] args) {
       System.out.println(I); //prints 0
       System.out.println(J); //prints 0
    }
}

I think a condified translation would throw or run into an endless loop?

To me, the crux of the issue is to draw a crisp line between things that 
can be condified and things that cannot (and have to fallback to 
<clinit>). But, the more I think, the less I'm convinced that such a 
line exist, or at least that a 'meaningful' one exists.

Since all method calls are potentially cycle-inducing (or 
forward-reference-inducing), lazy statics treatment cannot apply to an 
initializer that has a method call? And, you can have forward references 
or cycles through field access too (as above)... so, that seems to leave 
just simple constants, which doesn't seem to offer a lot of bang for the 
bucks? Am I missing something?

Sidebar: as a VM machinery, I'd love to see something like DynamicValue 
- in fact I was talking to Sundar the other day that, if we had it, we 
could solve some thorny issues we have on Panama/jextract bytecode 
generation. But as a language construct, this feels shaky, but maybe 
that's just me?

Maurizio

On 27/02/2019 20:33, John Rose wrote:
> On Feb 27, 2019, at 7:30 AM, Karen Kinnear <karen.kinnear at oracle.com> wrote:
>> Subject: Valhalla EG notes Feb 13, 2019
>> To: valhalla-spec-experts <valhalla-spec-experts at openjdk.java.net>
>> ...
>> III. [Remi Forax] DynamicValue attribute
>> Another project Remi will lead and create JEP
>>   language level: static lazy final
>>   improve startup by allowing init with Condy at first access of individual static
>>
>> Drawbacks: opt-in at source
>>   change in semantics
>>   in static block - there is a lock
>>   condy BSM can execute multiple times
> I was just talking with Vladimir Ivanov about lazy
> statics.  He is working on yet another performance
> pothole with <clinit>, generated by Clojure this time.
> (It's not their fault; the system had to clean up a problem
> with correct initialization order, and <clinit> execution
> is over-constrained already, so the JIT has to generate
> more conservative code now.)
>
> I believe lazy statics would allow programmers
> (and even more, language implementors) to
> use much smaller <clinit>s, or none at all,
> in favor of granular lazy statics.
>
> So, here's a brain dump, fresh from my recent
> lunch with Vladimir:
>
> Big problem #1:  If you touch one static, you buy
> them all.  Big problem #2:  If any one static
> misbehaves (blocking, bad bootstrap), all statics
> misbehave.  Big problem #3:  If <clinit> hasn't
> run yet, you need initialization barriers on all
> use points of statics; result is that <clinit> itself,
> and anything it calls, is uniquely non-optimizable.
> Big problem #4:  After touching one static, the
> program cannot make progress until the mutex
> on the whole Class object is released.  Big problem
> #5: Setting up multiple statics is not transactional;
> you can observe erroneous intermediate states during
> the run of the <clinit>.  Big problem #6:  Statics
> are really, really hard to process in an AOT engine,
> because nearly every pre-compiled code path must
> assume that the static might not be booted up yet,
> and if boot-up happens (just once per execution)
> it invalidates many of the assumptions the AOT
> engine wants to make about nearby code.
>
> Solutions from lazy statics:  Solution #1: If you touch
> one that's the one you buy (plus what's in the vestigial
> <clinit> if there is one at all).  Solution #2: Misbehaving
> statics don't misbehave until they are used (yes, bug
> masking, boo hoo).  Solution #3: Initialization barriers
> are trivial:  Just detect the T.default value of the variable.
> Solution #4: There is no mutex, just a CAS at the end
> of the BSM for the lazy static; no critical section.
> Solution #5:  The CAS at the end of the BSM is inherently
> transactional.  Solution #6: AOT engines can generate
> somewhat simpler fast-path code by just testing for
> T.default; the slow-path code is still hard to optimize,
> but the limits are from the complexity of the BSM
> that initializes the lazy static, not the total complexity
> of the <clinit> code.
>
> Objection: What if you *want* a mutex?  I didn't like
> the JVM blocking everything in <clinit> but I don't
> want a million racing threads computing the same
> BSM value either.  Ans: Fine, but make that an opt-in
> mechanism, by folding some kind of flow control
> into the relevant BSM, for your particular use case.
> The JVM doesn't have to know about it.
>
> Objection:  What if I want several statics to initialize in
> one event, with or without mutex or transactions?
> Ans: Easy, just have the BSM for each touch the others,
> or run a common BSM that sets everything up (and then
> returns the same value).  (Note: At the cost of an
> idempotency requirement during lazy init.)  In the
> most demanding cases, define a private static nested
> class to serialize everything, which is today's workaround.
>
> Objection:  Those aren't real statics, because you can't
> set them to their T.default values!  Ans:  They are as
> real as you are going to get without creating lots of
> side metadata to track the N+1st variable state, which
> is a cost nobody wants to pay.
>
> Objection: But I do want to opt into the overhead and
> you aren't giving me my T.default; I need the full range
> of values for my special use case.  Ans:  Then add an
> indirection for your use case, to a wrapped copy of your
> desired value; the null wrapper value is the T.default in
> this case.  It's at least as cheap as anything the JVM would
> have done intrinsically.
>
> Objection:  You disrespect 'boolean'.  It only has one
> state left after you filch 'false' to denote non-initialization.
> My VM hack can do much better than that.  Ans:  Let me
> introduce you to java.lang.Boolean.  It has three states.
>
> Objection:  What if someone uses bytecode to monkey
> with the state of my lazy static?  Your design is broken!
> Ans:  This is the sort of corner case that needs extra
> VM support.  In this case, it is sufficient to privatize
> write access to a variable, even though it may be public,
> to its declaring class.  You can trust the declaring class
> not to compile subverting assignments into itself,
> because javac won't let it.
>
> Objection:  I can't imagine the language design for this;
> surely there are difficulties you haven't foreseen.  Ans:
> Neither can I, and there certainly are.  The sooner we
> start trying out prototypes the sooner we'll shake out
> the issues.  There are several things to try:
>
> http://openjdk.java.net/jeps/8209964
> http://cr.openjdk.java.net/~jrose/draft/lazy-final.html
>
> Bonus:  The T.default hack scales to non-static
> fields as well.  So laziness is a separable tool
> from the decision to make things static or not;
> it survives more refactorings.  The technique
> is abundantly optimizable (both static and
> non-static versions) as proven by the good
> track record of @Stable inside the JDK.  We
> should share this gem outside the JDK,
> which requires language and (more) VM
> support.  Language design issue:  It's easier
> to do the lazy static with an attribute than
> doing the lazy non-static; you need an
> instance-specific callback for the latter.  TBD.
>
> The nice thing about this is that the OpenJDK JITs
> have been making good use of @Stable annotations
> for a long time.  So the main problem here is finding
> a language and VM framework that legitimizes this
> sort of pattern (including safety checks and rule
> enforcement on state changes).  When that is done,
> the JITs should make use of it with little extra effort.
>
> — John


More information about the valhalla-spec-observers mailing list