Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 6d7d366

Browse files
Rollup merge of #141260 - LuigiPiucco:volatile-null, r=RalfJung
Allow volatile access to non-Rust memory, including address 0 This PR relaxes the `ub_check` in the `read_volatile`/`write_volatile` pointer operations to allow passing null. This is needed to support processors which hard-code peripheral registers on address 0, like the AVR chip ATtiny1626. LLVM understands this as valid and handles it correctly, as tested in my [PR to add a note about it](llvm/llvm-project@6387c82#diff-81bbb96298c32fa901beb82ab3b97add27a410c01d577c1f8c01000ed2055826) (rustc generates the same LLVM IR as expected there when this PR is applied, and consequently the same AVR assembly). Follow-up and implementation of the discussions in: - https://internals.rust-lang.org/t/pre-rfc-conditionally-supported-volatile-access-to-address-0/12881/7 - Rahix/avr-device#185; - [#t-lang > Adding the possibility of volatile access to address 0](https://rust-lang.zulipchat.com/#narrow/channel/213817-t-lang/topic/Adding.20the.20possibility.20of.20volatile.20access.20to.20address.200/with/513303502) - https://discourse.llvm.org/t/rfc-volatile-access-to-non-dereferenceable-memory-may-be-well-defined/86303 r? ````@RalfJung```` Also fixes rust-lang/unsafe-code-guidelines#29 (about as good as it'll get, null will likely never be a "normal" address in Rust)
2 parents 6707bf0 + 8a8717e commit 6d7d366

File tree

7 files changed

+127
-138
lines changed

7 files changed

+127
-138
lines changed

‎compiler/rustc_lint/src/ptr_nulls.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,12 +160,10 @@ impl<'tcx> LateLintPass<'tcx> for PtrNullChecks {
160160
let (arg_indices, are_zsts_allowed): (&[_], _) = match diag_name {
161161
sym::ptr_read
162162
| sym::ptr_read_unaligned
163-
| sym::ptr_read_volatile
164163
| sym::ptr_replace
165164
| sym::ptr_write
166165
| sym::ptr_write_bytes
167-
| sym::ptr_write_unaligned
168-
| sym::ptr_write_volatile => (&[0], true),
166+
| sym::ptr_write_unaligned => (&[0], true),
169167
sym::slice_from_raw_parts | sym::slice_from_raw_parts_mut => (&[0], false),
170168
sym::ptr_copy
171169
| sym::ptr_copy_nonoverlapping

‎library/core/src/ptr/mod.rs

Lines changed: 93 additions & 75 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 they are 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. Other than that, all the usual rules for memory accesses
2040+
/// apply (including provenance). In particular, just like in C, whether an operation is volatile
2041+
/// has no bearing whatsoever on questions involving concurrent accesses from multiple threads.
2042+
/// Volatile accesses behave exactly like non-atomic accesses in that regard.
2043+
///
2044+
/// - Volatile operations, however, may also be used to access memory that is _outside_ of any Rust
2045+
/// allocation. In this use-case, the pointer does *not* have to be [valid] for reads. This is
2046+
/// typically used for CPU and peripheral registers that must be accessed via an I/O memory
2047+
/// mapping, most commonly at fixed addresses reserved by the hardware. These often have special
2048+
/// semantics associated to their manipulation, and cannot be used as general purpose memory.
2049+
/// Here, any address value is possible, including 0 and [`usize::MAX`], so long as the semantics
2050+
/// of such a read are well-defined by the target hardware. The provenance of the pointer is
2051+
/// irrelevant, and it can be created with [`without_provenance`]. The access must not trap. It
2052+
/// can cause side-effects, but those must not affect Rust-allocated memory in any way. This
2053+
/// access is still not considered [atomic], and as such it cannot be used for inter-thread
2054+
/// synchronization.
2055+
///
2056+
/// Note that volatile memory operations where T is a zero-sized type are noops 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:
@@ -2090,63 +2102,70 @@ pub unsafe fn read_volatile<T>(src: *const T) -> T {
20902102
unsafe {
20912103
ub_checks::assert_unsafe_precondition!(
20922104
check_language_ub,
2093-
"ptr::read_volatile requires that the pointer argument is aligned and non-null",
2105+
"ptr::read_volatile requires that the pointer argument is aligned",
20942106
(
20952107
addr: *const () = src as *const (),
20962108
align: usize = align_of::<T>(),
2097-
is_zst: bool = T::IS_ZST,
2098-
) => ub_checks::maybe_is_aligned_and_not_null(addr, align, is_zst)
2109+
) => ub_checks::maybe_is_aligned(addr, align)
20992110
);
21002111
intrinsics::volatile_load(src)
21012112
}
21022113
}
21032114

2104-
/// Performs a volatile write of a memory location with the given value without
2105-
/// reading or dropping the old value.
2106-
///
2107-
/// Volatile operations are intended to act on I/O memory, and are guaranteed
2108-
/// to not be elided or reordered by the compiler across other volatile
2109-
/// operations.
2110-
///
2111-
/// `write_volatile` does not drop the contents of `dst`. This is safe, but it
2112-
/// could leak allocations or resources, so care should be taken not to overwrite
2113-
/// an object that should be dropped.
2114-
///
2115-
/// Additionally, it does not drop `src`. Semantically, `src` is moved into the
2116-
/// location pointed to by `dst`.
2117-
///
2118-
/// # Notes
2119-
///
2120-
/// Rust does not currently have a rigorously and formally defined memory model,
2121-
/// so the precise semantics of what "volatile" means here is subject to change
2122-
/// over time. That being said, the semantics will almost always end up pretty
2123-
/// similar to [C11's definition of volatile][c11].
2124-
///
2125-
/// The compiler shouldn't change the relative order or number of volatile
2126-
/// memory operations. However, volatile memory operations on zero-sized types
2127-
/// (e.g., if a zero-sized type is passed to `write_volatile`) are noops
2128-
/// and may be ignored.
2129-
///
2130-
/// [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`][write()], except for the additional guarantee that it won't be elided or reordered
2125+
/// (see above). This implies that the operation will actually access memory and not e.g. be
2126+
/// lowered to a register access. 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. Additionally, it does not drop `src`. Semantically, `src`
2149+
/// is moved into the location pointed to by `dst`.
2150+
///
2151+
/// [allocation]: crate::ptr#allocated-object
2152+
/// [atomic]: crate::sync::atomic#memory-model-for-atomic-accesses
21312153
///
21322154
/// # Safety
21332155
///
21342156
/// Behavior is undefined if any of the following conditions are violated:
21352157
///
2136-
/// * `dst` must be [valid] for writes.
2158+
/// * `dst` must be either [valid] for writes, or it must point to memory outside of all Rust
2159+
/// allocations and writing to that memory must:
2160+
/// - not trap, and
2161+
/// - not cause any memory inside a Rust allocation to be modified.
21372162
///
21382163
/// * `dst` must be properly aligned.
21392164
///
21402165
/// Note that even if `T` has size `0`, the pointer must be properly aligned.
21412166
///
21422167
/// [valid]: self#safety
21432168
///
2144-
/// Just like in C, whether an operation is volatile has no bearing whatsoever
2145-
/// on questions involving concurrent access from multiple threads. Volatile
2146-
/// accesses behave exactly like non-atomic accesses in that regard. In particular,
2147-
/// a race between a `write_volatile` and any other operation (reading or writing)
2148-
/// on the same location is undefined behavior.
2149-
///
21502169
/// # Examples
21512170
///
21522171
/// Basic usage:
@@ -2170,12 +2189,11 @@ pub unsafe fn write_volatile<T>(dst: *mut T, src: T) {
21702189
unsafe {
21712190
ub_checks::assert_unsafe_precondition!(
21722191
check_language_ub,
2173-
"ptr::write_volatile requires that the pointer argument is aligned and non-null",
2192+
"ptr::write_volatile requires that the pointer argument is aligned",
21742193
(
21752194
addr: *mut () = dst as *mut (),
21762195
align: usize = align_of::<T>(),
2177-
is_zst: bool = T::IS_ZST,
2178-
) => ub_checks::maybe_is_aligned_and_not_null(addr, align, is_zst)
2196+
) => ub_checks::maybe_is_aligned(addr, align)
21792197
);
21802198
intrinsics::volatile_store(dst, src);
21812199
}

‎library/core/src/ub_checks.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,25 @@ pub(crate) const fn maybe_is_aligned_and_not_null(
120120
align: usize,
121121
is_zst: bool,
122122
) -> bool {
123+
// This is just for safety checks so we can const_eval_select.
124+
maybe_is_aligned(ptr, align) && (is_zst || !ptr.is_null())
125+
}
126+
127+
/// Checks whether `ptr` is properly aligned with respect to the given alignment.
128+
///
129+
/// In `const` this is approximate and can fail spuriously. It is primarily intended
130+
/// for `assert_unsafe_precondition!` with `check_language_ub`, in which case the
131+
/// check is anyway not executed in `const`.
132+
#[inline]
133+
#[rustc_allow_const_fn_unstable(const_eval_select)]
134+
pub(crate) const fn maybe_is_aligned(ptr: *const (), align: usize) -> bool {
123135
// This is just for safety checks so we can const_eval_select.
124136
const_eval_select!(
125-
@capture { ptr: *const (), align: usize, is_zst:bool } -> bool:
137+
@capture { ptr: *const (), align: usize } -> bool:
126138
if const {
127-
is_zst || !ptr.is_null()
139+
true
128140
} else {
129-
ptr.is_aligned_to(align) && (is_zst || !ptr.is_null())
141+
ptr.is_aligned_to(align)
130142
}
131143
)
132144
}

‎tests/ui/lint/invalid_null_args.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,9 @@ unsafe fn null_ptr() {
5858
let _a: A = ptr::read_unaligned(ptr::null_mut());
5959
//~^ ERROR calling this function with a null pointer is undefined behavior
6060

61+
// These two should *not* fire the lint.
6162
let _a: A = ptr::read_volatile(ptr::null());
62-
//~^ ERROR calling this function with a null pointer is undefined behavior
6363
let _a: A = ptr::read_volatile(ptr::null_mut());
64-
//~^ ERROR calling this function with a null pointer is undefined behavior
6564

6665
let _a: A = ptr::replace(ptr::null_mut(), v);
6766
//~^ ERROR calling this function with a null pointer is undefined behavior
@@ -82,8 +81,8 @@ unsafe fn null_ptr() {
8281
ptr::write_unaligned(ptr::null_mut(), v);
8382
//~^ ERROR calling this function with a null pointer is undefined behavior
8483

84+
// This one should *not* fire the lint.
8585
ptr::write_volatile(ptr::null_mut(), v);
86-
//~^ ERROR calling this function with a null pointer is undefined behavior
8786

8887
ptr::write_bytes::<usize>(ptr::null_mut(), 42, 0);
8988
//~^ ERROR calling this function with a null pointer is undefined behavior

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /