User model stacking: current status

Brian Goetz brian.goetz at oracle.com
Thu May 5 19:21:28 UTC 2022


> There are lots of other things to discuss here, including a discussion 
> of what does non-atomic B2 really mean, and whether there are 
> additional risks that come from tearing _between the null and the 
> fields_.

So, let's discuss non-atomic B2s.  (First, note that atomicity is only 
relevant in the heap; on the stack, everything is thread-confined, so 
there will be no tearing.)

If we have:

     non-atomic __b2 class DateTime {
         long date;
         long time;
     }

then the layout of a B2 (or a B3.ref) is really (long, long, boolean), 
not just (long, long), because of the null channel.  (We may be able to 
hide the null channel elsewhere, but that's an optimization.)

If two threads racily write (d1, t1) and (d2, t2) to a shared mutable 
DateTime, it is possible for an observer to observe (d1, t2) or (d2, 
t1).  Saying non-atomic says "this is the cost of data races".  But 
additionally, if we have a race between writing null and (d, t), there 
is another possible form of tearing.

Let's write this out more explicitly.  Suppose that T1 writes a non-null 
value (d, t, true), and T2 writes null as (0, 0, false). Then it would 
be possible to observe (0, 0, true), which means that we would be 
conceivably exposing the zero value to the user, even though a B2 class 
might want to hide its zero.

So, suppose instead that we implemented writing a null as simply storing 
false to the synthetic boolean field.  Then, in the event of a race 
between reader and writer, we could only see values for date and time 
that were previously put there by some thread.  This satisfies the OOTA 
(out of thin air) safety requirements of the JMM.

The other consequence we might have from this sort of tearing is if one 
of the other fields is an OOP.  If the GC is unaware of the significance 
of the null field (and we'd like for the GC to stay unaware of this), 
then it is possible to have a null value where one of the oop fields 
(from a previous write) is non-null, keeping that object reachable even 
when it is logically not reachable.  (As an interesting connection, the 
boolean here is "special" in the same way as the synthetic boolean 
channel is in pattern matching -- it dictates whether the _other_ 
channels are valid.  Which makes nullable values a good implementation 
strategy for pattern carriers.)

So we have a choice for how we implement writing nulls, with a 
pick-your-poison consequence:

  - If we do a wide write, and write all the fields to zero, we risk 
exposing a zero value even when the zero is a bad value;
  - If we do a narrow write, and only write the null field, we risk 
pinning other OOPs in memory



More information about the valhalla-spec-observers mailing list