Rollup merge of #128778 - RalfJung:atomic-read-read-races, r=Mark-Sim… · qinheping/verify-rust-std@7a5052a (original) (raw)
`@@ -24,25 +24,42 @@
`
24
24
`//!
`
25
25
`//! ## Memory model for atomic accesses
`
26
26
`//!
`
27
``
`` -
//! Rust atomics currently follow the same rules as [C++20 atomics][cpp], specifically atomic_ref
.
``
28
``
`-
//! Basically, creating a shared reference to one of the Rust atomic types corresponds to creating
`
29
``
`` -
//! an atomic_ref
in C++; the atomic_ref
is destroyed when the lifetime of the shared reference
``
30
``
`-
//! ends. A Rust atomic type that is exclusively owned or behind a mutable reference does not
`
31
``
`-
//! correspond to an “atomic object” in C++, since the underlying primitive can be mutably accessed,
`
32
``
`` -
//! for example with get_mut
, to perform non-atomic operations.
``
``
27
`+
//! Rust atomics currently follow the same rules as [C++20 atomics][cpp], specifically the rules
`
``
28
`` +
//! from the [intro.races
][cpp-intro.races] section, without the "consume" memory ordering. Since
``
``
29
`+
//! C++ uses an object-based memory model whereas Rust is access-based, a bit of translation work
`
``
30
`+
//! has to be done to apply the C++ rules to Rust: whenever C++ talks about "the value of an
`
``
31
`+
//! object", we understand that to mean the resulting bytes obtained when doing a read. When the C++
`
``
32
`+
//! standard talks about "the value of an atomic object", this refers to the result of doing an
`
``
33
`+
//! atomic load (via the operations provided in this module). A "modification of an atomic object"
`
``
34
`+
//! refers to an atomic store.
`
33
35
`//!
`
34
``
`-
//! [cpp]: https://en.cppreference.com/w/cpp/atomic
`
``
36
`+
//! The end result is almost equivalent to saying that creating a shared reference to one of the
`
``
37
`` +
//! Rust atomic types corresponds to creating an atomic_ref
in C++, with the atomic_ref
being
``
``
38
`+
//! destroyed when the lifetime of the shared reference ends. The main difference is that Rust
`
``
39
`+
//! permits concurrent atomic and non-atomic reads to the same memory as those cause no issue in the
`
``
40
`+
//! C++ memory model, they are just forbidden in C++ because memory is partitioned into "atomic
`
``
41
`` +
//! objects" and "non-atomic objects" (with atomic_ref
temporarily converting a non-atomic object
``
``
42
`+
//! into an atomic object).
`
``
43
`+
//!
`
``
44
`+
//! The most important aspect of this model is that data races are undefined behavior. A data race
`
``
45
`+
//! is defined as conflicting non-synchronized accesses where at least one of the accesses is
`
``
46
`+
//! non-atomic. Here, accesses are conflicting if they affect overlapping regions of memory and at
`
``
47
`+
//! least one of them is a write. They are non-synchronized if neither of them happens-before
`
``
48
`+
//! the other, according to the happens-before order of the memory model.
`
35
49
`//!
`
36
``
`` -
//! Each method takes an [Ordering
] which represents the strength of
``
37
``
`-
//! the memory barrier for that operation. These orderings are the
`
38
``
`-
//! same as the [C++20 atomic orderings][1]. For more information see the [nomicon][2].
`
``
50
`+
//! The other possible cause of undefined behavior in the memory model are mixed-size accesses: Rust
`
``
51
`+
//! inherits the C++ limitation that non-synchronized conflicting atomic accesses may not partially
`
``
52
`+
//! overlap. In other words, every pair of non-synchronized atomic accesses must be either disjoint,
`
``
53
`+
//! access the exact same memory (including using the same access size), or both be reads.
`
39
54
`//!
`
40
``
`-
//! [1]: https://en.cppreference.com/w/cpp/atomic/memory_order
`
41
``
`-
//! [2]: ../../../nomicon/atomics.html
`
``
55
`` +
//! Each atomic access takes an [Ordering
] which defines how the operation interacts with the
``
``
56
`+
//! happens-before order. These orderings behave the same as the corresponding [C++20 atomic
`
``
57
`+
//! orderings][cpp_memory_order]. For more information, see the [nomicon].
`
42
58
`//!
`
43
``
`-
//! Since C++ does not support mixing atomic and non-atomic accesses, or non-synchronized
`
44
``
`-
//! different-sized accesses to the same data, Rust does not support those operations either.
`
45
``
`-
//! Note that both of those restrictions only apply if the accesses are non-synchronized.
`
``
59
`+
//! [cpp]: https://en.cppreference.com/w/cpp/atomic
`
``
60
`+
//! [cpp-intro.races]: https://timsong-cpp.github.io/cppwp/n4868/intro.multithread#intro.races
`
``
61
`+
//! [cpp_memory_order]: https://en.cppreference.com/w/cpp/atomic/memory_order
`
``
62
`+
//! [nomicon]: ../../../nomicon/atomics.html
`
46
63
`//!
`
47
64
```` //! ```rust,no_run undefined_behavior
````
48
65
`//! use std::sync::atomic::{AtomicU16, AtomicU8, Ordering};
`
`@@ -52,27 +69,29 @@
`
52
69
`//! let atomic = AtomicU16::new(0);
`
53
70
`//!
`
54
71
`//! thread::scope(|s| {
`
55
``
`-
//! // This is UB: mixing atomic and non-atomic accesses
`
56
``
`-
//! s.spawn(|| atomic.store(1, Ordering::Relaxed));
`
57
``
`-
//! s.spawn(|| unsafe { atomic.as_ptr().write(2) });
`
``
72
`+
//! // This is UB: conflicting non-synchronized accesses, at least one of which is non-atomic.
`
``
73
`+
//! s.spawn(|| atomic.store(1, Ordering::Relaxed)); // atomic store
`
``
74
`+
//! s.spawn(|| unsafe { atomic.as_ptr().write(2) }); // non-atomic write
`
58
75
`//! });
`
59
76
`//!
`
60
77
`//! thread::scope(|s| {
`
61
``
`-
//! // This is UB: even reads are not allowed to be mixed
`
62
``
`-
//! s.spawn(|| atomic.load(Ordering::Relaxed));
`
63
``
`-
//! s.spawn(|| unsafe { atomic.as_ptr().read() });
`
``
78
`+
//! // This is fine: the accesses do not conflict (as none of them performs any modification).
`
``
79
`` +
//! // In C++ this would be disallowed since creating an atomic_ref
precludes
``
``
80
`+
//! // further non-atomic accesses, but Rust does not have that limitation.
`
``
81
`+
//! s.spawn(|| atomic.load(Ordering::Relaxed)); // atomic load
`
``
82
`+
//! s.spawn(|| unsafe { atomic.as_ptr().read() }); // non-atomic read
`
64
83
`//! });
`
65
84
`//!
`
66
85
`//! thread::scope(|s| {
`
67
``
`` -
//! // This is fine, join
synchronizes the code in a way such that atomic
``
68
``
`-
//! // and non-atomic accesses can't happen "at the same time"
`
69
``
`-
//! let handle = s.spawn(|| atomic.store(1, Ordering::Relaxed));
`
70
``
`-
//! handle.join().unwrap();
`
71
``
`-
//! s.spawn(|| unsafe { atomic.as_ptr().write(2) });
`
``
86
`` +
//! // This is fine: join
synchronizes the code in a way such that the atomic
``
``
87
`+
//! // store happens-before the non-atomic write.
`
``
88
`+
//! let handle = s.spawn(|| atomic.store(1, Ordering::Relaxed)); // atomic store
`
``
89
`+
//! handle.join().unwrap(); // synchronize
`
``
90
`+
//! s.spawn(|| unsafe { atomic.as_ptr().write(2) }); // non-atomic write
`
72
91
`//! });
`
73
92
`//!
`
74
93
`//! thread::scope(|s| {
`
75
``
`-
//! // This is UB: using different-sized atomic accesses to the same data
`
``
94
`+
//! // This is UB: non-synchronized conflicting differently-sized atomic accesses.
`
76
95
`//! s.spawn(|| atomic.store(1, Ordering::Relaxed));
`
77
96
`//! s.spawn(|| unsafe {
`
78
97
`//! let differently_sized = transmute::<&AtomicU16, &AtomicU8>(&atomic);
`
`@@ -81,8 +100,8 @@
`
81
100
`//! });
`
82
101
`//!
`
83
102
`//! thread::scope(|s| {
`
84
``
`` -
//! // This is fine, join
synchronizes the code in a way such that
``
85
``
`-
//! // differently-sized accesses can't happen "at the same time"
`
``
103
`` +
//! // This is fine: join
synchronizes the code in a way such that
``
``
104
`+
//! // the 1-byte store happens-before the 2-byte store.
`
86
105
`//! let handle = s.spawn(|| atomic.store(1, Ordering::Relaxed));
`
87
106
`//! handle.join().unwrap();
`
88
107
`//! s.spawn(|| unsafe {
`
137
156
`//!
`
138
157
`//! # Atomic accesses to read-only memory
`
139
158
`//!
`
140
``
`-
//! In general, all atomic accesses on read-only memory are Undefined Behavior. For instance, attempting
`
``
159
`+
//! In general, all atomic accesses on read-only memory are undefined behavior. For instance, attempting
`
141
160
`` //! to do a compare_exchange
that will definitely fail (making it conceptually a read-only
``
142
161
`//! operation) can still cause a segmentation fault if the underlying memory page is mapped read-only. Since
`
143
162
`` //! atomic load
s might be implemented using compare-exchange operations, even a load
can fault
``
`@@ -153,7 +172,7 @@
`
153
172
`//!
`
154
173
`//! As an exception from the general rule stated above, "sufficiently small" atomic loads with
`
155
174
`` //! Ordering::Relaxed
are implemented in a way that works on read-only memory, and are hence not
``
156
``
`-
//! Undefined Behavior. The exact size limit for what makes a load "sufficiently small" varies
`
``
175
`+
//! undefined behavior. The exact size limit for what makes a load "sufficiently small" varies
`
157
176
`//! depending on the target:
`
158
177
`//!
`
159
178
`` //! | target_arch
| Size limit |
``