Skip to content

Commit 9ba7d3b

Browse files
Jonas Oberhauserpaulmckrcu
authored andcommitted
tools: memory-model: Make plain accesses carry dependencies
As reported by Viktor, plain accesses in LKMM are weaker than accesses to registers: the latter carry dependencies but the former do not. This is exemplified in the following snippet: int r = READ_ONCE(*x); WRITE_ONCE(*y, r); Here a data dependency links the READ_ONCE() to the WRITE_ONCE(), preserving their order, because the model treats r as a register. If r is turned into a memory location accessed by plain accesses, however, the link is broken and the order between READ_ONCE() and WRITE_ONCE() is no longer preserved. This is too conservative, since any optimizations on plain accesses that might break dependencies are also possible on registers; it also contradicts the intuitive notion of "dependency" as the data stored by the WRITE_ONCE() does depend on the data read by the READ_ONCE(), independently of whether r is a register or a memory location. This is resolved by redefining all dependencies to include dependencies carried by memory accesses; a dependency is said to be carried by memory accesses (in the model: carry-dep) from one load to another load if the initial load is followed by an arbitrarily long sequence alternating between stores and loads of the same thread, where the data of each store depends on the previous load, and is read by the next load. Any dependency linking the final load in the sequence to another access also links the initial load in the sequence to that access. More deep details can be found in this LKML discussion: https://lore.kernel.org/lkml/[email protected]/ Reported-by: Viktor Vafeiadis <[email protected]> Signed-off-by: Jonas Oberhauser <[email protected]> Reviewed-by: Alan Stern <[email protected]> Signed-off-by: Paul E. McKenney <[email protected]>
1 parent aae0c8a commit 9ba7d3b

File tree

3 files changed

+45
-1
lines changed

3 files changed

+45
-1
lines changed

tools/memory-model/Documentation/explanation.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2575,7 +2575,7 @@ smp_store_release() -- which is basically how the Linux kernel treats
25752575
them.
25762576

25772577
Although we said that plain accesses are not linked by the ppo
2578-
relation, they do contribute to it indirectly. Namely, when there is
2578+
relation, they do contribute to it indirectly. Firstly, when there is
25792579
an address dependency from a marked load R to a plain store W,
25802580
followed by smp_wmb() and then a marked store W', the LKMM creates a
25812581
ppo link from R to W'. The reasoning behind this is perhaps a little
@@ -2584,6 +2584,13 @@ for this source code in which W' could execute before R. Just as with
25842584
pre-bounding by address dependencies, it is possible for the compiler
25852585
to undermine this relation if sufficient care is not taken.
25862586

2587+
Secondly, plain accesses can carry dependencies: If a data dependency
2588+
links a marked load R to a store W, and the store is read by a load R'
2589+
from the same thread, then the data loaded by R' depends on the data
2590+
loaded originally by R. Thus, if R' is linked to any access X by a
2591+
dependency, R is also linked to access X by the same dependency, even
2592+
if W' or R' (or both!) are plain.
2593+
25872594
There are a few oddball fences which need special treatment:
25882595
smp_mb__before_atomic(), smp_mb__after_atomic(), and
25892596
smp_mb__after_spinlock(). The LKMM uses fence events with special

tools/memory-model/linux-kernel.bell

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,9 @@ flag ~empty different-values(srcu-rscs) as srcu-bad-nesting
8282
let Marked = (~M) | IW | Once | Release | Acquire | domain(rmw) | range(rmw) |
8383
LKR | LKW | UL | LF | RL | RU
8484
let Plain = M \ Marked
85+
86+
(* Redefine dependencies to include those carried through plain accesses *)
87+
let carry-dep = (data ; rfi)*
88+
let addr = carry-dep ; addr
89+
let ctrl = carry-dep ; ctrl
90+
let data = carry-dep ; data
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
C dep+plain
2+
3+
(*
4+
* Result: Never
5+
*
6+
* This litmus test demonstrates that in LKMM, plain accesses
7+
* carry dependencies much like accesses to registers:
8+
* The data stored to *z1 and *z2 by P0() originates from P0()'s
9+
* READ_ONCE(), and therefore using that data to compute the
10+
* conditional of P0()'s if-statement creates a control dependency
11+
* from that READ_ONCE() to P0()'s WRITE_ONCE().
12+
*)
13+
14+
{}
15+
16+
P0(int *x, int *y, int *z1, int *z2)
17+
{
18+
int a = READ_ONCE(*x);
19+
*z1 = a;
20+
*z2 = *z1;
21+
if (*z2 == 1)
22+
WRITE_ONCE(*y, 1);
23+
}
24+
25+
P1(int *x, int *y)
26+
{
27+
int r = smp_load_acquire(y);
28+
smp_store_release(x, r);
29+
}
30+
31+
exists (x=1 /\ y=1)

0 commit comments

Comments
 (0)