Skip to content

Commit 36574d9

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 must 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 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.
1 parent 0559f50 commit 36574d9

File tree

1 file changed

+83
-41
lines changed

1 file changed

+83
-41
lines changed

library/core/src/ptr/mod.rs

Lines changed: 83 additions & 41 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 object is live and no reference (just raw pointers) is used to
3435
//! access the same memory. That is, reference and pointer accesses cannot be
@@ -1744,54 +1745,73 @@ pub const unsafe fn write_unaligned<T>(dst: *mut T, src: T) {
17441745
}
17451746
}
17461747

1747-
/// Performs a volatile read of the value from `src` without moving it. This
1748-
/// leaves the memory in `src` unchanged.
1749-
///
1750-
/// Volatile operations are intended to act on I/O memory, and are guaranteed
1751-
/// to not be elided or reordered by the compiler across other volatile
1752-
/// operations.
1753-
///
1754-
/// # Notes
1748+
/// Performs a volatile read of the value from `src` without moving it.
17551749
///
17561750
/// Rust does not currently have a rigorously and formally defined memory model,
17571751
/// so the precise semantics of what "volatile" means here is subject to change
17581752
/// over time. That being said, the semantics will almost always end up pretty
17591753
/// similar to [C11's definition of volatile][c11].
17601754
///
1755+
/// Volatile operations are intended to act on I/O memory, and are guaranteed
1756+
/// to not be elided or reordered by the compiler across other volatile
1757+
/// operations. With this in mind, there are two cases of usage that need to
1758+
/// be distinguished:
1759+
///
1760+
/// - When a volatile operation is used for memory inside an [allocation], all
1761+
/// the typical restrictions of Rust-allocated memory apply, meaning things
1762+
/// like data races and mutable aliasing remain as undefined behavior. In
1763+
/// addition, the volatile rule that the operation won't be elided or
1764+
/// reordered applies, such that the operation will access memory and not e.g.
1765+
/// be lowered to a register access or stack pop. The memory in `src` should
1766+
/// remain unchanged. Just like in C, whether an operation is volatile has no
1767+
/// bearing whatsoever on questions involving concurrent access from multiple
1768+
/// threads. Volatile accesses behave exactly like non-atomic accesses in that
1769+
/// regard. All this is because this kind of target-memory may be used from
1770+
/// safe code at any time, and its validity assumptions must not be violated.
1771+
///
1772+
/// - Volatile operations, however, provide a conditionally valid way to access
1773+
/// memory that is _outside_ of any allocation. The main use-case is CPU and
1774+
/// peripheral registers that must be accessed via an I/O memory mapping, most
1775+
/// commonly at fixed addresses reserved by the hardware. These often have
1776+
/// special semantics associated to their manipulation, and cannot be used as
1777+
/// general purpose memory. Here, any address value is possible, from 0 to
1778+
/// [`usize::MAX`], so long as its semantics are well-defined by the target
1779+
/// hardware. The access is restricted to not trap/interrupt. It can (and
1780+
/// usually will) cause side-effects, but note they shouldn't affect
1781+
/// Rust-allocated memory in any way.
1782+
///
17611783
/// The compiler shouldn't change the relative order or number of volatile
17621784
/// memory operations. However, volatile memory operations on zero-sized types
17631785
/// (e.g., if a zero-sized type is passed to `read_volatile`) are noops
17641786
/// and may be ignored.
17651787
///
17661788
/// [c11]: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
1789+
/// [allocation]: crate::ptr#allocated-object
17671790
///
17681791
/// # Safety
17691792
///
17701793
/// Behavior is undefined if any of the following conditions are violated:
17711794
///
1772-
/// * `src` must be [valid] for reads.
1795+
/// * `src` must be readable without trapping.
17731796
///
17741797
/// * `src` must be properly aligned.
17751798
///
1776-
/// * `src` must point to a properly initialized value of type `T`.
1799+
/// * No Rust memory may be modified.
1800+
///
1801+
/// * If operating on an allocation, `src` must point to a properly initialized
1802+
/// value of type `T`, and the safe usage assumptions of the data must be
1803+
/// satisfied.
17771804
///
17781805
/// Like [`read`], `read_volatile` creates a bitwise copy of `T`, regardless of
17791806
/// whether `T` is [`Copy`]. If `T` is not [`Copy`], using both the returned
17801807
/// value and the value at `*src` can [violate memory safety][read-ownership].
1781-
/// However, storing non-[`Copy`] types in volatile memory is almost certainly
1782-
/// incorrect.
1808+
/// However, modeling volatile memory with non-[`Copy`] types is almost
1809+
/// certainly incorrect.
17831810
///
17841811
/// Note that even if `T` has size `0`, the pointer must be properly aligned.
17851812
///
1786-
/// [valid]: self#safety
17871813
/// [read-ownership]: read#ownership-of-the-returned-value
17881814
///
1789-
/// Just like in C, whether an operation is volatile has no bearing whatsoever
1790-
/// on questions involving concurrent access from multiple threads. Volatile
1791-
/// accesses behave exactly like non-atomic accesses in that regard. In particular,
1792-
/// a race between a `read_volatile` and any write operation to the same location
1793-
/// is undefined behavior.
1794-
///
17951815
/// # Examples
17961816
///
17971817
/// Basic usage:
@@ -1826,48 +1846,70 @@ pub unsafe fn read_volatile<T>(src: *const T) -> T {
18261846
/// Performs a volatile write of a memory location with the given value without
18271847
/// reading or dropping the old value.
18281848
///
1829-
/// Volatile operations are intended to act on I/O memory, and are guaranteed
1830-
/// to not be elided or reordered by the compiler across other volatile
1831-
/// operations.
1832-
///
1833-
/// `write_volatile` does not drop the contents of `dst`. This is safe, but it
1834-
/// could leak allocations or resources, so care should be taken not to overwrite
1835-
/// an object that should be dropped.
1836-
///
1837-
/// Additionally, it does not drop `src`. Semantically, `src` is moved into the
1838-
/// location pointed to by `dst`.
1839-
///
1840-
/// # Notes
1841-
///
18421849
/// Rust does not currently have a rigorously and formally defined memory model,
18431850
/// so the precise semantics of what "volatile" means here is subject to change
18441851
/// over time. That being said, the semantics will almost always end up pretty
18451852
/// similar to [C11's definition of volatile][c11].
18461853
///
1854+
/// Volatile operations are intended to act on I/O memory, and are guaranteed
1855+
/// to not be elided or reordered by the compiler across other volatile
1856+
/// operations. With this in mind, there are two cases of usage that need to
1857+
/// be distinguished:
1858+
///
1859+
/// - When a volatile operation is used for memory inside an [allocation], all
1860+
/// the typical restrictions of Rust-allocated memory apply, meaning things
1861+
/// like data races and mutable aliasing remain as undefined behavior. In
1862+
/// addition, the volatile rule that the operation won't be elided or
1863+
/// reordered applies, such that the operation will access memory and not e.g.
1864+
/// be lowered to a register access or stack pop. The memory in `src` should
1865+
/// remain unchanged. Just like in C, whether an operation is volatile has no
1866+
/// bearing whatsoever on questions involving concurrent access from multiple
1867+
/// threads. Volatile accesses behave exactly like non-atomic accesses in that
1868+
/// regard. All this is because this kind of target-memory may be used from
1869+
/// safe code at any time, and its validity assumptions must not be violated.
1870+
///
1871+
/// - Volatile operations, however, provide a conditionally valid way to access
1872+
/// memory that is _outside_ of any allocation. The main use-case is CPU and
1873+
/// peripheral registers that must be accessed via an I/O memory mapping, most
1874+
/// commonly at fixed addresses reserved by the hardware. These often have
1875+
/// special semantics associated to their manipulation, and cannot be used as
1876+
/// general purpose memory. Here, any address value is possible, from 0 to
1877+
/// [`usize::MAX`], so long as its semantics are well-defined by the target
1878+
/// hardware. The access is restricted to not trap/interrupt. It can (and
1879+
/// usually will) cause side-effects, but note they shouldn't affect
1880+
/// Rust-allocated memory in any way.
1881+
///
18471882
/// The compiler shouldn't change the relative order or number of volatile
18481883
/// memory operations. However, volatile memory operations on zero-sized types
18491884
/// (e.g., if a zero-sized type is passed to `write_volatile`) are noops
18501885
/// and may be ignored.
18511886
///
1887+
/// `write_volatile` does not drop the contents of `dst`. This is safe, but it
1888+
/// could leak allocations or resources, so care should be taken not to
1889+
/// overwrite an object that should be dropped when operating on Rust memory.
1890+
///
1891+
/// Additionally, it does not drop `src`. Semantically, `src` is moved into the
1892+
/// location pointed to by `dst`.
1893+
///
18521894
/// [c11]: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
1895+
/// [allocation]: crate::ptr#allocated-object
18531896
///
18541897
/// # Safety
18551898
///
18561899
/// Behavior is undefined if any of the following conditions are violated:
18571900
///
1858-
/// * `dst` must be [valid] for writes.
1901+
/// * `dst` must be writable without trapping.
18591902
///
18601903
/// * `dst` must be properly aligned.
18611904
///
1862-
/// Note that even if `T` has size `0`, the pointer must be properly aligned.
1905+
/// * `src` must be a properly initialized value of type `T`.
18631906
///
1864-
/// [valid]: self#safety
1907+
/// * If operating on an allocation, no Rust memory outside of `dst` may be
1908+
/// modified.
18651909
///
1866-
/// Just like in C, whether an operation is volatile has no bearing whatsoever
1867-
/// on questions involving concurrent access from multiple threads. Volatile
1868-
/// accesses behave exactly like non-atomic accesses in that regard. In particular,
1869-
/// a race between a `write_volatile` and any other operation (reading or writing)
1870-
/// on the same location is undefined behavior.
1910+
/// * If not operating on an allocation, no Rust memory may be affected.
1911+
///
1912+
/// Note that even if `T` has size `0`, the pointer must be properly aligned.
18711913
///
18721914
/// # Examples
18731915
///

0 commit comments

Comments
 (0)