What we have lost ?

Dan Smith daniel.smith at oracle.com
Thu Sep 8 02:19:34 UTC 2022


Summarizing my takeaways from talking over these use cases today:

On Sep 7, 2022, at 12:56 AM, forax at univ-mlv.fr<mailto:forax at univ-mlv.fr> wrote:

Here is a list of such value types:
- unit types, value types like by example Nothing (which mean that a method
never returns) with no fields.
Because creating a ref on it creates something :)

If you truly mean for such a class to have no instances, that's not something a
class with a value type can assert—the default instance always exists. I can
see how it would be nice, for example, to have a type like Void that is also
non-nullable, but value types are not the feature to accomplish that.

no, such classes have instances but they are empty.
By example, an array of Nothing stores just its length in memory. Accessing a cell return the default value, Nothing.default which is equivalent to new Nothing().

And it can be used in generics too, a HashMap<Integer.val, Nothing> is more or less a set of ints.

Clarifying: we're not talking about Nothing types, we're talking about Singleton types.

The main concern is that an array of Singleton.ref must have an O(n) size, while an array of Singleton.val *could* have an O(1) size (if this optimization were implemented—it's not something we've prototyped so far). In a sense, that's a much worse penalty for forgetting ".val" than the up to 2x storage penalty you might typically pay for a null flag.

Is this practical? Singleton[] is probably not something you would write directly, but it could make sense in a generics context (something like the HashMap Rémi referenced above). Not in the mainstream of our use cases, but a use case to keep in mind.

- wrappers/monads that modify the semantics, by example a generic value class
Atomic that plays the same role as an AtomicReference, AtomicInteger, etc
the problem here is that the default semantics is not the semantics the user
want.

Okay, so say we have value class Atomic<T>, and we're in a future where this
gets specialized. I think you're saying it will be important to say
'Atomic.val<Foo>' at all uses rather than 'Atomic<Foo>'. But I'm not clear on
the argument for why that would be. Can you elaborate?

If Atomic is declared as a ref-by-default value class, Atomic<Foo> behave as a ref an not as a Foo + some atomic magic.
So it is always wrong to use it.

As the author of Atomic, i don't want my users to have a way to shoot themselves into the foot.
Here, Atomic should be flat-by-default and also the ref variant should not exist, so there is no way to misuse the Atomic API.

(Point of confusion for me: no, we're not talking whether the *field* of Atomic is flattened. This discussion is about the treatment of the Atomic instance, the *wrapper* around the field.)

The argument in this case is that the class Atomic is, essentially, a user-defined variable modifier. (If it could be spelled 'atomic Foo x', that would be great, but user-defined modifiers are not a feature of Java.) It doesn't need to create an extra layer of wrapping around whatever it's storing; it just needs to add some custom behavior to reads/writes. In standard usage, it should never be null (and should probably always be final...).

Except... (sorry, just thinking about this now, didn't raise it earlier): how can a variable wrapper be a value class? The whole point is to control *mutation* of the payload. Value class fields are final.

So I'll tentatively say that this isn't a category of use cases Valhalla is going to address. You can't have mutable fields without object pointers to get to them.

- SIMD vectors, if those are nullable, the VM/JIT will insert implicit null
checks which are not usually a problem apart in thigh loop like users write
with SIMD vectors.

The *storage* should definitely use a value type, so in this sort of application
we'd encourage value-typed array allocations (and value-typed type arguments
for wrapping data structures).

In a loop over a flat array, I would expect it to be okay to talk about the
reference type in source, and have the JIT generate optimal code to work with
the underlying flat storage, without any allocations or null checks. My sense
is that we suspect that this can work reliably, but it could use more targeted
performance testing to confirm.

Sorry for not being clear, i'm more worry about people using IntVector instead of IntVector.val on stack and the VM having to emit a nullcheck inside a loop, by example if there is a call to a method that returns an IntVector is not inlined. For me, the vector API represents operation so a ref-by-default IntVector is a kind of useless, worst it will work but performance will suffer with no simple way to fix that apart taking a look to the generated assembly code.

In theory, there's no problem here: value class reference types can be scalarized everywhere on stack, and null checks/flags can often be optimized away. (And even if they can't—if a loop body can't be inlined and so contains method calls that do null checks, say—the overhead of null checks is probably not a significant extra cost.)

But in practice, totally possible that there are some holes. Insert open invitation here for people to provide performance test cases where on-stack usage of reference types is noticeably more expensive than the equivalent code using value types. That would really help to ground this conversation (is it something we can optimize away, or an inherent limitation? is it a serious problem for reference-default, or a corner case where we can encourage sprinkling in ".val" as a workaround?)

- existing value classes in Scala or Kotlin, those are not nullable by default
but in the current design, getClass() will happily reflect them with a nullable
class making Scala/Kotlin second class citizens of the Java platform.

Is the problem here that Scala/Kotlin will want reference-default interpretation
of names in Java source? (If so, <shrug>, if you want a good user experience
with Kotlin types, write Kotlin source.)


I don't know the answer to that question but i disagree with your conclusion, interrop with Java, so with the Java ecosystem is important.

Answer: no, we're not talking about Java source using Kotlin types. It's reflection, discussed below.

Or is the problem that they will want the reflection API to behave differently,
making Foo.val.class the "primary" class object, not a secondary one? (If so,
<shrug> again, the Java reflection API is Java-oriented, interop is not a major
factor in its design.)

Maybe I'm missing your point on this one?

The problem is that we are forcing the companion class design to other languages than Java.
C# has done the same mistake with their generics being too specific to C# so other languages on the .Net platform all suffer.

Other languages than Java can not makes Foo.val.class their primary class object because the companion class design is bolt into the VM.

Right: other languages might have value classes that are a good match for value-default—in particular they would be happiest to see the value type when they call 'getClass'. (Perhaps doing something else would be a behavioral incompatibility, should they choose to migrate their implementation from roll-your-own to native JVM features.)

On this one, I think we agreed that this is a "nice to have", if it works out in a way that is convenient for other languages, but this isn't something that would drive our design decisions.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/valhalla-spec-observers/attachments/20220908/12efc971/attachment-0001.htm>


More information about the valhalla-spec-observers mailing list