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 loads 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 |

``