Skip to content

Commit 4e7f410

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 fd46953 commit 4e7f410

File tree

1 file changed

+91
-69
lines changed

1 file changed

+91
-69
lines changed

library/core/src/ptr/mod.rs

Lines changed: 91 additions & 69 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,61 @@ 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.
2042-
///
2043-
/// [c11]: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
2029+
/// Performs a volatile read of the value from `src` without moving it.
2030+
///
2031+
/// Volatile operations are intended to act on I/O memory. As such, they are considered externally
2032+
/// observable events (just like syscalls, but less opaque), and are guaranteed to not be elided or
2033+
/// reordered by the compiler across other externally observable events. With this in mind, there
2034+
/// are two cases of usage that need to be distinguished:
2035+
///
2036+
/// - When a volatile operation is used for memory inside an [allocation], it behaves exactly like
2037+
/// [`read`], except for the additional guarantee that it won't be elided or reordered (see
2038+
/// above). This implies that the operation will actually access memory and not e.g. be lowered to
2039+
/// reusing data from a previous read, such as from a register on which previous load of that
2040+
/// memory was performed. Other than that, all the usual rules for memory accesses apply
2041+
/// (including provenance). In particular, just like in C, whether an operation is volatile has
2042+
/// no bearing whatsoever on questions involving concurrent access from multiple threads. Volatile
2043+
/// accesses behave exactly like non-atomic accesses in that regard.
2044+
///
2045+
/// - Volatile operations, however, may also be used to access memory that is _outside_ of any Rust
2046+
/// allocation. In this use-case, the pointer does *not* have to be [valid] for reads. This is
2047+
/// typically used for CPU and peripheral registers that must be accessed via an I/O memory
2048+
/// mapping, most commonly at fixed addresses reserved by the hardware. These often have special
2049+
/// semantics associated to their manipulation, and cannot be used as general purpose memory.
2050+
/// Here, any address value is possible, including 0 and [`usize::MAX`], so long as the semantics
2051+
/// of such a read are well-defined by the target hardware. The provenance of the pointer is
2052+
/// irrelevant, and it can be created with [`without_provenance`]. The access must not trap. It
2053+
/// can cause side-effects, but those must not affect Rust-allocated memory in in any way. This
2054+
/// access is still not considered [atomic], and as such it cannot be used for inter-thread
2055+
/// synchronization. Note that volatile memory operations where T is a zero-sized type are noops
2056+
/// and may be ignored.
2057+
///
2058+
/// [allocation]: crate::ptr#allocated-object
2059+
/// [atomic]: crate::sync::atomic#memory-model-for-atomic-accesses
20442060
///
20452061
/// # Safety
20462062
///
2063+
/// Like [`read`], `read_volatile` creates a bitwise copy of `T`, regardless of whether `T` is
2064+
/// [`Copy`]. If `T` is not [`Copy`], using both the returned value and the value at `*src` can
2065+
/// [violate memory safety][read-ownership]. However, storing non-[`Copy`] types in volatile memory
2066+
/// is almost certainly incorrect.
2067+
///
20472068
/// Behavior is undefined if any of the following conditions are violated:
20482069
///
2049-
/// * `src` must be [valid] for reads.
2070+
/// * `src` must be either [valid] for reads, or it must point to memory outside of all Rust
2071+
/// allocations and reading from that memory must:
2072+
/// - not trap, and
2073+
/// - not cause any memory inside a Rust allocation to be modified.
20502074
///
20512075
/// * `src` must be properly aligned.
20522076
///
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.
2077+
/// * Reading from `src` must produce a properly initialized value of type `T`.
20602078
///
20612079
/// Note that even if `T` has size `0`, the pointer must be properly aligned.
20622080
///
20632081
/// [valid]: self#safety
20642082
/// [read-ownership]: read#ownership-of-the-returned-value
20652083
///
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-
///
20722084
/// # Examples
20732085
///
20742086
/// Basic usage:
@@ -2100,52 +2112,62 @@ pub unsafe fn read_volatile<T>(src: *const T) -> T {
21002112
}
21012113
}
21022114

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.
2128-
///
2129-
/// [c11]: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
2115+
/// Performs a volatile write of a memory location with the given value without reading or dropping
2116+
/// the old value.
2117+
///
2118+
/// Volatile operations are intended to act on I/O memory. As such, they are considered externally
2119+
/// observable events (just like syscalls), and are guaranteed to not be elided or reordered by the
2120+
/// compiler across other externally observable events. With this in mind, there are two cases of
2121+
/// usage that need to be distinguished:
2122+
///
2123+
/// - When a volatile operation is used for memory inside an [allocation], it behaves exactly like
2124+
/// [`write()`], except for the additional guarantee that it won't be elided or reordered (see
2125+
/// above). This implies that the operation will actually access memory and not e.g. be lowered to
2126+
/// a register access or stack pop. Other than that, all the usual rules for memory accesses apply
2127+
/// (including provenance). In particular, just like in C, whether an operation is volatile has no
2128+
/// bearing whatsoever on questions involving concurrent access from multiple threads. Volatile
2129+
/// accesses behave exactly like non-atomic accesses in that regard.
2130+
///
2131+
/// - Volatile operations, however, may also be used to access memory that is _outside_ of any Rust
2132+
/// allocation. In this use-case, the pointer does *not* have to be [valid] for writes. This is
2133+
/// typically used for CPU and peripheral registers that must be accessed via an I/O memory
2134+
/// mapping, most commonly at fixed addresses reserved by the hardware. These often have special
2135+
/// semantics associated to their manipulation, and cannot be used as general purpose memory.
2136+
/// Here, any address value is possible, including 0 and [`usize::MAX`], so long as the semantics
2137+
/// of such a write are well-defined by the target hardware. The provenance of the pointer is
2138+
/// irrelevant, and it can be created with [`without_provenance`]. The access must not trap. It
2139+
/// can cause side-effects, but those must not affect Rust-allocated memory in any way. This
2140+
/// access is still not considered [atomic], and as such it cannot be used for inter-thread
2141+
/// synchronization.
2142+
///
2143+
/// Note that volatile memory operations on zero-sized types (e.g., if a zero-sized type is passed
2144+
/// to `write_volatile`) are noops and may be ignored.
2145+
///
2146+
/// `write_volatile` does not drop the contents of `dst`. This is safe, but it could leak
2147+
/// allocations or resources, so care should be taken not to overwrite an object that should be
2148+
/// dropped when operating on Rust memory.
2149+
///
2150+
/// Additionally, it does not drop `src`. Semantically, `src` is moved into the location pointed to
2151+
/// by `dst`.
2152+
///
2153+
/// [allocation]: crate::ptr#allocated-object
2154+
/// [atomic]: crate::sync::atomic#memory-model-for-atomic-accesses
21302155
///
21312156
/// # Safety
21322157
///
21332158
/// Behavior is undefined if any of the following conditions are violated:
21342159
///
2135-
/// * `dst` must be [valid] for writes.
2160+
/// * `dst` must be either [valid] for writes, or it must point to memory outside of all Rust
2161+
/// allocations and writing to that memory must:
2162+
/// - not trap, and
2163+
/// - not cause any memory inside a Rust allocation to be modified.
21362164
///
21372165
/// * `dst` must be properly aligned.
21382166
///
21392167
/// Note that even if `T` has size `0`, the pointer must be properly aligned.
21402168
///
21412169
/// [valid]: self#safety
21422170
///
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-
///
21492171
/// # Examples
21502172
///
21512173
/// Basic usage:

0 commit comments

Comments
 (0)