Re: XDP socket rings, and LKMM litmus tests

Boqun Feng
2021-03-04T06:36:31Z

On Wed, Mar 03, 2021 at 10:13:22PM -0500, Alan Stern wrote:
> On Thu, Mar 04, 2021 at 09:26:31AM +0800, Boqun Feng wrote:
> > On Wed, Mar 03, 2021 at 03:22:46PM -0500, Alan Stern wrote:
>
> > > Which brings us back to the case of the
> > >
> > > dep ; rfi
> > >
> > > dependency relation, where the accesses in the middle are plain and
> > > non-racy. Should the LKMM be changed to allow this?
> > >
> >
> > For this particular question, do we need to consider code as the follow?
> >
> > r1 = READ_ONCE(x); // f
> > if (r == 1) {
> > local_v = &y; // g
> > do_something_a();
> > }
> > else {
> > local_v = &y;
> > do_something_b();
> > }
> >
> > r2 = READ_ONCE(*local_v); // e
> >
> > , do we have the guarantee that the first READ_ONCE() happens before the
> > second one? Can compiler optimize the code as:
> >
> > r2 = READ_ONCE(y);
> > r1 = READ_ONCE(x);
>
> Well, it can't do that because the compiler isn't allowed to reorder
> volatile accesses (which includes READ_ONCE). But the compiler could
> do:
>
> r1 = READ_ONCE(x);
> r2 = READ_ONCE(y);
>
> > if (r == 1) {
> > do_something_a();
> > }
> > else {
> > do_something_b();
> > }
> >
> > ? Although we have:
> >
> > f ->dep g ->rfi ->addr e
>
> This would be an example of a problem Paul has described on several
> occasions, where both arms of an "if" statement store the same value
> (in this case to local_v). This problem arises even when local
> variables are not involved. For example:
>
> if (READ_ONCE(x) == 0) {
> WRITE_ONCE(y, 1);
> do_a();
> } else {
> WRITE_ONCE(y, 1);
> do_b();
> }
>
> The compiler can change this to:
>
> r = READ_ONCE(x);
> WRITE_ONCE(y, 1);
> if (r == 0)
> do_a();
> else
> do_b();
>
> thus allowing the marked accesses to be reordered by the CPU and
> breaking the apparent control dependency.
>
> So the answer to your question is: No, we don't have this guarantee,
> but the reason is because of doing the same store in both arms, not
> because of the use of local variables.
>

Right, I was thinking about something unrelated.. but how about the
following case:

local_v = &y;
r1 = READ_ONCE(*x); // f

if (r1 == 1) {
local_v = &y; // e
} else {
local_v = &z; // d
}

p = READ_ONCE(local_v); // g

r2 = READ_ONCE(*p); // h

if r1 == 1, we definitely think we have:

f ->ctrl e ->rfi g ->addr h

, and if we treat ctrl;rfi as "to-r", then we have "f" happens before
"h". However compile can optimze the above as:

local_v = &y;

r1 = READ_ONCE(*x); // f

if (r1 != 1) {
local_v = &z; // d
}

p = READ_ONCE(local_v); // g

r2 = READ_ONCE(*p); // h

, and when this gets executed, I don't think we have the guarantee we
have "f" happens before "h", because CPU can do optimistic read for "g"
and "h".

Part of this is because when we take plain access into consideration, we
won't guarantee a read-from or other relations exists if compiler
optimization happens.

Maybe I'm missing something subtle, but just try to think through the
effect of making dep; rfi as "to-r".

Regards,
Boqun

> Alan