Valhalla EG notes Feb 28, 2018
john.r.rose at oracle.com
Sat Mar 10 21:01:10 UTC 2018
Excellent notes as always. (Impressive how you collect them and
also take a full part in the conversation.)
On Mar 9, 2018, at 2:13 PM, Karen Kinnear <karen.kinnear at oracle.com> wrote:
> Attendees: Remi, John, Dan H, Lois, Tobi, Frederic, Karen
> Corrections always welcome.
> John: this is a JVMS, not a java language specification. Java can choose its own default as well as any language syntax later.
Footnote: our current intention, at the language level, is to sweep as much
of the null corner casing under the rug as possible. One specific is that
I hope the "ACC_FLATTENABLE" bit will be placed on *all* fields that are
seen at compile-time to be value types. The two alternatives I hope to avoid
are (a) making ACC_FLATTENABLE explicit at the source level requiring
explicit opt-in to a flat container, and (b) default ACC_FLATTENABLE with
an opt-out that says "make this nullable". One reason: I don't want nullable
value types to be supported as a first-class language construct; they would
be misused. Another reason: Conversations like "A: this gives me no
performance benefit, you are such losers; B: did you remember to set your
nullability correctly? A: what's nullability?"
And, under the "field == element" heuristic, similar set of considerations for
whether to supply value arrays that are only flattenable, only nullable, or
sometimes both (and how to set the default in that case). Desired positions
are (a) flat is the default, and (b) nullable may be available as a workaround
if you try hard, but not as a first-class language feature.
> Frederic: rationale is the compatibility story. Allows existing value-based-classes like Optional to migrate to become value types and allow classfiles that declare Optional fields, and clients that access those fields to continue to work unchanged, including the ability to store/retrieve a null.
> Note: arguments, local variables can contain a null for a value type, it is only when publishing that an NPE is thrown.
> John: not calling this non-nullable from a language feature perspective. Might be something more like “probable flattening”.
> Remi: why preload?
> John: If we defer loading we might get circularity errors which should prevent the container from loading
Preloading, of field types, is required in order to discover the layout of
a field type. This isn't necessary for object types since those are always
reached by pointers, and all pointers are laid out the same. The need
for preloading a field type is only necessary if the field type is a value
type *and* the containing object is going to (attempt to) flatten the field.
The discussion I recall is that Remi wasn't asking why we preload, but
rather why *not* preload more often, in order to more correctly decide in
favor of flattening. In particular, we preload superclasses (again for layout
reasons!) and interfaces (for layout reasons, in the v-tables), so why
not just preload every field type, on the chance that it *might* be value
type, and then we can DTRT. I wish we could! IIRC Karen went through
this exercise and noticed that various circular loading dependencies
made this impossible. Loading all of your supers is possible because
the subtyping graph is acyclic, so you can use a lazy loading algorithm
to get everything straight. (With lots of hairy infra to manage concurrency
and cycle detection. See ClassCircularityError.) Doing this for fields
doesn't just require *more* hairy infra, it is *impossible* because field
incorporation is a cyclic graph, even for very simple programs.
Perhaps I misunderstood Remi's question. Remi has a very clever
proposal for the compiler to emit an extra attribute in the classfile
which says "I assume the following types are value types". This would
allow limited preloading, and we could exclude cycles in this graph.
(The reason is that you can't have value A including value B directly
or indirectly; you logically need an object somewhere in the graph,
and that breaks any cycle in Remi's attribute relations.) The use
of the modifier bit ACC_FLATTENABLE, on fields only rather than
general value types, is a simplification and limitation of Remi's
clever proposal, which we hope will be sufficient as well as easier
to specify and implement.
> Remi: what about compatibility?
> if you read an array that is empty today, you get null
> if you read a flattened array, you get a default value
> John: with arrays we do not need to do any preloading so no benefits to not always flattenable
> believe limited use cases for non-flattenable arrays
(Remi's clever proposal of a "values committee" in every classfile,
which determines the potential flatness of fields, may lead to annoying
questions like, "does the committee also determine the flatness of
arrays created by that class?" and "does the committee also
determine the null-permissiveness of instruction xyz?" As well as
dealing with cases when the committees in different classfiles
make different decisions.)
> solving the JVM part - not the user model or language
> Remi: concern Optional - will no longer see nulls
> John: Optional is not supposed to be seen in the heap
> - with generics, there are no Optional that are reified - so erasure saves us
> Remi: Kotlin, ??, Scala - reify
> John: languages can make their own compatibility decisions
> Remi: can buy argument
> John: let’s prototype this and see
One option for other languages, that I think will be workable in
many cases, is to use erasure to implement the occasional case
of a nullable use of a value type. I.e., erase the value type to its
bound, and insert casts. (Make user null checks happen before
any cast, since the cast will probably NPE on nulls.) So erase
the null-permissive type `ComplexDouble?` to JVM Object and
surround with casts of `ComplexDouble!` as needed, where the
JVM type of `ComplexDouble!` is `ComplexDouble`
Another more complex option is to wrap. Where erasure goes
upward, wrapping goes outward. Implement `ComplexDouble?` with
an auxiliary value type with a single nullable field (lacking the
ACC_FLATTENABLE bit). Or make an object type which boxes.
> John: language decision - maybe use C++ notation, drop the “new”, e.g. (typename)arg , …
"Drop the new" is TypeName(arg), as in var i = ComplexDouble(0, 1);
That's the consensus. Despite the fact that it's ambiguous, since there
might be a method nearby called ComplexDouble. Since this is unlikely
we can add a rule which resolves the ambiguity in a compatible way
(take the method if there is one) and be done. In such cases you also
need a way to opt into the non-legacy behavior. That would involve
an explicit qualifier on the class name, including maybe something
new like ComplexDouble.ComplexDouble(0, 1), just to handle the
vanishingly unlikely second-order corner cases on the corner cases.
(Doug Lea's guidance was, "just do it; don't worry about the corner cases".)
> Dan: Why would vm offer a canonical name?
> John: <init> today has special rules
> <make> - no special rules for verifier
> - naming - ensure not in valid namespace for java programmer
> Dan H: new/dup/<init> in nest in future? John: yes
> John: yes. <init> has signature with void return today, maybe if use <init> with non-void return?
> Remi: if retrofit void as a value type -> then not distinguishable
> John: maybe <make> overloaded with args. must always be static
(I'm going to send a separate note about this.)
More information about the valhalla-spec-observers