That’s a good point about “hidden indirections”, but it is not
true that we always eagerly “stamp out” the canned pointer to the
default value. In at least one place (`getstatic`), at the same time as
we dynamically indirect the hidden indirection, we *also check for null*
and substitute the default instance. That would work for arrays as
well. In any case, the JVM has the *option* of stamping out zeroes in
all cases. For hidden indirections, the *option* is to either eagerly
stamp out a pointer to the hidden default *or* stamp out zeroes and map
on the fly to the default instance.
On 19 Apr 2023, at 16:57, Dan Smith wrote:
On Apr 19, 2023, at 3:51 PM, John Rose <[email protected]>
wrote:
“Stamping out” zeroes is fundamentally simpler to do than
“stamping out” some other pattern. Especially managed references:
If you “stamp out” GC-managed references, you have to have a
store-barrier conversation with the GC at each reference, if the GC
is designed that way. This factor all by itself will slow array
creation down, no matter what other design decisions are taken.
In any case, array creation performance is likely to take some kind
of performance hit. The issue is that a every array type will have to
store the bit pattern to “stamp out” when creating an array that
has an element class with a non-zero default. One might think that
this is a pay-as-you go feature, incurring cost only to classes which
default non-zero defaults, but that it not completely true. There are
places in the JVM and JDK where arrays (and other objects) are
created from dynamically selected types. Such places are likely to
need a new slow-path branch to handle the possibility that the
selected type requires special special handling for a non-zero
default. Tests and branches are cheap but it cannot be assumed that
they are free.
As noted above, managed references are a particularly difficult cost
to control, whe considering the initialization of non-zero defaults,
particularly for arrays. It is somewhat dispiriting to contemplate a
flurry of GC activity to create the default state of an array,
immediately followed by a copy of non-default values, initiating a
second flurry on the same locations. One might avoid this particular
problem by allowing only primitive values to be “stamped out”,
but that reduces the utility of a user-defined default, and forces
users into workarounds anyway.
This discussion about arrays should acknowledge that we *do*, in fact,
"stamp out" non-zero pointers to default instances when the class has
been deemed unsuitable for flattening. At an implementation level,
there are 3 kinds of value classes and !-typed value class arrays:
1) The happy case that we'll flatten, in which case the array stores a
bunch of zeros
2) Value classes that are too big or atomic (or maybe fail some other
criteria?) and so won't be flattened, in which the array stores a
repeated non-zero pointer (an alternative strategy would check for
null on read and swap in the pointer; that's what happens with fields)
3) Value classes that don't have a default instance, in which case the
array stores a bunch of nulls and the '!' is erased (an alternative VM
design would check for null on read and throw)
We can argue that case (1) is expecting the best performance, so in
that case we don't want to do anything to slow down its array
allocations, but I think it's fair to say that examples of case (1)
wouldn't mind the trade-off of moving into case (2) and getting a
custom default value instead—especially when their alternative
workaround is to move into case (3) instead.
(I'm not advocating for this outcome, just critiquing the argument.)
• Also, the all-zero value will be visible in some states, even
though we are trying to make it disappear. At least while initial
field initializers are executed, and probably at other times as well.
That will lead to confusion.
Because of case (2), this should never happen (although we've had to
do extra work in order to properly hide the null pointer in case (2)).
• The simplest workaround is not to expose the default value.
Allow null to be the default value of your class, just as with all
pre-Valhalla classes. The standard workarounds for the null value
apply in all cases. Valhalla can succeed even if it doesn’t fully
solve all problems with null.
I'd add some acknowledgement of the role '!' has here, and refine the
analogy—instead of "allow null to be the default value of your
class", you mean "don't give the class a default instance". Then your
'!' types operate in case (3) instead of case (1) or (2), and the
arrays store null. (Your nullable type, meanwhile, continues to have
'!' as its default value.)