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