-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Allow volatile access to non-Rust memory, including address 0 #141260
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
LuigiPiucco
wants to merge
6
commits into
rust-lang:master
Choose a base branch
from
LuigiPiucco:volatile-null
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
2b0a5aa
fix: don't panic on volatile access to null
LuigiPiucco 80a4707
docs: revise restrictions on volatile operations
LuigiPiucco 96e3b63
docs: address review on wording
LuigiPiucco ffd0f1b
fix: don't lint null as UB for volatile
LuigiPiucco e836ce0
docs: deduplicate comment on ZSTs
LuigiPiucco 8368ac9
bless output for changed lint
LuigiPiucco File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,7 +28,8 @@ | |
//! undefined behavior to perform two concurrent accesses to the same location from different | ||
//! threads unless both accesses only read from memory. Notice that this explicitly | ||
//! includes [`read_volatile`] and [`write_volatile`]: Volatile accesses cannot | ||
//! be used for inter-thread synchronization. | ||
//! be used for inter-thread synchronization, regardless of whether it is acting on | ||
//! Rust memory or not. | ||
//! * The result of casting a reference to a pointer is valid for as long as the | ||
//! underlying allocation is live and no reference (just raw pointers) is used to | ||
//! access the same memory. That is, reference and pointer accesses cannot be | ||
|
@@ -114,6 +115,10 @@ | |
//! fully contiguous (i.e., has no "holes"), there is no guarantee that this | ||
//! will not change in the future. | ||
//! | ||
//! Allocations must behave like "normal" memory: in particular, reads must not have | ||
//! side-effects, and writes must become visible to other threads using the usual synchronization | ||
//! primitives. | ||
//! | ||
//! For any allocation with `base` address, `size`, and a set of | ||
//! `addresses`, the following are guaranteed: | ||
//! - For all addresses `a` in `addresses`, `a` is in the range `base .. (base + | ||
|
@@ -2021,54 +2026,66 @@ pub const unsafe fn write_unaligned<T>(dst: *mut T, src: T) { | |
} | ||
} | ||
|
||
/// Performs a volatile read of the value from `src` without moving it. This | ||
/// leaves the memory in `src` unchanged. | ||
/// | ||
/// Volatile operations are intended to act on I/O memory, and are guaranteed | ||
/// to not be elided or reordered by the compiler across other volatile | ||
/// operations. | ||
/// | ||
/// # Notes | ||
/// | ||
/// Rust does not currently have a rigorously and formally defined memory model, | ||
/// so the precise semantics of what "volatile" means here is subject to change | ||
/// over time. That being said, the semantics will almost always end up pretty | ||
/// similar to [C11's definition of volatile][c11]. | ||
/// | ||
/// The compiler shouldn't change the relative order or number of volatile | ||
/// memory operations. However, volatile memory operations on zero-sized types | ||
/// (e.g., if a zero-sized type is passed to `read_volatile`) are noops | ||
/// and may be ignored. | ||
/// Performs a volatile read of the value from `src` without moving it. | ||
/// | ||
/// Rust does not currently have a rigorously and formally defined memory model, so the precise | ||
/// semantics of what "volatile" means here is subject to change over time. That being said, the | ||
/// semantics will almost always end up pretty similar to [C11's definition of volatile][c11]. | ||
Comment on lines
+2031
to
+2033
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we still need this paragraph. The next paragraph says basically as much as C says, after all. @rust-lang/opsem what do you think? |
||
/// | ||
/// Volatile operations are intended to act on I/O memory. As such, they are considered externally | ||
/// observable events (just like syscalls, but less opaque), and are guaranteed to not be elided or | ||
/// reordered by the compiler across other externally observable events. With this in mind, there | ||
/// are two cases of usage that need to be distinguished: | ||
/// | ||
/// - When a volatile operation is used for memory inside an [allocation], it behaves exactly like | ||
/// [`read`], except for the additional guarantee that it won't be elided or reordered (see | ||
/// above). This implies that the operation will actually access memory and not e.g. be lowered to | ||
/// reusing data from a previous read, such as from a register on which previous load of that | ||
/// memory was performed. Other than that, all the usual rules for memory accesses apply | ||
/// (including provenance). In particular, just like in C, whether an operation is volatile has | ||
/// no bearing whatsoever on questions involving concurrent access from multiple threads. Volatile | ||
/// accesses behave exactly like non-atomic accesses in that regard. | ||
/// | ||
/// - Volatile operations, however, may also be used to access memory that is _outside_ of any Rust | ||
/// allocation. In this use-case, the pointer does *not* have to be [valid] for reads. This is | ||
/// typically used for CPU and peripheral registers that must be accessed via an I/O memory | ||
/// mapping, most commonly at fixed addresses reserved by the hardware. These often have special | ||
/// semantics associated to their manipulation, and cannot be used as general purpose memory. | ||
/// Here, any address value is possible, including 0 and [`usize::MAX`], so long as the semantics | ||
/// of such a read are well-defined by the target hardware. The provenance of the pointer is | ||
/// irrelevant, and it can be created with [`without_provenance`]. The access must not trap. It | ||
/// can cause side-effects, but those must not affect Rust-allocated memory in in any way. This | ||
/// access is still not considered [atomic], and as such it cannot be used for inter-thread | ||
/// synchronization. Note that volatile memory operations where T is a zero-sized type are noops | ||
/// and may be ignored. | ||
/// | ||
/// [c11]: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf | ||
/// [allocation]: crate::ptr#allocated-object | ||
/// [atomic]: crate::sync::atomic#memory-model-for-atomic-accesses | ||
/// | ||
/// # Safety | ||
/// | ||
/// Like [`read`], `read_volatile` creates a bitwise copy of `T`, regardless of whether `T` is | ||
/// [`Copy`]. If `T` is not [`Copy`], using both the returned value and the value at `*src` can | ||
/// [violate memory safety][read-ownership]. However, storing non-[`Copy`] types in volatile memory | ||
/// is almost certainly incorrect. | ||
/// | ||
/// Behavior is undefined if any of the following conditions are violated: | ||
/// | ||
/// * `src` must be [valid] for reads. | ||
/// * `src` must be either [valid] for reads, or it must point to memory outside of all Rust | ||
/// allocations and reading from that memory must: | ||
/// - not trap, and | ||
/// - not cause any memory inside a Rust allocation to be modified. | ||
/// | ||
/// * `src` must be properly aligned. | ||
/// | ||
/// * `src` must point to a properly initialized value of type `T`. | ||
/// | ||
/// Like [`read`], `read_volatile` creates a bitwise copy of `T`, regardless of | ||
/// whether `T` is [`Copy`]. If `T` is not [`Copy`], using both the returned | ||
/// value and the value at `*src` can [violate memory safety][read-ownership]. | ||
/// However, storing non-[`Copy`] types in volatile memory is almost certainly | ||
/// incorrect. | ||
/// * Reading from `src` must produce a properly initialized value of type `T`. | ||
/// | ||
/// Note that even if `T` has size `0`, the pointer must be properly aligned. | ||
LuigiPiucco marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// | ||
/// [valid]: self#safety | ||
/// [read-ownership]: read#ownership-of-the-returned-value | ||
/// | ||
/// Just like in C, whether an operation is volatile has no bearing whatsoever | ||
/// on questions involving concurrent access from multiple threads. Volatile | ||
/// accesses behave exactly like non-atomic accesses in that regard. In particular, | ||
/// a race between a `read_volatile` and any write operation to the same location | ||
/// is undefined behavior. | ||
/// | ||
/// # Examples | ||
/// | ||
/// Basic usage: | ||
|
@@ -2090,63 +2107,77 @@ pub unsafe fn read_volatile<T>(src: *const T) -> T { | |
unsafe { | ||
ub_checks::assert_unsafe_precondition!( | ||
check_language_ub, | ||
"ptr::read_volatile requires that the pointer argument is aligned and non-null", | ||
"ptr::read_volatile requires that the pointer argument is aligned", | ||
( | ||
addr: *const () = src as *const (), | ||
align: usize = align_of::<T>(), | ||
is_zst: bool = T::IS_ZST, | ||
) => ub_checks::maybe_is_aligned_and_not_null(addr, align, is_zst) | ||
) => ub_checks::maybe_is_aligned(addr, align) | ||
); | ||
intrinsics::volatile_load(src) | ||
} | ||
} | ||
|
||
/// Performs a volatile write of a memory location with the given value without | ||
/// reading or dropping the old value. | ||
/// | ||
/// Volatile operations are intended to act on I/O memory, and are guaranteed | ||
/// to not be elided or reordered by the compiler across other volatile | ||
/// operations. | ||
/// | ||
/// `write_volatile` does not drop the contents of `dst`. This is safe, but it | ||
/// could leak allocations or resources, so care should be taken not to overwrite | ||
/// an object that should be dropped. | ||
/// | ||
/// Additionally, it does not drop `src`. Semantically, `src` is moved into the | ||
/// location pointed to by `dst`. | ||
/// | ||
/// # Notes | ||
/// | ||
/// Rust does not currently have a rigorously and formally defined memory model, | ||
/// so the precise semantics of what "volatile" means here is subject to change | ||
/// over time. That being said, the semantics will almost always end up pretty | ||
/// similar to [C11's definition of volatile][c11]. | ||
/// | ||
/// The compiler shouldn't change the relative order or number of volatile | ||
/// memory operations. However, volatile memory operations on zero-sized types | ||
/// (e.g., if a zero-sized type is passed to `write_volatile`) are noops | ||
/// and may be ignored. | ||
/// Performs a volatile write of a memory location with the given value without reading or dropping | ||
/// the old value. | ||
/// | ||
/// Rust does not currently have a rigorously and formally defined memory model, so the precise | ||
/// semantics of what "volatile" means here is subject to change over time. That being said, the | ||
/// semantics will almost always end up pretty similar to [C11's definition of volatile][c11]. | ||
/// | ||
/// Volatile operations are intended to act on I/O memory. As such, they are considered externally | ||
/// observable events (just like syscalls), and are guaranteed to not be elided or reordered by the | ||
/// compiler across other externally observable events. With this in mind, there are two cases of | ||
/// usage that need to be distinguished: | ||
/// | ||
/// - When a volatile operation is used for memory inside an [allocation], it behaves exactly like | ||
/// [`write()`], except for the additional guarantee that it won't be elided or reordered (see | ||
/// above). This implies that the operation will actually access memory and not e.g. be lowered to | ||
/// a register access or stack pop. Other than that, all the usual rules for memory accesses apply | ||
/// (including provenance). In particular, just like in C, whether an operation is volatile has no | ||
/// bearing whatsoever on questions involving concurrent access from multiple threads. Volatile | ||
/// accesses behave exactly like non-atomic accesses in that regard. | ||
/// | ||
/// - Volatile operations, however, may also be used to access memory that is _outside_ of any Rust | ||
/// allocation. In this use-case, the pointer does *not* have to be [valid] for writes. This is | ||
/// typically used for CPU and peripheral registers that must be accessed via an I/O memory | ||
/// mapping, most commonly at fixed addresses reserved by the hardware. These often have special | ||
/// semantics associated to their manipulation, and cannot be used as general purpose memory. | ||
/// Here, any address value is possible, including 0 and [`usize::MAX`], so long as the semantics | ||
/// of such a write are well-defined by the target hardware. The provenance of the pointer is | ||
/// irrelevant, and it can be created with [`without_provenance`]. The access must not trap. It | ||
/// can cause side-effects, but those must not affect Rust-allocated memory in any way. This | ||
/// access is still not considered [atomic], and as such it cannot be used for inter-thread | ||
/// synchronization. | ||
/// | ||
/// Note that volatile memory operations on zero-sized types (e.g., if a zero-sized type is passed | ||
/// to `write_volatile`) are noops and may be ignored. | ||
/// | ||
/// `write_volatile` does not drop the contents of `dst`. This is safe, but it could leak | ||
/// allocations or resources, so care should be taken not to overwrite an object that should be | ||
/// dropped when operating on Rust memory. | ||
/// | ||
/// Additionally, it does not drop `src`. Semantically, `src` is moved into the location pointed to | ||
/// by `dst`. | ||
/// | ||
/// [c11]: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf | ||
/// [allocation]: crate::ptr#allocated-object | ||
/// [atomic]: crate::sync::atomic#memory-model-for-atomic-accesses | ||
/// | ||
/// # Safety | ||
/// | ||
/// Behavior is undefined if any of the following conditions are violated: | ||
/// | ||
/// * `dst` must be [valid] for writes. | ||
/// * `dst` must be either [valid] for writes, or it must point to memory outside of all Rust | ||
/// allocations and writing to that memory must: | ||
/// - not trap, and | ||
/// - not cause any memory inside a Rust allocation to be modified. | ||
/// | ||
/// * `dst` must be properly aligned. | ||
/// | ||
/// Note that even if `T` has size `0`, the pointer must be properly aligned. | ||
/// | ||
/// [valid]: self#safety | ||
/// | ||
/// Just like in C, whether an operation is volatile has no bearing whatsoever | ||
/// on questions involving concurrent access from multiple threads. Volatile | ||
/// accesses behave exactly like non-atomic accesses in that regard. In particular, | ||
/// a race between a `write_volatile` and any other operation (reading or writing) | ||
/// on the same location is undefined behavior. | ||
/// | ||
/// # Examples | ||
/// | ||
/// Basic usage: | ||
|
@@ -2170,12 +2201,11 @@ pub unsafe fn write_volatile<T>(dst: *mut T, src: T) { | |
unsafe { | ||
ub_checks::assert_unsafe_precondition!( | ||
check_language_ub, | ||
"ptr::write_volatile requires that the pointer argument is aligned and non-null", | ||
"ptr::write_volatile requires that the pointer argument is aligned", | ||
( | ||
addr: *mut () = dst as *mut (), | ||
align: usize = align_of::<T>(), | ||
is_zst: bool = T::IS_ZST, | ||
) => ub_checks::maybe_is_aligned_and_not_null(addr, align, is_zst) | ||
) => ub_checks::maybe_is_aligned(addr, align) | ||
); | ||
intrinsics::volatile_store(dst, src); | ||
} | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.