Commit 2456d2a6 authored by Paul E. McKenney's avatar Paul E. McKenney

memory-barriers: Fix description of 2-legged-if-based control dependencies

Sad to say, current compilers really will hoist identical stores from both
branches of an "if" statement to precede the conditional.  This commit
therefore updates the description of control dependencies to reflect this
ugly reality.
Reported-by: default avatarPranith Kumar <bobby.prani@gmail.com>
Reported-by: default avatarPeter Zijlstra <peterz@infradead.org>
Signed-off-by: default avatarPaul E. McKenney <paulmck@linux.vnet.ibm.com>
parent efdcd51a
...@@ -574,30 +574,14 @@ However, stores are not speculated. This means that ordering -is- provided ...@@ -574,30 +574,14 @@ However, stores are not speculated. This means that ordering -is- provided
in the following example: in the following example:
q = ACCESS_ONCE(a); q = ACCESS_ONCE(a);
if (ACCESS_ONCE(q)) {
ACCESS_ONCE(b) = p;
}
Please note that ACCESS_ONCE() is not optional! Without the ACCESS_ONCE(),
the compiler is within its rights to transform this example:
q = a;
if (q) { if (q) {
b = p; /* BUG: Compiler can reorder!!! */ ACCESS_ONCE(b) = p;
do_something();
} else {
b = p; /* BUG: Compiler can reorder!!! */
do_something_else();
} }
into this, which of course defeats the ordering: Please note that ACCESS_ONCE() is not optional! Without the
ACCESS_ONCE(), might combine the load from 'a' with other loads from
b = p; 'a', and the store to 'b' with other stores to 'b', with possible highly
q = a; counterintuitive effects on ordering.
if (q)
do_something();
else
do_something_else();
Worse yet, if the compiler is able to prove (say) that the value of Worse yet, if the compiler is able to prove (say) that the value of
variable 'a' is always non-zero, it would be well within its rights variable 'a' is always non-zero, it would be well within its rights
...@@ -605,11 +589,12 @@ to optimize the original example by eliminating the "if" statement ...@@ -605,11 +589,12 @@ to optimize the original example by eliminating the "if" statement
as follows: as follows:
q = a; q = a;
b = p; /* BUG: Compiler can reorder!!! */ b = p; /* BUG: Compiler and CPU can both reorder!!! */
do_something();
The solution is again ACCESS_ONCE() and barrier(), which preserves the So don't leave out the ACCESS_ONCE().
ordering between the load from variable 'a' and the store to variable 'b':
It is tempting to try to enforce ordering on identical stores on both
branches of the "if" statement as follows:
q = ACCESS_ONCE(a); q = ACCESS_ONCE(a);
if (q) { if (q) {
...@@ -622,18 +607,11 @@ ordering between the load from variable 'a' and the store to variable 'b': ...@@ -622,18 +607,11 @@ ordering between the load from variable 'a' and the store to variable 'b':
do_something_else(); do_something_else();
} }
The initial ACCESS_ONCE() is required to prevent the compiler from Unfortunately, current compilers will transform this as follows at high
proving the value of 'a', and the pair of barrier() invocations are optimization levels:
required to prevent the compiler from pulling the two identical stores
to 'b' out from the legs of the "if" statement.
It is important to note that control dependencies absolutely require a
a conditional. For example, the following "optimized" version of
the above example breaks ordering, which is why the barrier() invocations
are absolutely required if you have identical stores in both legs of
the "if" statement:
q = ACCESS_ONCE(a); q = ACCESS_ONCE(a);
barrier();
ACCESS_ONCE(b) = p; /* BUG: No ordering vs. load from a!!! */ ACCESS_ONCE(b) = p; /* BUG: No ordering vs. load from a!!! */
if (q) { if (q) {
/* ACCESS_ONCE(b) = p; -- moved up, BUG!!! */ /* ACCESS_ONCE(b) = p; -- moved up, BUG!!! */
...@@ -643,21 +621,36 @@ the "if" statement: ...@@ -643,21 +621,36 @@ the "if" statement:
do_something_else(); do_something_else();
} }
It is of course legal for the prior load to be part of the conditional, Now there is no conditional between the load from 'a' and the store to
for example, as follows: 'b', which means that the CPU is within its rights to reorder them:
The conditional is absolutely required, and must be present in the
assembly code even after all compiler optimizations have been applied.
Therefore, if you need ordering in this example, you need explicit
memory barriers, for example, smp_store_release():
if (ACCESS_ONCE(a) > 0) { q = ACCESS_ONCE(a);
barrier(); if (q) {
ACCESS_ONCE(b) = q / 2; smp_store_release(&b, p);
do_something(); do_something();
} else { } else {
barrier(); smp_store_release(&b, p);
ACCESS_ONCE(b) = q / 3; do_something_else();
}
In contrast, without explicit memory barriers, two-legged-if control
ordering is guaranteed only when the stores differ, for example:
q = ACCESS_ONCE(a);
if (q) {
ACCESS_ONCE(b) = p;
do_something();
} else {
ACCESS_ONCE(b) = r;
do_something_else(); do_something_else();
} }
This will again ensure that the load from variable 'a' is ordered before the The initial ACCESS_ONCE() is still required to prevent the compiler from
stores to variable 'b'. proving the value of 'a'.
In addition, you need to be careful what you do with the local variable 'q', In addition, you need to be careful what you do with the local variable 'q',
otherwise the compiler might be able to guess the value and again remove otherwise the compiler might be able to guess the value and again remove
...@@ -665,12 +658,10 @@ the needed conditional. For example: ...@@ -665,12 +658,10 @@ the needed conditional. For example:
q = ACCESS_ONCE(a); q = ACCESS_ONCE(a);
if (q % MAX) { if (q % MAX) {
barrier();
ACCESS_ONCE(b) = p; ACCESS_ONCE(b) = p;
do_something(); do_something();
} else { } else {
barrier(); ACCESS_ONCE(b) = r;
ACCESS_ONCE(b) = p;
do_something_else(); do_something_else();
} }
...@@ -679,15 +670,15 @@ equal to zero, in which case the compiler is within its rights to ...@@ -679,15 +670,15 @@ equal to zero, in which case the compiler is within its rights to
transform the above code into the following: transform the above code into the following:
q = ACCESS_ONCE(a); q = ACCESS_ONCE(a);
barrier();
ACCESS_ONCE(b) = p; ACCESS_ONCE(b) = p;
do_something_else(); do_something_else();
This transformation fails to require that the CPU respect the ordering Given this transformation, the CPU is not required to respect the ordering
between the load from variable 'a' and the store to variable 'b'. between the load from variable 'a' and the store to variable 'b'. It is
Yes, the barrier() is still there, but it affects only the compiler, tempting to add a barrier(), but this does not help. The conditional
not the CPU. Therefore, if you are relying on this ordering, you should is gone, and the barrier won't bring it back. Therefore, if you are
do something like the following: relying on this ordering, you should make sure that MAX is greater than
one, perhaps as follows:
q = ACCESS_ONCE(a); q = ACCESS_ONCE(a);
BUILD_BUG_ON(MAX <= 1); /* Order load from a with store to b. */ BUILD_BUG_ON(MAX <= 1); /* Order load from a with store to b. */
...@@ -695,10 +686,14 @@ do something like the following: ...@@ -695,10 +686,14 @@ do something like the following:
ACCESS_ONCE(b) = p; ACCESS_ONCE(b) = p;
do_something(); do_something();
} else { } else {
ACCESS_ONCE(b) = p; ACCESS_ONCE(b) = r;
do_something_else(); do_something_else();
} }
Please note once again that the stores to 'b' differ. If they were
identical, as noted earlier, the compiler could pull this store outside
of the 'if' statement.
Finally, control dependencies do -not- provide transitivity. This is Finally, control dependencies do -not- provide transitivity. This is
demonstrated by two related examples, with the initial values of demonstrated by two related examples, with the initial values of
x and y both being zero: x and y both being zero:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment