Skip to content

Commit 80a4707

Browse files
committed
docs: revise restrictions on volatile operations
A distinction between usage on Rust memory vs. non-Rust memory was introduced. Documentation was reworded to explain what that means, and make explicit that: - No trapping can occur from volatile operations; - On Rust memory, all safety rules must be respected; - On Rust memory, the primary difference from regular access is that volatile always involves a memory dereference; - On Rust memory, the only data affected by an operation is the one pointed to in the argument(s) of the function; - On Rust memory, provenance follows the same rules as non-volatile access; - On non-Rust memory, any address known to not contain Rust memory is valid (including 0 and usize::MAX); - On non-Rust memory, no Rust memory may be affected (it is implicit that any other non-Rust memory may be affected, though, even if not referenced by the pointer). This should be relevant when, for example, reading register A causes a flag to change in register B, or writing to A causes B to change in some way. Everything affected mustn't be inside an allocation. - On non-Rust memory, provenance is irrelevant and a pointer with none can be used in a valid way.
1 parent 2b0a5aa commit 80a4707

File tree

1 file changed

+99
-65
lines changed

1 file changed

+99
-65
lines changed

library/core/src/ptr/mod.rs

Lines changed: 99 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
//! undefined behavior to perform two concurrent accesses to the same location from different
2929
//! threads unless both accesses only read from memory. Notice that this explicitly
3030
//! includes [`read_volatile`] and [`write_volatile`]: Volatile accesses cannot
31-
//! be used for inter-thread synchronization.
31+
//! be used for inter-thread synchronization, regardless of whether it is acting on
32+
//! Rust memory or not.
3233
//! * The result of casting a reference to a pointer is valid for as long as the
3334
//! underlying allocation is live and no reference (just raw pointers) is used to
3435
//! access the same memory. That is, reference and pointer accesses cannot be
@@ -114,6 +115,10 @@
114115
//! fully contiguous (i.e., has no "holes"), there is no guarantee that this
115116
//! will not change in the future.
116117
//!
118+
//! Allocations must behave like "normal" memory: in particular, reads must not have
119+
//! side-effects, and writes must become visible to other threads using the usual synchronization
120+
//! primitives.
121+
//!
117122
//! For any allocation with `base` address, `size`, and a set of
118123
//! `addresses`, the following are guaranteed:
119124
//! - For all addresses `a` in `addresses`, `a` is in the range `base .. (base +
@@ -2021,54 +2026,68 @@ pub const unsafe fn write_unaligned<T>(dst: *mut T, src: T) {
20212026
}
20222027
}
20232028

2024-
/// Performs a volatile read of the value from `src` without moving it. This
2025-
/// leaves the memory in `src` unchanged.
2026-
///
2027-
/// Volatile operations are intended to act on I/O memory, and are guaranteed
2028-
/// to not be elided or reordered by the compiler across other volatile
2029-
/// operations.
2030-
///
2031-
/// # Notes
2032-
///
2033-
/// Rust does not currently have a rigorously and formally defined memory model,
2034-
/// so the precise semantics of what "volatile" means here is subject to change
2035-
/// over time. That being said, the semantics will almost always end up pretty
2036-
/// similar to [C11's definition of volatile][c11].
2037-
///
2038-
/// The compiler shouldn't change the relative order or number of volatile
2039-
/// memory operations. However, volatile memory operations on zero-sized types
2040-
/// (e.g., if a zero-sized type is passed to `read_volatile`) are noops
2041-
/// and may be ignored.
2029+
/// Performs a volatile read of the value from `src` without moving it.
2030+
///
2031+
/// Rust does not currently have a rigorously and formally defined memory model, so the precise
2032+
/// semantics of what "volatile" means here is subject to change over time. That being said, the
2033+
/// semantics will almost always end up pretty similar to [C11's definition of volatile][c11].
2034+
///
2035+
/// Volatile operations are intended to act on I/O memory. As such, they are considered externally
2036+
/// observable events (just like syscalls), and are guaranteed to not be elided or reordered by the
2037+
/// compiler across other externally observable events. With this in mind, there are two cases of
2038+
/// usage that need to be distinguished:
2039+
///
2040+
/// - When a volatile operation is used for memory inside an [allocation], it behaves exactly like
2041+
/// [`read`], except for the additional guarantee that it won't be elided or reordered (see
2042+
/// above). This implies that the operation will actually access memory and not e.g. be lowered to
2043+
/// reusing data from a previous read, such as a register that performed a previous load on that
2044+
/// memory. Other than that, all the usual rules for memory accesses apply (including provenance).
2045+
/// In particular, just like in C, whether an operation is volatile has no bearing whatsoever on
2046+
/// questions involving concurrent access from multiple threads. Volatile accesses behave exactly
2047+
/// like non-atomic accesses in that regard.
2048+
///
2049+
/// - Volatile operations, however, may also be used to access memory that is _outside_ of any Rust
2050+
/// allocation. In this use-case, the pointer does *not* have to be [valid] for reads. This is
2051+
/// typically used for CPU and peripheral registers that must be accessed via an I/O memory
2052+
/// mapping, most commonly at fixed addresses reserved by the hardware. These often have special
2053+
/// semantics associated to their manipulation, and cannot be used as general purpose memory.
2054+
/// Here, any address value is possible, including 0 and [`usize::MAX`], so long as the semantics
2055+
/// of such a read are well-defined by the target hardware. The provenance of the pointer is
2056+
/// irrelevant, and it can be created with [`without_provenance`]. The access must not trap. It
2057+
/// can cause side-effects, but those must not affect Rust-allocated memory in in any way. This
2058+
/// access is still not considered [atomic], and as such it cannot be used for inter-thread
2059+
/// synchronization.
2060+
///
2061+
/// Note that volatile memory operations on zero-sized types (e.g., if a zero-sized type is passed
2062+
/// to `read_volatile`) are noops and may be ignored.
20422063
///
20432064
/// [c11]: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
2065+
/// [allocation]: crate::ptr#allocated-object
2066+
/// [atomic]: crate::sync::atomic#memory-model-for-atomic-accesses
20442067
///
20452068
/// # Safety
20462069
///
2070+
/// Like [`read`], `read_volatile` creates a bitwise copy of `T`, regardless of whether `T` is
2071+
/// [`Copy`]. If `T` is not [`Copy`], using both the returned value and the value at `*src` can
2072+
/// [violate memory safety][read-ownership]. However, storing non-[`Copy`] types in volatile memory
2073+
/// is almost certainly incorrect.
2074+
///
20472075
/// Behavior is undefined if any of the following conditions are violated:
20482076
///
2049-
/// * `src` must be [valid] for reads.
2077+
/// * `src` must be either [valid] for reads, or it must point to memory outside of all Rust
2078+
/// allocations and reading from that memory must:
2079+
/// - not trap, and
2080+
/// - not cause any memory inside a Rust allocation to be modified.
20502081
///
20512082
/// * `src` must be properly aligned.
20522083
///
2053-
/// * `src` must point to a properly initialized value of type `T`.
2054-
///
2055-
/// Like [`read`], `read_volatile` creates a bitwise copy of `T`, regardless of
2056-
/// whether `T` is [`Copy`]. If `T` is not [`Copy`], using both the returned
2057-
/// value and the value at `*src` can [violate memory safety][read-ownership].
2058-
/// However, storing non-[`Copy`] types in volatile memory is almost certainly
2059-
/// incorrect.
2084+
/// * Reading from `src` must produce a properly initialized value of type `T`.
20602085
///
20612086
/// Note that even if `T` has size `0`, the pointer must be properly aligned.
20622087
///
20632088
/// [valid]: self#safety
20642089
/// [read-ownership]: read#ownership-of-the-returned-value
20652090
///
2066-
/// Just like in C, whether an operation is volatile has no bearing whatsoever
2067-
/// on questions involving concurrent access from multiple threads. Volatile
2068-
/// accesses behave exactly like non-atomic accesses in that regard. In particular,
2069-
/// a race between a `read_volatile` and any write operation to the same location
2070-
/// is undefined behavior.
2071-
///
20722091
/// # Examples
20732092
///
20742093
/// Basic usage:
@@ -2100,52 +2119,67 @@ pub unsafe fn read_volatile<T>(src: *const T) -> T {
21002119
}
21012120
}
21022121

2103-
/// Performs a volatile write of a memory location with the given value without
2104-
/// reading or dropping the old value.
2105-
///
2106-
/// Volatile operations are intended to act on I/O memory, and are guaranteed
2107-
/// to not be elided or reordered by the compiler across other volatile
2108-
/// operations.
2109-
///
2110-
/// `write_volatile` does not drop the contents of `dst`. This is safe, but it
2111-
/// could leak allocations or resources, so care should be taken not to overwrite
2112-
/// an object that should be dropped.
2113-
///
2114-
/// Additionally, it does not drop `src`. Semantically, `src` is moved into the
2115-
/// location pointed to by `dst`.
2116-
///
2117-
/// # Notes
2118-
///
2119-
/// Rust does not currently have a rigorously and formally defined memory model,
2120-
/// so the precise semantics of what "volatile" means here is subject to change
2121-
/// over time. That being said, the semantics will almost always end up pretty
2122-
/// similar to [C11's definition of volatile][c11].
2123-
///
2124-
/// The compiler shouldn't change the relative order or number of volatile
2125-
/// memory operations. However, volatile memory operations on zero-sized types
2126-
/// (e.g., if a zero-sized type is passed to `write_volatile`) are noops
2127-
/// and may be ignored.
2122+
/// Performs a volatile write of a memory location with the given value without reading or dropping
2123+
/// the old value.
2124+
///
2125+
/// Rust does not currently have a rigorously and formally defined memory model, so the precise
2126+
/// semantics of what "volatile" means here is subject to change over time. That being said, the
2127+
/// semantics will almost always end up pretty similar to [C11's definition of volatile][c11].
2128+
///
2129+
/// Volatile operations are intended to act on I/O memory. As such, they are considered externally
2130+
/// observable events (just like syscalls), and are guaranteed to not be elided or reordered by the
2131+
/// compiler across other externally observable events. With this in mind, there are two cases of
2132+
/// usage that need to be distinguished:
2133+
///
2134+
/// - When a volatile operation is used for memory inside an [allocation], it behaves exactly like
2135+
/// [`write()`], except for the additional guarantee that it won't be elided or reordered (see
2136+
/// above). This implies that the operation will actually access memory and not e.g. be lowered to
2137+
/// a register access or stack pop. Other than that, all the usual rules for memory accesses apply
2138+
/// (including provenance). In particular, just like in C, whether an operation is volatile has no
2139+
/// bearing whatsoever on questions involving concurrent access from multiple threads. Volatile
2140+
/// accesses behave exactly like non-atomic accesses in that regard.
2141+
///
2142+
/// - Volatile operations, however, may also be used to access memory that is _outside_ of any Rust
2143+
/// allocation. In this use-case, the pointer does *not* have to be [valid] for writes. This is
2144+
/// typically used for CPU and peripheral registers that must be accessed via an I/O memory
2145+
/// mapping, most commonly at fixed addresses reserved by the hardware. These often have special
2146+
/// semantics associated to their manipulation, and cannot be used as general purpose memory.
2147+
/// Here, any address value is possible, including 0 and [`usize::MAX`], so long as the semantics
2148+
/// of such a write are well-defined by the target hardware. The provenance of the pointer is
2149+
/// irrelevant, and it can be created with [`without_provenance`]. The access must not trap. It
2150+
/// can cause side-effects, but those must not affect Rust-allocated memory in any way. This
2151+
/// access is still not considered [atomic], and as such it cannot be used for inter-thread
2152+
/// synchronization.
2153+
///
2154+
/// Note that volatile memory operations on zero-sized types (e.g., if a zero-sized type is passed
2155+
/// to `write_volatile`) are noops and may be ignored.
2156+
///
2157+
/// `write_volatile` does not drop the contents of `dst`. This is safe, but it could leak
2158+
/// allocations or resources, so care should be taken not to overwrite an object that should be
2159+
/// dropped when operating on Rust memory.
2160+
///
2161+
/// Additionally, it does not drop `src`. Semantically, `src` is moved into the location pointed to
2162+
/// by `dst`.
21282163
///
21292164
/// [c11]: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
2165+
/// [allocation]: crate::ptr#allocated-object
2166+
/// [atomic]: crate::sync::atomic#memory-model-for-atomic-accesses
21302167
///
21312168
/// # Safety
21322169
///
21332170
/// Behavior is undefined if any of the following conditions are violated:
21342171
///
2135-
/// * `dst` must be [valid] for writes.
2172+
/// * `dst` must be either [valid] for writes, or it must point to memory outside of all Rust
2173+
/// allocations and writing to that memory must:
2174+
/// - not trap, and
2175+
/// - not cause any memory inside a Rust allocation to be modified.
21362176
///
21372177
/// * `dst` must be properly aligned.
21382178
///
21392179
/// Note that even if `T` has size `0`, the pointer must be properly aligned.
21402180
///
21412181
/// [valid]: self#safety
21422182
///
2143-
/// Just like in C, whether an operation is volatile has no bearing whatsoever
2144-
/// on questions involving concurrent access from multiple threads. Volatile
2145-
/// accesses behave exactly like non-atomic accesses in that regard. In particular,
2146-
/// a race between a `write_volatile` and any other operation (reading or writing)
2147-
/// on the same location is undefined behavior.
2148-
///
21492183
/// # Examples
21502184
///
21512185
/// Basic usage:

0 commit comments

Comments
 (0)