Re: [PATCH v2 2/2] tools/memory-model: Make ppo a subrelation of po
From: Jonas Oberhauser
Date: Sun Jan 29 2023 - 17:20:18 EST
Hi all, apologies on the confusion about the litmus test.
I should have explained it better but it seems you mostly figured it out.
As Alan said I'm tricking a little bit by not unlocking in certain
places to filter out all executions that aren't what I'm looking for.
I didn't have much time when I sent it (hence also the lack of
explanation and why I haven't responded earlier), so I didn't have time
to play around with the filter feature to do this the "proper"/non-cute way.
As such it really isn't about deadlocks.
I think one question is whether the distinction between the models could
be reproduced without using any kind of filtering at all.
I have a feeling it should be possible but I haven't had time to think
up a litmus test that does that.
On 1/28/2023 11:59 PM, Alan Stern wrote:
On Sat, Jan 28, 2023 at 11:14:17PM +0100, Andrea Parri wrote:
Evidently the plain-coherence check rules out x=1 at the
end, because when I relax that check, x=1 becomes a possible result.
Furthermore, the graphical output confirms that this execution has a
ww-incoh edge from Wx=2 to Wx=1. But there is no ww-vis edge from Wx=1
to Wx=2! How can this be possible? It seems like a bug in herd7.
By default, herd7 performs some edges removal when generating the
graphical outputs. The option -showraw can be useful to increase
the "verbosity", for example,
[with "exists (x=2)", output in /tmp/T.dot]
$ herd7 -conf linux-kernel.cfg T.litmus -show prop -o /tmp -skipchecks plain-coherence -doshow ww-vis -showraw ww-vis
Okay, thanks, that helps a lot.
So here's what we've got. The litmus test:
C hb-and-int
{}
P0(int *x, int *y)
{
*x = 1;
smp_store_release(y, 1);
}
P1(int *x, int *y, int *dx, int *dy, spinlock_t *l)
{
spin_lock(l);
int r1 = READ_ONCE(*dy);
if (r1==1)
spin_unlock(l);
int r0 = smp_load_acquire(y);
if (r0 == 1) {
WRITE_ONCE(*dx,1);
}
}
P2(int *dx, int *dy)
{
WRITE_ONCE(*dy,READ_ONCE(*dx));
}
P3(int *x, spinlock_t *l)
{
spin_lock(l);
smp_mb__after_unlock_lock();
*x = 2;
}
exists (x=2)
The reason why Wx=1 ->ww-vis Wx=2:
0:Wx=1 ->po-rel 0:Wy=1 and po-rel < fence < ww-post-bounded.
0:Wy=1 ->rfe 1:Ry=1 ->(hb* & int) 1:Rdy=1 and
(rfe ; hb* & int) <= (rfe ; xbstar & int) <= vis.
1:Rdy=1 ->po 1:unlock ->rfe 3:lock ->po 3:Wx=2
so 1:Rdy=1 ->po-unlock-lock-po 3:Wx=2
and po-unlock-lock-po <= mb <= fence <= w-pre-bounded.
[...]
This explains why the memory model says there isn't a data race. This
doesn't use the smp_mb__after_unlock_lock at all.
Note as Andrea said that po-unlock-lock-po is generally not in mb (and
also not otherwise in fence).
Only po-unlock-lock-po;[After-unlock-lock];po is in mb (resp. ...&int in
fence).
While the example uses smp_mb__after_unlock_lock, the anomaly should
generally cover any extended fences.
[Snipping in a part of an earlier e-mail you sent]
I think that herd7_should_ say there is a data race.
On the other hand, I also think that the operational model says there
isn't. This is a case where the formal model fails to match the
operational model.
The operational model says that if W is a release-store on CPU C and W'
is another store which propagates to C before W executes, then W'
propagates to every CPU before W does. (In other words, releases are
A-cumulative). But the formal model enforces this rule only in cases
the event reading W' on C is po-before W.
In your litmus test, the event reading y=1 on P1 is po-after the
spin_unlock (which is a release). Nevertheless, any feasible execution
requires that P1 must execute Ry=1 before the unlock. So the
operational model says that y=1 must propagate to P3 before the unlock
does, i.e., before P3 executes the spin_lock(). But the formal model
doesn't require this.
I see now. Somehow I thought stores must execute in program order, but I
guess it doesn't make sense.
In that sense, W ->xbstar&int X always means W propagates to X's CPU
before X executes.
Ideally we would fix this by changing the definition of po-rel to:
[M] ; (xbstar & int) ; [Release]
(This is closely related to the use of (xbstar & int) in the definition
of vis that you asked about.)
This misses the property of release stores that any po-earlier store
must also execute before the release store.
Perhaps it could be changed to the old po-rel | [M] ; (xbstar & int) ;
[Release] but then one could instead move this into the definition of
cumul-fence.
In fact you'd probably want this for all the propagation fences, so
cumul-fence and pb should be the right place.
Unfortunately we can't do this, because
po-rel has to be defined long before xbstar.
You could do it, by turning the relation into one massive recursive
definition.
Thinking about what the options are:
1) accept the difference and run with it by making it consistent inside
the axiomatic model
2) fix it through the recursive definition, which seems to be quite ugly
but also consistent with the power operational model as far as I can tell
3) weaken the operational model... somehow
4) just ignore the anomaly
5) ???
Currently my least favorite option is 4) since it seems a bit off that
the reasoning applies in one specific case of LKMM, more specifically
the data race definition which should be equivalent to "the order of the
two races isn't fixed", but here the order isn't fixed but it's a data race.
I think the patch happens to almost do 1) because the xbstar&int at the
end should already imply ordering through the prop&int <= hb rule.
What would remain is to also exclude rcu-fence somehow.
2) seems the most correct choice but also to complicate LKMM a lot.
Seems like being between a rock and hard place.
jonas
PS:
The other cases of *-pre-bounded seem to work out: they all link stuff via
xbstar to the instruction that is linked via po-unlock-lock-po ;
[After-unlock-lock] ; po to the potentially racy access, and
po-unlock-lock-po;po is xbstar ; acq-po, which allows closing the gap.
I could not follow your arguments at all; the writing was too confusing.
Sorry, I collapsed some cases. I'll show an example. I think all the
other cases are the same.
Let's pick an edge that links two events with ww-vis through
w-post-bounded ; vis ; w-pre-bounded
where the vis comes from
cumul-fence* ; rfe? ; [Marked] ;
(strong-fence ; [Marked] ; xbstar) <= vis
and the w-pre-bounded came from po-unlock-lock-po;[After-unlock-lock];po
but not po-unlock-lock-po & int.
Note that such po-unlock-lock-po;[After-unlock-lock];po must come from
po-rel ; rfe ; acq-po
So we have
w-post-bounded ; cumul-fence* ; rfe? ; [Marked] ;
strong-fence ; [Marked] ; xbstar ; po-rel ; rfe ; acq-po
<=
w-post-bounded ; cumul-fence* ; rfe? ; [Marked] ;
strong-fence ; [Marked] ; xbstar ; hb ; hb ; acq-po
<=
w-post-bounded ; cumul-fence* ; rfe? ; [Marked] ;
strong-fence ; [Marked] ; xbstar ; fence
<= ww-vis
All the other cases where w-pre-bounded are used have the shape
... ; xbstar ; w-pre-bounded
which can all be handled in the same manner.