Skip to content

Commit b2d9b63

Browse files
committed
Auto merge of #41092 - jonhoo:std-fence-intrinsics, r=alexcrichton
Add safe wrapper for atomic_compilerfence intrinsics This PR adds a proposed safe wrapper for the `atomic_singlethreadfence_*` intrinsics introduced by [RFC #888](rust-lang/rfcs#888). See #41091 for further discussion.
2 parents 666e714 + f093d59 commit b2d9b63

File tree

3 files changed

+148
-0
lines changed

3 files changed

+148
-0
lines changed

src/doc/unstable-book/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
- [collections](collections.md)
3838
- [collections_range](collections-range.md)
3939
- [command_envs](command-envs.md)
40+
- [compiler_barriers](compiler-barriers.md)
4041
- [compiler_builtins](compiler-builtins.md)
4142
- [compiler_builtins_lib](compiler-builtins-lib.md)
4243
- [concat_idents](concat-idents.md)
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# `compiler_barriers`
2+
3+
The tracking issue for this feature is: [#41091]
4+
5+
[#41091]: https://github.com/rust-lang/rust/issues/41091
6+
7+
------------------------
8+
9+
The `compiler_barriers` feature exposes the `compiler_barrier` function
10+
in `std::sync::atomic`. This function is conceptually similar to C++'s
11+
`atomic_signal_fence`, which can currently only be accessed in nightly
12+
Rust using the `atomic_singlethreadfence_*` instrinsic functions in
13+
`core`, or through the mostly equivalent literal assembly:
14+
15+
```rust
16+
#![feature(asm)]
17+
unsafe { asm!("" ::: "memory" : "volatile") };
18+
```
19+
20+
A `compiler_barrier` restricts the kinds of memory re-ordering the
21+
compiler is allowed to do. Specifically, depending on the given ordering
22+
semantics, the compiler may be disallowed from moving reads or writes
23+
from before or after the call to the other side of the call to
24+
`compiler_barrier`. Note that it does **not** prevent the *hardware*
25+
from doing such re-ordering. This is not a problem in a single-threaded,
26+
execution context, but when other threads may modify memory at the same
27+
time, stronger synchronization primitives are required.
28+
29+
## Examples
30+
31+
`compiler_barrier` is generally only useful for preventing a thread from
32+
racing *with itself*. That is, if a given thread is executing one piece
33+
of code, and is then interrupted, and starts executing code elsewhere
34+
(while still in the same thread, and conceptually still on the same
35+
core). In traditional programs, this can only occur when a signal
36+
handler is registered. In more low-level code, such situations can also
37+
arise when handling interrupts, when implementing green threads with
38+
pre-emption, etc.
39+
40+
To give a straightforward example of when a `compiler_barrier` is
41+
necessary, consider the following example:
42+
43+
```rust
44+
# use std::sync::atomic::{AtomicBool, AtomicUsize};
45+
# use std::sync::atomic::{ATOMIC_BOOL_INIT, ATOMIC_USIZE_INIT};
46+
# use std::sync::atomic::Ordering;
47+
static IMPORTANT_VARIABLE: AtomicUsize = ATOMIC_USIZE_INIT;
48+
static IS_READY: AtomicBool = ATOMIC_BOOL_INIT;
49+
50+
fn main() {
51+
IMPORTANT_VARIABLE.store(42, Ordering::Relaxed);
52+
IS_READY.store(true, Ordering::Relaxed);
53+
}
54+
55+
fn signal_handler() {
56+
if IS_READY.load(Ordering::Relaxed) {
57+
assert_eq!(IMPORTANT_VARIABLE.load(Ordering::Relaxed), 42);
58+
}
59+
}
60+
```
61+
62+
The way it is currently written, the `assert_eq!` is *not* guaranteed to
63+
succeed, despite everything happening in a single thread. To see why,
64+
remember that the compiler is free to swap the stores to
65+
`IMPORTANT_VARIABLE` and `IS_READ` since they are both
66+
`Ordering::Relaxed`. If it does, and the signal handler is invoked right
67+
after `IS_READY` is updated, then the signal handler will see
68+
`IS_READY=1`, but `IMPORTANT_VARIABLE=0`.
69+
70+
Using a `compiler_barrier`, we can remedy this situation:
71+
72+
```rust
73+
#![feature(compiler_barriers)]
74+
# use std::sync::atomic::{AtomicBool, AtomicUsize};
75+
# use std::sync::atomic::{ATOMIC_BOOL_INIT, ATOMIC_USIZE_INIT};
76+
# use std::sync::atomic::Ordering;
77+
use std::sync::atomic::compiler_barrier;
78+
79+
static IMPORTANT_VARIABLE: AtomicUsize = ATOMIC_USIZE_INIT;
80+
static IS_READY: AtomicBool = ATOMIC_BOOL_INIT;
81+
82+
fn main() {
83+
IMPORTANT_VARIABLE.store(42, Ordering::Relaxed);
84+
// prevent earlier writes from being moved beyond this point
85+
compiler_barrier(Ordering::Release);
86+
IS_READY.store(true, Ordering::Relaxed);
87+
}
88+
89+
fn signal_handler() {
90+
if IS_READY.load(Ordering::Relaxed) {
91+
assert_eq!(IMPORTANT_VARIABLE.load(Ordering::Relaxed), 42);
92+
}
93+
}
94+
```
95+
96+
A deeper discussion of compiler barriers with various re-ordering
97+
semantics (such as `Ordering::SeqCst`) is beyond the scope of this text.
98+
Curious readers are encouraged to read the Linux kernel's discussion of
99+
[memory barriers][1], the C++ references on [`std::memory_order`][2] and
100+
[`atomic_signal_fence`][3], and [this StackOverflow answer][4] for
101+
further details.
102+
103+
[1]: https://www.kernel.org/doc/Documentation/memory-barriers.txt
104+
[2]: http://en.cppreference.com/w/cpp/atomic/memory_order
105+
[3]: http://www.cplusplus.com/reference/atomic/atomic_signal_fence/
106+
[4]: http://stackoverflow.com/a/18454971/472927

src/libcore/sync/atomic.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1591,6 +1591,47 @@ pub fn fence(order: Ordering) {
15911591
}
15921592

15931593

1594+
/// A compiler memory barrier.
1595+
///
1596+
/// `compiler_barrier` does not emit any machine code, but prevents the compiler from re-ordering
1597+
/// memory operations across this point. Which reorderings are disallowed is dictated by the given
1598+
/// [`Ordering`]. Note that `compiler_barrier` does *not* introduce inter-thread memory
1599+
/// synchronization; for that, a [`fence`] is needed.
1600+
///
1601+
/// The re-ordering prevented by the different ordering semantics are:
1602+
///
1603+
/// - with [`SeqCst`], no re-ordering of reads and writes across this point is allowed.
1604+
/// - with [`Release`], preceding reads and writes cannot be moved past subsequent writes.
1605+
/// - with [`Acquire`], subsequent reads and writes cannot be moved ahead of preceding reads.
1606+
/// - with [`AcqRel`], both of the above rules are enforced.
1607+
///
1608+
/// # Panics
1609+
///
1610+
/// Panics if `order` is [`Relaxed`].
1611+
///
1612+
/// [`fence`]: fn.fence.html
1613+
/// [`Ordering`]: enum.Ordering.html
1614+
/// [`Acquire`]: enum.Ordering.html#variant.Acquire
1615+
/// [`SeqCst`]: enum.Ordering.html#variant.SeqCst
1616+
/// [`Release`]: enum.Ordering.html#variant.Release
1617+
/// [`AcqRel`]: enum.Ordering.html#variant.AcqRel
1618+
/// [`Relaxed`]: enum.Ordering.html#variant.Relaxed
1619+
#[inline]
1620+
#[unstable(feature = "compiler_barriers", issue = "41091")]
1621+
pub fn compiler_barrier(order: Ordering) {
1622+
unsafe {
1623+
match order {
1624+
Acquire => intrinsics::atomic_singlethreadfence_acq(),
1625+
Release => intrinsics::atomic_singlethreadfence_rel(),
1626+
AcqRel => intrinsics::atomic_singlethreadfence_acqrel(),
1627+
SeqCst => intrinsics::atomic_singlethreadfence(),
1628+
Relaxed => panic!("there is no such thing as a relaxed barrier"),
1629+
__Nonexhaustive => panic!("invalid memory ordering"),
1630+
}
1631+
}
1632+
}
1633+
1634+
15941635
#[cfg(target_has_atomic = "8")]
15951636
#[stable(feature = "atomic_debug", since = "1.3.0")]
15961637
impl fmt::Debug for AtomicBool {

0 commit comments

Comments
 (0)