[External] : Re: User model stacking

Brian Goetz brian.goetz at oracle.com
Wed Apr 27 23:12:47 UTC 2022


Let me try and put some more color on the bike shed (but, again, let’s focus on model, not syntax, for now.)

We have two axes of variation we want to express with non-identity classes: atomicity constraints, and whether there is an additional zero-default companion type.  These can be mostly orthogonal; you can have either, neither, or both.  We've been previously assuming that "primitiveness" lumps this all together; primitives get more flattening, primitives can be non-nullable/zero-default, primitives means the good name goes to the "val" type.  Primitive-ness implicitly flips the "safety vs performance" priority, which has been bothering us because primitives also code like a class.  So we were trying to claw back some atomicity for primitives.

But also, we're a little unhappy with B2 because B2 comes with _more_ atomicity than is necessarily needed; a B2 with no invariants still gets less flattening than a B3.  That's a little sad.  And also that it seems like a gratuitous difference, which makes the user model more complicated.  So we’re suggesting restacking towards:

- Value classes are those without identity
- Value classes can be atomic or non-atomic, the default is atomic (safe by default)
- Value classes can further opt into having a "val" projection (name TBD, val is probably not it)
- Val projections are non-nullable, zero-default — this is the only difference
- Both the ref and val projections inherit the atomicity constraints of the class, making atomicity mostly orthogonal to ref/val/zero/null

Example: classic B2

  value class B2a { }

Because the default is atomic, we get the classic B2 semantics -- no identity, but full final field safety guarantees.  VM has several strategies for flattening in the heap: single-field classes always flattened (“full flat”), multi-field classes can be flattened with "fat load and store" heroics in the future (“low flat”), otherwise, indirection (“no flat”)

Example: non-atomic B2

  non-atomic value class B2n { }

Here, the user has said "I have no atomicity rquirements."  A B2n is a loose aggregation of fields that can be individually written and read (full B3-like flattening), with maybe an extra boolean field to encode null (VM's choice how to encode, could use slack pointer bits etc.)

Example: atomic B3

  zero-capable value class B3a { }

This says I am declaring two types, B3a and B3a.zero.  (The syntax in this quadrant sucks; need to find better.)  B3a is just like B2a above, because we haven’t activated the zero capability at the use site.  B3a.zero/val/flat/whatever is non-nullable, zero-default, *but still has full B2-classic atomicity*.  With the same set of flattening choices on the part of the VM.

Example: full primitive

  non-atomic zero-capable value class B3n { }

Here, B3n is like B2n, and B3n.zero is a full classic-B3 Q primitive with full flattening.

So:

- value-ness means "no identity, == means state equality"
- You can add non-atomic to value-ness, meaning you give up state integrity
- You can orthogonally add zero-capable to value-ness, meaning you get a non-null, zero-happy companion, which inherits the atomic-ness

Some of the characteristics of this scheme:

- The default is atomicity / integrity FOR ALL BUCKETS (safe by default)
- The default is nullability FOR ALL BUCKETS
- All unadorned type names are reference types / nullable
- All Val-adorned type names (X.val) are non-nullable (or .zero, or .whatever)
- Atomicity is determined by declaration site, can’t be changed at use site

The main syntactic hole is finding the right spelling for "zeroable" / .val.  There is some chance we can get away with spelling it `T!`, though this has risks.

Spelling zero-happy as any form of “flat” is probably a bad idea, because B2 can still be flat.

A possible spelling for “non-atomic” is “relaxed”:

  relaxed value class B3n { }

Boilerplate-measurers would point out that to get full flattening, you have to say three things at the declaration site and one extra thing at the use site:

   relaxed zero-happy value class Complex { }
   …
   Complex! c;

If you forget relaxed, you might get atomicity (but might not cost anything, if the value is small.)  If you forget zero-happy, you can’t say `Complex!`, you can only say Complex, and the compiler will remind you.  If you forget the !, you maybe get some extra footprint for the null bit.  None of these are too bad, but the verbosity police might want to issue a warning here.

It is possible we might want to flip the declaration of zero-capable, where classes with no good default can opt OUT of the zero companion, rather than the the other way around:

   null-default value class LocalDate { }

which says that LocalDate must use the nullable (LocalDate) form, not the non-nullable (LocalDate.val/zero/bang) form.



More information about the valhalla-spec-observers mailing list