Skip to content

Commit efa8fee

Browse files
committed
Move read_volatile to volatile module and implement thumbv6 in asm.
The goal is to ensure LLVM doesn't do anything silly (:3) with the loads.
1 parent ae9173d commit efa8fee

File tree

2 files changed

+151
-80
lines changed

2 files changed

+151
-80
lines changed

library/core/src/ptr/mod.rs

Lines changed: 3 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -425,9 +425,12 @@ pub use non_null::NonNull;
425425
mod unique;
426426
#[unstable(feature = "ptr_internals", issue = "none")]
427427
pub use unique::Unique;
428+
#[stable(feature = "volatile", since = "1.9.0")]
429+
pub use volatile::read_volatile;
428430

429431
mod const_ptr;
430432
mod mut_ptr;
433+
mod volatile;
431434

432435
/// Executes the destructor (if any) of the pointed-to value.
433436
///
@@ -1669,86 +1672,6 @@ pub const unsafe fn write_unaligned<T>(dst: *mut T, src: T) {
16691672
}
16701673
}
16711674

1672-
/// Performs a volatile read of the value from `src` without moving it. This
1673-
/// leaves the memory in `src` unchanged.
1674-
///
1675-
/// Volatile operations are intended to act on I/O memory, and are guaranteed
1676-
/// to not be elided or reordered by the compiler across other volatile
1677-
/// operations.
1678-
///
1679-
/// # Notes
1680-
///
1681-
/// Rust does not currently have a rigorously and formally defined memory model,
1682-
/// so the precise semantics of what "volatile" means here is subject to change
1683-
/// over time. That being said, the semantics will almost always end up pretty
1684-
/// similar to [C11's definition of volatile][c11].
1685-
///
1686-
/// The compiler shouldn't change the relative order or number of volatile
1687-
/// memory operations. However, volatile memory operations on zero-sized types
1688-
/// (e.g., if a zero-sized type is passed to `read_volatile`) are noops
1689-
/// and may be ignored.
1690-
///
1691-
/// [c11]: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
1692-
///
1693-
/// # Safety
1694-
///
1695-
/// Behavior is undefined if any of the following conditions are violated:
1696-
///
1697-
/// * `src` must be [valid] for reads.
1698-
///
1699-
/// * `src` must be properly aligned.
1700-
///
1701-
/// * `src` must point to a properly initialized value of type `T`.
1702-
///
1703-
/// Like [`read`], `read_volatile` creates a bitwise copy of `T`, regardless of
1704-
/// whether `T` is [`Copy`]. If `T` is not [`Copy`], using both the returned
1705-
/// value and the value at `*src` can [violate memory safety][read-ownership].
1706-
/// However, storing non-[`Copy`] types in volatile memory is almost certainly
1707-
/// incorrect.
1708-
///
1709-
/// Note that even if `T` has size `0`, the pointer must be properly aligned.
1710-
///
1711-
/// [valid]: self#safety
1712-
/// [read-ownership]: read#ownership-of-the-returned-value
1713-
///
1714-
/// Just like in C, whether an operation is volatile has no bearing whatsoever
1715-
/// on questions involving concurrent access from multiple threads. Volatile
1716-
/// accesses behave exactly like non-atomic accesses in that regard. In particular,
1717-
/// a race between a `read_volatile` and any write operation to the same location
1718-
/// is undefined behavior.
1719-
///
1720-
/// # Examples
1721-
///
1722-
/// Basic usage:
1723-
///
1724-
/// ```
1725-
/// let x = 12;
1726-
/// let y = &x as *const i32;
1727-
///
1728-
/// unsafe {
1729-
/// assert_eq!(std::ptr::read_volatile(y), 12);
1730-
/// }
1731-
/// ```
1732-
#[inline]
1733-
#[stable(feature = "volatile", since = "1.9.0")]
1734-
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
1735-
#[rustc_diagnostic_item = "ptr_read_volatile"]
1736-
pub unsafe fn read_volatile<T>(src: *const T) -> T {
1737-
// SAFETY: the caller must uphold the safety contract for `volatile_load`.
1738-
unsafe {
1739-
ub_checks::assert_unsafe_precondition!(
1740-
check_language_ub,
1741-
"ptr::read_volatile requires that the pointer argument is aligned and non-null",
1742-
(
1743-
addr: *const () = src as *const (),
1744-
align: usize = align_of::<T>(),
1745-
is_zst: bool = T::IS_ZST,
1746-
) => ub_checks::maybe_is_aligned_and_not_null(addr, align, is_zst)
1747-
);
1748-
intrinsics::volatile_load(src)
1749-
}
1750-
}
1751-
17521675
/// Performs a volatile write of a memory location with the given value without
17531676
/// reading or dropping the old value.
17541677
///

library/core/src/ptr/volatile.rs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#![allow(unused_doc_comments, missing_docs)]
2+
use crate::{mem::SizedTypeProperties, cfg_match, intrinsics, ub_checks};
3+
4+
/// Performs a volatile read of the value from `src` without moving it. This
5+
/// leaves the memory in `src` unchanged.
6+
///
7+
/// Volatile operations are intended to act on I/O memory, and are guaranteed
8+
/// to not be elided or reordered by the compiler across other volatile
9+
/// operations.
10+
///
11+
/// # Notes
12+
///
13+
/// Rust does not currently have a rigorously and formally defined memory model,
14+
/// so the precise semantics of what "volatile" means here is subject to change
15+
/// over time. That being said, the semantics will almost always end up pretty
16+
/// similar to [C11's definition of volatile][c11].
17+
///
18+
/// The compiler shouldn't change the relative order or number of volatile
19+
/// memory operations. However, volatile memory operations on zero-sized types
20+
/// (e.g., if a zero-sized type is passed to `read_volatile`) are noops
21+
/// and may be ignored.
22+
///
23+
/// [c11]: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
24+
///
25+
/// # Safety
26+
///
27+
/// Behavior is undefined if any of the following conditions are violated:
28+
///
29+
/// * `src` must be [valid] for reads.
30+
///
31+
/// * `src` must be properly aligned.
32+
///
33+
/// * `src` must point to a properly initialized value of type `T`.
34+
///
35+
/// Like [`read`], `read_volatile` creates a bitwise copy of `T`, regardless of
36+
/// whether `T` is [`Copy`]. If `T` is not [`Copy`], using both the returned
37+
/// value and the value at `*src` can [violate memory safety][read-ownership].
38+
/// However, storing non-[`Copy`] types in volatile memory is almost certainly
39+
/// incorrect.
40+
///
41+
/// Note that even if `T` has size `0`, the pointer must be properly aligned.
42+
///
43+
/// [valid]: self#safety
44+
/// [read-ownership]: read#ownership-of-the-returned-value
45+
///
46+
/// Just like in C, whether an operation is volatile has no bearing whatsoever
47+
/// on questions involving concurrent access from multiple threads. Volatile
48+
/// accesses behave exactly like non-atomic accesses in that regard. In particular,
49+
/// a race between a `read_volatile` and any write operation to the same location
50+
/// is undefined behavior.
51+
///
52+
/// # Examples
53+
///
54+
/// Basic usage:
55+
///
56+
/// ```
57+
/// let x = 12;
58+
/// let y = &x as *const i32;
59+
///
60+
/// unsafe {
61+
/// assert_eq!(std::ptr::read_volatile(y), 12);
62+
/// }
63+
/// ```
64+
cfg_match! {
65+
all(target_arch = "arm", target_feature = "thumb-mode", target_pointer_width = "32") => {
66+
#[inline]
67+
#[stable(feature = "volatile", since = "1.9.0")]
68+
#[cfg_attr(miri, track_caller)] // Even without panics, this helps for Miri backtraces
69+
#[rustc_diagnostic_item = "ptr_read_volatile"]
70+
pub unsafe fn read_volatile<T>(src: *const T) -> T {
71+
use crate::mem::{size_of, MaybeUninit};
72+
use crate::arch::asm;
73+
use crate::intrinsics::transmute_unchecked;
74+
75+
// SAFETY: the caller must uphold the safety contract for `volatile_load`.
76+
unsafe {
77+
ub_checks::assert_unsafe_precondition!(
78+
check_language_ub,
79+
"ptr::read_volatile requires that the pointer argument is aligned and non-null",
80+
(
81+
addr: *const () = src as *const (),
82+
align: usize = align_of::<T>(),
83+
is_zst: bool = T::IS_ZST,
84+
) => ub_checks::maybe_is_aligned_and_not_null(addr, align, is_zst)
85+
);
86+
87+
let out_val: T = match size_of::<T>() {
88+
// For the relevant sizes, ensure that just a single load is emitted
89+
// for the read with nothing merged or split.
90+
1 => {
91+
let byte: MaybeUninit::<u8>;
92+
asm!(
93+
"LDRB {out}, [{in}]",
94+
in = in(reg) src,
95+
out = out(reg) byte
96+
);
97+
98+
transmute_unchecked(byte)
99+
}
100+
2 => {
101+
let halfword: MaybeUninit::<u16>;
102+
asm!(
103+
"LDRH {out}, [{in}]",
104+
in = in(reg) src,
105+
out = out(reg) halfword
106+
);
107+
108+
transmute_unchecked(halfword)
109+
},
110+
4 => {
111+
let word: MaybeUninit::<u32>;
112+
asm!(
113+
"LDR {out}, [{in}]",
114+
in = in(reg) src,
115+
out = out(reg) word
116+
);
117+
118+
transmute_unchecked(word)
119+
},
120+
// Anything else is mostly meaningless.
121+
_ => intrinsics::volatile_load(src),
122+
};
123+
out_val
124+
}
125+
}}
126+
// Fallback
127+
_ => {
128+
#[inline]
129+
#[stable(feature = "volatile", since = "1.9.0")]
130+
#[cfg_attr(miri, track_caller)] // Even without panics, this helps for Miri backtraces
131+
#[rustc_diagnostic_item = "ptr_read_volatile"]
132+
pub unsafe fn read_volatile<T>(src: *const T) -> T {
133+
// SAFETY: the caller must uphold the safety contract for `volatile_load`.
134+
unsafe {
135+
ub_checks::assert_unsafe_precondition!(
136+
check_language_ub,
137+
"ptr::read_volatile requires that the pointer argument is aligned and non-null",
138+
(
139+
addr: *const () = src as *const (),
140+
align: usize = align_of::<T>(),
141+
is_zst: bool = T::IS_ZST,
142+
) => ub_checks::maybe_is_aligned_and_not_null(addr, align, is_zst)
143+
);
144+
intrinsics::volatile_load(src)
145+
}
146+
}
147+
}
148+
}

0 commit comments

Comments
 (0)