From a7ae5797c6d9ffd5bd93c9d0c6599c5d16385612 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 18 May 2021 14:18:21 +0200 Subject: [PATCH 01/68] New design based on `UnsafeCell` --- src/access.rs | 7 + src/lib.rs | 567 +++++++++++++++++++++++++------------------------- 2 files changed, 290 insertions(+), 284 deletions(-) diff --git a/src/access.rs b/src/access.rs index 438d233..fff67e7 100644 --- a/src/access.rs +++ b/src/access.rs @@ -1,3 +1,6 @@ + +pub trait Access {} + /// Helper trait that is implemented by [`ReadWrite`] and [`ReadOnly`]. pub trait Readable {} @@ -7,6 +10,7 @@ pub trait Writable {} /// Zero-sized marker type for allowing both read and write access. #[derive(Debug, Copy, Clone)] pub struct ReadWrite; +impl Access for ReadWrite {} impl Readable for ReadWrite {} impl Writable for ReadWrite {} @@ -14,9 +18,12 @@ impl Writable for ReadWrite {} #[derive(Debug, Copy, Clone)] pub struct ReadOnly; +impl Access for ReadOnly {} impl Readable for ReadOnly {} /// Zero-sized marker type for allowing only write access. #[derive(Debug, Copy, Clone)] pub struct WriteOnly; + +impl Access for WriteOnly {} impl Writable for WriteOnly {} diff --git a/src/lib.rs b/src/lib.rs index b5b99ce..11d22f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,24 +9,20 @@ #![cfg_attr(feature = "unstable", feature(core_intrinsics))] #![cfg_attr(feature = "unstable", feature(const_generics))] #![cfg_attr(feature = "unstable", feature(slice_range))] +#![cfg_attr(feature = "unstable", feature(slice_ptr_get))] +#![cfg_attr(feature = "unstable", feature(slice_ptr_len))] #![cfg_attr(feature = "unstable", allow(incomplete_features))] #![cfg_attr(all(feature = "unstable", test), feature(slice_as_chunks))] #![warn(missing_docs)] +#![deny(unsafe_op_in_unsafe_fn)] use access::{ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; -use core::{ - fmt, - marker::PhantomData, - ops::Deref, - ops::{DerefMut, Index, IndexMut}, - ptr, - slice::SliceIndex, -}; +use core::{cell::UnsafeCell, fmt, marker::PhantomData, ptr}; #[cfg(feature = "unstable")] use core::{ intrinsics, ops::{Range, RangeBounds}, - slice::range, + slice::{range, SliceIndex}, }; /// Allows creating read-only and write-only `Volatile` values. @@ -46,8 +42,11 @@ pub mod access; /// The size of this struct is the same as the size of the contained reference. #[derive(Clone)] #[repr(transparent)] -pub struct Volatile { - reference: R, +pub struct Volatile<'a, T, A = ReadWrite> +where + T: ?Sized, +{ + reference: &'a UnsafeCell, access: PhantomData, } @@ -56,7 +55,10 @@ pub struct Volatile { /// These functions allow to construct a new `Volatile` instance from a reference type. While /// the `new` function creates a `Volatile` instance with unrestricted access, there are also /// functions for creating read-only or write-only instances. -impl Volatile { +impl Volatile<'_, T> +where + T: ?Sized, +{ /// Constructs a new volatile instance wrapping the given reference. /// /// While it is possible to construct `Volatile` instances from arbitrary values (including @@ -78,95 +80,38 @@ impl Volatile { /// volatile.write(1); /// assert_eq!(volatile.read(), 1); /// ``` - pub const fn new(reference: R) -> Volatile { + pub unsafe fn new(reference: &UnsafeCell, access: A) -> Volatile<'_, T, A> { Volatile { reference, access: PhantomData, } } - /// Constructs a new read-only volatile instance wrapping the given reference. - /// - /// This is equivalent to the `new` function with the difference that the returned - /// `Volatile` instance does not permit write operations. This is for example useful - /// with memory-mapped hardware registers that are defined as read-only by the hardware. - /// - /// ## Example - /// - /// Reading is allowed: - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let value = 0u32; - /// - /// let volatile = Volatile::new_read_only(&value); - /// assert_eq!(volatile.read(), 0); - /// ``` - /// - /// But writing is not: - /// - /// ```compile_fail - /// use volatile::Volatile; - /// - /// let mut value = 0u32; - /// - /// let mut volatile = Volatile::new_read_only(&mut value); - /// volatile.write(1); - /// //ERROR: ^^^^^ the trait `volatile::access::Writable` is not implemented - /// // for `volatile::access::ReadOnly` - /// ``` - pub const fn new_read_only(reference: R) -> Volatile { - Volatile { - reference, - access: PhantomData, - } + pub fn from_ref(reference: &T) -> Volatile<'_, T, ReadOnly> { + let raw = reference as *const T as *const UnsafeCell; + unsafe { Volatile::new(&*raw, ReadOnly) } } - /// Constructs a new write-only volatile instance wrapping the given reference. - /// - /// This is equivalent to the `new` function with the difference that the returned - /// `Volatile` instance does not permit read operations. This is for example useful - /// with memory-mapped hardware registers that are defined as write-only by the hardware. - /// - /// ## Example - /// - /// Writing is allowed: - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let mut value = 0u32; - /// - /// let mut volatile = Volatile::new_write_only(&mut value); - /// volatile.write(1); - /// ``` - /// - /// But reading is not: - /// - /// ```compile_fail - /// use volatile::Volatile; - /// - /// let value = 0u32; - /// - /// let volatile = Volatile::new_write_only(&value); - /// volatile.read(); - /// //ERROR: ^^^^ the trait `volatile::access::Readable` is not implemented - /// // for `volatile::access::WriteOnly` - /// ``` - pub const fn new_write_only(reference: R) -> Volatile { - Volatile { - reference, - access: PhantomData, - } + pub fn from_mut_ref(reference: &mut T) -> Volatile<'_, T> { + let raw = reference as *mut T as *mut UnsafeCell; + unsafe { Volatile::new(&*raw, ReadWrite) } + } + + pub unsafe fn from_ptr<'a>(reference: *const T) -> Volatile<'a, T, ReadOnly> { + let raw = reference as *const UnsafeCell; + unsafe { Volatile::new(&*raw, ReadOnly) } + } + + pub unsafe fn from_mut_ptr<'a>(reference: *mut T) -> Volatile<'a, T> { + let raw = reference as *mut UnsafeCell; + unsafe { Volatile::new(&*raw, ReadWrite) } } } /// Methods for references to `Copy` types -impl Volatile +impl<'a, T, A> Volatile<'a, T, A> where - R: Deref, - T: Copy, + T: Copy + ?Sized, { /// Performs a volatile read of the contained value. /// @@ -192,8 +137,8 @@ where where A: Readable, { - // UNSAFE: Safe, as we know that our internal value exists. - unsafe { ptr::read_volatile(&*self.reference) } + // UNSAFE: Safe, as ... TODO + unsafe { ptr::read_volatile(self.reference.get()) } } /// Performs a volatile write, setting the contained value to the given `value`. @@ -216,10 +161,9 @@ where pub fn write(&mut self, value: T) where A: Writable, - R: DerefMut, { - // UNSAFE: Safe, as we know that our internal value exists. - unsafe { ptr::write_volatile(&mut *self.reference, value) }; + // UNSAFE: Safe, as ... TODO + unsafe { ptr::write_volatile(self.reference.get(), value) }; } /// Updates the contained value using the given closure and volatile instructions. @@ -240,7 +184,6 @@ where pub fn update(&mut self, f: F) where A: Readable + Writable, - R: DerefMut, F: FnOnce(&mut T), { let mut value = self.read(); @@ -250,7 +193,10 @@ where } /// Method for extracting the wrapped value. -impl Volatile { +impl<'a, T, A> Volatile<'a, T, A> +where + T: ?Sized, +{ /// Extracts the inner value stored in the wrapper type. /// /// This method gives direct access to the wrapped reference and thus allows @@ -275,15 +221,14 @@ impl Volatile { /// /// assert_eq!(*unwrapped, 50); // non volatile access, be careful! /// ``` - pub fn extract_inner(self) -> R { + pub fn extract_inner(self) -> &'a UnsafeCell { self.reference } } /// Transformation methods for accessing struct fields -impl Volatile +impl Volatile<'_, T, A> where - R: Deref, T: ?Sized, { /// Constructs a new `Volatile` reference by mapping the wrapped value. @@ -325,76 +270,32 @@ where /// value /// }); /// ``` - pub fn map<'a, F, U>(&'a self, f: F) -> Volatile<&'a U, A> + pub fn map(&self, f: F) -> Volatile where - F: FnOnce(&'a T) -> &'a U, + F: FnOnce(&UnsafeCell) -> &UnsafeCell, U: ?Sized, - T: 'a, { Volatile { - reference: f(self.reference.deref()), - access: self.access, + reference: f(&self.reference), + access: PhantomData, } } - /// Constructs a new mutable `Volatile` reference by mapping the wrapped value. - /// - /// This method is useful for accessing individual fields of volatile structs. - /// - /// Note that this method gives temporary access to the wrapped reference, which allows - /// accessing the value in a non-volatile way. This is normally not what you want, so - /// **this method should only be used for reference-to-reference transformations**. - /// - /// ## Examples - /// - /// Accessing a struct field: - /// - /// ``` - /// use volatile::Volatile; - /// - /// struct Example { field_1: u32, field_2: u8, } - /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = Volatile::new(&mut value); - /// - /// // construct a volatile reference to a field - /// let mut field_2 = volatile.map_mut(|example| &mut example.field_2); - /// field_2.write(128); - /// assert_eq!(field_2.read(), 128); - /// ``` - /// - /// Don't misuse this method to do a non-volatile read or write of the referenced value: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut value = 5; - /// let mut volatile = Volatile::new(&mut value); - /// - /// // DON'T DO THIS: - /// volatile.map_mut(|value| { - /// *value = 10; // non-volatile write, might lead to bugs - /// value - /// }); - /// ``` - pub fn map_mut<'a, F, U>(&'a mut self, f: F) -> Volatile<&'a mut U, A> + pub fn map_mut(&mut self, f: F) -> Volatile where - F: FnOnce(&mut T) -> &mut U, - R: DerefMut, + F: FnOnce(&UnsafeCell) -> &UnsafeCell, U: ?Sized, - T: 'a, { Volatile { - reference: f(&mut self.reference), + reference: f(&self.reference), access: self.access, } } } /// Methods for volatile slices -impl Volatile -where - R: Deref, -{ +#[cfg(feature = "unstable")] +impl<'a, T, A> Volatile<'a, [T], A> { /// Applies the index operation on the wrapped slice. /// /// Returns a shared `Volatile` reference to the resulting subslice. @@ -427,55 +328,24 @@ where /// let subslice = volatile.index(1..); /// assert_eq!(subslice.index(0).read(), 2); /// ``` - pub fn index<'a, I>(&'a self, index: I) -> Volatile<&'a I::Output, A> + pub fn index(&self, index: I) -> Volatile where I: SliceIndex<[T]>, - T: 'a, { - self.map(|slice| slice.index(index)) + self.map(|slice| unsafe { + let element: *mut I::Output = slice.get().get_unchecked_mut(index); + &*(element as *mut UnsafeCell) + }) } - /// Applies the mutable index operation on the wrapped slice. - /// - /// Returns a mutable `Volatile` reference to the resulting subslice. - /// - /// This is a convenience method for the `map_mut(|slice| slice.index_mut(index))` - /// operation, so it has the same behavior as the indexing operation on slice - /// (e.g. panic if index is out-of-bounds). - /// - /// ## Examples - /// - /// Accessing a single slice element: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut array = [1, 2, 3]; - /// let slice = &mut array[..]; - /// let mut volatile = Volatile::new(slice); - /// volatile.index_mut(1).write(6); - /// assert_eq!(volatile.index(1).read(), 6); - /// ``` - /// - /// Accessing a subslice: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut array = [1, 2, 3]; - /// let slice = &mut array[..]; - /// let mut volatile = Volatile::new(slice); - /// let mut subslice = volatile.index_mut(1..); - /// subslice.index_mut(0).write(6); - /// assert_eq!(subslice.index(0).read(), 6); - /// ``` - pub fn index_mut<'a, I>(&'a mut self, index: I) -> Volatile<&mut I::Output, A> + pub fn index_mut(&mut self, index: I) -> Volatile where I: SliceIndex<[T]>, - R: DerefMut, - T: 'a, { - self.map_mut(|slice| slice.index_mut(index)) + self.map_mut(|slice| unsafe { + let element: *mut I::Output = slice.get().get_unchecked_mut(index); + &*(element as *mut UnsafeCell) + }) } /// Copies all elements from `self` into `dst`, using a volatile memcpy. @@ -510,21 +380,21 @@ where /// assert_eq!(src, [1, 2]); /// assert_eq!(dst, [5, 1, 2]); /// ``` - #[cfg(feature = "unstable")] pub fn copy_into_slice(&self, dst: &mut [T]) where T: Copy, { + let len = self.reference.get().len(); assert_eq!( - self.reference.len(), + len, dst.len(), "destination and source slices have different lengths" ); unsafe { intrinsics::volatile_copy_nonoverlapping_memory( dst.as_mut_ptr(), - self.reference.as_ptr(), - self.reference.len(), + self.reference.get().as_mut_ptr(), + len, ); } } @@ -564,22 +434,21 @@ where /// assert_eq!(src, [1, 2, 3, 4]); /// assert_eq!(dst, [3, 4]); /// ``` - #[cfg(feature = "unstable")] pub fn copy_from_slice(&mut self, src: &[T]) where T: Copy, - R: DerefMut, { + let len = self.reference.get().len(); assert_eq!( - self.reference.len(), + len, src.len(), "destination and source slices have different lengths" ); unsafe { intrinsics::volatile_copy_nonoverlapping_memory( - self.reference.as_mut_ptr(), + self.reference.get().as_mut_ptr(), src.as_ptr(), - self.reference.len(), + len, ); } } @@ -611,44 +480,189 @@ where /// /// let mut byte_array = *b"Hello, World!"; /// let mut slice: &mut [u8] = &mut byte_array[..]; - /// let mut volatile = Volatile::new(slice); + /// let mut volatile = Volatile::from_mut_ref(slice); /// /// volatile.copy_within(1..5, 8); /// /// assert_eq!(&byte_array, b"Hello, Wello!"); - #[cfg(feature = "unstable")] pub fn copy_within(&mut self, src: impl RangeBounds, dest: usize) where T: Copy, - R: DerefMut, { + let len = self.reference.get().len(); // implementation taken from https://github.com/rust-lang/rust/blob/683d1bcd405727fcc9209f64845bd3b9104878b8/library/core/src/slice/mod.rs#L2726-L2738 let Range { start: src_start, end: src_end, - } = range(src, ..self.reference.len()); + } = range(src, ..len); let count = src_end - src_start; - assert!( - dest <= self.reference.len() - count, - "dest is out of bounds" - ); + assert!(dest <= len - count, "dest is out of bounds"); // SAFETY: the conditions for `volatile_copy_memory` have all been checked above, // as have those for `ptr::add`. unsafe { intrinsics::volatile_copy_memory( - self.reference.as_mut_ptr().add(dest), - self.reference.as_ptr().add(src_start), + self.reference.get().as_mut_ptr().add(dest), + self.reference.get().as_mut_ptr().add(src_start), count, ); } } + + pub fn split_at(&self, mid: usize) -> (Volatile<[T], ReadOnly>, Volatile<[T], ReadOnly>) { + assert!(mid <= self.reference.get().len()); + // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which + // fulfills the requirements of `from_raw_parts_mut`. + unsafe { self.split_at_unchecked(mid) } + } + + pub fn split_at_mut(&mut self, mid: usize) -> (Volatile<[T], A>, Volatile<[T], A>) { + assert!(mid <= self.reference.get().len()); + // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which + // fulfills the requirements of `from_raw_parts_mut`. + unsafe { self.split_at_mut_unchecked(mid) } + } + + unsafe fn split_at_unchecked( + &self, + mid: usize, + ) -> (Volatile<[T], ReadOnly>, Volatile<[T], ReadOnly>) { + // SAFETY: Caller has to check that `0 <= mid <= self.len()` + unsafe { + ( + Volatile { + reference: { + let raw: *const [T] = + (self.reference.get() as *const [T]).get_unchecked(..mid); + &*(raw as *const UnsafeCell<[T]>) + }, + access: PhantomData, + }, + Volatile { + reference: { + let raw: *const [T] = + (self.reference.get() as *const [T]).get_unchecked(mid..); + &*(raw as *const UnsafeCell<[T]>) + }, + access: PhantomData, + }, + ) + } + } + + unsafe fn split_at_mut_unchecked( + &mut self, + mid: usize, + ) -> (Volatile<[T], A>, Volatile<[T], A>) { + let len = self.reference.get().len(); + let ptr = self.reference.get().as_mut_ptr(); + + // SAFETY: Caller has to check that `0 <= mid <= self.len()`. + // + // `[ptr; mid]` and `[mid; len]` are not overlapping, so returning a mutable reference + // is fine. + unsafe { + ( + Volatile { + reference: { + let raw: *mut [T] = ptr::slice_from_raw_parts_mut(ptr, mid); + &*(raw as *mut UnsafeCell<[T]>) + }, + access: self.access, + }, + Volatile { + reference: { + let raw: *mut [T] = ptr::slice_from_raw_parts_mut(ptr.add(mid), len - mid); + &*(raw as *mut UnsafeCell<[T]>) + }, + access: self.access, + }, + ) + } + } + + pub fn as_chunks( + &self, + ) -> (Volatile<[[T; N]], ReadOnly>, Volatile<[T], ReadOnly>) { + assert_ne!(N, 0); + let len = self.reference.get().len() / N; + let (multiple_of_n, remainder) = self.split_at(len * N); + // SAFETY: We already panicked for zero, and ensured by construction + // that the length of the subslice is a multiple of N. + let array_slice = unsafe { multiple_of_n.as_chunks_unchecked_by_val() }; + (array_slice, remainder) + } + + pub unsafe fn as_chunks_unchecked(&self) -> Volatile<[[T; N]], ReadOnly> { + debug_assert_ne!(N, 0); + debug_assert_eq!(self.reference.get().len() % N, 0); + let new_len = + // SAFETY: Our precondition is exactly what's needed to call this + unsafe { crate::intrinsics::exact_div(self.reference.get().len(), N) }; + // SAFETY: We cast a slice of `new_len * N` elements into + // a slice of `new_len` many `N` elements chunks. + let reference = unsafe { + let raw: *const [[T; N]] = + ptr::slice_from_raw_parts(self.reference.get().as_mut_ptr().cast(), new_len); + &*(raw as *const UnsafeCell<[[T; N]]>) + }; + Volatile { + reference, + access: PhantomData, + } + } + + pub fn as_chunks_mut(&mut self) -> (Volatile<[[T; N]], A>, Volatile<[T], A>) { + assert_ne!(N, 0); + let len = self.reference.get().len() / N; + let (multiple_of_n, remainder) = self.split_at_mut(len * N); + // SAFETY: We already panicked for zero, and ensured by construction + // that the length of the subslice is a multiple of N. + let array_slice = unsafe { multiple_of_n.as_chunks_unchecked_by_val() }; + (array_slice, remainder) + } + + pub unsafe fn as_chunks_unchecked_mut(&mut self) -> Volatile<[[T; N]], A> { + debug_assert_ne!(N, 0); + debug_assert_eq!(self.reference.get().len() % N, 0); + let new_len = + // SAFETY: Our precondition is exactly what's needed to call this + unsafe { crate::intrinsics::exact_div(self.reference.get().len(), N) }; + // SAFETY: We cast a slice of `new_len * N` elements into + // a slice of `new_len` many `N` elements chunks. + let reference = unsafe { + let raw: *mut [[T; N]] = + ptr::slice_from_raw_parts_mut(self.reference.get().as_mut_ptr().cast(), new_len); + &*(raw as *mut UnsafeCell<[[T; N]]>) + }; + Volatile { + reference, + access: self.access, + } + } + + pub unsafe fn as_chunks_unchecked_by_val(self) -> Volatile<'a, [[T; N]], A> { + debug_assert_ne!(N, 0); + debug_assert_eq!(self.reference.get().len() % N, 0); + let new_len = + // SAFETY: Our precondition is exactly what's needed to call this + unsafe { crate::intrinsics::exact_div(self.reference.get().len(), N) }; + // SAFETY: We cast a slice of `new_len * N` elements into + // a slice of `new_len` many `N` elements chunks. + let reference = unsafe { + let raw: *mut [[T; N]] = + ptr::slice_from_raw_parts_mut(self.reference.get().as_mut_ptr().cast(), new_len); + &*(raw as *mut UnsafeCell<[[T; N]]>) + }; + Volatile { + reference, + access: self.access, + } + } } /// Methods for volatile byte slices -impl Volatile -where - R: Deref, -{ +#[cfg(feature = "unstable")] +impl Volatile<'_, [u8], A> { /// Sets all elements of the byte slice to the given `value` using a volatile `memset`. /// /// This method is similar to the `slice::fill` method of the standard library, with the @@ -668,16 +682,12 @@ where /// buf.fill(1); /// assert_eq!(buf.extract_inner(), vec![1; 10]); /// ``` - #[cfg(feature = "unstable")] - pub fn fill(&mut self, value: u8) - where - R: DerefMut, - { + pub fn fill(&mut self, value: u8) { unsafe { intrinsics::volatile_set_memory( - self.reference.as_mut_ptr(), + self.reference.get().as_mut_ptr(), value, - self.reference.len(), + self.reference.get().len(), ); } } @@ -688,10 +698,7 @@ where /// These methods are only available with the `unstable` feature enabled (requires a nightly /// Rust compiler). #[cfg(feature = "unstable")] -impl Volatile -where - R: Deref, -{ +impl Volatile<'_, [T; N], A> { /// Converts an array reference to a shared slice. /// /// This makes it possible to use the methods defined on slices. @@ -714,42 +721,19 @@ where /// /// assert_eq!(dst, [1, 2]); /// ``` - pub fn as_slice(&self) -> Volatile<&[T], A> { - self.map(|array| &array[..]) - } - - /// Converts a mutable array reference to a mutable slice. - /// - /// This makes it possible to use the methods defined on slices. - /// - /// ## Example - /// - /// Copying two elements from a slice into a mutable array reference: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let src = [1, 2, 3, 4]; - /// let mut dst = [0, 0]; - /// let mut volatile = Volatile::new(&mut dst); - /// - /// // convert the `Volatile<&mut [i32; 2]>` array reference to a `Volatile<&mut [i32]>` slice - /// let mut volatile_slice = volatile.as_mut_slice(); - /// // we can now use the slice methods - /// volatile_slice.copy_from_slice(&src[2..]); - /// - /// assert_eq!(dst, [3, 4]); - /// ``` - pub fn as_mut_slice(&mut self) -> Volatile<&mut [T], A> - where - R: DerefMut, - { - self.map_mut(|array| &mut array[..]) + pub fn as_slice(&self) -> Volatile<[T], ReadOnly> { + self.map(|array| { + let slice: *mut [T] = ptr::slice_from_raw_parts_mut(array.get() as *mut T, N); + unsafe { &*(slice as *const UnsafeCell<[T]>) } + }) } } /// Methods for restricting access. -impl Volatile { +impl<'a, T> Volatile<'a, T> +where + T: ?Sized, +{ /// Restricts access permissions to read-only. /// /// ## Example @@ -764,7 +748,7 @@ impl Volatile { /// assert_eq!(read_only.read(), -4); /// // read_only.write(10); // compile-time error /// ``` - pub fn read_only(self) -> Volatile { + pub fn read_only(self) -> Volatile<'a, T, ReadOnly> { Volatile { reference: self.reference, access: PhantomData, @@ -789,7 +773,7 @@ impl Volatile { /// field_2.write(14); /// // field_2.read(); // compile-time error /// ``` - pub fn write_only(self) -> Volatile { + pub fn write_only(self) -> Volatile<'a, T, WriteOnly> { Volatile { reference: self.reference, access: PhantomData, @@ -797,10 +781,9 @@ impl Volatile { } } -impl fmt::Debug for Volatile +impl fmt::Debug for Volatile<'_, T, A> where - R: Deref, - T: Copy + fmt::Debug, + T: Copy + fmt::Debug + ?Sized, A: Readable, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -808,9 +791,9 @@ where } } -impl fmt::Debug for Volatile +impl fmt::Debug for Volatile<'_, T, WriteOnly> where - R: Deref, + T: ?Sized, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("Volatile").field(&"[write-only]").finish() @@ -820,17 +803,18 @@ where #[cfg(test)] mod tests { use super::Volatile; + use core::cell::UnsafeCell; #[test] fn test_read() { let val = 42; - assert_eq!(Volatile::new(&val).read(), 42); + assert_eq!(Volatile::from_ref(&val).read(), 42); } #[test] fn test_write() { let mut val = 50; - let mut volatile = Volatile::new(&mut val); + let mut volatile = Volatile::from_mut_ref(&mut val); volatile.write(50); assert_eq!(val, 50); } @@ -838,21 +822,14 @@ mod tests { #[test] fn test_update() { let mut val = 42; - let mut volatile = Volatile::new(&mut val); + let mut volatile = Volatile::from_mut_ref(&mut val); volatile.update(|v| *v += 1); assert_eq!(val, 43); } - #[test] - fn test_slice() { - let mut val = [1, 2, 3]; - let mut volatile = Volatile::new(&mut val[..]); - volatile.index_mut(0).update(|v| *v += 1); - assert_eq!(val, [2, 2, 3]); - } - #[test] fn test_struct() { + #[derive(Debug, PartialEq)] struct S { field_1: u32, field_2: bool, @@ -862,21 +839,43 @@ mod tests { field_1: 60, field_2: true, }; - let mut volatile = Volatile::new(&mut val); - volatile.map_mut(|s| &mut s.field_1).update(|v| *v += 1); - let mut field_2 = volatile.map_mut(|s| &mut s.field_2); + let mut volatile = Volatile::from_mut_ref(&mut val); + volatile + .map_mut(|s| unsafe { + let ptr = core::ptr::addr_of_mut!((*s.get()).field_1); + &*(ptr as *mut UnsafeCell) + }) + .update(|v| *v += 1); + let mut field_2 = volatile.map_mut(|s| unsafe { + let ptr = core::ptr::addr_of_mut!((*s.get()).field_2); + &*(ptr as *mut UnsafeCell) + }); assert!(field_2.read()); field_2.write(false); - assert_eq!(volatile.map(|s| &s.field_1).read(), 61); - assert_eq!(volatile.map(|s| &s.field_2).read(), false); + assert_eq!( + val, + S { + field_1: 61, + field_2: false + } + ); + } + + #[cfg(feature = "unstable")] + #[test] + fn test_slice() { + let mut val: &mut [u32] = &mut [1, 2, 3]; + let mut volatile = Volatile::from_mut_ref(val); + volatile.index_mut(0).update(|v| *v += 1); + assert_eq!(val, [2, 2, 3]); } #[cfg(feature = "unstable")] #[test] fn test_chunks() { - let mut val = [1, 2, 3, 4, 5, 6]; - let mut volatile = Volatile::new(&mut val[..]); - let mut chunks = volatile.map_mut(|s| s.as_chunks_mut().0); + let mut val: &mut [u32] = &mut [1, 2, 3, 4, 5, 6]; + let mut volatile = Volatile::from_mut_ref(val); + let mut chunks = volatile.as_chunks_mut().0; chunks.index_mut(1).write([10, 11, 12]); assert_eq!(chunks.index(0).read(), [1, 2, 3]); assert_eq!(chunks.index(1).read(), [10, 11, 12]); From 946b37bef1052ee8eb78cc292904a25353436523 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 21 May 2021 15:56:47 +0200 Subject: [PATCH 02/68] Use `*mut T` instead of `&UnsafeCell` since the latter is dereferencable --- src/access.rs | 1 - src/lib.rs | 254 +++++++++++++++++++++++--------------------------- 2 files changed, 118 insertions(+), 137 deletions(-) diff --git a/src/access.rs b/src/access.rs index fff67e7..88b3df6 100644 --- a/src/access.rs +++ b/src/access.rs @@ -1,4 +1,3 @@ - pub trait Access {} /// Helper trait that is implemented by [`ReadWrite`] and [`ReadOnly`]. diff --git a/src/lib.rs b/src/lib.rs index 11d22f1..8a492a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,7 @@ #![deny(unsafe_op_in_unsafe_fn)] use access::{ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; -use core::{cell::UnsafeCell, fmt, marker::PhantomData, ptr}; +use core::{fmt, marker::PhantomData, ptr}; #[cfg(feature = "unstable")] use core::{ intrinsics, @@ -40,13 +40,12 @@ pub mod access; /// to `ReadWrite`, which allows all operations. /// /// The size of this struct is the same as the size of the contained reference. -#[derive(Clone)] #[repr(transparent)] -pub struct Volatile<'a, T, A = ReadWrite> +pub struct Volatile where T: ?Sized, { - reference: &'a UnsafeCell, + pointer: *mut T, access: PhantomData, } @@ -55,7 +54,7 @@ where /// These functions allow to construct a new `Volatile` instance from a reference type. While /// the `new` function creates a `Volatile` instance with unrestricted access, there are also /// functions for creating read-only or write-only instances. -impl Volatile<'_, T> +impl Volatile where T: ?Sized, { @@ -80,36 +79,25 @@ where /// volatile.write(1); /// assert_eq!(volatile.read(), 1); /// ``` - pub unsafe fn new(reference: &UnsafeCell, access: A) -> Volatile<'_, T, A> { + pub unsafe fn new(pointer: *mut T, access: A) -> Volatile { + let _: A = access; Volatile { - reference, + pointer, access: PhantomData, } } - pub fn from_ref(reference: &T) -> Volatile<'_, T, ReadOnly> { - let raw = reference as *const T as *const UnsafeCell; - unsafe { Volatile::new(&*raw, ReadOnly) } + pub unsafe fn from_ptr(pointer: *const T) -> Volatile { + unsafe { Volatile::new(pointer as *mut _, ReadOnly) } } - pub fn from_mut_ref(reference: &mut T) -> Volatile<'_, T> { - let raw = reference as *mut T as *mut UnsafeCell; - unsafe { Volatile::new(&*raw, ReadWrite) } - } - - pub unsafe fn from_ptr<'a>(reference: *const T) -> Volatile<'a, T, ReadOnly> { - let raw = reference as *const UnsafeCell; - unsafe { Volatile::new(&*raw, ReadOnly) } - } - - pub unsafe fn from_mut_ptr<'a>(reference: *mut T) -> Volatile<'a, T> { - let raw = reference as *mut UnsafeCell; - unsafe { Volatile::new(&*raw, ReadWrite) } + pub unsafe fn from_mut_ptr(pointer: *mut T) -> Volatile { + unsafe { Volatile::new(pointer, ReadWrite) } } } /// Methods for references to `Copy` types -impl<'a, T, A> Volatile<'a, T, A> +impl<'a, T, A> Volatile where T: Copy + ?Sized, { @@ -138,7 +126,7 @@ where A: Readable, { // UNSAFE: Safe, as ... TODO - unsafe { ptr::read_volatile(self.reference.get()) } + unsafe { ptr::read_volatile(self.pointer) } } /// Performs a volatile write, setting the contained value to the given `value`. @@ -163,7 +151,7 @@ where A: Writable, { // UNSAFE: Safe, as ... TODO - unsafe { ptr::write_volatile(self.reference.get(), value) }; + unsafe { ptr::write_volatile(self.pointer, value) }; } /// Updates the contained value using the given closure and volatile instructions. @@ -193,7 +181,7 @@ where } /// Method for extracting the wrapped value. -impl<'a, T, A> Volatile<'a, T, A> +impl<'a, T, A> Volatile where T: ?Sized, { @@ -221,13 +209,13 @@ where /// /// assert_eq!(*unwrapped, 50); // non volatile access, be careful! /// ``` - pub fn extract_inner(self) -> &'a UnsafeCell { - self.reference + pub fn as_ptr(&self) -> *mut T { + self.pointer } } /// Transformation methods for accessing struct fields -impl Volatile<'_, T, A> +impl Volatile where T: ?Sized, { @@ -270,24 +258,24 @@ where /// value /// }); /// ``` - pub fn map(&self, f: F) -> Volatile + pub unsafe fn map(&self, f: F) -> Volatile where - F: FnOnce(&UnsafeCell) -> &UnsafeCell, + F: FnOnce(*mut T) -> *mut U, U: ?Sized, { Volatile { - reference: f(&self.reference), + pointer: f(self.pointer), access: PhantomData, } } - pub fn map_mut(&mut self, f: F) -> Volatile + pub unsafe fn map_mut(&mut self, f: F) -> Volatile where - F: FnOnce(&UnsafeCell) -> &UnsafeCell, + F: FnOnce(*mut T) -> *mut U, U: ?Sized, { Volatile { - reference: f(&self.reference), + pointer: f(self.pointer), access: self.access, } } @@ -295,7 +283,7 @@ where /// Methods for volatile slices #[cfg(feature = "unstable")] -impl<'a, T, A> Volatile<'a, [T], A> { +impl<'a, T, A> Volatile<[T], A> { /// Applies the index operation on the wrapped slice. /// /// Returns a shared `Volatile` reference to the resulting subslice. @@ -332,20 +320,14 @@ impl<'a, T, A> Volatile<'a, [T], A> { where I: SliceIndex<[T]>, { - self.map(|slice| unsafe { - let element: *mut I::Output = slice.get().get_unchecked_mut(index); - &*(element as *mut UnsafeCell) - }) + unsafe { self.map(|slice| slice.get_unchecked_mut(index)) } } pub fn index_mut(&mut self, index: I) -> Volatile where I: SliceIndex<[T]>, { - self.map_mut(|slice| unsafe { - let element: *mut I::Output = slice.get().get_unchecked_mut(index); - &*(element as *mut UnsafeCell) - }) + unsafe { self.map_mut(|slice| slice.get_unchecked_mut(index)) } } /// Copies all elements from `self` into `dst`, using a volatile memcpy. @@ -384,7 +366,7 @@ impl<'a, T, A> Volatile<'a, [T], A> { where T: Copy, { - let len = self.reference.get().len(); + let len = self.pointer.len(); assert_eq!( len, dst.len(), @@ -393,7 +375,7 @@ impl<'a, T, A> Volatile<'a, [T], A> { unsafe { intrinsics::volatile_copy_nonoverlapping_memory( dst.as_mut_ptr(), - self.reference.get().as_mut_ptr(), + self.pointer.as_mut_ptr(), len, ); } @@ -438,7 +420,7 @@ impl<'a, T, A> Volatile<'a, [T], A> { where T: Copy, { - let len = self.reference.get().len(); + let len = self.pointer.len(); assert_eq!( len, src.len(), @@ -446,7 +428,7 @@ impl<'a, T, A> Volatile<'a, [T], A> { ); unsafe { intrinsics::volatile_copy_nonoverlapping_memory( - self.reference.get().as_mut_ptr(), + self.pointer.as_mut_ptr(), src.as_ptr(), len, ); @@ -480,7 +462,7 @@ impl<'a, T, A> Volatile<'a, [T], A> { /// /// let mut byte_array = *b"Hello, World!"; /// let mut slice: &mut [u8] = &mut byte_array[..]; - /// let mut volatile = Volatile::from_mut_ref(slice); + /// let mut volatile = Volatile::from_mut_ptr(slice); /// /// volatile.copy_within(1..5, 8); /// @@ -489,7 +471,7 @@ impl<'a, T, A> Volatile<'a, [T], A> { where T: Copy, { - let len = self.reference.get().len(); + let len = self.pointer.len(); // implementation taken from https://github.com/rust-lang/rust/blob/683d1bcd405727fcc9209f64845bd3b9104878b8/library/core/src/slice/mod.rs#L2726-L2738 let Range { start: src_start, @@ -501,22 +483,22 @@ impl<'a, T, A> Volatile<'a, [T], A> { // as have those for `ptr::add`. unsafe { intrinsics::volatile_copy_memory( - self.reference.get().as_mut_ptr().add(dest), - self.reference.get().as_mut_ptr().add(src_start), + self.pointer.as_mut_ptr().add(dest), + self.pointer.as_mut_ptr().add(src_start), count, ); } } pub fn split_at(&self, mid: usize) -> (Volatile<[T], ReadOnly>, Volatile<[T], ReadOnly>) { - assert!(mid <= self.reference.get().len()); + assert!(mid <= self.pointer.len()); // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which // fulfills the requirements of `from_raw_parts_mut`. unsafe { self.split_at_unchecked(mid) } } pub fn split_at_mut(&mut self, mid: usize) -> (Volatile<[T], A>, Volatile<[T], A>) { - assert!(mid <= self.reference.get().len()); + assert!(mid <= self.pointer.len()); // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which // fulfills the requirements of `from_raw_parts_mut`. unsafe { self.split_at_mut_unchecked(mid) } @@ -530,19 +512,11 @@ impl<'a, T, A> Volatile<'a, [T], A> { unsafe { ( Volatile { - reference: { - let raw: *const [T] = - (self.reference.get() as *const [T]).get_unchecked(..mid); - &*(raw as *const UnsafeCell<[T]>) - }, + pointer: { (self.pointer).get_unchecked_mut(..mid) }, access: PhantomData, }, Volatile { - reference: { - let raw: *const [T] = - (self.reference.get() as *const [T]).get_unchecked(mid..); - &*(raw as *const UnsafeCell<[T]>) - }, + pointer: { (self.pointer).get_unchecked_mut(mid..) }, access: PhantomData, }, ) @@ -553,8 +527,8 @@ impl<'a, T, A> Volatile<'a, [T], A> { &mut self, mid: usize, ) -> (Volatile<[T], A>, Volatile<[T], A>) { - let len = self.reference.get().len(); - let ptr = self.reference.get().as_mut_ptr(); + let len = self.pointer.len(); + let ptr = self.pointer.as_mut_ptr(); // SAFETY: Caller has to check that `0 <= mid <= self.len()`. // @@ -563,17 +537,11 @@ impl<'a, T, A> Volatile<'a, [T], A> { unsafe { ( Volatile { - reference: { - let raw: *mut [T] = ptr::slice_from_raw_parts_mut(ptr, mid); - &*(raw as *mut UnsafeCell<[T]>) - }, + pointer: { ptr::slice_from_raw_parts_mut(ptr, mid) }, access: self.access, }, Volatile { - reference: { - let raw: *mut [T] = ptr::slice_from_raw_parts_mut(ptr.add(mid), len - mid); - &*(raw as *mut UnsafeCell<[T]>) - }, + pointer: { ptr::slice_from_raw_parts_mut(ptr.add(mid), len - mid) }, access: self.access, }, ) @@ -584,7 +552,7 @@ impl<'a, T, A> Volatile<'a, [T], A> { &self, ) -> (Volatile<[[T; N]], ReadOnly>, Volatile<[T], ReadOnly>) { assert_ne!(N, 0); - let len = self.reference.get().len() / N; + let len = self.pointer.len() / N; let (multiple_of_n, remainder) = self.split_at(len * N); // SAFETY: We already panicked for zero, and ensured by construction // that the length of the subslice is a multiple of N. @@ -594,26 +562,22 @@ impl<'a, T, A> Volatile<'a, [T], A> { pub unsafe fn as_chunks_unchecked(&self) -> Volatile<[[T; N]], ReadOnly> { debug_assert_ne!(N, 0); - debug_assert_eq!(self.reference.get().len() % N, 0); + debug_assert_eq!(self.pointer.len() % N, 0); let new_len = // SAFETY: Our precondition is exactly what's needed to call this - unsafe { crate::intrinsics::exact_div(self.reference.get().len(), N) }; + unsafe { crate::intrinsics::exact_div(self.pointer.len(), N) }; // SAFETY: We cast a slice of `new_len * N` elements into // a slice of `new_len` many `N` elements chunks. - let reference = unsafe { - let raw: *const [[T; N]] = - ptr::slice_from_raw_parts(self.reference.get().as_mut_ptr().cast(), new_len); - &*(raw as *const UnsafeCell<[[T; N]]>) - }; + let pointer = ptr::slice_from_raw_parts_mut(self.pointer.as_mut_ptr().cast(), new_len); Volatile { - reference, + pointer: pointer, access: PhantomData, } } pub fn as_chunks_mut(&mut self) -> (Volatile<[[T; N]], A>, Volatile<[T], A>) { assert_ne!(N, 0); - let len = self.reference.get().len() / N; + let len = self.pointer.len() / N; let (multiple_of_n, remainder) = self.split_at_mut(len * N); // SAFETY: We already panicked for zero, and ensured by construction // that the length of the subslice is a multiple of N. @@ -623,38 +587,30 @@ impl<'a, T, A> Volatile<'a, [T], A> { pub unsafe fn as_chunks_unchecked_mut(&mut self) -> Volatile<[[T; N]], A> { debug_assert_ne!(N, 0); - debug_assert_eq!(self.reference.get().len() % N, 0); + debug_assert_eq!(self.pointer.len() % N, 0); let new_len = // SAFETY: Our precondition is exactly what's needed to call this - unsafe { crate::intrinsics::exact_div(self.reference.get().len(), N) }; + unsafe { crate::intrinsics::exact_div(self.pointer.len(), N) }; // SAFETY: We cast a slice of `new_len * N` elements into // a slice of `new_len` many `N` elements chunks. - let reference = unsafe { - let raw: *mut [[T; N]] = - ptr::slice_from_raw_parts_mut(self.reference.get().as_mut_ptr().cast(), new_len); - &*(raw as *mut UnsafeCell<[[T; N]]>) - }; + let pointer = ptr::slice_from_raw_parts_mut(self.pointer.as_mut_ptr().cast(), new_len); Volatile { - reference, + pointer, access: self.access, } } - pub unsafe fn as_chunks_unchecked_by_val(self) -> Volatile<'a, [[T; N]], A> { + pub unsafe fn as_chunks_unchecked_by_val(self) -> Volatile<[[T; N]], A> { debug_assert_ne!(N, 0); - debug_assert_eq!(self.reference.get().len() % N, 0); + debug_assert_eq!(self.pointer.len() % N, 0); let new_len = // SAFETY: Our precondition is exactly what's needed to call this - unsafe { crate::intrinsics::exact_div(self.reference.get().len(), N) }; + unsafe { crate::intrinsics::exact_div(self.pointer.len(), N) }; // SAFETY: We cast a slice of `new_len * N` elements into // a slice of `new_len` many `N` elements chunks. - let reference = unsafe { - let raw: *mut [[T; N]] = - ptr::slice_from_raw_parts_mut(self.reference.get().as_mut_ptr().cast(), new_len); - &*(raw as *mut UnsafeCell<[[T; N]]>) - }; + let pointer = ptr::slice_from_raw_parts_mut(self.pointer.as_mut_ptr().cast(), new_len); Volatile { - reference, + pointer, access: self.access, } } @@ -662,7 +618,7 @@ impl<'a, T, A> Volatile<'a, [T], A> { /// Methods for volatile byte slices #[cfg(feature = "unstable")] -impl Volatile<'_, [u8], A> { +impl Volatile<[u8], A> { /// Sets all elements of the byte slice to the given `value` using a volatile `memset`. /// /// This method is similar to the `slice::fill` method of the standard library, with the @@ -684,11 +640,7 @@ impl Volatile<'_, [u8], A> { /// ``` pub fn fill(&mut self, value: u8) { unsafe { - intrinsics::volatile_set_memory( - self.reference.get().as_mut_ptr(), - value, - self.reference.get().len(), - ); + intrinsics::volatile_set_memory(self.pointer.as_mut_ptr(), value, self.pointer.len()); } } } @@ -698,7 +650,7 @@ impl Volatile<'_, [u8], A> { /// These methods are only available with the `unstable` feature enabled (requires a nightly /// Rust compiler). #[cfg(feature = "unstable")] -impl Volatile<'_, [T; N], A> { +impl Volatile<[T; N], A> { /// Converts an array reference to a shared slice. /// /// This makes it possible to use the methods defined on slices. @@ -722,15 +674,12 @@ impl Volatile<'_, [T; N], A> { /// assert_eq!(dst, [1, 2]); /// ``` pub fn as_slice(&self) -> Volatile<[T], ReadOnly> { - self.map(|array| { - let slice: *mut [T] = ptr::slice_from_raw_parts_mut(array.get() as *mut T, N); - unsafe { &*(slice as *const UnsafeCell<[T]>) } - }) + unsafe { self.map(|array| ptr::slice_from_raw_parts_mut(array as *mut T, N)) } } } /// Methods for restricting access. -impl<'a, T> Volatile<'a, T> +impl<'a, T> Volatile where T: ?Sized, { @@ -748,9 +697,9 @@ where /// assert_eq!(read_only.read(), -4); /// // read_only.write(10); // compile-time error /// ``` - pub fn read_only(self) -> Volatile<'a, T, ReadOnly> { + pub fn read_only(self) -> Volatile { Volatile { - reference: self.reference, + pointer: self.pointer, access: PhantomData, } } @@ -773,15 +722,15 @@ where /// field_2.write(14); /// // field_2.read(); // compile-time error /// ``` - pub fn write_only(self) -> Volatile<'a, T, WriteOnly> { + pub fn write_only(self) -> Volatile { Volatile { - reference: self.reference, + pointer: self.pointer, access: PhantomData, } } } -impl fmt::Debug for Volatile<'_, T, A> +impl fmt::Debug for Volatile where T: Copy + fmt::Debug + ?Sized, A: Readable, @@ -791,7 +740,7 @@ where } } -impl fmt::Debug for Volatile<'_, T, WriteOnly> +impl fmt::Debug for Volatile where T: ?Sized, { @@ -800,6 +749,20 @@ where } } +#[macro_export] +macro_rules! map_field { + ($volatile:ident.$place:ident) => { + unsafe { $volatile.map(|ptr| core::ptr::addr_of_mut!((*ptr).$place)) } + }; +} + +#[macro_export] +macro_rules! map_field_mut { + ($volatile:ident.$place:ident) => { + unsafe { $volatile.map_mut(|ptr| core::ptr::addr_of_mut!((*ptr).$place)) } + }; +} + #[cfg(test)] mod tests { use super::Volatile; @@ -808,13 +771,13 @@ mod tests { #[test] fn test_read() { let val = 42; - assert_eq!(Volatile::from_ref(&val).read(), 42); + assert_eq!(unsafe { Volatile::from_ptr(&val) }.read(), 42); } #[test] fn test_write() { let mut val = 50; - let mut volatile = Volatile::from_mut_ref(&mut val); + let mut volatile = unsafe { Volatile::from_mut_ptr(&mut val) }; volatile.write(50); assert_eq!(val, 50); } @@ -822,7 +785,7 @@ mod tests { #[test] fn test_update() { let mut val = 42; - let mut volatile = Volatile::from_mut_ref(&mut val); + let mut volatile = unsafe { Volatile::from_mut_ptr(&mut val) }; volatile.update(|v| *v += 1); assert_eq!(val, 43); } @@ -839,17 +802,36 @@ mod tests { field_1: 60, field_2: true, }; - let mut volatile = Volatile::from_mut_ref(&mut val); - volatile - .map_mut(|s| unsafe { - let ptr = core::ptr::addr_of_mut!((*s.get()).field_1); - &*(ptr as *mut UnsafeCell) - }) - .update(|v| *v += 1); - let mut field_2 = volatile.map_mut(|s| unsafe { - let ptr = core::ptr::addr_of_mut!((*s.get()).field_2); - &*(ptr as *mut UnsafeCell) - }); + let mut volatile = unsafe { Volatile::from_mut_ptr(&mut val) }; + unsafe { volatile.map_mut(|s| core::ptr::addr_of_mut!((*s).field_1)) }.update(|v| *v += 1); + let mut field_2 = unsafe { volatile.map_mut(|s| core::ptr::addr_of_mut!((*s).field_2)) }; + assert!(field_2.read()); + field_2.write(false); + assert_eq!( + val, + S { + field_1: 61, + field_2: false + } + ); + } + + #[test] + fn test_struct_macro() { + #[derive(Debug, PartialEq)] + struct S { + field_1: u32, + field_2: bool, + } + + let mut val = S { + field_1: 60, + field_2: true, + }; + let mut volatile = unsafe { Volatile::from_mut_ptr(&mut val) }; + let mut field_1 = map_field_mut!(volatile.field_1); + field_1.update(|v| *v += 1); + let mut field_2 = map_field_mut!(volatile.field_2); assert!(field_2.read()); field_2.write(false); assert_eq!( @@ -865,7 +847,7 @@ mod tests { #[test] fn test_slice() { let mut val: &mut [u32] = &mut [1, 2, 3]; - let mut volatile = Volatile::from_mut_ref(val); + let mut volatile = Volatile::from_mut_ptr(val); volatile.index_mut(0).update(|v| *v += 1); assert_eq!(val, [2, 2, 3]); } @@ -874,7 +856,7 @@ mod tests { #[test] fn test_chunks() { let mut val: &mut [u32] = &mut [1, 2, 3, 4, 5, 6]; - let mut volatile = Volatile::from_mut_ref(val); + let mut volatile = Volatile::from_mut_ptr(val); let mut chunks = volatile.as_chunks_mut().0; chunks.index_mut(1).write([10, 11, 12]); assert_eq!(chunks.index(0).read(), [1, 2, 3]); From 682dd7092e8fc0cc7226db5cfaf4208d09530321 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 21 May 2021 18:36:01 +0200 Subject: [PATCH 03/68] Add access types for allowing only unsafe reads and/or writes --- Cargo.toml | 3 ++ src/access.rs | 32 +++++++++++++- src/lib.rs | 116 +++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 147 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d353c41..63a2565 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,9 @@ repository = "https://github.com/rust-osdev/volatile" # Enable unstable features; requires Rust nightly; might break on compiler updates unstable = [] +[dev-dependencies] +rand = "0.8.3" + [package.metadata.release] no-dev-version = true pre-release-replacements = [ diff --git a/src/access.rs b/src/access.rs index 88b3df6..d1f098e 100644 --- a/src/access.rs +++ b/src/access.rs @@ -1,17 +1,23 @@ pub trait Access {} /// Helper trait that is implemented by [`ReadWrite`] and [`ReadOnly`]. -pub trait Readable {} +pub trait Readable: UnsafelyReadable {} /// Helper trait that is implemented by [`ReadWrite`] and [`WriteOnly`]. -pub trait Writable {} +pub trait Writable: UnsafelyWritable {} + +pub trait UnsafelyReadable {} + +pub trait UnsafelyWritable {} /// Zero-sized marker type for allowing both read and write access. #[derive(Debug, Copy, Clone)] pub struct ReadWrite; impl Access for ReadWrite {} impl Readable for ReadWrite {} +impl UnsafelyReadable for ReadWrite {} impl Writable for ReadWrite {} +impl UnsafelyWritable for ReadWrite {} /// Zero-sized marker type for allowing only read access. #[derive(Debug, Copy, Clone)] @@ -19,6 +25,7 @@ pub struct ReadOnly; impl Access for ReadOnly {} impl Readable for ReadOnly {} +impl UnsafelyReadable for ReadOnly {} /// Zero-sized marker type for allowing only write access. #[derive(Debug, Copy, Clone)] @@ -26,3 +33,24 @@ pub struct WriteOnly; impl Access for WriteOnly {} impl Writable for WriteOnly {} +impl UnsafelyWritable for WriteOnly {} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct Custom { + pub read: R, + pub write: W, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct NoAccess; +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct SafeAccess; +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct UnsafeAccess; + +impl Readable for Custom {} +impl UnsafelyReadable for Custom {} +impl UnsafelyReadable for Custom {} +impl Writable for Custom {} +impl UnsafelyWritable for Custom {} +impl UnsafelyWritable for Custom {} diff --git a/src/lib.rs b/src/lib.rs index 8a492a9..745366e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,9 @@ #![warn(missing_docs)] #![deny(unsafe_op_in_unsafe_fn)] -use access::{ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; +use access::{ + ReadOnly, ReadWrite, Readable, UnsafelyReadable, UnsafelyWritable, Writable, WriteOnly, +}; use core::{fmt, marker::PhantomData, ptr}; #[cfg(feature = "unstable")] use core::{ @@ -178,6 +180,30 @@ where f(&mut value); self.write(value); } + + pub unsafe fn read_unsafe(&self) -> T + where + A: UnsafelyReadable, + { + unsafe { ptr::read_volatile(self.pointer) } + } + + pub unsafe fn write_unsafe(&mut self, value: T) + where + A: UnsafelyWritable, + { + unsafe { ptr::write_volatile(self.pointer, value) }; + } + + pub unsafe fn update_unsafe(&mut self, f: F) + where + A: UnsafelyReadable + UnsafelyWritable, + F: FnOnce(&mut T), + { + let mut value = unsafe { self.read_unsafe() }; + f(&mut value); + unsafe { self.write_unsafe(value) }; + } } /// Method for extracting the wrapped value. @@ -765,7 +791,7 @@ macro_rules! map_field_mut { #[cfg(test)] mod tests { - use super::Volatile; + use super::{access::*, Volatile}; use core::cell::UnsafeCell; #[test] @@ -790,6 +816,92 @@ mod tests { assert_eq!(val, 43); } + #[test] + fn test_access() { + let mut val: i64 = 42; + + // ReadWrite + assert_eq!(unsafe { Volatile::new(&mut val, ReadWrite) }.read(), 42); + unsafe { Volatile::new(&mut val, ReadWrite) }.write(50); + assert_eq!(val, 50); + unsafe { Volatile::new(&mut val, ReadWrite) }.update(|i| *i += 1); + assert_eq!(val, 51); + + // ReadOnly and WriteOnly + assert_eq!(unsafe { Volatile::new(&mut val, ReadOnly) }.read(), 51); + unsafe { Volatile::new(&mut val, WriteOnly) }.write(12); + assert_eq!(val, 12); + + // Custom: safe read + safe write + { + let access = Custom { + read: SafeAccess, + write: SafeAccess, + }; + let mut volatile = unsafe { Volatile::new(&mut val, access) }; + let random: i32 = rand::random(); + volatile.write(i64::from(random)); + assert_eq!(volatile.read(), i64::from(random)); + let random2: i32 = rand::random(); + volatile.update(|i| *i += i64::from(random2)); + assert_eq!(volatile.read(), i64::from(random) + i64::from(random2)); + } + + // Custom: safe read + unsafe write + { + let access = Custom { + read: SafeAccess, + write: UnsafeAccess, + }; + let mut volatile = unsafe { Volatile::new(&mut val, access) }; + let random: i32 = rand::random(); + unsafe { volatile.write_unsafe(i64::from(random)) }; + assert_eq!(volatile.read(), i64::from(random)); + let random2: i32 = rand::random(); + unsafe { volatile.update_unsafe(|i| *i += i64::from(random2)) }; + assert_eq!(volatile.read(), i64::from(random) + i64::from(random2)); + } + + // Custom: safe read + no write + { + let access = Custom { + read: SafeAccess, + write: NoAccess, + }; + let random = rand::random(); + val = random; + let mut volatile = unsafe { Volatile::new(&mut val, access) }; + assert_eq!(volatile.read(), i64::from(random)); + } + + // Custom: unsafe read + safe write + { + let access = Custom { + read: UnsafeAccess, + write: SafeAccess, + }; + let mut volatile = unsafe { Volatile::new(&mut val, access) }; + let random: i32 = rand::random(); + volatile.write(i64::from(random)); + assert_eq!(unsafe { volatile.read_unsafe() }, i64::from(random)); + let random2: i32 = rand::random(); + volatile.update_unsafe(|i| *i += i64::from(random2)); + assert_eq!( + volatile.read_unsafe(), + i64::from(random) + i64::from(random2) + ); + } + + // Todo: Custom: unsafe read + unsafe write + // Todo: Custom: unsafe read + no write + // Todo: Custom: no read + safe write + // Todo: Custom: no read + unsafe write + // Todo: Custom: no read + no write + + // Todo: is there a way to check that a compile error occurs when trying to use + // unavailable methods (e.g. `write` when write permission is `UnsafeAccess`)? + } + #[test] fn test_struct() { #[derive(Debug, PartialEq)] From 0699e4e2279437e232d45a80443e36bd33260be7 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 13 Jun 2021 13:20:16 +0200 Subject: [PATCH 04/68] Remove stabilized const_generics feature --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 745366e..7c7d779 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,6 @@ #![no_std] #![cfg_attr(feature = "unstable", feature(core_intrinsics))] -#![cfg_attr(feature = "unstable", feature(const_generics))] #![cfg_attr(feature = "unstable", feature(slice_range))] #![cfg_attr(feature = "unstable", feature(slice_ptr_get))] #![cfg_attr(feature = "unstable", feature(slice_ptr_len))] From 2ece021ac1e3cbcf0ddcc73f45a9f5cd19ec5e99 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 13 Jun 2021 15:05:43 +0200 Subject: [PATCH 05/68] Redesign access types and adjust (doc)tests --- src/access.rs | 84 +++++---- src/lib.rs | 458 ++++++++++++++++++-------------------------------- src/tests.rs | 188 +++++++++++++++++++++ 3 files changed, 391 insertions(+), 339 deletions(-) create mode 100644 src/tests.rs diff --git a/src/access.rs b/src/access.rs index d1f098e..a9ab53d 100644 --- a/src/access.rs +++ b/src/access.rs @@ -1,56 +1,48 @@ -pub trait Access {} - -/// Helper trait that is implemented by [`ReadWrite`] and [`ReadOnly`]. -pub trait Readable: UnsafelyReadable {} - -/// Helper trait that is implemented by [`ReadWrite`] and [`WriteOnly`]. -pub trait Writable: UnsafelyWritable {} - -pub trait UnsafelyReadable {} - -pub trait UnsafelyWritable {} - -/// Zero-sized marker type for allowing both read and write access. -#[derive(Debug, Copy, Clone)] -pub struct ReadWrite; -impl Access for ReadWrite {} -impl Readable for ReadWrite {} -impl UnsafelyReadable for ReadWrite {} -impl Writable for ReadWrite {} -impl UnsafelyWritable for ReadWrite {} +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct NoAccess; -/// Zero-sized marker type for allowing only read access. -#[derive(Debug, Copy, Clone)] -pub struct ReadOnly; +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct UnsafeAccess; -impl Access for ReadOnly {} -impl Readable for ReadOnly {} -impl UnsafelyReadable for ReadOnly {} +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct SafeAccess; -/// Zero-sized marker type for allowing only write access. -#[derive(Debug, Copy, Clone)] -pub struct WriteOnly; +pub trait Unsafe {} +pub trait Safe: Unsafe {} -impl Access for WriteOnly {} -impl Writable for WriteOnly {} -impl UnsafelyWritable for WriteOnly {} +impl Unsafe for UnsafeAccess {} +impl Unsafe for SafeAccess {} +impl Safe for SafeAccess {} -#[derive(Clone, Copy, PartialEq, Eq)] -pub struct Custom { +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Access { pub read: R, pub write: W, } -#[derive(Clone, Copy, PartialEq, Eq)] -pub struct NoAccess; -#[derive(Clone, Copy, PartialEq, Eq)] -pub struct SafeAccess; -#[derive(Clone, Copy, PartialEq, Eq)] -pub struct UnsafeAccess; +impl Access { + pub const fn read_only() -> ReadOnly { + Access { + read: SafeAccess, + write: NoAccess, + } + } + + pub fn write_only() -> WriteOnly { + Access { + read: NoAccess, + write: SafeAccess, + } + } + + pub fn read_write() -> ReadWrite { + Access { + read: SafeAccess, + write: SafeAccess, + } + } +} -impl Readable for Custom {} -impl UnsafelyReadable for Custom {} -impl UnsafelyReadable for Custom {} -impl Writable for Custom {} -impl UnsafelyWritable for Custom {} -impl UnsafelyWritable for Custom {} +pub type ReadOnly = Access; +pub type WriteOnly = Access; +pub type ReadWrite = Access; diff --git a/src/lib.rs b/src/lib.rs index 7c7d779..a62e649 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,10 +15,8 @@ #![warn(missing_docs)] #![deny(unsafe_op_in_unsafe_fn)] -use access::{ - ReadOnly, ReadWrite, Readable, UnsafelyReadable, UnsafelyWritable, Writable, WriteOnly, -}; -use core::{fmt, marker::PhantomData, ptr}; +use access::{Access, ReadOnly, SafeAccess}; +use core::{fmt, marker::PhantomData, mem, ptr}; #[cfg(feature = "unstable")] use core::{ intrinsics, @@ -29,6 +27,41 @@ use core::{ /// Allows creating read-only and write-only `Volatile` values. pub mod access; +/// TODO +/// +/// ## Examples +/// +/// Accessing a struct field: +/// +/// ``` +/// use volatile::{Volatile, map_field}; +/// +/// struct Example { field_1: u32, field_2: u8, } +/// let mut value = Example { field_1: 15, field_2: 255 }; +/// let mut volatile = unsafe { Volatile::from_mut_ptr(&mut value) }; +/// +/// // construct a volatile reference to a field +/// let field_2 = map_field!(volatile.field_2); +/// assert_eq!(field_2.read(), 255); +/// ``` +#[macro_export] +macro_rules! map_field { + ($volatile:ident.$place:ident) => { + unsafe { $volatile.map(|ptr| core::ptr::addr_of_mut!((*ptr).$place)) } + }; +} + +#[macro_export] +macro_rules! map_field_mut { + ($volatile:ident.$place:ident) => { + unsafe { $volatile.map_mut(|ptr| core::ptr::addr_of_mut!((*ptr).$place)) } + }; +} + +// this must be defined after the `map_field` macros +#[cfg(test)] +mod tests; + /// Wraps a reference to make accesses to the referenced value volatile. /// /// Allows volatile reads and writes on the referenced value. The referenced value needs to @@ -42,7 +75,7 @@ pub mod access; /// /// The size of this struct is the same as the size of the contained reference. #[repr(transparent)] -pub struct Volatile +pub struct Volatile> where T: ?Sized, { @@ -76,29 +109,29 @@ where /// /// let mut value = 0u32; /// - /// let mut volatile = Volatile::new(&mut value); + /// let mut volatile = unsafe { Volatile::from_mut_ptr(&mut value) }; /// volatile.write(1); /// assert_eq!(volatile.read(), 1); /// ``` - pub unsafe fn new(pointer: *mut T, access: A) -> Volatile { - let _: A = access; + pub const unsafe fn new(pointer: *mut T, access: A) -> Volatile { + mem::forget(access); // needed because we cannot require `A: Copy` on stable Rust yet Volatile { pointer, access: PhantomData, } } - pub unsafe fn from_ptr(pointer: *const T) -> Volatile { - unsafe { Volatile::new(pointer as *mut _, ReadOnly) } + pub const unsafe fn from_ptr(pointer: *const T) -> Volatile { + unsafe { Volatile::new(pointer as *mut _, Access::read_only()) } } pub unsafe fn from_mut_ptr(pointer: *mut T) -> Volatile { - unsafe { Volatile::new(pointer, ReadWrite) } + unsafe { Volatile::new(pointer, Access::read_write()) } } } /// Methods for references to `Copy` types -impl<'a, T, A> Volatile +impl<'a, T, R, W> Volatile> where T: Copy + ?Sized, { @@ -115,19 +148,19 @@ where /// use volatile::Volatile; /// /// let value = 42; - /// let shared_reference = Volatile::new(&value); + /// let shared_reference = unsafe { Volatile::from_ptr(&value) }; /// assert_eq!(shared_reference.read(), 42); /// /// let mut value = 50; - /// let mut_reference = Volatile::new(&mut value); + /// let mut_reference = unsafe { Volatile::from_mut_ptr(&mut value) }; /// assert_eq!(mut_reference.read(), 50); /// ``` pub fn read(&self) -> T where - A: Readable, + R: access::Safe, { // UNSAFE: Safe, as ... TODO - unsafe { ptr::read_volatile(self.pointer) } + unsafe { self.read_unsafe() } } /// Performs a volatile write, setting the contained value to the given `value`. @@ -142,17 +175,17 @@ where /// use volatile::Volatile; /// /// let mut value = 42; - /// let mut volatile = Volatile::new(&mut value); + /// let mut volatile = unsafe { Volatile::from_mut_ptr(&mut value) }; /// volatile.write(50); /// /// assert_eq!(volatile.read(), 50); /// ``` pub fn write(&mut self, value: T) where - A: Writable, + W: access::Safe, { // UNSAFE: Safe, as ... TODO - unsafe { ptr::write_volatile(self.pointer, value) }; + unsafe { self.write_unsafe(value) }; } /// Updates the contained value using the given closure and volatile instructions. @@ -165,43 +198,18 @@ where /// use volatile::Volatile; /// /// let mut value = 42; - /// let mut volatile = Volatile::new(&mut value); + /// let mut volatile = unsafe { Volatile::from_mut_ptr(&mut value) }; /// volatile.update(|val| *val += 1); /// /// assert_eq!(volatile.read(), 43); /// ``` pub fn update(&mut self, f: F) where - A: Readable + Writable, + R: access::Safe, + W: access::Safe, F: FnOnce(&mut T), { - let mut value = self.read(); - f(&mut value); - self.write(value); - } - - pub unsafe fn read_unsafe(&self) -> T - where - A: UnsafelyReadable, - { - unsafe { ptr::read_volatile(self.pointer) } - } - - pub unsafe fn write_unsafe(&mut self, value: T) - where - A: UnsafelyWritable, - { - unsafe { ptr::write_volatile(self.pointer, value) }; - } - - pub unsafe fn update_unsafe(&mut self, f: F) - where - A: UnsafelyReadable + UnsafelyWritable, - F: FnOnce(&mut T), - { - let mut value = unsafe { self.read_unsafe() }; - f(&mut value); - unsafe { self.write_unsafe(value) }; + unsafe { self.update_unsafe(f) } } } @@ -228,11 +236,11 @@ where /// use volatile::Volatile; /// /// let mut value = 42; - /// let mut volatile = Volatile::new(&mut value); + /// let mut volatile = unsafe { Volatile::from_mut_ptr(&mut value) }; /// volatile.write(50); - /// let unwrapped: &mut i32 = volatile.extract_inner(); + /// let unwrapped: *mut i32 = volatile.as_ptr(); /// - /// assert_eq!(*unwrapped, 50); // non volatile access, be careful! + /// assert_eq!(unsafe { *unwrapped }, 50); // non volatile access, be careful! /// ``` pub fn as_ptr(&self) -> *mut T { self.pointer @@ -240,17 +248,15 @@ where } /// Transformation methods for accessing struct fields -impl Volatile +impl Volatile> where T: ?Sized, { - /// Constructs a new `Volatile` reference by mapping the wrapped value. + /// Constructs a new `Volatile` reference by mapping the wrapped pointer. /// - /// This method is useful for accessing individual fields of volatile structs. - /// - /// Note that this method gives temporary access to the wrapped reference, which allows - /// accessing the value in a non-volatile way. This is normally not what you want, so - /// **this method should only be used for reference-to-reference transformations**. + /// This method is useful for accessing only a part of a volatile value, e.g. a subslice or + /// a struct field. For struct field access, there is also the safe [`map_field`] macro that + /// wraps this function. /// /// ## Examples /// @@ -261,10 +267,10 @@ where /// /// struct Example { field_1: u32, field_2: u8, } /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = Volatile::new(&mut value); + /// let mut volatile = unsafe { Volatile::from_mut_ptr(&mut value) }; /// /// // construct a volatile reference to a field - /// let field_2 = volatile.map(|example| &example.field_2); + /// let field_2 = unsafe { volatile.map(|ptr| core::ptr::addr_of_mut!((*ptr).field_2)) }; /// assert_eq!(field_2.read(), 255); /// ``` /// @@ -274,16 +280,16 @@ where /// use volatile::Volatile; /// /// let mut value = 5; - /// let mut volatile = Volatile::new(&mut value); + /// let mut volatile = unsafe { Volatile::from_mut_ptr(&mut value) }; /// /// // DON'T DO THIS: /// let mut readout = 0; - /// volatile.map(|value| { + /// unsafe { volatile.map(|value| { /// readout = *value; // non-volatile read, might lead to bugs /// value - /// }); + /// })}; /// ``` - pub unsafe fn map(&self, f: F) -> Volatile + pub unsafe fn map(&self, f: F) -> Volatile> where F: FnOnce(*mut T) -> *mut U, U: ?Sized, @@ -294,7 +300,7 @@ where } } - pub unsafe fn map_mut(&mut self, f: F) -> Volatile + pub unsafe fn map_mut(&mut self, f: F) -> Volatile> where F: FnOnce(*mut T) -> *mut U, U: ?Sized, @@ -308,7 +314,7 @@ where /// Methods for volatile slices #[cfg(feature = "unstable")] -impl<'a, T, A> Volatile<[T], A> { +impl<'a, T, R, W> Volatile<[T], Access> { /// Applies the index operation on the wrapped slice. /// /// Returns a shared `Volatile` reference to the resulting subslice. @@ -341,14 +347,14 @@ impl<'a, T, A> Volatile<[T], A> { /// let subslice = volatile.index(1..); /// assert_eq!(subslice.index(0).read(), 2); /// ``` - pub fn index(&self, index: I) -> Volatile + pub fn index(&self, index: I) -> Volatile> where I: SliceIndex<[T]>, { unsafe { self.map(|slice| slice.get_unchecked_mut(index)) } } - pub fn index_mut(&mut self, index: I) -> Volatile + pub fn index_mut(&mut self, index: I) -> Volatile> where I: SliceIndex<[T]>, { @@ -515,14 +521,23 @@ impl<'a, T, A> Volatile<[T], A> { } } - pub fn split_at(&self, mid: usize) -> (Volatile<[T], ReadOnly>, Volatile<[T], ReadOnly>) { + pub fn split_at( + &self, + mid: usize, + ) -> ( + Volatile<[T], Access>, + Volatile<[T], Access>, + ) { assert!(mid <= self.pointer.len()); // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which // fulfills the requirements of `from_raw_parts_mut`. unsafe { self.split_at_unchecked(mid) } } - pub fn split_at_mut(&mut self, mid: usize) -> (Volatile<[T], A>, Volatile<[T], A>) { + pub fn split_at_mut( + &mut self, + mid: usize, + ) -> (Volatile<[T], Access>, Volatile<[T], Access>) { assert!(mid <= self.pointer.len()); // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which // fulfills the requirements of `from_raw_parts_mut`. @@ -532,7 +547,10 @@ impl<'a, T, A> Volatile<[T], A> { unsafe fn split_at_unchecked( &self, mid: usize, - ) -> (Volatile<[T], ReadOnly>, Volatile<[T], ReadOnly>) { + ) -> ( + Volatile<[T], Access>, + Volatile<[T], Access>, + ) { // SAFETY: Caller has to check that `0 <= mid <= self.len()` unsafe { ( @@ -551,7 +569,7 @@ impl<'a, T, A> Volatile<[T], A> { unsafe fn split_at_mut_unchecked( &mut self, mid: usize, - ) -> (Volatile<[T], A>, Volatile<[T], A>) { + ) -> (Volatile<[T], Access>, Volatile<[T], Access>) { let len = self.pointer.len(); let ptr = self.pointer.as_mut_ptr(); @@ -575,7 +593,10 @@ impl<'a, T, A> Volatile<[T], A> { pub fn as_chunks( &self, - ) -> (Volatile<[[T; N]], ReadOnly>, Volatile<[T], ReadOnly>) { + ) -> ( + Volatile<[[T; N]], Access>, + Volatile<[T], Access>, + ) { assert_ne!(N, 0); let len = self.pointer.len() / N; let (multiple_of_n, remainder) = self.split_at(len * N); @@ -585,22 +606,29 @@ impl<'a, T, A> Volatile<[T], A> { (array_slice, remainder) } - pub unsafe fn as_chunks_unchecked(&self) -> Volatile<[[T; N]], ReadOnly> { + pub unsafe fn as_chunks_unchecked( + &self, + ) -> Volatile<[[T; N]], Access> { debug_assert_ne!(N, 0); debug_assert_eq!(self.pointer.len() % N, 0); let new_len = // SAFETY: Our precondition is exactly what's needed to call this - unsafe { crate::intrinsics::exact_div(self.pointer.len(), N) }; + unsafe { core::intrinsics::exact_div(self.pointer.len(), N) }; // SAFETY: We cast a slice of `new_len * N` elements into // a slice of `new_len` many `N` elements chunks. let pointer = ptr::slice_from_raw_parts_mut(self.pointer.as_mut_ptr().cast(), new_len); Volatile { - pointer: pointer, + pointer, access: PhantomData, } } - pub fn as_chunks_mut(&mut self) -> (Volatile<[[T; N]], A>, Volatile<[T], A>) { + pub fn as_chunks_mut( + &mut self, + ) -> ( + Volatile<[[T; N]], Access>, + Volatile<[T], Access>, + ) { assert_ne!(N, 0); let len = self.pointer.len() / N; let (multiple_of_n, remainder) = self.split_at_mut(len * N); @@ -610,12 +638,14 @@ impl<'a, T, A> Volatile<[T], A> { (array_slice, remainder) } - pub unsafe fn as_chunks_unchecked_mut(&mut self) -> Volatile<[[T; N]], A> { + pub unsafe fn as_chunks_unchecked_mut( + &mut self, + ) -> Volatile<[[T; N]], Access> { debug_assert_ne!(N, 0); debug_assert_eq!(self.pointer.len() % N, 0); let new_len = // SAFETY: Our precondition is exactly what's needed to call this - unsafe { crate::intrinsics::exact_div(self.pointer.len(), N) }; + unsafe { core::intrinsics::exact_div(self.pointer.len(), N) }; // SAFETY: We cast a slice of `new_len * N` elements into // a slice of `new_len` many `N` elements chunks. let pointer = ptr::slice_from_raw_parts_mut(self.pointer.as_mut_ptr().cast(), new_len); @@ -625,12 +655,14 @@ impl<'a, T, A> Volatile<[T], A> { } } - pub unsafe fn as_chunks_unchecked_by_val(self) -> Volatile<[[T; N]], A> { + pub unsafe fn as_chunks_unchecked_by_val( + self, + ) -> Volatile<[[T; N]], Access> { debug_assert_ne!(N, 0); debug_assert_eq!(self.pointer.len() % N, 0); let new_len = // SAFETY: Our precondition is exactly what's needed to call this - unsafe { crate::intrinsics::exact_div(self.pointer.len(), N) }; + unsafe { core::intrinsics::exact_div(self.pointer.len(), N) }; // SAFETY: We cast a slice of `new_len * N` elements into // a slice of `new_len` many `N` elements chunks. let pointer = ptr::slice_from_raw_parts_mut(self.pointer.as_mut_ptr().cast(), new_len); @@ -661,7 +693,7 @@ impl Volatile<[u8], A> { /// /// let mut buf = Volatile::new(vec![0; 10]); /// buf.fill(1); - /// assert_eq!(buf.extract_inner(), vec![1; 10]); + /// assert_eq!(buf.as_ptr(), vec![1; 10]); /// ``` pub fn fill(&mut self, value: u8) { unsafe { @@ -675,7 +707,7 @@ impl Volatile<[u8], A> { /// These methods are only available with the `unstable` feature enabled (requires a nightly /// Rust compiler). #[cfg(feature = "unstable")] -impl Volatile<[T; N], A> { +impl Volatile<[T; N], Access> { /// Converts an array reference to a shared slice. /// /// This makes it possible to use the methods defined on slices. @@ -698,13 +730,13 @@ impl Volatile<[T; N], A> { /// /// assert_eq!(dst, [1, 2]); /// ``` - pub fn as_slice(&self) -> Volatile<[T], ReadOnly> { + pub fn as_slice(&self) -> Volatile<[T], Access> { unsafe { self.map(|array| ptr::slice_from_raw_parts_mut(array as *mut T, N)) } } } /// Methods for restricting access. -impl<'a, T> Volatile +impl<'a, T, R, W> Volatile> where T: ?Sized, { @@ -716,13 +748,13 @@ where /// use volatile::Volatile; /// /// let mut value: i16 = -4; - /// let mut volatile = Volatile::new(&mut value); + /// let mut volatile = unsafe { Volatile::from_mut_ptr(&mut value) }; /// /// let read_only = volatile.read_only(); /// assert_eq!(read_only.read(), -4); /// // read_only.write(10); // compile-time error /// ``` - pub fn read_only(self) -> Volatile { + pub fn read_only(self) -> Volatile> { Volatile { pointer: self.pointer, access: PhantomData, @@ -736,18 +768,18 @@ where /// Creating a write-only reference to a struct field: /// /// ``` - /// use volatile::Volatile; + /// use volatile::{Volatile, map_field_mut}; /// /// struct Example { field_1: u32, field_2: u8, } /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = Volatile::new(&mut value); + /// let mut volatile = unsafe { Volatile::from_mut_ptr(&mut value) }; /// /// // construct a volatile write-only reference to `field_2` - /// let mut field_2 = volatile.map_mut(|example| &mut example.field_2).write_only(); + /// let mut field_2 = map_field_mut!(volatile.field_2).write_only(); /// field_2.write(14); /// // field_2.read(); // compile-time error /// ``` - pub fn write_only(self) -> Volatile { + pub fn write_only(self) -> Volatile> { Volatile { pointer: self.pointer, access: PhantomData, @@ -755,222 +787,62 @@ where } } -impl fmt::Debug for Volatile +/// Unsafe access methods for references to `Copy` types +impl<'a, T, R, W> Volatile> +where + T: Copy + ?Sized, +{ + pub unsafe fn read_unsafe(&self) -> T + where + R: access::Unsafe, + { + unsafe { ptr::read_volatile(self.pointer) } + } + + pub unsafe fn write_unsafe(&mut self, value: T) + where + W: access::Unsafe, + { + unsafe { ptr::write_volatile(self.pointer, value) }; + } + + pub unsafe fn update_unsafe(&mut self, f: F) + where + R: access::Unsafe, + W: access::Unsafe, + F: FnOnce(&mut T), + { + let mut value = unsafe { self.read_unsafe() }; + f(&mut value); + unsafe { self.write_unsafe(value) }; + } +} + +impl fmt::Debug for Volatile> where T: Copy + fmt::Debug + ?Sized, - A: Readable, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("Volatile").field(&self.read()).finish() } } -impl fmt::Debug for Volatile +impl fmt::Debug for Volatile> where T: ?Sized, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Volatile").field(&"[write-only]").finish() + f.debug_tuple("Volatile").field(&"[unsafe read]").finish() } } -#[macro_export] -macro_rules! map_field { - ($volatile:ident.$place:ident) => { - unsafe { $volatile.map(|ptr| core::ptr::addr_of_mut!((*ptr).$place)) } - }; -} - -#[macro_export] -macro_rules! map_field_mut { - ($volatile:ident.$place:ident) => { - unsafe { $volatile.map_mut(|ptr| core::ptr::addr_of_mut!((*ptr).$place)) } - }; -} - -#[cfg(test)] -mod tests { - use super::{access::*, Volatile}; - use core::cell::UnsafeCell; - - #[test] - fn test_read() { - let val = 42; - assert_eq!(unsafe { Volatile::from_ptr(&val) }.read(), 42); - } - - #[test] - fn test_write() { - let mut val = 50; - let mut volatile = unsafe { Volatile::from_mut_ptr(&mut val) }; - volatile.write(50); - assert_eq!(val, 50); - } - - #[test] - fn test_update() { - let mut val = 42; - let mut volatile = unsafe { Volatile::from_mut_ptr(&mut val) }; - volatile.update(|v| *v += 1); - assert_eq!(val, 43); - } - - #[test] - fn test_access() { - let mut val: i64 = 42; - - // ReadWrite - assert_eq!(unsafe { Volatile::new(&mut val, ReadWrite) }.read(), 42); - unsafe { Volatile::new(&mut val, ReadWrite) }.write(50); - assert_eq!(val, 50); - unsafe { Volatile::new(&mut val, ReadWrite) }.update(|i| *i += 1); - assert_eq!(val, 51); - - // ReadOnly and WriteOnly - assert_eq!(unsafe { Volatile::new(&mut val, ReadOnly) }.read(), 51); - unsafe { Volatile::new(&mut val, WriteOnly) }.write(12); - assert_eq!(val, 12); - - // Custom: safe read + safe write - { - let access = Custom { - read: SafeAccess, - write: SafeAccess, - }; - let mut volatile = unsafe { Volatile::new(&mut val, access) }; - let random: i32 = rand::random(); - volatile.write(i64::from(random)); - assert_eq!(volatile.read(), i64::from(random)); - let random2: i32 = rand::random(); - volatile.update(|i| *i += i64::from(random2)); - assert_eq!(volatile.read(), i64::from(random) + i64::from(random2)); - } - - // Custom: safe read + unsafe write - { - let access = Custom { - read: SafeAccess, - write: UnsafeAccess, - }; - let mut volatile = unsafe { Volatile::new(&mut val, access) }; - let random: i32 = rand::random(); - unsafe { volatile.write_unsafe(i64::from(random)) }; - assert_eq!(volatile.read(), i64::from(random)); - let random2: i32 = rand::random(); - unsafe { volatile.update_unsafe(|i| *i += i64::from(random2)) }; - assert_eq!(volatile.read(), i64::from(random) + i64::from(random2)); - } - - // Custom: safe read + no write - { - let access = Custom { - read: SafeAccess, - write: NoAccess, - }; - let random = rand::random(); - val = random; - let mut volatile = unsafe { Volatile::new(&mut val, access) }; - assert_eq!(volatile.read(), i64::from(random)); - } - - // Custom: unsafe read + safe write - { - let access = Custom { - read: UnsafeAccess, - write: SafeAccess, - }; - let mut volatile = unsafe { Volatile::new(&mut val, access) }; - let random: i32 = rand::random(); - volatile.write(i64::from(random)); - assert_eq!(unsafe { volatile.read_unsafe() }, i64::from(random)); - let random2: i32 = rand::random(); - volatile.update_unsafe(|i| *i += i64::from(random2)); - assert_eq!( - volatile.read_unsafe(), - i64::from(random) + i64::from(random2) - ); - } - - // Todo: Custom: unsafe read + unsafe write - // Todo: Custom: unsafe read + no write - // Todo: Custom: no read + safe write - // Todo: Custom: no read + unsafe write - // Todo: Custom: no read + no write - - // Todo: is there a way to check that a compile error occurs when trying to use - // unavailable methods (e.g. `write` when write permission is `UnsafeAccess`)? - } - - #[test] - fn test_struct() { - #[derive(Debug, PartialEq)] - struct S { - field_1: u32, - field_2: bool, - } - - let mut val = S { - field_1: 60, - field_2: true, - }; - let mut volatile = unsafe { Volatile::from_mut_ptr(&mut val) }; - unsafe { volatile.map_mut(|s| core::ptr::addr_of_mut!((*s).field_1)) }.update(|v| *v += 1); - let mut field_2 = unsafe { volatile.map_mut(|s| core::ptr::addr_of_mut!((*s).field_2)) }; - assert!(field_2.read()); - field_2.write(false); - assert_eq!( - val, - S { - field_1: 61, - field_2: false - } - ); - } - - #[test] - fn test_struct_macro() { - #[derive(Debug, PartialEq)] - struct S { - field_1: u32, - field_2: bool, - } - - let mut val = S { - field_1: 60, - field_2: true, - }; - let mut volatile = unsafe { Volatile::from_mut_ptr(&mut val) }; - let mut field_1 = map_field_mut!(volatile.field_1); - field_1.update(|v| *v += 1); - let mut field_2 = map_field_mut!(volatile.field_2); - assert!(field_2.read()); - field_2.write(false); - assert_eq!( - val, - S { - field_1: 61, - field_2: false - } - ); - } - - #[cfg(feature = "unstable")] - #[test] - fn test_slice() { - let mut val: &mut [u32] = &mut [1, 2, 3]; - let mut volatile = Volatile::from_mut_ptr(val); - volatile.index_mut(0).update(|v| *v += 1); - assert_eq!(val, [2, 2, 3]); - } - - #[cfg(feature = "unstable")] - #[test] - fn test_chunks() { - let mut val: &mut [u32] = &mut [1, 2, 3, 4, 5, 6]; - let mut volatile = Volatile::from_mut_ptr(val); - let mut chunks = volatile.as_chunks_mut().0; - chunks.index_mut(1).write([10, 11, 12]); - assert_eq!(chunks.index(0).read(), [1, 2, 3]); - assert_eq!(chunks.index(1).read(), [10, 11, 12]); +impl fmt::Debug for Volatile> +where + T: ?Sized, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Volatile") + .field(&"[no read access]") + .finish() } } diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..5dcf9a9 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,188 @@ +use super::{access::*, Volatile}; + +#[test] +fn test_read() { + let val = 42; + assert_eq!(unsafe { Volatile::from_ptr(&val) }.read(), 42); +} + +#[test] +fn test_write() { + let mut val = 50; + let mut volatile = unsafe { Volatile::from_mut_ptr(&mut val) }; + volatile.write(50); + assert_eq!(val, 50); +} + +#[test] +fn test_update() { + let mut val = 42; + let mut volatile = unsafe { Volatile::from_mut_ptr(&mut val) }; + volatile.update(|v| *v += 1); + assert_eq!(val, 43); +} + +#[test] +fn test_access() { + let mut val: i64 = 42; + + // ReadWrite + assert_eq!( + unsafe { Volatile::new(&mut val, Access::read_write()) }.read(), + 42 + ); + unsafe { Volatile::new(&mut val, Access::read_write()) }.write(50); + assert_eq!(val, 50); + unsafe { Volatile::new(&mut val, Access::read_write()) }.update(|i| *i += 1); + assert_eq!(val, 51); + + // ReadOnly and WriteOnly + assert_eq!( + unsafe { Volatile::new(&mut val, Access::read_only()) }.read(), + 51 + ); + unsafe { Volatile::new(&mut val, Access::write_only()) }.write(12); + assert_eq!(val, 12); + + // Custom: safe read + safe write + { + let access = Access { + read: SafeAccess, + write: SafeAccess, + }; + let mut volatile = unsafe { Volatile::new(&mut val, access) }; + let random: i32 = rand::random(); + volatile.write(i64::from(random)); + assert_eq!(volatile.read(), i64::from(random)); + let random2: i32 = rand::random(); + volatile.update(|i| *i += i64::from(random2)); + assert_eq!(volatile.read(), i64::from(random) + i64::from(random2)); + } + + // Custom: safe read + unsafe write + { + let access = Access { + read: SafeAccess, + write: UnsafeAccess, + }; + let mut volatile = unsafe { Volatile::new(&mut val, access) }; + let random: i32 = rand::random(); + unsafe { volatile.write_unsafe(i64::from(random)) }; + assert_eq!(volatile.read(), i64::from(random)); + let random2: i32 = rand::random(); + unsafe { volatile.update_unsafe(|i| *i += i64::from(random2)) }; + assert_eq!(volatile.read(), i64::from(random) + i64::from(random2)); + } + + // Custom: safe read + no write + { + let access = Access { + read: SafeAccess, + write: NoAccess, + }; + let random = rand::random(); + val = random; + let mut volatile = unsafe { Volatile::new(&mut val, access) }; + assert_eq!(volatile.read(), i64::from(random)); + } + + // Custom: unsafe read + safe write + { + let access = Access { + read: UnsafeAccess, + write: SafeAccess, + }; + let mut volatile = unsafe { Volatile::new(&mut val, access) }; + let random: i32 = rand::random(); + volatile.write(i64::from(random)); + assert_eq!(unsafe { volatile.read_unsafe() }, i64::from(random)); + let random2: i32 = rand::random(); + unsafe { volatile.update_unsafe(|i| *i += i64::from(random2)) }; + assert_eq!( + unsafe { volatile.read_unsafe() }, + i64::from(random) + i64::from(random2) + ); + } + + // Todo: Custom: unsafe read + unsafe write + // Todo: Custom: unsafe read + no write + // Todo: Custom: no read + safe write + // Todo: Custom: no read + unsafe write + // Todo: Custom: no read + no write + + // Todo: is there a way to check that a compile error occurs when trying to use + // unavailable methods (e.g. `write` when write permission is `UnsafeAccess`)? +} + +#[test] +fn test_struct() { + #[derive(Debug, PartialEq)] + struct S { + field_1: u32, + field_2: bool, + } + + let mut val = S { + field_1: 60, + field_2: true, + }; + let mut volatile = unsafe { Volatile::from_mut_ptr(&mut val) }; + unsafe { volatile.map_mut(|s| core::ptr::addr_of_mut!((*s).field_1)) }.update(|v| *v += 1); + let mut field_2 = unsafe { volatile.map_mut(|s| core::ptr::addr_of_mut!((*s).field_2)) }; + assert!(field_2.read()); + field_2.write(false); + assert_eq!( + val, + S { + field_1: 61, + field_2: false + } + ); +} + +#[test] +fn test_struct_macro() { + #[derive(Debug, PartialEq)] + struct S { + field_1: u32, + field_2: bool, + } + + let mut val = S { + field_1: 60, + field_2: true, + }; + let mut volatile = unsafe { Volatile::from_mut_ptr(&mut val) }; + let mut field_1 = map_field_mut!(volatile.field_1); + field_1.update(|v| *v += 1); + let mut field_2 = map_field_mut!(volatile.field_2); + assert!(field_2.read()); + field_2.write(false); + assert_eq!( + val, + S { + field_1: 61, + field_2: false + } + ); +} + +#[cfg(feature = "unstable")] +#[test] +fn test_slice() { + let mut val: &mut [u32] = &mut [1, 2, 3]; + let mut volatile = unsafe { Volatile::from_mut_ptr(val) }; + volatile.index_mut(0).update(|v| *v += 1); + assert_eq!(val, [2, 2, 3]); +} + +#[cfg(feature = "unstable")] +#[test] +fn test_chunks() { + let mut val: &mut [u32] = &mut [1, 2, 3, 4, 5, 6]; + let mut volatile = unsafe { Volatile::from_mut_ptr(val) }; + let mut chunks = volatile.as_chunks_mut().0; + chunks.index_mut(1).write([10, 11, 12]); + assert_eq!(chunks.index(0).read(), [1, 2, 3]); + assert_eq!(chunks.index(1).read(), [10, 11, 12]); +} From 534ec6e172fa2d6ab94ed23262335a51897b3aeb Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 13 Jun 2021 15:57:55 +0200 Subject: [PATCH 06/68] Use `NonNull` instead of `*mut T` --- src/access.rs | 4 +- src/lib.rs | 166 +++++++++++++++++++++++++++++++++++--------------- src/tests.rs | 54 ++++++++++------ 3 files changed, 154 insertions(+), 70 deletions(-) diff --git a/src/access.rs b/src/access.rs index a9ab53d..be88c83 100644 --- a/src/access.rs +++ b/src/access.rs @@ -28,14 +28,14 @@ impl Access { } } - pub fn write_only() -> WriteOnly { + pub const fn write_only() -> WriteOnly { Access { read: NoAccess, write: SafeAccess, } } - pub fn read_write() -> ReadWrite { + pub const fn read_write() -> ReadWrite { Access { read: SafeAccess, write: SafeAccess, diff --git a/src/lib.rs b/src/lib.rs index a62e649..4044cec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,8 +15,13 @@ #![warn(missing_docs)] #![deny(unsafe_op_in_unsafe_fn)] -use access::{Access, ReadOnly, SafeAccess}; -use core::{fmt, marker::PhantomData, mem, ptr}; +use access::{Access, ReadOnly, ReadWrite, SafeAccess, WriteOnly}; +use core::{ + fmt, + marker::PhantomData, + mem, + ptr::{self, NonNull}, +}; #[cfg(feature = "unstable")] use core::{ intrinsics, @@ -34,11 +39,13 @@ pub mod access; /// Accessing a struct field: /// /// ``` +/// # extern crate core; /// use volatile::{Volatile, map_field}; +/// use core::ptr::NonNull; /// /// struct Example { field_1: u32, field_2: u8, } /// let mut value = Example { field_1: 15, field_2: 255 }; -/// let mut volatile = unsafe { Volatile::from_mut_ptr(&mut value) }; +/// let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut value)) }; /// /// // construct a volatile reference to a field /// let field_2 = map_field!(volatile.field_2); @@ -47,14 +54,22 @@ pub mod access; #[macro_export] macro_rules! map_field { ($volatile:ident.$place:ident) => { - unsafe { $volatile.map(|ptr| core::ptr::addr_of_mut!((*ptr).$place)) } + unsafe { + $volatile.map(|ptr| { + core::ptr::NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).$place)).unwrap() + }) + } }; } #[macro_export] macro_rules! map_field_mut { ($volatile:ident.$place:ident) => { - unsafe { $volatile.map_mut(|ptr| core::ptr::addr_of_mut!((*ptr).$place)) } + unsafe { + $volatile.map_mut(|ptr| { + core::ptr::NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).$place)).unwrap() + }) + } }; } @@ -75,11 +90,11 @@ mod tests; /// /// The size of this struct is the same as the size of the contained reference. #[repr(transparent)] -pub struct Volatile> +pub struct Volatile where T: ?Sized, { - pointer: *mut T, + pointer: NonNull, access: PhantomData, } @@ -92,6 +107,18 @@ impl Volatile where T: ?Sized, { + pub unsafe fn new_read_write(pointer: NonNull) -> Volatile { + unsafe { Volatile::new_with_access(pointer, Access::read_write()) } + } + + pub const unsafe fn new_read_only(pointer: NonNull) -> Volatile { + unsafe { Volatile::new_with_access(pointer, Access::read_only()) } + } + + pub const unsafe fn new_write_only(pointer: NonNull) -> Volatile { + unsafe { Volatile::new_with_access(pointer, Access::write_only()) } + } + /// Constructs a new volatile instance wrapping the given reference. /// /// While it is possible to construct `Volatile` instances from arbitrary values (including @@ -105,29 +132,23 @@ where /// ## Example /// /// ```rust + /// # extern crate core; /// use volatile::Volatile; + /// use core::ptr::NonNull; /// /// let mut value = 0u32; /// - /// let mut volatile = unsafe { Volatile::from_mut_ptr(&mut value) }; + /// let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut value)) }; /// volatile.write(1); /// assert_eq!(volatile.read(), 1); /// ``` - pub const unsafe fn new(pointer: *mut T, access: A) -> Volatile { + pub const unsafe fn new_with_access(pointer: NonNull, access: A) -> Volatile { mem::forget(access); // needed because we cannot require `A: Copy` on stable Rust yet Volatile { pointer, access: PhantomData, } } - - pub const unsafe fn from_ptr(pointer: *const T) -> Volatile { - unsafe { Volatile::new(pointer as *mut _, Access::read_only()) } - } - - pub unsafe fn from_mut_ptr(pointer: *mut T) -> Volatile { - unsafe { Volatile::new(pointer, Access::read_write()) } - } } /// Methods for references to `Copy` types @@ -145,14 +166,16 @@ where /// ## Examples /// /// ```rust + /// # extern crate core; /// use volatile::Volatile; + /// use core::ptr::NonNull; /// /// let value = 42; - /// let shared_reference = unsafe { Volatile::from_ptr(&value) }; + /// let shared_reference = unsafe { Volatile::new_read_only(NonNull::from(&value)) }; /// assert_eq!(shared_reference.read(), 42); /// /// let mut value = 50; - /// let mut_reference = unsafe { Volatile::from_mut_ptr(&mut value) }; + /// let mut_reference = unsafe { Volatile::new_read_write(NonNull::from(&mut value)) }; /// assert_eq!(mut_reference.read(), 50); /// ``` pub fn read(&self) -> T @@ -172,10 +195,12 @@ where /// ## Example /// /// ```rust + /// # extern crate core; /// use volatile::Volatile; + /// use core::ptr::NonNull; /// /// let mut value = 42; - /// let mut volatile = unsafe { Volatile::from_mut_ptr(&mut value) }; + /// let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut value)) }; /// volatile.write(50); /// /// assert_eq!(volatile.read(), 50); @@ -195,10 +220,12 @@ where /// the contained value. /// /// ```rust + /// # extern crate core; /// use volatile::Volatile; + /// use core::ptr::NonNull; /// /// let mut value = 42; - /// let mut volatile = unsafe { Volatile::from_mut_ptr(&mut value) }; + /// let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut value)) }; /// volatile.update(|val| *val += 1); /// /// assert_eq!(volatile.read(), 43); @@ -233,16 +260,18 @@ where /// ## Example /// /// ``` + /// # extern crate core; /// use volatile::Volatile; + /// use core::ptr::NonNull; /// /// let mut value = 42; - /// let mut volatile = unsafe { Volatile::from_mut_ptr(&mut value) }; + /// let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut value)) }; /// volatile.write(50); - /// let unwrapped: *mut i32 = volatile.as_ptr(); + /// let unwrapped: *mut i32 = volatile.as_ptr().as_ptr(); /// /// assert_eq!(unsafe { *unwrapped }, 50); // non volatile access, be careful! /// ``` - pub fn as_ptr(&self) -> *mut T { + pub fn as_ptr(&self) -> NonNull { self.pointer } } @@ -263,35 +292,39 @@ where /// Accessing a struct field: /// /// ``` + /// # extern crate core; /// use volatile::Volatile; + /// use core::ptr::NonNull; /// /// struct Example { field_1: u32, field_2: u8, } /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = unsafe { Volatile::from_mut_ptr(&mut value) }; + /// let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut value)) }; /// /// // construct a volatile reference to a field - /// let field_2 = unsafe { volatile.map(|ptr| core::ptr::addr_of_mut!((*ptr).field_2)) }; + /// let field_2 = unsafe { volatile.map(|ptr| NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).field_2)).unwrap()) }; /// assert_eq!(field_2.read(), 255); /// ``` /// /// Don't misuse this method to do a non-volatile read of the referenced value: /// /// ``` + /// # extern crate core; /// use volatile::Volatile; + /// use core::ptr::NonNull; /// /// let mut value = 5; - /// let mut volatile = unsafe { Volatile::from_mut_ptr(&mut value) }; + /// let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut value)) }; /// /// // DON'T DO THIS: /// let mut readout = 0; /// unsafe { volatile.map(|value| { - /// readout = *value; // non-volatile read, might lead to bugs + /// readout = *value.as_ptr(); // non-volatile read, might lead to bugs /// value /// })}; /// ``` pub unsafe fn map(&self, f: F) -> Volatile> where - F: FnOnce(*mut T) -> *mut U, + F: FnOnce(NonNull) -> NonNull, U: ?Sized, { Volatile { @@ -302,7 +335,7 @@ where pub unsafe fn map_mut(&mut self, f: F) -> Volatile> where - F: FnOnce(*mut T) -> *mut U, + F: FnOnce(NonNull) -> NonNull, U: ?Sized, { Volatile { @@ -328,22 +361,26 @@ impl<'a, T, R, W> Volatile<[T], Access> { /// Accessing a single slice element: /// /// ``` + /// # extern crate core; /// use volatile::Volatile; + /// use core::ptr::NonNull; /// /// let array = [1, 2, 3]; /// let slice = &array[..]; - /// let volatile = Volatile::new(slice); + /// let volatile = unsafe { Volatile::new_read_only(NonNull::from(slice)) }; /// assert_eq!(volatile.index(1).read(), 2); /// ``` /// /// Accessing a subslice: /// /// ``` + /// # extern crate core; /// use volatile::Volatile; + /// use core::ptr::NonNull; /// /// let array = [1, 2, 3]; /// let slice = &array[..]; - /// let volatile = Volatile::new(slice); + /// let volatile = unsafe { Volatile::new_read_only(NonNull::from(slice)) }; /// let subslice = volatile.index(1..); /// assert_eq!(subslice.index(0).read(), 2); /// ``` @@ -377,12 +414,14 @@ impl<'a, T, R, W> Volatile<[T], Access> { /// Copying two elements from a volatile slice: /// /// ``` + /// # extern crate core; /// use volatile::Volatile; + /// use core::ptr::NonNull; /// /// let src = [1, 2]; /// // the `Volatile` type does not work with arrays, so convert `src` to a slice /// let slice = &src[..]; - /// let volatile = Volatile::new(slice); + /// let volatile = unsafe { Volatile::new_read_only(NonNull::from(slice)) }; /// let mut dst = [5, 0, 0]; /// /// // Because the slices have to be the same length, @@ -431,13 +470,15 @@ impl<'a, T, R, W> Volatile<[T], Access> { /// Copying two elements from a slice into a volatile slice: /// /// ``` + /// # extern crate core; /// use volatile::Volatile; + /// use core::ptr::NonNull; /// /// let src = [1, 2, 3, 4]; /// let mut dst = [0, 0]; /// // the `Volatile` type does not work with arrays, so convert `dst` to a slice /// let slice = &mut dst[..]; - /// let mut volatile = Volatile::new(slice); + /// let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(slice))}; /// /// // Because the slices have to be the same length, /// // we slice the source slice from four elements @@ -489,11 +530,13 @@ impl<'a, T, R, W> Volatile<[T], Access> { /// Copying four bytes within a slice: /// /// ``` + /// extern crate core; /// use volatile::Volatile; + /// use core::ptr::NonNull; /// /// let mut byte_array = *b"Hello, World!"; /// let mut slice: &mut [u8] = &mut byte_array[..]; - /// let mut volatile = Volatile::from_mut_ptr(slice); + /// let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(slice)) }; /// /// volatile.copy_within(1..5, 8); /// @@ -580,11 +623,12 @@ impl<'a, T, R, W> Volatile<[T], Access> { unsafe { ( Volatile { - pointer: { ptr::slice_from_raw_parts_mut(ptr, mid) }, + pointer: NonNull::new(ptr::slice_from_raw_parts_mut(ptr, mid)).unwrap(), access: self.access, }, Volatile { - pointer: { ptr::slice_from_raw_parts_mut(ptr.add(mid), len - mid) }, + pointer: NonNull::new(ptr::slice_from_raw_parts_mut(ptr.add(mid), len - mid)) + .unwrap(), access: self.access, }, ) @@ -616,7 +660,13 @@ impl<'a, T, R, W> Volatile<[T], Access> { unsafe { core::intrinsics::exact_div(self.pointer.len(), N) }; // SAFETY: We cast a slice of `new_len * N` elements into // a slice of `new_len` many `N` elements chunks. - let pointer = ptr::slice_from_raw_parts_mut(self.pointer.as_mut_ptr().cast(), new_len); + let pointer = unsafe { + NonNull::new(ptr::slice_from_raw_parts_mut( + self.pointer.as_mut_ptr().cast(), + new_len, + )) + .unwrap() + }; Volatile { pointer, access: PhantomData, @@ -648,7 +698,11 @@ impl<'a, T, R, W> Volatile<[T], Access> { unsafe { core::intrinsics::exact_div(self.pointer.len(), N) }; // SAFETY: We cast a slice of `new_len * N` elements into // a slice of `new_len` many `N` elements chunks. - let pointer = ptr::slice_from_raw_parts_mut(self.pointer.as_mut_ptr().cast(), new_len); + let pointer = NonNull::new(ptr::slice_from_raw_parts_mut( + self.pointer.as_mut_ptr().cast(), + new_len, + )) + .unwrap(); Volatile { pointer, access: self.access, @@ -665,7 +719,11 @@ impl<'a, T, R, W> Volatile<[T], Access> { unsafe { core::intrinsics::exact_div(self.pointer.len(), N) }; // SAFETY: We cast a slice of `new_len * N` elements into // a slice of `new_len` many `N` elements chunks. - let pointer = ptr::slice_from_raw_parts_mut(self.pointer.as_mut_ptr().cast(), new_len); + let pointer = NonNull::new(ptr::slice_from_raw_parts_mut( + self.pointer.as_mut_ptr().cast(), + new_len, + )) + .unwrap(); Volatile { pointer, access: self.access, @@ -689,11 +747,13 @@ impl Volatile<[u8], A> { /// ## Example /// /// ```rust + /// # extern crate core; /// use volatile::Volatile; + /// use core::ptr::NonNull; /// - /// let mut buf = Volatile::new(vec![0; 10]); + /// let mut buf = unsafe { Volatile::new_read_write(NonNull::from(vec![0; 10].as_mut_slice())) }; /// buf.fill(1); - /// assert_eq!(buf.as_ptr(), vec![1; 10]); + /// assert_eq!(unsafe { buf.as_ptr().as_mut() }, &mut vec![1; 10]); /// ``` pub fn fill(&mut self, value: u8) { unsafe { @@ -717,10 +777,12 @@ impl Volatile<[T; N], Access> { /// Copying two elements from a volatile array reference using `copy_into_slice`: /// /// ``` + /// # extern crate core; /// use volatile::Volatile; + /// use core::ptr::NonNull; /// /// let src = [1, 2]; - /// let volatile = Volatile::new(&src); + /// let volatile = unsafe { Volatile::new_read_only(NonNull::from(&src)) }; /// let mut dst = [0, 0]; /// /// // convert the `Volatile<&[i32; 2]>` array reference to a `Volatile<&[i32]>` slice @@ -731,7 +793,11 @@ impl Volatile<[T; N], Access> { /// assert_eq!(dst, [1, 2]); /// ``` pub fn as_slice(&self) -> Volatile<[T], Access> { - unsafe { self.map(|array| ptr::slice_from_raw_parts_mut(array as *mut T, N)) } + unsafe { + self.map(|array| { + NonNull::new(ptr::slice_from_raw_parts_mut(array.as_ptr() as *mut T, N)).unwrap() + }) + } } } @@ -745,10 +811,12 @@ where /// ## Example /// /// ``` + /// # extern crate core; /// use volatile::Volatile; + /// use core::ptr::NonNull; /// /// let mut value: i16 = -4; - /// let mut volatile = unsafe { Volatile::from_mut_ptr(&mut value) }; + /// let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut value)) }; /// /// let read_only = volatile.read_only(); /// assert_eq!(read_only.read(), -4); @@ -768,11 +836,13 @@ where /// Creating a write-only reference to a struct field: /// /// ``` + /// # extern crate core; /// use volatile::{Volatile, map_field_mut}; + /// use core::ptr::NonNull; /// /// struct Example { field_1: u32, field_2: u8, } /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = unsafe { Volatile::from_mut_ptr(&mut value) }; + /// let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut value)) }; /// /// // construct a volatile write-only reference to `field_2` /// let mut field_2 = map_field_mut!(volatile.field_2).write_only(); @@ -796,14 +866,14 @@ where where R: access::Unsafe, { - unsafe { ptr::read_volatile(self.pointer) } + unsafe { ptr::read_volatile(self.pointer.as_ptr()) } } pub unsafe fn write_unsafe(&mut self, value: T) where W: access::Unsafe, { - unsafe { ptr::write_volatile(self.pointer, value) }; + unsafe { ptr::write_volatile(self.pointer.as_ptr(), value) }; } pub unsafe fn update_unsafe(&mut self, f: F) diff --git a/src/tests.rs b/src/tests.rs index 5dcf9a9..0d0f0e2 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,15 +1,20 @@ +use core::ptr::NonNull; + use super::{access::*, Volatile}; #[test] fn test_read() { let val = 42; - assert_eq!(unsafe { Volatile::from_ptr(&val) }.read(), 42); + assert_eq!( + unsafe { Volatile::new_read_only(NonNull::from(&val)) }.read(), + 42 + ); } #[test] fn test_write() { let mut val = 50; - let mut volatile = unsafe { Volatile::from_mut_ptr(&mut val) }; + let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut val)) }; volatile.write(50); assert_eq!(val, 50); } @@ -17,7 +22,7 @@ fn test_write() { #[test] fn test_update() { let mut val = 42; - let mut volatile = unsafe { Volatile::from_mut_ptr(&mut val) }; + let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut val)) }; volatile.update(|v| *v += 1); assert_eq!(val, 43); } @@ -28,20 +33,21 @@ fn test_access() { // ReadWrite assert_eq!( - unsafe { Volatile::new(&mut val, Access::read_write()) }.read(), + unsafe { Volatile::new_with_access(NonNull::from(&mut val), Access::read_write()) }.read(), 42 ); - unsafe { Volatile::new(&mut val, Access::read_write()) }.write(50); + unsafe { Volatile::new_with_access(NonNull::from(&mut val), Access::read_write()) }.write(50); assert_eq!(val, 50); - unsafe { Volatile::new(&mut val, Access::read_write()) }.update(|i| *i += 1); + unsafe { Volatile::new_with_access(NonNull::from(&mut val), Access::read_write()) } + .update(|i| *i += 1); assert_eq!(val, 51); // ReadOnly and WriteOnly assert_eq!( - unsafe { Volatile::new(&mut val, Access::read_only()) }.read(), + unsafe { Volatile::new_with_access(NonNull::from(&mut val), Access::read_only()) }.read(), 51 ); - unsafe { Volatile::new(&mut val, Access::write_only()) }.write(12); + unsafe { Volatile::new_with_access(NonNull::from(&mut val), Access::write_only()) }.write(12); assert_eq!(val, 12); // Custom: safe read + safe write @@ -50,7 +56,7 @@ fn test_access() { read: SafeAccess, write: SafeAccess, }; - let mut volatile = unsafe { Volatile::new(&mut val, access) }; + let mut volatile = unsafe { Volatile::new_with_access(NonNull::from(&mut val), access) }; let random: i32 = rand::random(); volatile.write(i64::from(random)); assert_eq!(volatile.read(), i64::from(random)); @@ -65,7 +71,7 @@ fn test_access() { read: SafeAccess, write: UnsafeAccess, }; - let mut volatile = unsafe { Volatile::new(&mut val, access) }; + let mut volatile = unsafe { Volatile::new_with_access(NonNull::from(&mut val), access) }; let random: i32 = rand::random(); unsafe { volatile.write_unsafe(i64::from(random)) }; assert_eq!(volatile.read(), i64::from(random)); @@ -82,7 +88,7 @@ fn test_access() { }; let random = rand::random(); val = random; - let mut volatile = unsafe { Volatile::new(&mut val, access) }; + let mut volatile = unsafe { Volatile::new_with_access(NonNull::from(&mut val), access) }; assert_eq!(volatile.read(), i64::from(random)); } @@ -92,7 +98,7 @@ fn test_access() { read: UnsafeAccess, write: SafeAccess, }; - let mut volatile = unsafe { Volatile::new(&mut val, access) }; + let mut volatile = unsafe { Volatile::new_with_access(NonNull::from(&mut val), access) }; let random: i32 = rand::random(); volatile.write(i64::from(random)); assert_eq!(unsafe { volatile.read_unsafe() }, i64::from(random)); @@ -126,9 +132,14 @@ fn test_struct() { field_1: 60, field_2: true, }; - let mut volatile = unsafe { Volatile::from_mut_ptr(&mut val) }; - unsafe { volatile.map_mut(|s| core::ptr::addr_of_mut!((*s).field_1)) }.update(|v| *v += 1); - let mut field_2 = unsafe { volatile.map_mut(|s| core::ptr::addr_of_mut!((*s).field_2)) }; + let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut val)) }; + unsafe { + volatile.map_mut(|s| NonNull::new(core::ptr::addr_of_mut!((*s.as_ptr()).field_1)).unwrap()) + } + .update(|v| *v += 1); + let mut field_2 = unsafe { + volatile.map_mut(|s| NonNull::new(core::ptr::addr_of_mut!((*s.as_ptr()).field_2)).unwrap()) + }; assert!(field_2.read()); field_2.write(false); assert_eq!( @@ -152,7 +163,7 @@ fn test_struct_macro() { field_1: 60, field_2: true, }; - let mut volatile = unsafe { Volatile::from_mut_ptr(&mut val) }; + let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut val)) }; let mut field_1 = map_field_mut!(volatile.field_1); field_1.update(|v| *v += 1); let mut field_2 = map_field_mut!(volatile.field_2); @@ -170,17 +181,20 @@ fn test_struct_macro() { #[cfg(feature = "unstable")] #[test] fn test_slice() { - let mut val: &mut [u32] = &mut [1, 2, 3]; - let mut volatile = unsafe { Volatile::from_mut_ptr(val) }; + let val: &mut [u32] = &mut [1, 2, 3]; + let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(val)) }; volatile.index_mut(0).update(|v| *v += 1); - assert_eq!(val, [2, 2, 3]); + + let mut dst = [0; 3]; + volatile.copy_into_slice(&mut dst); + assert_eq!(dst, [2, 2, 3]); } #[cfg(feature = "unstable")] #[test] fn test_chunks() { let mut val: &mut [u32] = &mut [1, 2, 3, 4, 5, 6]; - let mut volatile = unsafe { Volatile::from_mut_ptr(val) }; + let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(val)) }; let mut chunks = volatile.as_chunks_mut().0; chunks.index_mut(1).write([10, 11, 12]); assert_eq!(chunks.index(0).read(), [1, 2, 3]); From c3d6b9a8a1692d71a90e2947b11c4c0d24a729b0 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 14 Jun 2021 10:19:15 +0200 Subject: [PATCH 07/68] Add lifetime parameter and rename to `VolatilePtr` The lifetime parameter is required to make `map_mut` sound (otherwise we might get mutable aliasing). Also: - add new `from_ref` and `from_mut_ref` contructor methods again. - add a `new_generic` constructor method to construct a `VolatilePtr` with arbitrary lifetime and access parameters. --- src/lib.rs | 255 +++++++++++++++++++++++++-------------------------- src/tests.rs | 48 ++++++---- 2 files changed, 156 insertions(+), 147 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4044cec..6f142f9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,7 @@ #![warn(missing_docs)] #![deny(unsafe_op_in_unsafe_fn)] -use access::{Access, ReadOnly, ReadWrite, SafeAccess, WriteOnly}; +use access::{Access, ReadOnly, ReadWrite, WriteOnly}; use core::{ fmt, marker::PhantomData, @@ -40,12 +40,12 @@ pub mod access; /// /// ``` /// # extern crate core; -/// use volatile::{Volatile, map_field}; +/// use volatile::{VolatilePtr, map_field}; /// use core::ptr::NonNull; /// /// struct Example { field_1: u32, field_2: u8, } /// let mut value = Example { field_1: 15, field_2: 255 }; -/// let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut value)) }; +/// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; /// /// // construct a volatile reference to a field /// let field_2 = map_field!(volatile.field_2); @@ -90,11 +90,12 @@ mod tests; /// /// The size of this struct is the same as the size of the contained reference. #[repr(transparent)] -pub struct Volatile +pub struct VolatilePtr<'a, T, A = ReadWrite> where T: ?Sized, { pointer: NonNull, + reference: PhantomData<&'a T>, access: PhantomData, } @@ -103,20 +104,20 @@ where /// These functions allow to construct a new `Volatile` instance from a reference type. While /// the `new` function creates a `Volatile` instance with unrestricted access, there are also /// functions for creating read-only or write-only instances. -impl Volatile +impl VolatilePtr<'_, T> where T: ?Sized, { - pub unsafe fn new_read_write(pointer: NonNull) -> Volatile { - unsafe { Volatile::new_with_access(pointer, Access::read_write()) } + pub unsafe fn new_read_write(pointer: NonNull) -> VolatilePtr<'static, T> { + unsafe { VolatilePtr::new_with_access(pointer, Access::read_write()) } } - pub const unsafe fn new_read_only(pointer: NonNull) -> Volatile { - unsafe { Volatile::new_with_access(pointer, Access::read_only()) } + pub const unsafe fn new_read_only(pointer: NonNull) -> VolatilePtr<'static, T, ReadOnly> { + unsafe { VolatilePtr::new_with_access(pointer, Access::read_only()) } } - pub const unsafe fn new_write_only(pointer: NonNull) -> Volatile { - unsafe { Volatile::new_with_access(pointer, Access::write_only()) } + pub const unsafe fn new_write_only(pointer: NonNull) -> VolatilePtr<'static, T, WriteOnly> { + unsafe { VolatilePtr::new_with_access(pointer, Access::write_only()) } } /// Constructs a new volatile instance wrapping the given reference. @@ -133,26 +134,48 @@ where /// /// ```rust /// # extern crate core; - /// use volatile::Volatile; + /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// /// let mut value = 0u32; /// - /// let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut value)) }; + /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; /// volatile.write(1); /// assert_eq!(volatile.read(), 1); /// ``` - pub const unsafe fn new_with_access(pointer: NonNull, access: A) -> Volatile { + pub const unsafe fn new_with_access( + pointer: NonNull, + access: A, + ) -> VolatilePtr<'static, T, A> { mem::forget(access); // needed because we cannot require `A: Copy` on stable Rust yet - Volatile { + unsafe { Self::new_generic(pointer) } + } + + pub const unsafe fn new_generic<'a, A>(pointer: NonNull) -> VolatilePtr<'a, T, A> { + VolatilePtr { pointer, + reference: PhantomData, access: PhantomData, } } + + pub fn from_ref<'a>(reference: &'a T) -> VolatilePtr<'a, T, ReadOnly> + where + T: 'a, + { + unsafe { VolatilePtr::new_generic(reference.into()) } + } + + pub fn from_mut_ref<'a>(reference: &'a mut T) -> VolatilePtr<'a, T> + where + T: 'a, + { + unsafe { VolatilePtr::new_generic(reference.into()) } + } } /// Methods for references to `Copy` types -impl<'a, T, R, W> Volatile> +impl VolatilePtr<'_, T, Access> where T: Copy + ?Sized, { @@ -167,15 +190,15 @@ where /// /// ```rust /// # extern crate core; - /// use volatile::Volatile; + /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// /// let value = 42; - /// let shared_reference = unsafe { Volatile::new_read_only(NonNull::from(&value)) }; + /// let shared_reference = unsafe { VolatilePtr::new_read_only(NonNull::from(&value)) }; /// assert_eq!(shared_reference.read(), 42); /// /// let mut value = 50; - /// let mut_reference = unsafe { Volatile::new_read_write(NonNull::from(&mut value)) }; + /// let mut_reference = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; /// assert_eq!(mut_reference.read(), 50); /// ``` pub fn read(&self) -> T @@ -196,11 +219,11 @@ where /// /// ```rust /// # extern crate core; - /// use volatile::Volatile; + /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// /// let mut value = 42; - /// let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut value)) }; + /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; /// volatile.write(50); /// /// assert_eq!(volatile.read(), 50); @@ -221,11 +244,11 @@ where /// /// ```rust /// # extern crate core; - /// use volatile::Volatile; + /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// /// let mut value = 42; - /// let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut value)) }; + /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; /// volatile.update(|val| *val += 1); /// /// assert_eq!(volatile.read(), 43); @@ -241,7 +264,7 @@ where } /// Method for extracting the wrapped value. -impl<'a, T, A> Volatile +impl VolatilePtr<'_, T, A> where T: ?Sized, { @@ -261,11 +284,11 @@ where /// /// ``` /// # extern crate core; - /// use volatile::Volatile; + /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// /// let mut value = 42; - /// let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut value)) }; + /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; /// volatile.write(50); /// let unwrapped: *mut i32 = volatile.as_ptr().as_ptr(); /// @@ -277,7 +300,7 @@ where } /// Transformation methods for accessing struct fields -impl Volatile> +impl VolatilePtr<'_, T, Access> where T: ?Sized, { @@ -293,12 +316,12 @@ where /// /// ``` /// # extern crate core; - /// use volatile::Volatile; + /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// /// struct Example { field_1: u32, field_2: u8, } /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut value)) }; + /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; /// /// // construct a volatile reference to a field /// let field_2 = unsafe { volatile.map(|ptr| NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).field_2)).unwrap()) }; @@ -309,11 +332,11 @@ where /// /// ``` /// # extern crate core; - /// use volatile::Volatile; + /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// /// let mut value = 5; - /// let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut value)) }; + /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; /// /// // DON'T DO THIS: /// let mut readout = 0; @@ -322,32 +345,26 @@ where /// value /// })}; /// ``` - pub unsafe fn map(&self, f: F) -> Volatile> + pub unsafe fn map<'a, F, U>(&'a self, f: F) -> VolatilePtr<'a, U, Access> where F: FnOnce(NonNull) -> NonNull, U: ?Sized, { - Volatile { - pointer: f(self.pointer), - access: PhantomData, - } + unsafe { VolatilePtr::new_generic(f(self.pointer)) } } - pub unsafe fn map_mut(&mut self, f: F) -> Volatile> + pub unsafe fn map_mut(&mut self, f: F) -> VolatilePtr> where F: FnOnce(NonNull) -> NonNull, U: ?Sized, { - Volatile { - pointer: f(self.pointer), - access: self.access, - } + unsafe { VolatilePtr::new_generic(f(self.pointer)) } } } /// Methods for volatile slices #[cfg(feature = "unstable")] -impl<'a, T, R, W> Volatile<[T], Access> { +impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { /// Applies the index operation on the wrapped slice. /// /// Returns a shared `Volatile` reference to the resulting subslice. @@ -362,12 +379,12 @@ impl<'a, T, R, W> Volatile<[T], Access> { /// /// ``` /// # extern crate core; - /// use volatile::Volatile; + /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// /// let array = [1, 2, 3]; /// let slice = &array[..]; - /// let volatile = unsafe { Volatile::new_read_only(NonNull::from(slice)) }; + /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(slice)) }; /// assert_eq!(volatile.index(1).read(), 2); /// ``` /// @@ -375,23 +392,23 @@ impl<'a, T, R, W> Volatile<[T], Access> { /// /// ``` /// # extern crate core; - /// use volatile::Volatile; + /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// /// let array = [1, 2, 3]; /// let slice = &array[..]; - /// let volatile = unsafe { Volatile::new_read_only(NonNull::from(slice)) }; + /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(slice)) }; /// let subslice = volatile.index(1..); /// assert_eq!(subslice.index(0).read(), 2); /// ``` - pub fn index(&self, index: I) -> Volatile> + pub fn index(&self, index: I) -> VolatilePtr> where I: SliceIndex<[T]>, { unsafe { self.map(|slice| slice.get_unchecked_mut(index)) } } - pub fn index_mut(&mut self, index: I) -> Volatile> + pub fn index_mut(&mut self, index: I) -> VolatilePtr> where I: SliceIndex<[T]>, { @@ -415,13 +432,13 @@ impl<'a, T, R, W> Volatile<[T], Access> { /// /// ``` /// # extern crate core; - /// use volatile::Volatile; + /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// /// let src = [1, 2]; /// // the `Volatile` type does not work with arrays, so convert `src` to a slice /// let slice = &src[..]; - /// let volatile = unsafe { Volatile::new_read_only(NonNull::from(slice)) }; + /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(slice)) }; /// let mut dst = [5, 0, 0]; /// /// // Because the slices have to be the same length, @@ -471,14 +488,14 @@ impl<'a, T, R, W> Volatile<[T], Access> { /// /// ``` /// # extern crate core; - /// use volatile::Volatile; + /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// /// let src = [1, 2, 3, 4]; /// let mut dst = [0, 0]; /// // the `Volatile` type does not work with arrays, so convert `dst` to a slice /// let slice = &mut dst[..]; - /// let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(slice))}; + /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(slice))}; /// /// // Because the slices have to be the same length, /// // we slice the source slice from four elements @@ -531,12 +548,12 @@ impl<'a, T, R, W> Volatile<[T], Access> { /// /// ``` /// extern crate core; - /// use volatile::Volatile; + /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// /// let mut byte_array = *b"Hello, World!"; /// let mut slice: &mut [u8] = &mut byte_array[..]; - /// let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(slice)) }; + /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(slice)) }; /// /// volatile.copy_within(1..5, 8); /// @@ -568,8 +585,8 @@ impl<'a, T, R, W> Volatile<[T], Access> { &self, mid: usize, ) -> ( - Volatile<[T], Access>, - Volatile<[T], Access>, + VolatilePtr<[T], Access>, + VolatilePtr<[T], Access>, ) { assert!(mid <= self.pointer.len()); // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which @@ -580,7 +597,10 @@ impl<'a, T, R, W> Volatile<[T], Access> { pub fn split_at_mut( &mut self, mid: usize, - ) -> (Volatile<[T], Access>, Volatile<[T], Access>) { + ) -> ( + VolatilePtr<[T], Access>, + VolatilePtr<[T], Access>, + ) { assert!(mid <= self.pointer.len()); // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which // fulfills the requirements of `from_raw_parts_mut`. @@ -591,20 +611,14 @@ impl<'a, T, R, W> Volatile<[T], Access> { &self, mid: usize, ) -> ( - Volatile<[T], Access>, - Volatile<[T], Access>, + VolatilePtr<[T], Access>, + VolatilePtr<[T], Access>, ) { // SAFETY: Caller has to check that `0 <= mid <= self.len()` unsafe { ( - Volatile { - pointer: { (self.pointer).get_unchecked_mut(..mid) }, - access: PhantomData, - }, - Volatile { - pointer: { (self.pointer).get_unchecked_mut(mid..) }, - access: PhantomData, - }, + VolatilePtr::new_generic((self.pointer).get_unchecked_mut(..mid)), + VolatilePtr::new_generic((self.pointer).get_unchecked_mut(mid..)), ) } } @@ -612,7 +626,10 @@ impl<'a, T, R, W> Volatile<[T], Access> { unsafe fn split_at_mut_unchecked( &mut self, mid: usize, - ) -> (Volatile<[T], Access>, Volatile<[T], Access>) { + ) -> ( + VolatilePtr<[T], Access>, + VolatilePtr<[T], Access>, + ) { let len = self.pointer.len(); let ptr = self.pointer.as_mut_ptr(); @@ -622,15 +639,12 @@ impl<'a, T, R, W> Volatile<[T], Access> { // is fine. unsafe { ( - Volatile { - pointer: NonNull::new(ptr::slice_from_raw_parts_mut(ptr, mid)).unwrap(), - access: self.access, - }, - Volatile { - pointer: NonNull::new(ptr::slice_from_raw_parts_mut(ptr.add(mid), len - mid)) - .unwrap(), - access: self.access, - }, + VolatilePtr::new_generic( + NonNull::new(ptr::slice_from_raw_parts_mut(ptr, mid)).unwrap(), + ), + VolatilePtr::new_generic( + NonNull::new(ptr::slice_from_raw_parts_mut(ptr.add(mid), len - mid)).unwrap(), + ), ) } } @@ -638,8 +652,8 @@ impl<'a, T, R, W> Volatile<[T], Access> { pub fn as_chunks( &self, ) -> ( - Volatile<[[T; N]], Access>, - Volatile<[T], Access>, + VolatilePtr<[[T; N]], Access>, + VolatilePtr<[T], Access>, ) { assert_ne!(N, 0); let len = self.pointer.len() / N; @@ -652,7 +666,7 @@ impl<'a, T, R, W> Volatile<[T], Access> { pub unsafe fn as_chunks_unchecked( &self, - ) -> Volatile<[[T; N]], Access> { + ) -> VolatilePtr<[[T; N]], Access> { debug_assert_ne!(N, 0); debug_assert_eq!(self.pointer.len() % N, 0); let new_len = @@ -660,24 +674,19 @@ impl<'a, T, R, W> Volatile<[T], Access> { unsafe { core::intrinsics::exact_div(self.pointer.len(), N) }; // SAFETY: We cast a slice of `new_len * N` elements into // a slice of `new_len` many `N` elements chunks. - let pointer = unsafe { - NonNull::new(ptr::slice_from_raw_parts_mut( - self.pointer.as_mut_ptr().cast(), - new_len, - )) - .unwrap() - }; - Volatile { - pointer, - access: PhantomData, - } + let pointer = NonNull::new(ptr::slice_from_raw_parts_mut( + self.pointer.as_mut_ptr().cast(), + new_len, + )) + .unwrap(); + unsafe { VolatilePtr::new_generic(pointer) } } pub fn as_chunks_mut( &mut self, ) -> ( - Volatile<[[T; N]], Access>, - Volatile<[T], Access>, + VolatilePtr<[[T; N]], Access>, + VolatilePtr<[T], Access>, ) { assert_ne!(N, 0); let len = self.pointer.len() / N; @@ -690,7 +699,7 @@ impl<'a, T, R, W> Volatile<[T], Access> { pub unsafe fn as_chunks_unchecked_mut( &mut self, - ) -> Volatile<[[T; N]], Access> { + ) -> VolatilePtr<[[T; N]], Access> { debug_assert_ne!(N, 0); debug_assert_eq!(self.pointer.len() % N, 0); let new_len = @@ -703,15 +712,12 @@ impl<'a, T, R, W> Volatile<[T], Access> { new_len, )) .unwrap(); - Volatile { - pointer, - access: self.access, - } + unsafe { VolatilePtr::new_generic(pointer) } } pub unsafe fn as_chunks_unchecked_by_val( self, - ) -> Volatile<[[T; N]], Access> { + ) -> VolatilePtr<'a, [[T; N]], Access> { debug_assert_ne!(N, 0); debug_assert_eq!(self.pointer.len() % N, 0); let new_len = @@ -724,16 +730,13 @@ impl<'a, T, R, W> Volatile<[T], Access> { new_len, )) .unwrap(); - Volatile { - pointer, - access: self.access, - } + unsafe { VolatilePtr::new_generic(pointer) } } } /// Methods for volatile byte slices #[cfg(feature = "unstable")] -impl Volatile<[u8], A> { +impl VolatilePtr<'_, [u8], A> { /// Sets all elements of the byte slice to the given `value` using a volatile `memset`. /// /// This method is similar to the `slice::fill` method of the standard library, with the @@ -748,10 +751,10 @@ impl Volatile<[u8], A> { /// /// ```rust /// # extern crate core; - /// use volatile::Volatile; + /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// - /// let mut buf = unsafe { Volatile::new_read_write(NonNull::from(vec![0; 10].as_mut_slice())) }; + /// let mut buf = unsafe { VolatilePtr::new_read_write(NonNull::from(vec![0; 10].as_mut_slice())) }; /// buf.fill(1); /// assert_eq!(unsafe { buf.as_ptr().as_mut() }, &mut vec![1; 10]); /// ``` @@ -767,7 +770,7 @@ impl Volatile<[u8], A> { /// These methods are only available with the `unstable` feature enabled (requires a nightly /// Rust compiler). #[cfg(feature = "unstable")] -impl Volatile<[T; N], Access> { +impl VolatilePtr<'_, [T; N], Access> { /// Converts an array reference to a shared slice. /// /// This makes it possible to use the methods defined on slices. @@ -778,11 +781,11 @@ impl Volatile<[T; N], Access> { /// /// ``` /// # extern crate core; - /// use volatile::Volatile; + /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// /// let src = [1, 2]; - /// let volatile = unsafe { Volatile::new_read_only(NonNull::from(&src)) }; + /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(&src)) }; /// let mut dst = [0, 0]; /// /// // convert the `Volatile<&[i32; 2]>` array reference to a `Volatile<&[i32]>` slice @@ -792,7 +795,7 @@ impl Volatile<[T; N], Access> { /// /// assert_eq!(dst, [1, 2]); /// ``` - pub fn as_slice(&self) -> Volatile<[T], Access> { + pub fn as_slice(&self) -> VolatilePtr<[T], Access> { unsafe { self.map(|array| { NonNull::new(ptr::slice_from_raw_parts_mut(array.as_ptr() as *mut T, N)).unwrap() @@ -802,7 +805,7 @@ impl Volatile<[T; N], Access> { } /// Methods for restricting access. -impl<'a, T, R, W> Volatile> +impl<'a, T, R, W> VolatilePtr<'a, T, Access> where T: ?Sized, { @@ -812,21 +815,18 @@ where /// /// ``` /// # extern crate core; - /// use volatile::Volatile; + /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// /// let mut value: i16 = -4; - /// let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut value)) }; + /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; /// /// let read_only = volatile.read_only(); /// assert_eq!(read_only.read(), -4); /// // read_only.write(10); // compile-time error /// ``` - pub fn read_only(self) -> Volatile> { - Volatile { - pointer: self.pointer, - access: PhantomData, - } + pub fn read_only(self) -> VolatilePtr<'a, T, Access> { + unsafe { VolatilePtr::new_generic(self.pointer) } } /// Restricts access permissions to write-only. @@ -837,28 +837,25 @@ where /// /// ``` /// # extern crate core; - /// use volatile::{Volatile, map_field_mut}; + /// use volatile::{VolatilePtr, map_field_mut}; /// use core::ptr::NonNull; /// /// struct Example { field_1: u32, field_2: u8, } /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut value)) }; + /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; /// /// // construct a volatile write-only reference to `field_2` /// let mut field_2 = map_field_mut!(volatile.field_2).write_only(); /// field_2.write(14); /// // field_2.read(); // compile-time error /// ``` - pub fn write_only(self) -> Volatile> { - Volatile { - pointer: self.pointer, - access: PhantomData, - } + pub fn write_only(self) -> VolatilePtr<'a, T, Access> { + unsafe { VolatilePtr::new_generic(self.pointer) } } } /// Unsafe access methods for references to `Copy` types -impl<'a, T, R, W> Volatile> +impl VolatilePtr<'_, T, Access> where T: Copy + ?Sized, { @@ -888,7 +885,7 @@ where } } -impl fmt::Debug for Volatile> +impl fmt::Debug for VolatilePtr<'_, T, Access> where T: Copy + fmt::Debug + ?Sized, { @@ -897,7 +894,7 @@ where } } -impl fmt::Debug for Volatile> +impl fmt::Debug for VolatilePtr<'_, T, Access> where T: ?Sized, { @@ -906,7 +903,7 @@ where } } -impl fmt::Debug for Volatile> +impl fmt::Debug for VolatilePtr<'_, T, Access> where T: ?Sized, { diff --git a/src/tests.rs b/src/tests.rs index 0d0f0e2..da809b3 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,12 +1,12 @@ use core::ptr::NonNull; -use super::{access::*, Volatile}; +use super::{access::*, VolatilePtr}; #[test] fn test_read() { let val = 42; assert_eq!( - unsafe { Volatile::new_read_only(NonNull::from(&val)) }.read(), + unsafe { VolatilePtr::new_read_only(NonNull::from(&val)) }.read(), 42 ); } @@ -14,7 +14,7 @@ fn test_read() { #[test] fn test_write() { let mut val = 50; - let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut val)) }; + let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut val)) }; volatile.write(50); assert_eq!(val, 50); } @@ -22,7 +22,7 @@ fn test_write() { #[test] fn test_update() { let mut val = 42; - let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut val)) }; + let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut val)) }; volatile.update(|v| *v += 1); assert_eq!(val, 43); } @@ -33,21 +33,25 @@ fn test_access() { // ReadWrite assert_eq!( - unsafe { Volatile::new_with_access(NonNull::from(&mut val), Access::read_write()) }.read(), + unsafe { VolatilePtr::new_with_access(NonNull::from(&mut val), Access::read_write()) } + .read(), 42 ); - unsafe { Volatile::new_with_access(NonNull::from(&mut val), Access::read_write()) }.write(50); + unsafe { VolatilePtr::new_with_access(NonNull::from(&mut val), Access::read_write()) } + .write(50); assert_eq!(val, 50); - unsafe { Volatile::new_with_access(NonNull::from(&mut val), Access::read_write()) } + unsafe { VolatilePtr::new_with_access(NonNull::from(&mut val), Access::read_write()) } .update(|i| *i += 1); assert_eq!(val, 51); // ReadOnly and WriteOnly assert_eq!( - unsafe { Volatile::new_with_access(NonNull::from(&mut val), Access::read_only()) }.read(), + unsafe { VolatilePtr::new_with_access(NonNull::from(&mut val), Access::read_only()) } + .read(), 51 ); - unsafe { Volatile::new_with_access(NonNull::from(&mut val), Access::write_only()) }.write(12); + unsafe { VolatilePtr::new_with_access(NonNull::from(&mut val), Access::write_only()) } + .write(12); assert_eq!(val, 12); // Custom: safe read + safe write @@ -56,7 +60,7 @@ fn test_access() { read: SafeAccess, write: SafeAccess, }; - let mut volatile = unsafe { Volatile::new_with_access(NonNull::from(&mut val), access) }; + let mut volatile = unsafe { VolatilePtr::new_with_access(NonNull::from(&mut val), access) }; let random: i32 = rand::random(); volatile.write(i64::from(random)); assert_eq!(volatile.read(), i64::from(random)); @@ -71,7 +75,7 @@ fn test_access() { read: SafeAccess, write: UnsafeAccess, }; - let mut volatile = unsafe { Volatile::new_with_access(NonNull::from(&mut val), access) }; + let mut volatile = unsafe { VolatilePtr::new_with_access(NonNull::from(&mut val), access) }; let random: i32 = rand::random(); unsafe { volatile.write_unsafe(i64::from(random)) }; assert_eq!(volatile.read(), i64::from(random)); @@ -88,7 +92,7 @@ fn test_access() { }; let random = rand::random(); val = random; - let mut volatile = unsafe { Volatile::new_with_access(NonNull::from(&mut val), access) }; + let volatile = unsafe { VolatilePtr::new_with_access(NonNull::from(&mut val), access) }; assert_eq!(volatile.read(), i64::from(random)); } @@ -98,7 +102,7 @@ fn test_access() { read: UnsafeAccess, write: SafeAccess, }; - let mut volatile = unsafe { Volatile::new_with_access(NonNull::from(&mut val), access) }; + let mut volatile = unsafe { VolatilePtr::new_with_access(NonNull::from(&mut val), access) }; let random: i32 = rand::random(); volatile.write(i64::from(random)); assert_eq!(unsafe { volatile.read_unsafe() }, i64::from(random)); @@ -132,7 +136,7 @@ fn test_struct() { field_1: 60, field_2: true, }; - let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut val)) }; + let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut val)) }; unsafe { volatile.map_mut(|s| NonNull::new(core::ptr::addr_of_mut!((*s.as_ptr()).field_1)).unwrap()) } @@ -163,7 +167,7 @@ fn test_struct_macro() { field_1: 60, field_2: true, }; - let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(&mut val)) }; + let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut val)) }; let mut field_1 = map_field_mut!(volatile.field_1); field_1.update(|v| *v += 1); let mut field_2 = map_field_mut!(volatile.field_2); @@ -182,7 +186,7 @@ fn test_struct_macro() { #[test] fn test_slice() { let val: &mut [u32] = &mut [1, 2, 3]; - let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(val)) }; + let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; volatile.index_mut(0).update(|v| *v += 1); let mut dst = [0; 3]; @@ -193,10 +197,18 @@ fn test_slice() { #[cfg(feature = "unstable")] #[test] fn test_chunks() { - let mut val: &mut [u32] = &mut [1, 2, 3, 4, 5, 6]; - let mut volatile = unsafe { Volatile::new_read_write(NonNull::from(val)) }; + let val: &mut [u32] = &mut [1, 2, 3, 4, 5, 6]; + let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; let mut chunks = volatile.as_chunks_mut().0; chunks.index_mut(1).write([10, 11, 12]); assert_eq!(chunks.index(0).read(), [1, 2, 3]); assert_eq!(chunks.index(1).read(), [10, 11, 12]); } + +#[test] +fn test_lifetime() { + let mut val = 50; + let mut volatile = VolatilePtr::from_mut_ref(&mut val); + volatile.write(50); + assert_eq!(val, 50); +} From 9f7b015a7dd976ec89214ecca0c44d2ee970e54a Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 14 Jun 2021 12:12:41 +0200 Subject: [PATCH 08/68] Fix: do bounds checking in `index` and `index_mut` --- src/lib.rs | 30 ++++++++++++++++++++++++++---- src/tests.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6f142f9..2e7c5cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -365,6 +365,10 @@ where /// Methods for volatile slices #[cfg(feature = "unstable")] impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { + pub fn len(&self) -> usize { + self.pointer.len() + } + /// Applies the index operation on the wrapped slice. /// /// Returns a shared `Volatile` reference to the resulting subslice. @@ -401,17 +405,27 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { /// let subslice = volatile.index(1..); /// assert_eq!(subslice.index(0).read(), 2); /// ``` - pub fn index(&self, index: I) -> VolatilePtr> + pub fn index( + &self, + index: I, + ) -> VolatilePtr<>::Output, Access> where - I: SliceIndex<[T]>, + I: SliceIndex<[T]> + SliceIndex<[()]> + Clone, { + bounds_check(self.pointer.len(), index.clone()); + unsafe { self.map(|slice| slice.get_unchecked_mut(index)) } } - pub fn index_mut(&mut self, index: I) -> VolatilePtr> + pub fn index_mut( + &mut self, + index: I, + ) -> VolatilePtr<>::Output, Access> where - I: SliceIndex<[T]>, + I: SliceIndex<[T]> + SliceIndex<[()]> + Clone, { + bounds_check(self.pointer.len(), index.clone()); + unsafe { self.map_mut(|slice| slice.get_unchecked_mut(index)) } } @@ -913,3 +927,11 @@ where .finish() } } + +#[cfg(feature = "unstable")] +fn bounds_check(len: usize, index: impl SliceIndex<[()]>) { + const MAX_ARRAY: [(); usize::MAX] = [(); usize::MAX]; + + let bound_check_slice = &MAX_ARRAY[..len]; + &bound_check_slice[index]; +} diff --git a/src/tests.rs b/src/tests.rs index da809b3..840c7ff 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -194,6 +194,50 @@ fn test_slice() { assert_eq!(dst, [2, 2, 3]); } +#[cfg(feature = "unstable")] +#[test] +#[should_panic] +fn test_bounds_check_1() { + let val: &mut [u32] = &mut [1, 2, 3]; + let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; + volatile.index_mut(3); +} + +#[cfg(feature = "unstable")] +#[test] +#[should_panic] +fn test_bounds_check_2() { + let val: &mut [u32] = &mut [1, 2, 3]; + let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; + volatile.index_mut(2..1); +} + +#[cfg(feature = "unstable")] +#[test] +#[should_panic] +fn test_bounds_check_3() { + let val: &mut [u32] = &mut [1, 2, 3]; + let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; + volatile.index_mut(4..); // `3..` is is still ok (see next test) +} + +#[cfg(feature = "unstable")] +#[test] +fn test_bounds_check_4() { + let val: &mut [u32] = &mut [1, 2, 3]; + let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; + assert_eq!(volatile.index_mut(3..).len(), 0); +} + +#[cfg(feature = "unstable")] +#[test] +#[should_panic] +fn test_bounds_check_5() { + let val: &mut [u32] = &mut [1, 2, 3]; + let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; + volatile.index_mut(..4); +} + #[cfg(feature = "unstable")] #[test] fn test_chunks() { From b525f383af7db00f392bd78e5b05390c252acce9 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 14 Jun 2021 12:18:06 +0200 Subject: [PATCH 09/68] Add `const` index functions under a new `very_unstable` feature --- Cargo.toml | 4 +++- src/lib.rs | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 63a2565..55940af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,8 @@ repository = "https://github.com/rust-osdev/volatile" [features] # Enable unstable features; requires Rust nightly; might break on compiler updates unstable = [] +# Enable unstable and experimental features; requires Rust nightly; might break on compiler updates +very_unstable = ["unstable"] [dev-dependencies] rand = "0.8.3" @@ -20,7 +22,7 @@ rand = "0.8.3" [package.metadata.release] no-dev-version = true pre-release-replacements = [ - { file="Changelog.md", search="# Unreleased", replace="# Unreleased\n\n# {{version}} – {{date}}", exactly=1 }, + { file = "Changelog.md", search = "# Unreleased", replace = "# Unreleased\n\n# {{version}} – {{date}}", exactly = 1 }, ] pre-release-commit-message = "Release version {{version}}" diff --git a/src/lib.rs b/src/lib.rs index 2e7c5cf..43afb11 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,13 @@ #![cfg_attr(feature = "unstable", feature(slice_range))] #![cfg_attr(feature = "unstable", feature(slice_ptr_get))] #![cfg_attr(feature = "unstable", feature(slice_ptr_len))] -#![cfg_attr(feature = "unstable", allow(incomplete_features))] +#![cfg_attr(feature = "very_unstable", feature(const_slice_ptr_len))] +#![cfg_attr(feature = "very_unstable", feature(const_panic))] +#![cfg_attr(feature = "very_unstable", feature(const_fn_trait_bound))] +#![cfg_attr(feature = "very_unstable", feature(const_fn_fn_ptr_basics))] +#![cfg_attr(feature = "very_unstable", feature(const_trait_impl))] +#![cfg_attr(feature = "very_unstable", feature(const_mut_refs))] +#![cfg_attr(feature = "very_unstable", allow(incomplete_features))] #![cfg_attr(all(feature = "unstable", test), feature(slice_as_chunks))] #![warn(missing_docs)] #![deny(unsafe_op_in_unsafe_fn)] @@ -353,6 +359,18 @@ where unsafe { VolatilePtr::new_generic(f(self.pointer)) } } + #[cfg(feature = "very_unstable")] + pub const unsafe fn map_const<'a, F, U>( + &'a self, + f: F, + ) -> VolatilePtr<'a, U, Access> + where + F: FnOnce(NonNull) -> NonNull, + U: ?Sized, + { + unsafe { VolatilePtr::new_generic(f(self.pointer)) } + } + pub unsafe fn map_mut(&mut self, f: F) -> VolatilePtr> where F: FnOnce(NonNull) -> NonNull, @@ -360,6 +378,15 @@ where { unsafe { VolatilePtr::new_generic(f(self.pointer)) } } + + #[cfg(feature = "very_unstable")] + pub const unsafe fn map_mut_const(&mut self, f: F) -> VolatilePtr> + where + F: FnOnce(NonNull) -> NonNull, + U: ?Sized, + { + unsafe { VolatilePtr::new_generic(f(self.pointer)) } + } } /// Methods for volatile slices @@ -417,6 +444,16 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { unsafe { self.map(|slice| slice.get_unchecked_mut(index)) } } + #[cfg(feature = "very_unstable")] + pub const fn index_const(&self, index: usize) -> VolatilePtr> { + assert!(index < self.pointer.len(), "index out of bounds"); + unsafe { + self.map_const(|slice| { + NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(index)) + }) + } + } + pub fn index_mut( &mut self, index: I, @@ -429,6 +466,16 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { unsafe { self.map_mut(|slice| slice.get_unchecked_mut(index)) } } + #[cfg(feature = "very_unstable")] + pub const fn index_mut_const(&mut self, index: usize) -> VolatilePtr> { + assert!(index < self.pointer.len(), "index out of bounds"); + unsafe { + self.map_mut_const(|slice| { + NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(index)) + }) + } + } + /// Copies all elements from `self` into `dst`, using a volatile memcpy. /// /// The length of `dst` must be the same as `self`. From 4accd5192b6903b5fa968904fbd7c154c20d17db Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Sat, 4 Jun 2022 17:06:32 +0200 Subject: [PATCH 10/68] add `VolatilePtr::as_slice_mut` --- src/lib.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 43afb11..053f15c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -863,6 +863,38 @@ impl VolatilePtr<'_, [T; N], Access> { }) } } + + /// Converts an array reference to a shared slice. + /// + /// This makes it possible to use the methods defined on slices. + /// + /// ## Example + /// + /// Copying two elements into a volatile array reference using `copy_from_slice`: + /// + /// ``` + /// # extern crate core; + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let src = [1, 2]; + /// let mut dst = [0, 0]; + /// let mut volatile = unsafe { VolatilePtr::new_write_only(NonNull::from(&dst)) }; + /// + /// // convert the `Volatile<[i32; 2]>` array reference to a `Volatile<[i32]>` slice + /// let mut volatile_slice = volatile.as_slice_mut(); + /// // we can now use the slice methods + /// volatile_slice.copy_from_slice(&src); + /// + /// assert_eq!(dst, [1, 2]); + /// ``` + pub fn as_slice_mut<'a>(&'a mut self) -> VolatilePtr<'a, [T], Access> { + unsafe { + self.map_mut(|array| { + NonNull::new(ptr::slice_from_raw_parts_mut(array.as_ptr() as *mut T, N)).unwrap() + }) + } + } } /// Methods for restricting access. From a9e8509b18ea19c4a41d9edd4e3ba800d5e53ed8 Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Sat, 4 Jun 2022 17:06:55 +0200 Subject: [PATCH 11/68] add `VolatilePtr::iter` & `VolatilePtr::iter_mut` --- src/lib.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 053f15c..ec92e8d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -476,6 +476,24 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { } } + /// Returns an iterator over the slice. + pub fn iter<'b>( + &'b self, + ) -> impl Iterator>> + 'b { + let len = self.len(); + (0..len).map(move |i| self.index(i)) + } + + /// Returns an iterator that allows modifying each value. + pub fn iter_mut<'b>( + &'b mut self, + ) -> impl Iterator>> + 'b { + let ptr = self.as_ptr().as_ptr() as *mut T; + let len = self.len(); + (0..len) + .map(move |i| unsafe { VolatilePtr::new_generic(NonNull::new_unchecked(ptr.add(i))) }) + } + /// Copies all elements from `self` into `dst`, using a volatile memcpy. /// /// The length of `dst` must be the same as `self`. From 167adec176cecc731049b10e1b552d38c9204242 Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Sat, 4 Jun 2022 17:08:49 +0200 Subject: [PATCH 12/68] add `VolatilePtr::is_empty` --- src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index ec92e8d..fb8ad0b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -396,6 +396,10 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { self.pointer.len() } + pub fn is_empty(&self) -> bool { + self.pointer.len() == 0 + } + /// Applies the index operation on the wrapped slice. /// /// Returns a shared `Volatile` reference to the resulting subslice. From aa93fb97f8eebd81fc01833c181652c7a6f510df Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Sat, 4 Jun 2022 17:12:31 +0200 Subject: [PATCH 13/68] add missing access where bounds --- src/lib.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fb8ad0b..bdecf16 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -535,6 +535,7 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { pub fn copy_into_slice(&self, dst: &mut [T]) where T: Copy, + R: access::Safe, { let len = self.pointer.len(); assert_eq!( @@ -591,6 +592,7 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { pub fn copy_from_slice(&mut self, src: &[T]) where T: Copy, + W: access::Safe, { let len = self.pointer.len(); assert_eq!( @@ -644,6 +646,8 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { pub fn copy_within(&mut self, src: impl RangeBounds, dest: usize) where T: Copy, + R: access::Safe, + W: access::Safe, { let len = self.pointer.len(); // implementation taken from https://github.com/rust-lang/rust/blob/683d1bcd405727fcc9209f64845bd3b9104878b8/library/core/src/slice/mod.rs#L2726-L2738 @@ -819,7 +823,7 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { /// Methods for volatile byte slices #[cfg(feature = "unstable")] -impl VolatilePtr<'_, [u8], A> { +impl VolatilePtr<'_, [u8], Access> { /// Sets all elements of the byte slice to the given `value` using a volatile `memset`. /// /// This method is similar to the `slice::fill` method of the standard library, with the @@ -841,7 +845,10 @@ impl VolatilePtr<'_, [u8], A> { /// buf.fill(1); /// assert_eq!(unsafe { buf.as_ptr().as_mut() }, &mut vec![1; 10]); /// ``` - pub fn fill(&mut self, value: u8) { + pub fn fill(&mut self, value: u8) + where + W: access::Safe, + { unsafe { intrinsics::volatile_set_memory(self.pointer.as_mut_ptr(), value, self.pointer.len()); } From 554c806c50613b0686640f881015e8d46ab11520 Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Sat, 4 Jun 2022 17:49:35 +0200 Subject: [PATCH 14/68] reject mapping to unaligned fields --- src/lib.rs | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bdecf16..95ccf73 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,26 +57,57 @@ pub mod access; /// let field_2 = map_field!(volatile.field_2); /// assert_eq!(field_2.read(), 255); /// ``` +/// +/// Creating `VolatilePtr`s to unaligned field in packed structs is not allowed: +/// ```compile_fail +/// # extern crate core; +/// use volatile::{VolatilePtr, map_field}; +/// use core::ptr::NonNull; +/// +/// #[repr(packed)] +/// struct Example { field_1: u8, field_2: usize, } +/// let mut value = Example { field_1: 15, field_2: 255 }; +/// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; +/// +/// // Constructing a volatile reference to an unaligned field doesn't compile. +/// let field_2 = map_field!(volatile.field_2); +/// ``` #[macro_export] macro_rules! map_field { - ($volatile:ident.$place:ident) => { + ($volatile:ident.$place:ident) => {{ + // Simulate creating a reference to the field. This is done to make + // sure that the field is not potentially unaligned. The body of the + // if statement will never be executed, so it can never cause any UB. + if false { + #[deny(unaligned_references)] + let _ref_to_field = &(unsafe { &*$volatile.as_ptr().as_ptr() }).$place; + } + unsafe { $volatile.map(|ptr| { core::ptr::NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).$place)).unwrap() }) } - }; + }}; } #[macro_export] macro_rules! map_field_mut { - ($volatile:ident.$place:ident) => { + ($volatile:ident.$place:ident) => {{ + // Simulate creating a reference to the field. This is done to make + // sure that the field is not potentially unaligned. The body of the + // if statement will never be executed, so it can never cause any UB. + if false { + #[deny(unaligned_references)] + let _ref_to_field = &(unsafe { &*$volatile.as_ptr().as_ptr() }).$place; + } + unsafe { $volatile.map_mut(|ptr| { core::ptr::NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).$place)).unwrap() }) } - }; + }}; } // this must be defined after the `map_field` macros From ff0ee4f9a77bfdb7f76af2b725f52011be422e53 Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Wed, 15 Jun 2022 13:30:49 +0200 Subject: [PATCH 15/68] WIP: owned with borrow --- src/lib.rs | 119 ++++++++++++++++++++++++--------------------------- src/tests.rs | 25 ++++++----- 2 files changed, 70 insertions(+), 74 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 95ccf73..90f285d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,7 @@ #![warn(missing_docs)] #![deny(unsafe_op_in_unsafe_fn)] -use access::{Access, ReadOnly, ReadWrite, WriteOnly}; +use access::{Access, NoAccess, ReadOnly, ReadWrite, WriteOnly}; use core::{ fmt, marker::PhantomData, @@ -337,10 +337,20 @@ where } /// Transformation methods for accessing struct fields -impl VolatilePtr<'_, T, Access> +impl<'a, T, R, W> VolatilePtr<'a, T, Access> where T: ?Sized, { + // TODO: Add documentation + pub fn borrow(&self) -> VolatilePtr> { + unsafe { VolatilePtr::new_generic(self.pointer) } + } + + // TODO: Add documentation + pub fn borrow_mut(&mut self) -> VolatilePtr> { + unsafe { VolatilePtr::new_generic(self.pointer) } + } + /// Constructs a new `Volatile` reference by mapping the wrapped pointer. /// /// This method is useful for accessing only a part of a volatile value, e.g. a subslice or @@ -382,7 +392,7 @@ where /// value /// })}; /// ``` - pub unsafe fn map<'a, F, U>(&'a self, f: F) -> VolatilePtr<'a, U, Access> + pub unsafe fn map(self, f: F) -> VolatilePtr<'a, U, Access> where F: FnOnce(NonNull) -> NonNull, U: ?Sized, @@ -391,8 +401,8 @@ where } #[cfg(feature = "very_unstable")] - pub const unsafe fn map_const<'a, F, U>( - &'a self, + pub const unsafe fn map_const( + self, f: F, ) -> VolatilePtr<'a, U, Access> where @@ -402,7 +412,7 @@ where unsafe { VolatilePtr::new_generic(f(self.pointer)) } } - pub unsafe fn map_mut(&mut self, f: F) -> VolatilePtr> + pub unsafe fn map_mut(self, f: F) -> VolatilePtr<'a, U, Access> where F: FnOnce(NonNull) -> NonNull, U: ?Sized, @@ -411,7 +421,7 @@ where } #[cfg(feature = "very_unstable")] - pub const unsafe fn map_mut_const(&mut self, f: F) -> VolatilePtr> + pub const unsafe fn map_mut_const(self, f: F) -> VolatilePtr<'a, U, Access> where F: FnOnce(NonNull) -> NonNull, U: ?Sized, @@ -468,9 +478,9 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { /// assert_eq!(subslice.index(0).read(), 2); /// ``` pub fn index( - &self, + self, index: I, - ) -> VolatilePtr<>::Output, Access> + ) -> VolatilePtr<'a, >::Output, Access> where I: SliceIndex<[T]> + SliceIndex<[()]> + Clone, { @@ -480,7 +490,10 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { } #[cfg(feature = "very_unstable")] - pub const fn index_const(&self, index: usize) -> VolatilePtr> { + pub const fn index_const( + self, + index: usize, + ) -> VolatilePtr<'a, T, Access> { assert!(index < self.pointer.len(), "index out of bounds"); unsafe { self.map_const(|slice| { @@ -490,9 +503,9 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { } pub fn index_mut( - &mut self, + self, index: I, - ) -> VolatilePtr<>::Output, Access> + ) -> VolatilePtr<'a, >::Output, Access> where I: SliceIndex<[T]> + SliceIndex<[()]> + Clone, { @@ -502,7 +515,7 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { } #[cfg(feature = "very_unstable")] - pub const fn index_mut_const(&mut self, index: usize) -> VolatilePtr> { + pub const fn index_mut_const(self, index: usize) -> VolatilePtr<'a, T, Access> { assert!(index < self.pointer.len(), "index out of bounds"); unsafe { self.map_mut_const(|slice| { @@ -512,17 +525,15 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { } /// Returns an iterator over the slice. - pub fn iter<'b>( - &'b self, - ) -> impl Iterator>> + 'b { + pub fn iter(self) -> impl Iterator>> { + let ptr = self.as_ptr().as_ptr() as *mut T; let len = self.len(); - (0..len).map(move |i| self.index(i)) + (0..len) + .map(move |i| unsafe { VolatilePtr::new_generic(NonNull::new_unchecked(ptr.add(i))) }) } /// Returns an iterator that allows modifying each value. - pub fn iter_mut<'b>( - &'b mut self, - ) -> impl Iterator>> + 'b { + pub fn iter_mut(self) -> impl Iterator>> { let ptr = self.as_ptr().as_ptr() as *mut T; let len = self.len(); (0..len) @@ -700,11 +711,11 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { } pub fn split_at( - &self, + self, mid: usize, ) -> ( - VolatilePtr<[T], Access>, - VolatilePtr<[T], Access>, + VolatilePtr<'a, [T], Access>, + VolatilePtr<'a, [T], Access>, ) { assert!(mid <= self.pointer.len()); // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which @@ -713,11 +724,11 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { } pub fn split_at_mut( - &mut self, + self, mid: usize, ) -> ( - VolatilePtr<[T], Access>, - VolatilePtr<[T], Access>, + VolatilePtr<'a, [T], Access>, + VolatilePtr<'a, [T], Access>, ) { assert!(mid <= self.pointer.len()); // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which @@ -726,11 +737,11 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { } unsafe fn split_at_unchecked( - &self, + self, mid: usize, ) -> ( - VolatilePtr<[T], Access>, - VolatilePtr<[T], Access>, + VolatilePtr<'a, [T], Access>, + VolatilePtr<'a, [T], Access>, ) { // SAFETY: Caller has to check that `0 <= mid <= self.len()` unsafe { @@ -742,11 +753,11 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { } unsafe fn split_at_mut_unchecked( - &mut self, + self, mid: usize, ) -> ( - VolatilePtr<[T], Access>, - VolatilePtr<[T], Access>, + VolatilePtr<'a, [T], Access>, + VolatilePtr<'a, [T], Access>, ) { let len = self.pointer.len(); let ptr = self.pointer.as_mut_ptr(); @@ -768,23 +779,23 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { } pub fn as_chunks( - &self, + self, ) -> ( - VolatilePtr<[[T; N]], Access>, - VolatilePtr<[T], Access>, + VolatilePtr<'a, [[T; N]], Access>, + VolatilePtr<'a, [T], Access>, ) { assert_ne!(N, 0); let len = self.pointer.len() / N; let (multiple_of_n, remainder) = self.split_at(len * N); // SAFETY: We already panicked for zero, and ensured by construction // that the length of the subslice is a multiple of N. - let array_slice = unsafe { multiple_of_n.as_chunks_unchecked_by_val() }; + let array_slice = unsafe { multiple_of_n.as_chunks_unchecked() }; (array_slice, remainder) } pub unsafe fn as_chunks_unchecked( - &self, - ) -> VolatilePtr<[[T; N]], Access> { + self, + ) -> VolatilePtr<'a, [[T; N]], Access> { debug_assert_ne!(N, 0); debug_assert_eq!(self.pointer.len() % N, 0); let new_len = @@ -801,39 +812,21 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { } pub fn as_chunks_mut( - &mut self, + self, ) -> ( - VolatilePtr<[[T; N]], Access>, - VolatilePtr<[T], Access>, + VolatilePtr<'a, [[T; N]], Access>, + VolatilePtr<'a, [T], Access>, ) { assert_ne!(N, 0); let len = self.pointer.len() / N; let (multiple_of_n, remainder) = self.split_at_mut(len * N); // SAFETY: We already panicked for zero, and ensured by construction // that the length of the subslice is a multiple of N. - let array_slice = unsafe { multiple_of_n.as_chunks_unchecked_by_val() }; + let array_slice = unsafe { multiple_of_n.as_chunks_unchecked_mut() }; (array_slice, remainder) } pub unsafe fn as_chunks_unchecked_mut( - &mut self, - ) -> VolatilePtr<[[T; N]], Access> { - debug_assert_ne!(N, 0); - debug_assert_eq!(self.pointer.len() % N, 0); - let new_len = - // SAFETY: Our precondition is exactly what's needed to call this - unsafe { core::intrinsics::exact_div(self.pointer.len(), N) }; - // SAFETY: We cast a slice of `new_len * N` elements into - // a slice of `new_len` many `N` elements chunks. - let pointer = NonNull::new(ptr::slice_from_raw_parts_mut( - self.pointer.as_mut_ptr().cast(), - new_len, - )) - .unwrap(); - unsafe { VolatilePtr::new_generic(pointer) } - } - - pub unsafe fn as_chunks_unchecked_by_val( self, ) -> VolatilePtr<'a, [[T; N]], Access> { debug_assert_ne!(N, 0); @@ -891,7 +884,7 @@ impl VolatilePtr<'_, [u8], Access> { /// These methods are only available with the `unstable` feature enabled (requires a nightly /// Rust compiler). #[cfg(feature = "unstable")] -impl VolatilePtr<'_, [T; N], Access> { +impl<'a, T, R, W, const N: usize> VolatilePtr<'a, [T; N], Access> { /// Converts an array reference to a shared slice. /// /// This makes it possible to use the methods defined on slices. @@ -916,7 +909,7 @@ impl VolatilePtr<'_, [T; N], Access> { /// /// assert_eq!(dst, [1, 2]); /// ``` - pub fn as_slice(&self) -> VolatilePtr<[T], Access> { + pub fn as_slice(self) -> VolatilePtr<'a, [T], Access> { unsafe { self.map(|array| { NonNull::new(ptr::slice_from_raw_parts_mut(array.as_ptr() as *mut T, N)).unwrap() @@ -948,7 +941,7 @@ impl VolatilePtr<'_, [T; N], Access> { /// /// assert_eq!(dst, [1, 2]); /// ``` - pub fn as_slice_mut<'a>(&'a mut self) -> VolatilePtr<'a, [T], Access> { + pub fn as_slice_mut(self) -> VolatilePtr<'a, [T], Access> { unsafe { self.map_mut(|array| { NonNull::new(ptr::slice_from_raw_parts_mut(array.as_ptr() as *mut T, N)).unwrap() diff --git a/src/tests.rs b/src/tests.rs index 840c7ff..bc22063 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -138,7 +138,9 @@ fn test_struct() { }; let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut val)) }; unsafe { - volatile.map_mut(|s| NonNull::new(core::ptr::addr_of_mut!((*s.as_ptr()).field_1)).unwrap()) + volatile + .borrow_mut() + .map_mut(|s| NonNull::new(core::ptr::addr_of_mut!((*s.as_ptr()).field_1)).unwrap()) } .update(|v| *v += 1); let mut field_2 = unsafe { @@ -168,7 +170,8 @@ fn test_struct_macro() { field_2: true, }; let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut val)) }; - let mut field_1 = map_field_mut!(volatile.field_1); + let volatile_borrowed = volatile.borrow_mut(); + let mut field_1 = map_field_mut!(volatile_borrowed.field_1); field_1.update(|v| *v += 1); let mut field_2 = map_field_mut!(volatile.field_2); assert!(field_2.read()); @@ -187,7 +190,7 @@ fn test_struct_macro() { fn test_slice() { let val: &mut [u32] = &mut [1, 2, 3]; let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; - volatile.index_mut(0).update(|v| *v += 1); + volatile.borrow_mut().index_mut(0).update(|v| *v += 1); let mut dst = [0; 3]; volatile.copy_into_slice(&mut dst); @@ -199,7 +202,7 @@ fn test_slice() { #[should_panic] fn test_bounds_check_1() { let val: &mut [u32] = &mut [1, 2, 3]; - let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; + let volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; volatile.index_mut(3); } @@ -208,7 +211,7 @@ fn test_bounds_check_1() { #[should_panic] fn test_bounds_check_2() { let val: &mut [u32] = &mut [1, 2, 3]; - let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; + let volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; volatile.index_mut(2..1); } @@ -217,7 +220,7 @@ fn test_bounds_check_2() { #[should_panic] fn test_bounds_check_3() { let val: &mut [u32] = &mut [1, 2, 3]; - let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; + let volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; volatile.index_mut(4..); // `3..` is is still ok (see next test) } @@ -225,7 +228,7 @@ fn test_bounds_check_3() { #[test] fn test_bounds_check_4() { let val: &mut [u32] = &mut [1, 2, 3]; - let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; + let volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; assert_eq!(volatile.index_mut(3..).len(), 0); } @@ -234,7 +237,7 @@ fn test_bounds_check_4() { #[should_panic] fn test_bounds_check_5() { let val: &mut [u32] = &mut [1, 2, 3]; - let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; + let volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; volatile.index_mut(..4); } @@ -242,10 +245,10 @@ fn test_bounds_check_5() { #[test] fn test_chunks() { let val: &mut [u32] = &mut [1, 2, 3, 4, 5, 6]; - let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; + let volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; let mut chunks = volatile.as_chunks_mut().0; - chunks.index_mut(1).write([10, 11, 12]); - assert_eq!(chunks.index(0).read(), [1, 2, 3]); + chunks.borrow_mut().index_mut(1).write([10, 11, 12]); + assert_eq!(chunks.borrow().index(0).read(), [1, 2, 3]); assert_eq!(chunks.index(1).read(), [10, 11, 12]); } From cd57be929e4bdc722720fd71b91854ba487d3b60 Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Sun, 3 Jul 2022 20:32:59 +0200 Subject: [PATCH 16/68] fix UAF in doctest --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 90f285d..8f0c749 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -865,7 +865,8 @@ impl VolatilePtr<'_, [u8], Access> { /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// - /// let mut buf = unsafe { VolatilePtr::new_read_write(NonNull::from(vec![0; 10].as_mut_slice())) }; + /// let mut vec = vec![0; 10]; + /// let mut buf = unsafe { VolatilePtr::new_read_write(NonNull::from(vec.as_mut_slice())) }; /// buf.fill(1); /// assert_eq!(unsafe { buf.as_ptr().as_mut() }, &mut vec![1; 10]); /// ``` From 73cd996b3e3690c902eaac6f86e17a0526753569 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 13:05:56 +0100 Subject: [PATCH 17/68] Re-add the wrapper type used in `v0.3` as `VolatileCell` --- src/access.rs | 36 ++- src/cell.rs | 258 +++++++++++++++ src/lib.rs | 873 +------------------------------------------------ src/ptr.rs | 0 src/ref.rs | 874 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1165 insertions(+), 876 deletions(-) create mode 100644 src/cell.rs create mode 100644 src/ptr.rs create mode 100644 src/ref.rs diff --git a/src/access.rs b/src/access.rs index 438d233..3234a3a 100644 --- a/src/access.rs +++ b/src/access.rs @@ -1,22 +1,46 @@ +pub trait Access: Copy + Default { + /// Ensures that this trait cannot be implemented outside of this crate. + #[doc(hidden)] + fn _private() -> _Private { + _Private + } +} + /// Helper trait that is implemented by [`ReadWrite`] and [`ReadOnly`]. -pub trait Readable {} +pub trait Readable: Access { + /// Ensures that this trait cannot be implemented outside of this crate. + fn _private() -> _Private { + _Private + } +} /// Helper trait that is implemented by [`ReadWrite`] and [`WriteOnly`]. -pub trait Writable {} +pub trait Writable: Access { + /// Ensures that this trait cannot be implemented outside of this crate. + fn _private() -> _Private { + _Private + } +} /// Zero-sized marker type for allowing both read and write access. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone)] pub struct ReadWrite; +impl Access for ReadWrite {} impl Readable for ReadWrite {} impl Writable for ReadWrite {} /// Zero-sized marker type for allowing only read access. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone)] pub struct ReadOnly; - +impl Access for ReadOnly {} impl Readable for ReadOnly {} /// Zero-sized marker type for allowing only write access. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone)] pub struct WriteOnly; +impl Access for WriteOnly {} impl Writable for WriteOnly {} + +#[non_exhaustive] +#[doc(hidden)] +pub struct _Private; diff --git a/src/cell.rs b/src/cell.rs new file mode 100644 index 0000000..336fd93 --- /dev/null +++ b/src/cell.rs @@ -0,0 +1,258 @@ +// ! Provides the wrapper type `VolatileCell`, which wraps any copy-able type and allows for +// ! volatile memory access to wrapped value. Volatile memory accesses are never optimized away by +// ! the compiler, and are useful in many low-level systems programming and concurrent contexts. +// ! +// ! # Dealing with Volatile Pointers +// ! +// ! Frequently, one may have to deal with volatile pointers, eg, writes to specific memory +// ! locations. The canonical way to solve this is to cast the pointer to a volatile wrapper +// ! directly, eg: +// ! +// ! ```rust +// ! use volatile::VolatileCell; +// ! +// ! let mut_ptr = 0xFEE00000 as *mut u32; +// ! +// ! let volatile_ptr = mut_ptr as *mut VolatileCell; +// ! ``` +// ! +// ! and then perform operations on the pointer as usual in a volatile way. This method works as all +// ! of the volatile wrapper types are the same size as their contained values. + +use crate::access::{Access, ReadWrite, Readable, Writable}; +use core::{fmt, marker::PhantomData, ptr}; + +/// A wrapper type around a volatile variable, which allows for volatile reads and writes +/// to the contained value. The stored type needs to be `Copy`, as volatile reads and writes +/// take and return copies of the value. +/// +/// Volatile operations instruct the compiler to skip certain optimizations for these +/// operations. For example, the compiler will not optimize them away even if it thinks +/// that the operations have no observable effect. This is for example desirable when +/// the value is stored in a special memory region that has side effects, such as +/// memory-mapped device registers. +/// +/// Note that this wrapper types *does not* enforce any atomicity guarantees. To get atomicity, +/// use the [`core::sync::atomic`] module. +/// +/// The size of this struct is the same as the size of the contained type. +#[derive(Default)] +#[repr(transparent)] +pub struct VolatileCell { + value: T, + access: PhantomData, +} + +impl VolatileCell { + /// Construct a new volatile cell wrapping the given value. + /// + /// The returned cell allows read and write operations. Use + /// [`new_restricted`][VolatileCell::new_restricted] to create read-only + /// or write-only cells. + /// + /// Calling `VolatileCell::new(v)` is equivalent to calling + /// `VolatileCell::new_restricted(access::ReadWrite, v)`. + /// + /// ## Example + /// + /// ```rust + /// use volatile::VolatileCell; + /// + /// let mut value = VolatileCell::new(0u32); + /// assert_eq!(value.read(), 0); + /// value.write(42); + /// assert_eq!(value.read(), 42); + /// value.update(|v| v + 2 ); + /// assert_eq!(value.read(), 44); + /// ``` + pub const fn new(value: T) -> Self { + VolatileCell::new_restricted(ReadWrite, value) + } +} + +impl VolatileCell { + /// Construct a new volatile cell with restricted access, wrapping the given value. + /// + /// ## Examples + /// + /// ``` + /// use volatile::{VolatileCell, access}; + /// + /// let mut read_write = VolatileCell::new_restricted(access::ReadWrite, 0u32); + /// read_write.write(100); + /// read_write.update(|v| v / 2); + /// assert_eq!(read_write.read(), 50); + /// + /// let read_only = VolatileCell::new_restricted(access::ReadOnly, 0u32); + /// assert_eq!(read_only.read(), 0); + /// + /// let mut write_only = VolatileCell::new_restricted(access::WriteOnly, 0u32); + /// write_only.write(1); + /// ``` + /// + /// ```compile_fail + /// # use volatile::{VolatileCell, access}; + /// // reading or updating a write-only value is not allowed + /// let write_only = VolatileCell::new_restricted(access::WriteOnly, 0u32); + /// write_only.read(); // -> compile error + /// write_only.update(|v| v + 1); // -> compile error + /// ``` + /// + /// ```compile_fail + /// # use volatile::{VolatileCell, access}; + /// // writing or updating a write-only value is not allowed + /// let read_only = VolatileCell::new_restricted(access::ReadOnly, 0u32); + /// read_only.write(5); // -> compile error + /// read_only.update(|v| v + 1); // -> compile error + /// ``` + pub const fn new_restricted(access: A, value: T) -> Self { + let _ = access; + VolatileCell { + value, + access: PhantomData, + } + } + + pub fn access(&self) -> A { + A::default() + } +} + +impl VolatileCell { + /// Performs a volatile read of the contained value, returning a copy + /// of the read value. Volatile reads are guaranteed not to be optimized + /// away by the compiler, but by themselves do not have atomic ordering + /// guarantees. To also get atomicity, consider looking at the `Atomic` wrapper type. + /// + /// ```rust + /// use volatile::VolatileCell; + /// + /// let value = VolatileCell::new(42u32); + /// assert_eq!(value.read(), 42u32); + /// ``` + pub fn read(&self) -> T + where + A: Readable, + { + // UNSAFE: Safe, as we know that our internal value exists. + unsafe { ptr::read_volatile(&self.value) } + } + + /// Performs a volatile write, setting the contained value to the given value `value`. Volatile + /// writes are guaranteed to not be optimized away by the compiler, but by themselves do not + /// have atomic ordering guarantees. To also get atomicity, consider looking at the `Atomic` + /// wrapper type. + /// + /// ```rust + /// use volatile::VolatileCell; + /// + /// let mut value = VolatileCell::new(0u32); + /// value.write(42u32); + /// assert_eq!(value.read(), 42u32); + /// ``` + pub fn write(&mut self, value: T) + where + A: Writable, + { + // UNSAFE: Safe, as we know that our internal value exists. + unsafe { ptr::write_volatile(&mut self.value, value) }; + } + + /// Performs a volatile read of the contained value, passes a mutable reference to it to the + /// function `f`, and then performs a volatile write of the (potentially updated) value back to + /// the contained value. + /// + /// ```rust + /// use volatile::VolatileCell; + /// + /// let mut value = VolatileCell::new(21u32); + /// value.update(|val| val * 2); + /// assert_eq!(value.read(), 42u32); + /// ``` + pub fn update(&mut self, f: F) + where + F: FnOnce(T) -> T, + A: Readable + Writable, + { + let new = f(self.read()); + self.write(new); + } +} + +/// Create a clone of the `VolatileCell`. +/// +/// A `VolatileCell` is clonable only if the cell is marked as readable. +/// +/// Note that using a `VolatileCell` only makes sense if the backing memory is +/// actually volatile. Stack memory is not volatile normally, so this clone +/// implementation is not needed in most situations. Instead, it is recommended +/// to read out the wrapped value instead. +/// +/// Cloning a `VolatileCell` is equivalent to: +/// +/// ```rust +/// # use volatile::VolatileCell; +/// # let volatile_cell = VolatileCell::new(0u32); +/// VolatileCell::new_restricted(volatile_cell.access(), volatile_cell.read()) +/// # ; +/// ``` +impl Clone for VolatileCell +where + T: Copy, + A: Readable, +{ + fn clone(&self) -> Self { + VolatileCell::new_restricted(self.access(), self.read()) + } +} + +/// This `Debug` implementation only applies to cells that are [`Readable`] +/// because it includes the wrapped value. +impl fmt::Debug for VolatileCell +where + T: Copy + fmt::Debug, + A: Readable, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("VolatileCell").field(&self.read()).finish() + } +} + +#[cfg(test)] +mod tests { + use super::VolatileCell; + + #[test] + fn test_read() { + assert_eq!(VolatileCell::new(42).read(), 42); + } + + #[test] + fn test_write() { + let mut volatile = VolatileCell::new(42); + volatile.write(50); + assert_eq!(volatile.value, 50); + } + + #[test] + fn test_update() { + let mut volatile = VolatileCell::new(42); + volatile.update(|v| v + 1); + assert_eq!(volatile.value, 43); + } + + #[test] + fn test_pointer_recast() { + let mut target_value = 0u32; + + let target_ptr: *mut u32 = &mut target_value; + let volatile_ptr = target_ptr as *mut VolatileCell; + + // UNSAFE: Safe, as we know the value exists on the stack. + unsafe { + (*volatile_ptr).write(42u32); + } + + assert_eq!(target_value, 42u32); + } +} diff --git a/src/lib.rs b/src/lib.rs index 5581eb0..2cf1dd1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,874 +1,7 @@ -//! Provides the wrapper type `Volatile`, which wraps a reference to any copy-able type and allows -//! for volatile memory access to wrapped value. Volatile memory accesses are never optimized away -//! by the compiler, and are useful in many low-level systems programming and concurrent contexts. -//! -//! The wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider -//! looking at the `Atomic` wrapper types found in `libcore` or `libstd`. - #![no_std] -#![cfg_attr(feature = "unstable", feature(core_intrinsics))] -#![cfg_attr(feature = "unstable", feature(slice_range))] -#![cfg_attr(feature = "unstable", allow(incomplete_features))] -#![cfg_attr(all(feature = "unstable", test), feature(slice_as_chunks))] -#![warn(missing_docs)] -use access::{ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; -use core::{ - fmt, - marker::PhantomData, - ops::{Deref, DerefMut, Index, IndexMut}, - ptr, - slice::SliceIndex, -}; -#[cfg(feature = "unstable")] -use core::{ - intrinsics, - ops::{Range, RangeBounds}, - slice::range, -}; +pub use cell::VolatileCell; -/// Allows creating read-only and write-only `Volatile` values. pub mod access; - -/// Wraps a reference to make accesses to the referenced value volatile. -/// -/// Allows volatile reads and writes on the referenced value. The referenced value needs to -/// be `Copy` for reading and writing, as volatile reads and writes take and return copies -/// of the value. -/// -/// Since not all volatile resources (e.g. memory mapped device registers) are both readable -/// and writable, this type supports limiting the allowed access types through an optional second -/// generic parameter `A` that can be one of `ReadWrite`, `ReadOnly`, or `WriteOnly`. It defaults -/// to `ReadWrite`, which allows all operations. -/// -/// The size of this struct is the same as the size of the contained reference. -#[derive(Clone)] -#[repr(transparent)] -pub struct Volatile { - reference: R, - access: PhantomData, -} - -/// Constructor functions for creating new values -/// -/// These functions allow to construct a new `Volatile` instance from a reference type. While -/// the `new` function creates a `Volatile` instance with unrestricted access, there are also -/// functions for creating read-only or write-only instances. -impl Volatile { - /// Constructs a new volatile instance wrapping the given reference. - /// - /// While it is possible to construct `Volatile` instances from arbitrary values (including - /// non-reference values), most of the methods are only available when the wrapped type is - /// a reference. The only reason that we don't forbid non-reference types in the constructor - /// functions is that the Rust compiler does not support trait bounds on generic `const` - /// functions yet. When this becomes possible, we will release a new version of this library - /// with removed support for non-references. For these reasons it is recommended to use - /// the `Volatile` type only with references. - /// - /// ## Example - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let mut value = 0u32; - /// - /// let mut volatile = Volatile::new(&mut value); - /// volatile.write(1); - /// assert_eq!(volatile.read(), 1); - /// ``` - pub const fn new(reference: R) -> Volatile { - Volatile { - reference, - access: PhantomData, - } - } - - /// Constructs a new read-only volatile instance wrapping the given reference. - /// - /// This is equivalent to the `new` function with the difference that the returned - /// `Volatile` instance does not permit write operations. This is for example useful - /// with memory-mapped hardware registers that are defined as read-only by the hardware. - /// - /// ## Example - /// - /// Reading is allowed: - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let value = 0u32; - /// - /// let volatile = Volatile::new_read_only(&value); - /// assert_eq!(volatile.read(), 0); - /// ``` - /// - /// But writing is not: - /// - /// ```compile_fail - /// use volatile::Volatile; - /// - /// let mut value = 0u32; - /// - /// let mut volatile = Volatile::new_read_only(&mut value); - /// volatile.write(1); - /// //ERROR: ^^^^^ the trait `volatile::access::Writable` is not implemented - /// // for `volatile::access::ReadOnly` - /// ``` - pub const fn new_read_only(reference: R) -> Volatile { - Volatile { - reference, - access: PhantomData, - } - } - - /// Constructs a new write-only volatile instance wrapping the given reference. - /// - /// This is equivalent to the `new` function with the difference that the returned - /// `Volatile` instance does not permit read operations. This is for example useful - /// with memory-mapped hardware registers that are defined as write-only by the hardware. - /// - /// ## Example - /// - /// Writing is allowed: - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let mut value = 0u32; - /// - /// let mut volatile = Volatile::new_write_only(&mut value); - /// volatile.write(1); - /// ``` - /// - /// But reading is not: - /// - /// ```compile_fail - /// use volatile::Volatile; - /// - /// let value = 0u32; - /// - /// let volatile = Volatile::new_write_only(&value); - /// volatile.read(); - /// //ERROR: ^^^^ the trait `volatile::access::Readable` is not implemented - /// // for `volatile::access::WriteOnly` - /// ``` - pub const fn new_write_only(reference: R) -> Volatile { - Volatile { - reference, - access: PhantomData, - } - } -} - -/// Methods for references to `Copy` types -impl Volatile -where - R: Deref, - T: Copy, -{ - /// Performs a volatile read of the contained value. - /// - /// Returns a copy of the read value. Volatile reads are guaranteed not to be optimized - /// away by the compiler, but by themselves do not have atomic ordering - /// guarantees. To also get atomicity, consider looking at the `Atomic` wrapper types of - /// the standard/`core` library. - /// - /// ## Examples - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let value = 42; - /// let shared_reference = Volatile::new(&value); - /// assert_eq!(shared_reference.read(), 42); - /// - /// let mut value = 50; - /// let mut_reference = Volatile::new(&mut value); - /// assert_eq!(mut_reference.read(), 50); - /// ``` - pub fn read(&self) -> T - where - A: Readable, - { - // UNSAFE: Safe, as we know that our internal value exists. - unsafe { ptr::read_volatile(&*self.reference) } - } - - /// Performs a volatile write, setting the contained value to the given `value`. - /// - /// Volatile writes are guaranteed to not be optimized away by the compiler, but by - /// themselves do not have atomic ordering guarantees. To also get atomicity, consider - /// looking at the `Atomic` wrapper types of the standard/`core` library. - /// - /// ## Example - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let mut value = 42; - /// let mut volatile = Volatile::new(&mut value); - /// volatile.write(50); - /// - /// assert_eq!(volatile.read(), 50); - /// ``` - pub fn write(&mut self, value: T) - where - A: Writable, - R: DerefMut, - { - // UNSAFE: Safe, as we know that our internal value exists. - unsafe { ptr::write_volatile(&mut *self.reference, value) }; - } - - /// Updates the contained value using the given closure and volatile instructions. - /// - /// Performs a volatile read of the contained value, passes a mutable reference to it to the - /// function `f`, and then performs a volatile write of the (potentially updated) value back to - /// the contained value. - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let mut value = 42; - /// let mut volatile = Volatile::new(&mut value); - /// volatile.update(|val| *val += 1); - /// - /// assert_eq!(volatile.read(), 43); - /// ``` - pub fn update(&mut self, f: F) - where - A: Readable + Writable, - R: DerefMut, - F: FnOnce(&mut T), - { - let mut value = self.read(); - f(&mut value); - self.write(value); - } -} - -/// Method for extracting the wrapped value. -impl Volatile { - /// Extracts the inner value stored in the wrapper type. - /// - /// This method gives direct access to the wrapped reference and thus allows - /// non-volatile access again. This is seldom what you want since there is usually - /// a reason that a reference is wrapped in `Volatile`. However, in some cases it might - /// be required or useful to use the `read_volatile`/`write_volatile` pointer methods of - /// the standard library directly, which this method makes possible. - /// - /// Since no memory safety violation can occur when accessing the referenced value using - /// non-volatile operations, this method is safe. However, it _can_ lead to bugs at the - /// application level, so this method should be used with care. - /// - /// ## Example - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut value = 42; - /// let mut volatile = Volatile::new(&mut value); - /// volatile.write(50); - /// let unwrapped: &mut i32 = volatile.extract_inner(); - /// - /// assert_eq!(*unwrapped, 50); // non volatile access, be careful! - /// ``` - pub fn extract_inner(self) -> R { - self.reference - } -} - -/// Transformation methods for accessing struct fields -impl Volatile -where - R: Deref, - T: ?Sized, -{ - /// Constructs a new `Volatile` reference by mapping the wrapped value. - /// - /// This method is useful for accessing individual fields of volatile structs. - /// - /// Note that this method gives temporary access to the wrapped reference, which allows - /// accessing the value in a non-volatile way. This is normally not what you want, so - /// **this method should only be used for reference-to-reference transformations**. - /// - /// ## Examples - /// - /// Accessing a struct field: - /// - /// ``` - /// use volatile::Volatile; - /// - /// struct Example { field_1: u32, field_2: u8, } - /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = Volatile::new(&mut value); - /// - /// // construct a volatile reference to a field - /// let field_2 = volatile.map(|example| &example.field_2); - /// assert_eq!(field_2.read(), 255); - /// ``` - /// - /// Don't misuse this method to do a non-volatile read of the referenced value: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut value = 5; - /// let mut volatile = Volatile::new(&mut value); - /// - /// // DON'T DO THIS: - /// let mut readout = 0; - /// volatile.map(|value| { - /// readout = *value; // non-volatile read, might lead to bugs - /// value - /// }); - /// ``` - pub fn map<'a, F, U>(&'a self, f: F) -> Volatile<&'a U, A> - where - F: FnOnce(&'a T) -> &'a U, - U: ?Sized, - T: 'a, - { - Volatile { - reference: f(self.reference.deref()), - access: self.access, - } - } - - /// Constructs a new mutable `Volatile` reference by mapping the wrapped value. - /// - /// This method is useful for accessing individual fields of volatile structs. - /// - /// Note that this method gives temporary access to the wrapped reference, which allows - /// accessing the value in a non-volatile way. This is normally not what you want, so - /// **this method should only be used for reference-to-reference transformations**. - /// - /// ## Examples - /// - /// Accessing a struct field: - /// - /// ``` - /// use volatile::Volatile; - /// - /// struct Example { field_1: u32, field_2: u8, } - /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = Volatile::new(&mut value); - /// - /// // construct a volatile reference to a field - /// let mut field_2 = volatile.map_mut(|example| &mut example.field_2); - /// field_2.write(128); - /// assert_eq!(field_2.read(), 128); - /// ``` - /// - /// Don't misuse this method to do a non-volatile read or write of the referenced value: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut value = 5; - /// let mut volatile = Volatile::new(&mut value); - /// - /// // DON'T DO THIS: - /// volatile.map_mut(|value| { - /// *value = 10; // non-volatile write, might lead to bugs - /// value - /// }); - /// ``` - pub fn map_mut<'a, F, U>(&'a mut self, f: F) -> Volatile<&'a mut U, A> - where - F: FnOnce(&mut T) -> &mut U, - R: DerefMut, - U: ?Sized, - T: 'a, - { - Volatile { - reference: f(&mut self.reference), - access: self.access, - } - } -} - -/// Methods for volatile slices -impl Volatile -where - R: Deref, -{ - /// Applies the index operation on the wrapped slice. - /// - /// Returns a shared `Volatile` reference to the resulting subslice. - /// - /// This is a convenience method for the `map(|slice| slice.index(index))` operation, so it - /// has the same behavior as the indexing operation on slice (e.g. panic if index is - /// out-of-bounds). - /// - /// ## Examples - /// - /// Accessing a single slice element: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let array = [1, 2, 3]; - /// let slice = &array[..]; - /// let volatile = Volatile::new(slice); - /// assert_eq!(volatile.index(1).read(), 2); - /// ``` - /// - /// Accessing a subslice: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let array = [1, 2, 3]; - /// let slice = &array[..]; - /// let volatile = Volatile::new(slice); - /// let subslice = volatile.index(1..); - /// assert_eq!(subslice.index(0).read(), 2); - /// ``` - pub fn index<'a, I>(&'a self, index: I) -> Volatile<&'a I::Output, A> - where - I: SliceIndex<[T]>, - T: 'a, - { - self.map(|slice| slice.index(index)) - } - - /// Applies the mutable index operation on the wrapped slice. - /// - /// Returns a mutable `Volatile` reference to the resulting subslice. - /// - /// This is a convenience method for the `map_mut(|slice| slice.index_mut(index))` - /// operation, so it has the same behavior as the indexing operation on slice - /// (e.g. panic if index is out-of-bounds). - /// - /// ## Examples - /// - /// Accessing a single slice element: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut array = [1, 2, 3]; - /// let slice = &mut array[..]; - /// let mut volatile = Volatile::new(slice); - /// volatile.index_mut(1).write(6); - /// assert_eq!(volatile.index(1).read(), 6); - /// ``` - /// - /// Accessing a subslice: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut array = [1, 2, 3]; - /// let slice = &mut array[..]; - /// let mut volatile = Volatile::new(slice); - /// let mut subslice = volatile.index_mut(1..); - /// subslice.index_mut(0).write(6); - /// assert_eq!(subslice.index(0).read(), 6); - /// ``` - pub fn index_mut<'a, I>(&'a mut self, index: I) -> Volatile<&mut I::Output, A> - where - I: SliceIndex<[T]>, - R: DerefMut, - T: 'a, - { - self.map_mut(|slice| slice.index_mut(index)) - } - - /// Copies all elements from `self` into `dst`, using a volatile memcpy. - /// - /// The length of `dst` must be the same as `self`. - /// - /// The method is only available with the `unstable` feature enabled (requires a nightly - /// Rust compiler). - /// - /// ## Panics - /// - /// This function will panic if the two slices have different lengths. - /// - /// ## Examples - /// - /// Copying two elements from a volatile slice: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let src = [1, 2]; - /// // the `Volatile` type does not work with arrays, so convert `src` to a slice - /// let slice = &src[..]; - /// let volatile = Volatile::new(slice); - /// let mut dst = [5, 0, 0]; - /// - /// // Because the slices have to be the same length, - /// // we slice the destination slice from three elements - /// // to two. It will panic if we don't do this. - /// volatile.copy_into_slice(&mut dst[1..]); - /// - /// assert_eq!(src, [1, 2]); - /// assert_eq!(dst, [5, 1, 2]); - /// ``` - #[cfg(feature = "unstable")] - pub fn copy_into_slice(&self, dst: &mut [T]) - where - T: Copy, - { - let src = self.reference.deref(); - assert_eq!( - src.len(), - dst.len(), - "destination and source slices have different lengths" - ); - unsafe { - intrinsics::volatile_copy_nonoverlapping_memory( - dst.as_mut_ptr(), - src.as_ptr(), - src.len(), - ); - } - } - - /// Copies all elements from `src` into `self`, using a volatile memcpy. - /// - /// The length of `src` must be the same as `self`. - /// - /// This method is similar to the `slice::copy_from_slice` method of the standard library. The - /// difference is that this method performs a volatile copy. - /// - /// The method is only available with the `unstable` feature enabled (requires a nightly - /// Rust compiler). - /// - /// ## Panics - /// - /// This function will panic if the two slices have different lengths. - /// - /// ## Examples - /// - /// Copying two elements from a slice into a volatile slice: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let src = [1, 2, 3, 4]; - /// let mut dst = [0, 0]; - /// // the `Volatile` type does not work with arrays, so convert `dst` to a slice - /// let slice = &mut dst[..]; - /// let mut volatile = Volatile::new(slice); - /// - /// // Because the slices have to be the same length, - /// // we slice the source slice from four elements - /// // to two. It will panic if we don't do this. - /// volatile.copy_from_slice(&src[2..]); - /// - /// assert_eq!(src, [1, 2, 3, 4]); - /// assert_eq!(dst, [3, 4]); - /// ``` - #[cfg(feature = "unstable")] - pub fn copy_from_slice(&mut self, src: &[T]) - where - T: Copy, - R: DerefMut, - { - let dest = self.reference.deref_mut(); - assert_eq!( - dest.len(), - src.len(), - "destination and source slices have different lengths" - ); - unsafe { - intrinsics::volatile_copy_nonoverlapping_memory( - dest.as_mut_ptr(), - src.as_ptr(), - dest.len(), - ); - } - } - - /// Copies elements from one part of the slice to another part of itself, using a - /// volatile `memmove`. - /// - /// `src` is the range within `self` to copy from. `dest` is the starting index of the - /// range within `self` to copy to, which will have the same length as `src`. The two ranges - /// may overlap. The ends of the two ranges must be less than or equal to `self.len()`. - /// - /// This method is similar to the `slice::copy_within` method of the standard library. The - /// difference is that this method performs a volatile copy. - /// - /// This method is only available with the `unstable` feature enabled (requires a nightly - /// Rust compiler). - /// - /// ## Panics - /// - /// This function will panic if either range exceeds the end of the slice, or if the end - /// of `src` is before the start. - /// - /// ## Examples - /// - /// Copying four bytes within a slice: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut byte_array = *b"Hello, World!"; - /// let mut slice: &mut [u8] = &mut byte_array[..]; - /// let mut volatile = Volatile::new(slice); - /// - /// volatile.copy_within(1..5, 8); - /// - /// assert_eq!(&byte_array, b"Hello, Wello!"); - #[cfg(feature = "unstable")] - pub fn copy_within(&mut self, src: impl RangeBounds, dest: usize) - where - T: Copy, - R: DerefMut, - { - let slice = self.reference.deref_mut(); - // implementation taken from https://github.com/rust-lang/rust/blob/683d1bcd405727fcc9209f64845bd3b9104878b8/library/core/src/slice/mod.rs#L2726-L2738 - let Range { - start: src_start, - end: src_end, - } = range(src, ..slice.len()); - let count = src_end - src_start; - assert!(dest <= slice.len() - count, "dest is out of bounds"); - // SAFETY: the conditions for `volatile_copy_memory` have all been checked above, - // as have those for `ptr::add`. - unsafe { - intrinsics::volatile_copy_memory( - slice.as_mut_ptr().add(dest), - slice.as_ptr().add(src_start), - count, - ); - } - } -} - -/// Methods for volatile byte slices -impl Volatile -where - R: Deref, -{ - /// Sets all elements of the byte slice to the given `value` using a volatile `memset`. - /// - /// This method is similar to the `slice::fill` method of the standard library, with the - /// difference that this method performs a volatile write operation. Another difference - /// is that this method is only available for byte slices (not general `&mut [T]` slices) - /// because there currently isn't a instrinsic function that allows non-`u8` values. - /// - /// This method is only available with the `unstable` feature enabled (requires a nightly - /// Rust compiler). - /// - /// ## Example - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let mut buf = Volatile::new(vec![0; 10]); - /// buf.fill(1); - /// assert_eq!(buf.extract_inner(), vec![1; 10]); - /// ``` - #[cfg(feature = "unstable")] - pub fn fill(&mut self, value: u8) - where - R: DerefMut, - { - let dest = self.reference.deref_mut(); - unsafe { - intrinsics::volatile_set_memory(dest.as_mut_ptr(), value, dest.len()); - } - } -} - -/// Methods for converting arrays to slices -impl Volatile -where - R: Deref, -{ - /// Converts an array reference to a shared slice. - /// - /// This makes it possible to use the methods defined on slices. - /// - /// ## Example - /// - /// Reading a subslice from a volatile array reference using `index`: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let src = [1, 2, 3, 4]; - /// let volatile = Volatile::new(&src); - /// - /// // convert the `Volatile<&[i32; 4]>` array reference to a `Volatile<&[i32]>` slice - /// let volatile_slice = volatile.as_slice(); - /// // we can now use the slice methods - /// let subslice = volatile_slice.index(2..); - /// - /// assert_eq!(subslice.index(0).read(), 3); - /// assert_eq!(subslice.index(1).read(), 4); - /// ``` - pub fn as_slice(&self) -> Volatile<&[T], A> { - self.map(|array| &array[..]) - } - - /// Converts a mutable array reference to a mutable slice. - /// - /// This makes it possible to use the methods defined on slices. - /// - /// ## Example - /// - /// Writing to an index of a mutable array reference: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut dst = [0, 0]; - /// let mut volatile = Volatile::new(&mut dst); - /// - /// // convert the `Volatile<&mut [i32; 2]>` array reference to a `Volatile<&mut [i32]>` slice - /// let mut volatile_slice = volatile.as_mut_slice(); - /// // we can now use the slice methods - /// volatile_slice.index_mut(1).write(1); - /// - /// assert_eq!(dst, [0, 1]); - /// ``` - pub fn as_mut_slice(&mut self) -> Volatile<&mut [T], A> - where - R: DerefMut, - { - self.map_mut(|array| &mut array[..]) - } -} - -/// Methods for restricting access. -impl Volatile { - /// Restricts access permissions to read-only. - /// - /// ## Example - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut value: i16 = -4; - /// let mut volatile = Volatile::new(&mut value); - /// - /// let read_only = volatile.read_only(); - /// assert_eq!(read_only.read(), -4); - /// // read_only.write(10); // compile-time error - /// ``` - pub fn read_only(self) -> Volatile { - Volatile { - reference: self.reference, - access: PhantomData, - } - } - - /// Restricts access permissions to write-only. - /// - /// ## Example - /// - /// Creating a write-only reference to a struct field: - /// - /// ``` - /// use volatile::Volatile; - /// - /// struct Example { field_1: u32, field_2: u8, } - /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = Volatile::new(&mut value); - /// - /// // construct a volatile write-only reference to `field_2` - /// let mut field_2 = volatile.map_mut(|example| &mut example.field_2).write_only(); - /// field_2.write(14); - /// // field_2.read(); // compile-time error - /// ``` - pub fn write_only(self) -> Volatile { - Volatile { - reference: self.reference, - access: PhantomData, - } - } -} - -impl fmt::Debug for Volatile -where - R: Deref, - T: Copy + fmt::Debug, - A: Readable, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Volatile").field(&self.read()).finish() - } -} - -impl fmt::Debug for Volatile -where - R: Deref, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Volatile").field(&"[write-only]").finish() - } -} - -#[cfg(test)] -mod tests { - use super::Volatile; - - #[test] - fn test_read() { - let val = 42; - assert_eq!(Volatile::new(&val).read(), 42); - } - - #[test] - fn test_write() { - let mut val = 50; - let mut volatile = Volatile::new(&mut val); - volatile.write(50); - assert_eq!(val, 50); - } - - #[test] - fn test_update() { - let mut val = 42; - let mut volatile = Volatile::new(&mut val); - volatile.update(|v| *v += 1); - assert_eq!(val, 43); - } - - #[test] - fn test_slice() { - let mut val = [1, 2, 3]; - let mut volatile = Volatile::new(&mut val[..]); - volatile.index_mut(0).update(|v| *v += 1); - assert_eq!(val, [2, 2, 3]); - } - - #[test] - fn test_struct() { - struct S { - field_1: u32, - field_2: bool, - } - - let mut val = S { - field_1: 60, - field_2: true, - }; - let mut volatile = Volatile::new(&mut val); - volatile.map_mut(|s| &mut s.field_1).update(|v| *v += 1); - let mut field_2 = volatile.map_mut(|s| &mut s.field_2); - assert!(field_2.read()); - field_2.write(false); - assert_eq!(volatile.map(|s| &s.field_1).read(), 61); - assert_eq!(volatile.map(|s| &s.field_2).read(), false); - } - - #[cfg(feature = "unstable")] - #[test] - fn test_chunks() { - let mut val = [1, 2, 3, 4, 5, 6]; - let mut volatile = Volatile::new(&mut val[..]); - let mut chunks = volatile.map_mut(|s| s.as_chunks_mut().0); - chunks.index_mut(1).write([10, 11, 12]); - assert_eq!(chunks.index(0).read(), [1, 2, 3]); - assert_eq!(chunks.index(1).read(), [10, 11, 12]); - } -} +mod cell; +mod ptr; diff --git a/src/ptr.rs b/src/ptr.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/ref.rs b/src/ref.rs new file mode 100644 index 0000000..5581eb0 --- /dev/null +++ b/src/ref.rs @@ -0,0 +1,874 @@ +//! Provides the wrapper type `Volatile`, which wraps a reference to any copy-able type and allows +//! for volatile memory access to wrapped value. Volatile memory accesses are never optimized away +//! by the compiler, and are useful in many low-level systems programming and concurrent contexts. +//! +//! The wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider +//! looking at the `Atomic` wrapper types found in `libcore` or `libstd`. + +#![no_std] +#![cfg_attr(feature = "unstable", feature(core_intrinsics))] +#![cfg_attr(feature = "unstable", feature(slice_range))] +#![cfg_attr(feature = "unstable", allow(incomplete_features))] +#![cfg_attr(all(feature = "unstable", test), feature(slice_as_chunks))] +#![warn(missing_docs)] + +use access::{ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; +use core::{ + fmt, + marker::PhantomData, + ops::{Deref, DerefMut, Index, IndexMut}, + ptr, + slice::SliceIndex, +}; +#[cfg(feature = "unstable")] +use core::{ + intrinsics, + ops::{Range, RangeBounds}, + slice::range, +}; + +/// Allows creating read-only and write-only `Volatile` values. +pub mod access; + +/// Wraps a reference to make accesses to the referenced value volatile. +/// +/// Allows volatile reads and writes on the referenced value. The referenced value needs to +/// be `Copy` for reading and writing, as volatile reads and writes take and return copies +/// of the value. +/// +/// Since not all volatile resources (e.g. memory mapped device registers) are both readable +/// and writable, this type supports limiting the allowed access types through an optional second +/// generic parameter `A` that can be one of `ReadWrite`, `ReadOnly`, or `WriteOnly`. It defaults +/// to `ReadWrite`, which allows all operations. +/// +/// The size of this struct is the same as the size of the contained reference. +#[derive(Clone)] +#[repr(transparent)] +pub struct Volatile { + reference: R, + access: PhantomData, +} + +/// Constructor functions for creating new values +/// +/// These functions allow to construct a new `Volatile` instance from a reference type. While +/// the `new` function creates a `Volatile` instance with unrestricted access, there are also +/// functions for creating read-only or write-only instances. +impl Volatile { + /// Constructs a new volatile instance wrapping the given reference. + /// + /// While it is possible to construct `Volatile` instances from arbitrary values (including + /// non-reference values), most of the methods are only available when the wrapped type is + /// a reference. The only reason that we don't forbid non-reference types in the constructor + /// functions is that the Rust compiler does not support trait bounds on generic `const` + /// functions yet. When this becomes possible, we will release a new version of this library + /// with removed support for non-references. For these reasons it is recommended to use + /// the `Volatile` type only with references. + /// + /// ## Example + /// + /// ```rust + /// use volatile::Volatile; + /// + /// let mut value = 0u32; + /// + /// let mut volatile = Volatile::new(&mut value); + /// volatile.write(1); + /// assert_eq!(volatile.read(), 1); + /// ``` + pub const fn new(reference: R) -> Volatile { + Volatile { + reference, + access: PhantomData, + } + } + + /// Constructs a new read-only volatile instance wrapping the given reference. + /// + /// This is equivalent to the `new` function with the difference that the returned + /// `Volatile` instance does not permit write operations. This is for example useful + /// with memory-mapped hardware registers that are defined as read-only by the hardware. + /// + /// ## Example + /// + /// Reading is allowed: + /// + /// ```rust + /// use volatile::Volatile; + /// + /// let value = 0u32; + /// + /// let volatile = Volatile::new_read_only(&value); + /// assert_eq!(volatile.read(), 0); + /// ``` + /// + /// But writing is not: + /// + /// ```compile_fail + /// use volatile::Volatile; + /// + /// let mut value = 0u32; + /// + /// let mut volatile = Volatile::new_read_only(&mut value); + /// volatile.write(1); + /// //ERROR: ^^^^^ the trait `volatile::access::Writable` is not implemented + /// // for `volatile::access::ReadOnly` + /// ``` + pub const fn new_read_only(reference: R) -> Volatile { + Volatile { + reference, + access: PhantomData, + } + } + + /// Constructs a new write-only volatile instance wrapping the given reference. + /// + /// This is equivalent to the `new` function with the difference that the returned + /// `Volatile` instance does not permit read operations. This is for example useful + /// with memory-mapped hardware registers that are defined as write-only by the hardware. + /// + /// ## Example + /// + /// Writing is allowed: + /// + /// ```rust + /// use volatile::Volatile; + /// + /// let mut value = 0u32; + /// + /// let mut volatile = Volatile::new_write_only(&mut value); + /// volatile.write(1); + /// ``` + /// + /// But reading is not: + /// + /// ```compile_fail + /// use volatile::Volatile; + /// + /// let value = 0u32; + /// + /// let volatile = Volatile::new_write_only(&value); + /// volatile.read(); + /// //ERROR: ^^^^ the trait `volatile::access::Readable` is not implemented + /// // for `volatile::access::WriteOnly` + /// ``` + pub const fn new_write_only(reference: R) -> Volatile { + Volatile { + reference, + access: PhantomData, + } + } +} + +/// Methods for references to `Copy` types +impl Volatile +where + R: Deref, + T: Copy, +{ + /// Performs a volatile read of the contained value. + /// + /// Returns a copy of the read value. Volatile reads are guaranteed not to be optimized + /// away by the compiler, but by themselves do not have atomic ordering + /// guarantees. To also get atomicity, consider looking at the `Atomic` wrapper types of + /// the standard/`core` library. + /// + /// ## Examples + /// + /// ```rust + /// use volatile::Volatile; + /// + /// let value = 42; + /// let shared_reference = Volatile::new(&value); + /// assert_eq!(shared_reference.read(), 42); + /// + /// let mut value = 50; + /// let mut_reference = Volatile::new(&mut value); + /// assert_eq!(mut_reference.read(), 50); + /// ``` + pub fn read(&self) -> T + where + A: Readable, + { + // UNSAFE: Safe, as we know that our internal value exists. + unsafe { ptr::read_volatile(&*self.reference) } + } + + /// Performs a volatile write, setting the contained value to the given `value`. + /// + /// Volatile writes are guaranteed to not be optimized away by the compiler, but by + /// themselves do not have atomic ordering guarantees. To also get atomicity, consider + /// looking at the `Atomic` wrapper types of the standard/`core` library. + /// + /// ## Example + /// + /// ```rust + /// use volatile::Volatile; + /// + /// let mut value = 42; + /// let mut volatile = Volatile::new(&mut value); + /// volatile.write(50); + /// + /// assert_eq!(volatile.read(), 50); + /// ``` + pub fn write(&mut self, value: T) + where + A: Writable, + R: DerefMut, + { + // UNSAFE: Safe, as we know that our internal value exists. + unsafe { ptr::write_volatile(&mut *self.reference, value) }; + } + + /// Updates the contained value using the given closure and volatile instructions. + /// + /// Performs a volatile read of the contained value, passes a mutable reference to it to the + /// function `f`, and then performs a volatile write of the (potentially updated) value back to + /// the contained value. + /// + /// ```rust + /// use volatile::Volatile; + /// + /// let mut value = 42; + /// let mut volatile = Volatile::new(&mut value); + /// volatile.update(|val| *val += 1); + /// + /// assert_eq!(volatile.read(), 43); + /// ``` + pub fn update(&mut self, f: F) + where + A: Readable + Writable, + R: DerefMut, + F: FnOnce(&mut T), + { + let mut value = self.read(); + f(&mut value); + self.write(value); + } +} + +/// Method for extracting the wrapped value. +impl Volatile { + /// Extracts the inner value stored in the wrapper type. + /// + /// This method gives direct access to the wrapped reference and thus allows + /// non-volatile access again. This is seldom what you want since there is usually + /// a reason that a reference is wrapped in `Volatile`. However, in some cases it might + /// be required or useful to use the `read_volatile`/`write_volatile` pointer methods of + /// the standard library directly, which this method makes possible. + /// + /// Since no memory safety violation can occur when accessing the referenced value using + /// non-volatile operations, this method is safe. However, it _can_ lead to bugs at the + /// application level, so this method should be used with care. + /// + /// ## Example + /// + /// ``` + /// use volatile::Volatile; + /// + /// let mut value = 42; + /// let mut volatile = Volatile::new(&mut value); + /// volatile.write(50); + /// let unwrapped: &mut i32 = volatile.extract_inner(); + /// + /// assert_eq!(*unwrapped, 50); // non volatile access, be careful! + /// ``` + pub fn extract_inner(self) -> R { + self.reference + } +} + +/// Transformation methods for accessing struct fields +impl Volatile +where + R: Deref, + T: ?Sized, +{ + /// Constructs a new `Volatile` reference by mapping the wrapped value. + /// + /// This method is useful for accessing individual fields of volatile structs. + /// + /// Note that this method gives temporary access to the wrapped reference, which allows + /// accessing the value in a non-volatile way. This is normally not what you want, so + /// **this method should only be used for reference-to-reference transformations**. + /// + /// ## Examples + /// + /// Accessing a struct field: + /// + /// ``` + /// use volatile::Volatile; + /// + /// struct Example { field_1: u32, field_2: u8, } + /// let mut value = Example { field_1: 15, field_2: 255 }; + /// let mut volatile = Volatile::new(&mut value); + /// + /// // construct a volatile reference to a field + /// let field_2 = volatile.map(|example| &example.field_2); + /// assert_eq!(field_2.read(), 255); + /// ``` + /// + /// Don't misuse this method to do a non-volatile read of the referenced value: + /// + /// ``` + /// use volatile::Volatile; + /// + /// let mut value = 5; + /// let mut volatile = Volatile::new(&mut value); + /// + /// // DON'T DO THIS: + /// let mut readout = 0; + /// volatile.map(|value| { + /// readout = *value; // non-volatile read, might lead to bugs + /// value + /// }); + /// ``` + pub fn map<'a, F, U>(&'a self, f: F) -> Volatile<&'a U, A> + where + F: FnOnce(&'a T) -> &'a U, + U: ?Sized, + T: 'a, + { + Volatile { + reference: f(self.reference.deref()), + access: self.access, + } + } + + /// Constructs a new mutable `Volatile` reference by mapping the wrapped value. + /// + /// This method is useful for accessing individual fields of volatile structs. + /// + /// Note that this method gives temporary access to the wrapped reference, which allows + /// accessing the value in a non-volatile way. This is normally not what you want, so + /// **this method should only be used for reference-to-reference transformations**. + /// + /// ## Examples + /// + /// Accessing a struct field: + /// + /// ``` + /// use volatile::Volatile; + /// + /// struct Example { field_1: u32, field_2: u8, } + /// let mut value = Example { field_1: 15, field_2: 255 }; + /// let mut volatile = Volatile::new(&mut value); + /// + /// // construct a volatile reference to a field + /// let mut field_2 = volatile.map_mut(|example| &mut example.field_2); + /// field_2.write(128); + /// assert_eq!(field_2.read(), 128); + /// ``` + /// + /// Don't misuse this method to do a non-volatile read or write of the referenced value: + /// + /// ``` + /// use volatile::Volatile; + /// + /// let mut value = 5; + /// let mut volatile = Volatile::new(&mut value); + /// + /// // DON'T DO THIS: + /// volatile.map_mut(|value| { + /// *value = 10; // non-volatile write, might lead to bugs + /// value + /// }); + /// ``` + pub fn map_mut<'a, F, U>(&'a mut self, f: F) -> Volatile<&'a mut U, A> + where + F: FnOnce(&mut T) -> &mut U, + R: DerefMut, + U: ?Sized, + T: 'a, + { + Volatile { + reference: f(&mut self.reference), + access: self.access, + } + } +} + +/// Methods for volatile slices +impl Volatile +where + R: Deref, +{ + /// Applies the index operation on the wrapped slice. + /// + /// Returns a shared `Volatile` reference to the resulting subslice. + /// + /// This is a convenience method for the `map(|slice| slice.index(index))` operation, so it + /// has the same behavior as the indexing operation on slice (e.g. panic if index is + /// out-of-bounds). + /// + /// ## Examples + /// + /// Accessing a single slice element: + /// + /// ``` + /// use volatile::Volatile; + /// + /// let array = [1, 2, 3]; + /// let slice = &array[..]; + /// let volatile = Volatile::new(slice); + /// assert_eq!(volatile.index(1).read(), 2); + /// ``` + /// + /// Accessing a subslice: + /// + /// ``` + /// use volatile::Volatile; + /// + /// let array = [1, 2, 3]; + /// let slice = &array[..]; + /// let volatile = Volatile::new(slice); + /// let subslice = volatile.index(1..); + /// assert_eq!(subslice.index(0).read(), 2); + /// ``` + pub fn index<'a, I>(&'a self, index: I) -> Volatile<&'a I::Output, A> + where + I: SliceIndex<[T]>, + T: 'a, + { + self.map(|slice| slice.index(index)) + } + + /// Applies the mutable index operation on the wrapped slice. + /// + /// Returns a mutable `Volatile` reference to the resulting subslice. + /// + /// This is a convenience method for the `map_mut(|slice| slice.index_mut(index))` + /// operation, so it has the same behavior as the indexing operation on slice + /// (e.g. panic if index is out-of-bounds). + /// + /// ## Examples + /// + /// Accessing a single slice element: + /// + /// ``` + /// use volatile::Volatile; + /// + /// let mut array = [1, 2, 3]; + /// let slice = &mut array[..]; + /// let mut volatile = Volatile::new(slice); + /// volatile.index_mut(1).write(6); + /// assert_eq!(volatile.index(1).read(), 6); + /// ``` + /// + /// Accessing a subslice: + /// + /// ``` + /// use volatile::Volatile; + /// + /// let mut array = [1, 2, 3]; + /// let slice = &mut array[..]; + /// let mut volatile = Volatile::new(slice); + /// let mut subslice = volatile.index_mut(1..); + /// subslice.index_mut(0).write(6); + /// assert_eq!(subslice.index(0).read(), 6); + /// ``` + pub fn index_mut<'a, I>(&'a mut self, index: I) -> Volatile<&mut I::Output, A> + where + I: SliceIndex<[T]>, + R: DerefMut, + T: 'a, + { + self.map_mut(|slice| slice.index_mut(index)) + } + + /// Copies all elements from `self` into `dst`, using a volatile memcpy. + /// + /// The length of `dst` must be the same as `self`. + /// + /// The method is only available with the `unstable` feature enabled (requires a nightly + /// Rust compiler). + /// + /// ## Panics + /// + /// This function will panic if the two slices have different lengths. + /// + /// ## Examples + /// + /// Copying two elements from a volatile slice: + /// + /// ``` + /// use volatile::Volatile; + /// + /// let src = [1, 2]; + /// // the `Volatile` type does not work with arrays, so convert `src` to a slice + /// let slice = &src[..]; + /// let volatile = Volatile::new(slice); + /// let mut dst = [5, 0, 0]; + /// + /// // Because the slices have to be the same length, + /// // we slice the destination slice from three elements + /// // to two. It will panic if we don't do this. + /// volatile.copy_into_slice(&mut dst[1..]); + /// + /// assert_eq!(src, [1, 2]); + /// assert_eq!(dst, [5, 1, 2]); + /// ``` + #[cfg(feature = "unstable")] + pub fn copy_into_slice(&self, dst: &mut [T]) + where + T: Copy, + { + let src = self.reference.deref(); + assert_eq!( + src.len(), + dst.len(), + "destination and source slices have different lengths" + ); + unsafe { + intrinsics::volatile_copy_nonoverlapping_memory( + dst.as_mut_ptr(), + src.as_ptr(), + src.len(), + ); + } + } + + /// Copies all elements from `src` into `self`, using a volatile memcpy. + /// + /// The length of `src` must be the same as `self`. + /// + /// This method is similar to the `slice::copy_from_slice` method of the standard library. The + /// difference is that this method performs a volatile copy. + /// + /// The method is only available with the `unstable` feature enabled (requires a nightly + /// Rust compiler). + /// + /// ## Panics + /// + /// This function will panic if the two slices have different lengths. + /// + /// ## Examples + /// + /// Copying two elements from a slice into a volatile slice: + /// + /// ``` + /// use volatile::Volatile; + /// + /// let src = [1, 2, 3, 4]; + /// let mut dst = [0, 0]; + /// // the `Volatile` type does not work with arrays, so convert `dst` to a slice + /// let slice = &mut dst[..]; + /// let mut volatile = Volatile::new(slice); + /// + /// // Because the slices have to be the same length, + /// // we slice the source slice from four elements + /// // to two. It will panic if we don't do this. + /// volatile.copy_from_slice(&src[2..]); + /// + /// assert_eq!(src, [1, 2, 3, 4]); + /// assert_eq!(dst, [3, 4]); + /// ``` + #[cfg(feature = "unstable")] + pub fn copy_from_slice(&mut self, src: &[T]) + where + T: Copy, + R: DerefMut, + { + let dest = self.reference.deref_mut(); + assert_eq!( + dest.len(), + src.len(), + "destination and source slices have different lengths" + ); + unsafe { + intrinsics::volatile_copy_nonoverlapping_memory( + dest.as_mut_ptr(), + src.as_ptr(), + dest.len(), + ); + } + } + + /// Copies elements from one part of the slice to another part of itself, using a + /// volatile `memmove`. + /// + /// `src` is the range within `self` to copy from. `dest` is the starting index of the + /// range within `self` to copy to, which will have the same length as `src`. The two ranges + /// may overlap. The ends of the two ranges must be less than or equal to `self.len()`. + /// + /// This method is similar to the `slice::copy_within` method of the standard library. The + /// difference is that this method performs a volatile copy. + /// + /// This method is only available with the `unstable` feature enabled (requires a nightly + /// Rust compiler). + /// + /// ## Panics + /// + /// This function will panic if either range exceeds the end of the slice, or if the end + /// of `src` is before the start. + /// + /// ## Examples + /// + /// Copying four bytes within a slice: + /// + /// ``` + /// use volatile::Volatile; + /// + /// let mut byte_array = *b"Hello, World!"; + /// let mut slice: &mut [u8] = &mut byte_array[..]; + /// let mut volatile = Volatile::new(slice); + /// + /// volatile.copy_within(1..5, 8); + /// + /// assert_eq!(&byte_array, b"Hello, Wello!"); + #[cfg(feature = "unstable")] + pub fn copy_within(&mut self, src: impl RangeBounds, dest: usize) + where + T: Copy, + R: DerefMut, + { + let slice = self.reference.deref_mut(); + // implementation taken from https://github.com/rust-lang/rust/blob/683d1bcd405727fcc9209f64845bd3b9104878b8/library/core/src/slice/mod.rs#L2726-L2738 + let Range { + start: src_start, + end: src_end, + } = range(src, ..slice.len()); + let count = src_end - src_start; + assert!(dest <= slice.len() - count, "dest is out of bounds"); + // SAFETY: the conditions for `volatile_copy_memory` have all been checked above, + // as have those for `ptr::add`. + unsafe { + intrinsics::volatile_copy_memory( + slice.as_mut_ptr().add(dest), + slice.as_ptr().add(src_start), + count, + ); + } + } +} + +/// Methods for volatile byte slices +impl Volatile +where + R: Deref, +{ + /// Sets all elements of the byte slice to the given `value` using a volatile `memset`. + /// + /// This method is similar to the `slice::fill` method of the standard library, with the + /// difference that this method performs a volatile write operation. Another difference + /// is that this method is only available for byte slices (not general `&mut [T]` slices) + /// because there currently isn't a instrinsic function that allows non-`u8` values. + /// + /// This method is only available with the `unstable` feature enabled (requires a nightly + /// Rust compiler). + /// + /// ## Example + /// + /// ```rust + /// use volatile::Volatile; + /// + /// let mut buf = Volatile::new(vec![0; 10]); + /// buf.fill(1); + /// assert_eq!(buf.extract_inner(), vec![1; 10]); + /// ``` + #[cfg(feature = "unstable")] + pub fn fill(&mut self, value: u8) + where + R: DerefMut, + { + let dest = self.reference.deref_mut(); + unsafe { + intrinsics::volatile_set_memory(dest.as_mut_ptr(), value, dest.len()); + } + } +} + +/// Methods for converting arrays to slices +impl Volatile +where + R: Deref, +{ + /// Converts an array reference to a shared slice. + /// + /// This makes it possible to use the methods defined on slices. + /// + /// ## Example + /// + /// Reading a subslice from a volatile array reference using `index`: + /// + /// ``` + /// use volatile::Volatile; + /// + /// let src = [1, 2, 3, 4]; + /// let volatile = Volatile::new(&src); + /// + /// // convert the `Volatile<&[i32; 4]>` array reference to a `Volatile<&[i32]>` slice + /// let volatile_slice = volatile.as_slice(); + /// // we can now use the slice methods + /// let subslice = volatile_slice.index(2..); + /// + /// assert_eq!(subslice.index(0).read(), 3); + /// assert_eq!(subslice.index(1).read(), 4); + /// ``` + pub fn as_slice(&self) -> Volatile<&[T], A> { + self.map(|array| &array[..]) + } + + /// Converts a mutable array reference to a mutable slice. + /// + /// This makes it possible to use the methods defined on slices. + /// + /// ## Example + /// + /// Writing to an index of a mutable array reference: + /// + /// ``` + /// use volatile::Volatile; + /// + /// let mut dst = [0, 0]; + /// let mut volatile = Volatile::new(&mut dst); + /// + /// // convert the `Volatile<&mut [i32; 2]>` array reference to a `Volatile<&mut [i32]>` slice + /// let mut volatile_slice = volatile.as_mut_slice(); + /// // we can now use the slice methods + /// volatile_slice.index_mut(1).write(1); + /// + /// assert_eq!(dst, [0, 1]); + /// ``` + pub fn as_mut_slice(&mut self) -> Volatile<&mut [T], A> + where + R: DerefMut, + { + self.map_mut(|array| &mut array[..]) + } +} + +/// Methods for restricting access. +impl Volatile { + /// Restricts access permissions to read-only. + /// + /// ## Example + /// + /// ``` + /// use volatile::Volatile; + /// + /// let mut value: i16 = -4; + /// let mut volatile = Volatile::new(&mut value); + /// + /// let read_only = volatile.read_only(); + /// assert_eq!(read_only.read(), -4); + /// // read_only.write(10); // compile-time error + /// ``` + pub fn read_only(self) -> Volatile { + Volatile { + reference: self.reference, + access: PhantomData, + } + } + + /// Restricts access permissions to write-only. + /// + /// ## Example + /// + /// Creating a write-only reference to a struct field: + /// + /// ``` + /// use volatile::Volatile; + /// + /// struct Example { field_1: u32, field_2: u8, } + /// let mut value = Example { field_1: 15, field_2: 255 }; + /// let mut volatile = Volatile::new(&mut value); + /// + /// // construct a volatile write-only reference to `field_2` + /// let mut field_2 = volatile.map_mut(|example| &mut example.field_2).write_only(); + /// field_2.write(14); + /// // field_2.read(); // compile-time error + /// ``` + pub fn write_only(self) -> Volatile { + Volatile { + reference: self.reference, + access: PhantomData, + } + } +} + +impl fmt::Debug for Volatile +where + R: Deref, + T: Copy + fmt::Debug, + A: Readable, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Volatile").field(&self.read()).finish() + } +} + +impl fmt::Debug for Volatile +where + R: Deref, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Volatile").field(&"[write-only]").finish() + } +} + +#[cfg(test)] +mod tests { + use super::Volatile; + + #[test] + fn test_read() { + let val = 42; + assert_eq!(Volatile::new(&val).read(), 42); + } + + #[test] + fn test_write() { + let mut val = 50; + let mut volatile = Volatile::new(&mut val); + volatile.write(50); + assert_eq!(val, 50); + } + + #[test] + fn test_update() { + let mut val = 42; + let mut volatile = Volatile::new(&mut val); + volatile.update(|v| *v += 1); + assert_eq!(val, 43); + } + + #[test] + fn test_slice() { + let mut val = [1, 2, 3]; + let mut volatile = Volatile::new(&mut val[..]); + volatile.index_mut(0).update(|v| *v += 1); + assert_eq!(val, [2, 2, 3]); + } + + #[test] + fn test_struct() { + struct S { + field_1: u32, + field_2: bool, + } + + let mut val = S { + field_1: 60, + field_2: true, + }; + let mut volatile = Volatile::new(&mut val); + volatile.map_mut(|s| &mut s.field_1).update(|v| *v += 1); + let mut field_2 = volatile.map_mut(|s| &mut s.field_2); + assert!(field_2.read()); + field_2.write(false); + assert_eq!(volatile.map(|s| &s.field_1).read(), 61); + assert_eq!(volatile.map(|s| &s.field_2).read(), false); + } + + #[cfg(feature = "unstable")] + #[test] + fn test_chunks() { + let mut val = [1, 2, 3, 4, 5, 6]; + let mut volatile = Volatile::new(&mut val[..]); + let mut chunks = volatile.map_mut(|s| s.as_chunks_mut().0); + chunks.index_mut(1).write([10, 11, 12]); + assert_eq!(chunks.index(0).read(), [1, 2, 3]); + assert_eq!(chunks.index(1).read(), [10, 11, 12]); + } +} From 6ab1af471a1291b44f31a6403f64a3894eab12dd Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 13:18:58 +0100 Subject: [PATCH 18/68] Move pointer-based implementation to submodule --- src/{access.rs => access_ptr.rs} | 0 src/lib.rs | 1052 +----------------------------- src/ptr.rs | 1049 +++++++++++++++++++++++++++++ src/tests.rs | 2 +- 4 files changed, 1053 insertions(+), 1050 deletions(-) rename src/{access.rs => access_ptr.rs} (100%) create mode 100644 src/ptr.rs diff --git a/src/access.rs b/src/access_ptr.rs similarity index 100% rename from src/access.rs rename to src/access_ptr.rs diff --git a/src/lib.rs b/src/lib.rs index 8f0c749..f49d439 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,3 @@ -//! Provides the wrapper type `Volatile`, which wraps a reference to any copy-able type and allows -//! for volatile memory access to wrapped value. Volatile memory accesses are never optimized away -//! by the compiler, and are useful in many low-level systems programming and concurrent contexts. -//! -//! The wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider -//! looking at the `Atomic` wrapper types found in `libcore` or `libstd`. - #![no_std] #![cfg_attr(feature = "unstable", feature(core_intrinsics))] #![cfg_attr(feature = "unstable", feature(slice_range))] @@ -21,1050 +14,11 @@ #![warn(missing_docs)] #![deny(unsafe_op_in_unsafe_fn)] -use access::{Access, NoAccess, ReadOnly, ReadWrite, WriteOnly}; -use core::{ - fmt, - marker::PhantomData, - mem, - ptr::{self, NonNull}, -}; -#[cfg(feature = "unstable")] -use core::{ - intrinsics, - ops::{Range, RangeBounds}, - slice::{range, SliceIndex}, -}; - /// Allows creating read-only and write-only `Volatile` values. -pub mod access; - -/// TODO -/// -/// ## Examples -/// -/// Accessing a struct field: -/// -/// ``` -/// # extern crate core; -/// use volatile::{VolatilePtr, map_field}; -/// use core::ptr::NonNull; -/// -/// struct Example { field_1: u32, field_2: u8, } -/// let mut value = Example { field_1: 15, field_2: 255 }; -/// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; -/// -/// // construct a volatile reference to a field -/// let field_2 = map_field!(volatile.field_2); -/// assert_eq!(field_2.read(), 255); -/// ``` -/// -/// Creating `VolatilePtr`s to unaligned field in packed structs is not allowed: -/// ```compile_fail -/// # extern crate core; -/// use volatile::{VolatilePtr, map_field}; -/// use core::ptr::NonNull; -/// -/// #[repr(packed)] -/// struct Example { field_1: u8, field_2: usize, } -/// let mut value = Example { field_1: 15, field_2: 255 }; -/// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; -/// -/// // Constructing a volatile reference to an unaligned field doesn't compile. -/// let field_2 = map_field!(volatile.field_2); -/// ``` -#[macro_export] -macro_rules! map_field { - ($volatile:ident.$place:ident) => {{ - // Simulate creating a reference to the field. This is done to make - // sure that the field is not potentially unaligned. The body of the - // if statement will never be executed, so it can never cause any UB. - if false { - #[deny(unaligned_references)] - let _ref_to_field = &(unsafe { &*$volatile.as_ptr().as_ptr() }).$place; - } - - unsafe { - $volatile.map(|ptr| { - core::ptr::NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).$place)).unwrap() - }) - } - }}; -} - -#[macro_export] -macro_rules! map_field_mut { - ($volatile:ident.$place:ident) => {{ - // Simulate creating a reference to the field. This is done to make - // sure that the field is not potentially unaligned. The body of the - // if statement will never be executed, so it can never cause any UB. - if false { - #[deny(unaligned_references)] - let _ref_to_field = &(unsafe { &*$volatile.as_ptr().as_ptr() }).$place; - } - - unsafe { - $volatile.map_mut(|ptr| { - core::ptr::NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).$place)).unwrap() - }) - } - }}; -} +pub mod access_ptr; +#[macro_use] +pub mod ptr; // this must be defined after the `map_field` macros #[cfg(test)] mod tests; - -/// Wraps a reference to make accesses to the referenced value volatile. -/// -/// Allows volatile reads and writes on the referenced value. The referenced value needs to -/// be `Copy` for reading and writing, as volatile reads and writes take and return copies -/// of the value. -/// -/// Since not all volatile resources (e.g. memory mapped device registers) are both readable -/// and writable, this type supports limiting the allowed access types through an optional second -/// generic parameter `A` that can be one of `ReadWrite`, `ReadOnly`, or `WriteOnly`. It defaults -/// to `ReadWrite`, which allows all operations. -/// -/// The size of this struct is the same as the size of the contained reference. -#[repr(transparent)] -pub struct VolatilePtr<'a, T, A = ReadWrite> -where - T: ?Sized, -{ - pointer: NonNull, - reference: PhantomData<&'a T>, - access: PhantomData, -} - -/// Constructor functions for creating new values -/// -/// These functions allow to construct a new `Volatile` instance from a reference type. While -/// the `new` function creates a `Volatile` instance with unrestricted access, there are also -/// functions for creating read-only or write-only instances. -impl VolatilePtr<'_, T> -where - T: ?Sized, -{ - pub unsafe fn new_read_write(pointer: NonNull) -> VolatilePtr<'static, T> { - unsafe { VolatilePtr::new_with_access(pointer, Access::read_write()) } - } - - pub const unsafe fn new_read_only(pointer: NonNull) -> VolatilePtr<'static, T, ReadOnly> { - unsafe { VolatilePtr::new_with_access(pointer, Access::read_only()) } - } - - pub const unsafe fn new_write_only(pointer: NonNull) -> VolatilePtr<'static, T, WriteOnly> { - unsafe { VolatilePtr::new_with_access(pointer, Access::write_only()) } - } - - /// Constructs a new volatile instance wrapping the given reference. - /// - /// While it is possible to construct `Volatile` instances from arbitrary values (including - /// non-reference values), most of the methods are only available when the wrapped type is - /// a reference. The only reason that we don't forbid non-reference types in the constructor - /// functions is that the Rust compiler does not support trait bounds on generic `const` - /// functions yet. When this becomes possible, we will release a new version of this library - /// with removed support for non-references. For these reasons it is recommended to use - /// the `Volatile` type only with references. - /// - /// ## Example - /// - /// ```rust - /// # extern crate core; - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let mut value = 0u32; - /// - /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; - /// volatile.write(1); - /// assert_eq!(volatile.read(), 1); - /// ``` - pub const unsafe fn new_with_access( - pointer: NonNull, - access: A, - ) -> VolatilePtr<'static, T, A> { - mem::forget(access); // needed because we cannot require `A: Copy` on stable Rust yet - unsafe { Self::new_generic(pointer) } - } - - pub const unsafe fn new_generic<'a, A>(pointer: NonNull) -> VolatilePtr<'a, T, A> { - VolatilePtr { - pointer, - reference: PhantomData, - access: PhantomData, - } - } - - pub fn from_ref<'a>(reference: &'a T) -> VolatilePtr<'a, T, ReadOnly> - where - T: 'a, - { - unsafe { VolatilePtr::new_generic(reference.into()) } - } - - pub fn from_mut_ref<'a>(reference: &'a mut T) -> VolatilePtr<'a, T> - where - T: 'a, - { - unsafe { VolatilePtr::new_generic(reference.into()) } - } -} - -/// Methods for references to `Copy` types -impl VolatilePtr<'_, T, Access> -where - T: Copy + ?Sized, -{ - /// Performs a volatile read of the contained value. - /// - /// Returns a copy of the read value. Volatile reads are guaranteed not to be optimized - /// away by the compiler, but by themselves do not have atomic ordering - /// guarantees. To also get atomicity, consider looking at the `Atomic` wrapper types of - /// the standard/`core` library. - /// - /// ## Examples - /// - /// ```rust - /// # extern crate core; - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let value = 42; - /// let shared_reference = unsafe { VolatilePtr::new_read_only(NonNull::from(&value)) }; - /// assert_eq!(shared_reference.read(), 42); - /// - /// let mut value = 50; - /// let mut_reference = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; - /// assert_eq!(mut_reference.read(), 50); - /// ``` - pub fn read(&self) -> T - where - R: access::Safe, - { - // UNSAFE: Safe, as ... TODO - unsafe { self.read_unsafe() } - } - - /// Performs a volatile write, setting the contained value to the given `value`. - /// - /// Volatile writes are guaranteed to not be optimized away by the compiler, but by - /// themselves do not have atomic ordering guarantees. To also get atomicity, consider - /// looking at the `Atomic` wrapper types of the standard/`core` library. - /// - /// ## Example - /// - /// ```rust - /// # extern crate core; - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let mut value = 42; - /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; - /// volatile.write(50); - /// - /// assert_eq!(volatile.read(), 50); - /// ``` - pub fn write(&mut self, value: T) - where - W: access::Safe, - { - // UNSAFE: Safe, as ... TODO - unsafe { self.write_unsafe(value) }; - } - - /// Updates the contained value using the given closure and volatile instructions. - /// - /// Performs a volatile read of the contained value, passes a mutable reference to it to the - /// function `f`, and then performs a volatile write of the (potentially updated) value back to - /// the contained value. - /// - /// ```rust - /// # extern crate core; - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let mut value = 42; - /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; - /// volatile.update(|val| *val += 1); - /// - /// assert_eq!(volatile.read(), 43); - /// ``` - pub fn update(&mut self, f: F) - where - R: access::Safe, - W: access::Safe, - F: FnOnce(&mut T), - { - unsafe { self.update_unsafe(f) } - } -} - -/// Method for extracting the wrapped value. -impl VolatilePtr<'_, T, A> -where - T: ?Sized, -{ - /// Extracts the inner value stored in the wrapper type. - /// - /// This method gives direct access to the wrapped reference and thus allows - /// non-volatile access again. This is seldom what you want since there is usually - /// a reason that a reference is wrapped in `Volatile`. However, in some cases it might - /// be required or useful to use the `read_volatile`/`write_volatile` pointer methods of - /// the standard library directly, which this method makes possible. - /// - /// Since no memory safety violation can occur when accessing the referenced value using - /// non-volatile operations, this method is safe. However, it _can_ lead to bugs at the - /// application level, so this method should be used with care. - /// - /// ## Example - /// - /// ``` - /// # extern crate core; - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let mut value = 42; - /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; - /// volatile.write(50); - /// let unwrapped: *mut i32 = volatile.as_ptr().as_ptr(); - /// - /// assert_eq!(unsafe { *unwrapped }, 50); // non volatile access, be careful! - /// ``` - pub fn as_ptr(&self) -> NonNull { - self.pointer - } -} - -/// Transformation methods for accessing struct fields -impl<'a, T, R, W> VolatilePtr<'a, T, Access> -where - T: ?Sized, -{ - // TODO: Add documentation - pub fn borrow(&self) -> VolatilePtr> { - unsafe { VolatilePtr::new_generic(self.pointer) } - } - - // TODO: Add documentation - pub fn borrow_mut(&mut self) -> VolatilePtr> { - unsafe { VolatilePtr::new_generic(self.pointer) } - } - - /// Constructs a new `Volatile` reference by mapping the wrapped pointer. - /// - /// This method is useful for accessing only a part of a volatile value, e.g. a subslice or - /// a struct field. For struct field access, there is also the safe [`map_field`] macro that - /// wraps this function. - /// - /// ## Examples - /// - /// Accessing a struct field: - /// - /// ``` - /// # extern crate core; - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// struct Example { field_1: u32, field_2: u8, } - /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; - /// - /// // construct a volatile reference to a field - /// let field_2 = unsafe { volatile.map(|ptr| NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).field_2)).unwrap()) }; - /// assert_eq!(field_2.read(), 255); - /// ``` - /// - /// Don't misuse this method to do a non-volatile read of the referenced value: - /// - /// ``` - /// # extern crate core; - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let mut value = 5; - /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; - /// - /// // DON'T DO THIS: - /// let mut readout = 0; - /// unsafe { volatile.map(|value| { - /// readout = *value.as_ptr(); // non-volatile read, might lead to bugs - /// value - /// })}; - /// ``` - pub unsafe fn map(self, f: F) -> VolatilePtr<'a, U, Access> - where - F: FnOnce(NonNull) -> NonNull, - U: ?Sized, - { - unsafe { VolatilePtr::new_generic(f(self.pointer)) } - } - - #[cfg(feature = "very_unstable")] - pub const unsafe fn map_const( - self, - f: F, - ) -> VolatilePtr<'a, U, Access> - where - F: FnOnce(NonNull) -> NonNull, - U: ?Sized, - { - unsafe { VolatilePtr::new_generic(f(self.pointer)) } - } - - pub unsafe fn map_mut(self, f: F) -> VolatilePtr<'a, U, Access> - where - F: FnOnce(NonNull) -> NonNull, - U: ?Sized, - { - unsafe { VolatilePtr::new_generic(f(self.pointer)) } - } - - #[cfg(feature = "very_unstable")] - pub const unsafe fn map_mut_const(self, f: F) -> VolatilePtr<'a, U, Access> - where - F: FnOnce(NonNull) -> NonNull, - U: ?Sized, - { - unsafe { VolatilePtr::new_generic(f(self.pointer)) } - } -} - -/// Methods for volatile slices -#[cfg(feature = "unstable")] -impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { - pub fn len(&self) -> usize { - self.pointer.len() - } - - pub fn is_empty(&self) -> bool { - self.pointer.len() == 0 - } - - /// Applies the index operation on the wrapped slice. - /// - /// Returns a shared `Volatile` reference to the resulting subslice. - /// - /// This is a convenience method for the `map(|slice| slice.index(index))` operation, so it - /// has the same behavior as the indexing operation on slice (e.g. panic if index is - /// out-of-bounds). - /// - /// ## Examples - /// - /// Accessing a single slice element: - /// - /// ``` - /// # extern crate core; - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let array = [1, 2, 3]; - /// let slice = &array[..]; - /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(slice)) }; - /// assert_eq!(volatile.index(1).read(), 2); - /// ``` - /// - /// Accessing a subslice: - /// - /// ``` - /// # extern crate core; - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let array = [1, 2, 3]; - /// let slice = &array[..]; - /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(slice)) }; - /// let subslice = volatile.index(1..); - /// assert_eq!(subslice.index(0).read(), 2); - /// ``` - pub fn index( - self, - index: I, - ) -> VolatilePtr<'a, >::Output, Access> - where - I: SliceIndex<[T]> + SliceIndex<[()]> + Clone, - { - bounds_check(self.pointer.len(), index.clone()); - - unsafe { self.map(|slice| slice.get_unchecked_mut(index)) } - } - - #[cfg(feature = "very_unstable")] - pub const fn index_const( - self, - index: usize, - ) -> VolatilePtr<'a, T, Access> { - assert!(index < self.pointer.len(), "index out of bounds"); - unsafe { - self.map_const(|slice| { - NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(index)) - }) - } - } - - pub fn index_mut( - self, - index: I, - ) -> VolatilePtr<'a, >::Output, Access> - where - I: SliceIndex<[T]> + SliceIndex<[()]> + Clone, - { - bounds_check(self.pointer.len(), index.clone()); - - unsafe { self.map_mut(|slice| slice.get_unchecked_mut(index)) } - } - - #[cfg(feature = "very_unstable")] - pub const fn index_mut_const(self, index: usize) -> VolatilePtr<'a, T, Access> { - assert!(index < self.pointer.len(), "index out of bounds"); - unsafe { - self.map_mut_const(|slice| { - NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(index)) - }) - } - } - - /// Returns an iterator over the slice. - pub fn iter(self) -> impl Iterator>> { - let ptr = self.as_ptr().as_ptr() as *mut T; - let len = self.len(); - (0..len) - .map(move |i| unsafe { VolatilePtr::new_generic(NonNull::new_unchecked(ptr.add(i))) }) - } - - /// Returns an iterator that allows modifying each value. - pub fn iter_mut(self) -> impl Iterator>> { - let ptr = self.as_ptr().as_ptr() as *mut T; - let len = self.len(); - (0..len) - .map(move |i| unsafe { VolatilePtr::new_generic(NonNull::new_unchecked(ptr.add(i))) }) - } - - /// Copies all elements from `self` into `dst`, using a volatile memcpy. - /// - /// The length of `dst` must be the same as `self`. - /// - /// The method is only available with the `unstable` feature enabled (requires a nightly - /// Rust compiler). - /// - /// ## Panics - /// - /// This function will panic if the two slices have different lengths. - /// - /// ## Examples - /// - /// Copying two elements from a volatile slice: - /// - /// ``` - /// # extern crate core; - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let src = [1, 2]; - /// // the `Volatile` type does not work with arrays, so convert `src` to a slice - /// let slice = &src[..]; - /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(slice)) }; - /// let mut dst = [5, 0, 0]; - /// - /// // Because the slices have to be the same length, - /// // we slice the destination slice from three elements - /// // to two. It will panic if we don't do this. - /// volatile.copy_into_slice(&mut dst[1..]); - /// - /// assert_eq!(src, [1, 2]); - /// assert_eq!(dst, [5, 1, 2]); - /// ``` - pub fn copy_into_slice(&self, dst: &mut [T]) - where - T: Copy, - R: access::Safe, - { - let len = self.pointer.len(); - assert_eq!( - len, - dst.len(), - "destination and source slices have different lengths" - ); - unsafe { - intrinsics::volatile_copy_nonoverlapping_memory( - dst.as_mut_ptr(), - self.pointer.as_mut_ptr(), - len, - ); - } - } - - /// Copies all elements from `src` into `self`, using a volatile memcpy. - /// - /// The length of `src` must be the same as `self`. - /// - /// This method is similar to the `slice::copy_from_slice` method of the standard library. The - /// difference is that this method performs a volatile copy. - /// - /// The method is only available with the `unstable` feature enabled (requires a nightly - /// Rust compiler). - /// - /// ## Panics - /// - /// This function will panic if the two slices have different lengths. - /// - /// ## Examples - /// - /// Copying two elements from a slice into a volatile slice: - /// - /// ``` - /// # extern crate core; - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let src = [1, 2, 3, 4]; - /// let mut dst = [0, 0]; - /// // the `Volatile` type does not work with arrays, so convert `dst` to a slice - /// let slice = &mut dst[..]; - /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(slice))}; - /// - /// // Because the slices have to be the same length, - /// // we slice the source slice from four elements - /// // to two. It will panic if we don't do this. - /// volatile.copy_from_slice(&src[2..]); - /// - /// assert_eq!(src, [1, 2, 3, 4]); - /// assert_eq!(dst, [3, 4]); - /// ``` - pub fn copy_from_slice(&mut self, src: &[T]) - where - T: Copy, - W: access::Safe, - { - let len = self.pointer.len(); - assert_eq!( - len, - src.len(), - "destination and source slices have different lengths" - ); - unsafe { - intrinsics::volatile_copy_nonoverlapping_memory( - self.pointer.as_mut_ptr(), - src.as_ptr(), - len, - ); - } - } - - /// Copies elements from one part of the slice to another part of itself, using a - /// volatile `memmove`. - /// - /// `src` is the range within `self` to copy from. `dest` is the starting index of the - /// range within `self` to copy to, which will have the same length as `src`. The two ranges - /// may overlap. The ends of the two ranges must be less than or equal to `self.len()`. - /// - /// This method is similar to the `slice::copy_within` method of the standard library. The - /// difference is that this method performs a volatile copy. - /// - /// This method is only available with the `unstable` feature enabled (requires a nightly - /// Rust compiler). - /// - /// ## Panics - /// - /// This function will panic if either range exceeds the end of the slice, or if the end - /// of `src` is before the start. - /// - /// ## Examples - /// - /// Copying four bytes within a slice: - /// - /// ``` - /// extern crate core; - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let mut byte_array = *b"Hello, World!"; - /// let mut slice: &mut [u8] = &mut byte_array[..]; - /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(slice)) }; - /// - /// volatile.copy_within(1..5, 8); - /// - /// assert_eq!(&byte_array, b"Hello, Wello!"); - pub fn copy_within(&mut self, src: impl RangeBounds, dest: usize) - where - T: Copy, - R: access::Safe, - W: access::Safe, - { - let len = self.pointer.len(); - // implementation taken from https://github.com/rust-lang/rust/blob/683d1bcd405727fcc9209f64845bd3b9104878b8/library/core/src/slice/mod.rs#L2726-L2738 - let Range { - start: src_start, - end: src_end, - } = range(src, ..len); - let count = src_end - src_start; - assert!(dest <= len - count, "dest is out of bounds"); - // SAFETY: the conditions for `volatile_copy_memory` have all been checked above, - // as have those for `ptr::add`. - unsafe { - intrinsics::volatile_copy_memory( - self.pointer.as_mut_ptr().add(dest), - self.pointer.as_mut_ptr().add(src_start), - count, - ); - } - } - - pub fn split_at( - self, - mid: usize, - ) -> ( - VolatilePtr<'a, [T], Access>, - VolatilePtr<'a, [T], Access>, - ) { - assert!(mid <= self.pointer.len()); - // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which - // fulfills the requirements of `from_raw_parts_mut`. - unsafe { self.split_at_unchecked(mid) } - } - - pub fn split_at_mut( - self, - mid: usize, - ) -> ( - VolatilePtr<'a, [T], Access>, - VolatilePtr<'a, [T], Access>, - ) { - assert!(mid <= self.pointer.len()); - // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which - // fulfills the requirements of `from_raw_parts_mut`. - unsafe { self.split_at_mut_unchecked(mid) } - } - - unsafe fn split_at_unchecked( - self, - mid: usize, - ) -> ( - VolatilePtr<'a, [T], Access>, - VolatilePtr<'a, [T], Access>, - ) { - // SAFETY: Caller has to check that `0 <= mid <= self.len()` - unsafe { - ( - VolatilePtr::new_generic((self.pointer).get_unchecked_mut(..mid)), - VolatilePtr::new_generic((self.pointer).get_unchecked_mut(mid..)), - ) - } - } - - unsafe fn split_at_mut_unchecked( - self, - mid: usize, - ) -> ( - VolatilePtr<'a, [T], Access>, - VolatilePtr<'a, [T], Access>, - ) { - let len = self.pointer.len(); - let ptr = self.pointer.as_mut_ptr(); - - // SAFETY: Caller has to check that `0 <= mid <= self.len()`. - // - // `[ptr; mid]` and `[mid; len]` are not overlapping, so returning a mutable reference - // is fine. - unsafe { - ( - VolatilePtr::new_generic( - NonNull::new(ptr::slice_from_raw_parts_mut(ptr, mid)).unwrap(), - ), - VolatilePtr::new_generic( - NonNull::new(ptr::slice_from_raw_parts_mut(ptr.add(mid), len - mid)).unwrap(), - ), - ) - } - } - - pub fn as_chunks( - self, - ) -> ( - VolatilePtr<'a, [[T; N]], Access>, - VolatilePtr<'a, [T], Access>, - ) { - assert_ne!(N, 0); - let len = self.pointer.len() / N; - let (multiple_of_n, remainder) = self.split_at(len * N); - // SAFETY: We already panicked for zero, and ensured by construction - // that the length of the subslice is a multiple of N. - let array_slice = unsafe { multiple_of_n.as_chunks_unchecked() }; - (array_slice, remainder) - } - - pub unsafe fn as_chunks_unchecked( - self, - ) -> VolatilePtr<'a, [[T; N]], Access> { - debug_assert_ne!(N, 0); - debug_assert_eq!(self.pointer.len() % N, 0); - let new_len = - // SAFETY: Our precondition is exactly what's needed to call this - unsafe { core::intrinsics::exact_div(self.pointer.len(), N) }; - // SAFETY: We cast a slice of `new_len * N` elements into - // a slice of `new_len` many `N` elements chunks. - let pointer = NonNull::new(ptr::slice_from_raw_parts_mut( - self.pointer.as_mut_ptr().cast(), - new_len, - )) - .unwrap(); - unsafe { VolatilePtr::new_generic(pointer) } - } - - pub fn as_chunks_mut( - self, - ) -> ( - VolatilePtr<'a, [[T; N]], Access>, - VolatilePtr<'a, [T], Access>, - ) { - assert_ne!(N, 0); - let len = self.pointer.len() / N; - let (multiple_of_n, remainder) = self.split_at_mut(len * N); - // SAFETY: We already panicked for zero, and ensured by construction - // that the length of the subslice is a multiple of N. - let array_slice = unsafe { multiple_of_n.as_chunks_unchecked_mut() }; - (array_slice, remainder) - } - - pub unsafe fn as_chunks_unchecked_mut( - self, - ) -> VolatilePtr<'a, [[T; N]], Access> { - debug_assert_ne!(N, 0); - debug_assert_eq!(self.pointer.len() % N, 0); - let new_len = - // SAFETY: Our precondition is exactly what's needed to call this - unsafe { core::intrinsics::exact_div(self.pointer.len(), N) }; - // SAFETY: We cast a slice of `new_len * N` elements into - // a slice of `new_len` many `N` elements chunks. - let pointer = NonNull::new(ptr::slice_from_raw_parts_mut( - self.pointer.as_mut_ptr().cast(), - new_len, - )) - .unwrap(); - unsafe { VolatilePtr::new_generic(pointer) } - } -} - -/// Methods for volatile byte slices -#[cfg(feature = "unstable")] -impl VolatilePtr<'_, [u8], Access> { - /// Sets all elements of the byte slice to the given `value` using a volatile `memset`. - /// - /// This method is similar to the `slice::fill` method of the standard library, with the - /// difference that this method performs a volatile write operation. Another difference - /// is that this method is only available for byte slices (not general `&mut [T]` slices) - /// because there currently isn't a instrinsic function that allows non-`u8` values. - /// - /// This method is only available with the `unstable` feature enabled (requires a nightly - /// Rust compiler). - /// - /// ## Example - /// - /// ```rust - /// # extern crate core; - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let mut vec = vec![0; 10]; - /// let mut buf = unsafe { VolatilePtr::new_read_write(NonNull::from(vec.as_mut_slice())) }; - /// buf.fill(1); - /// assert_eq!(unsafe { buf.as_ptr().as_mut() }, &mut vec![1; 10]); - /// ``` - pub fn fill(&mut self, value: u8) - where - W: access::Safe, - { - unsafe { - intrinsics::volatile_set_memory(self.pointer.as_mut_ptr(), value, self.pointer.len()); - } - } -} - -/// Methods for converting arrays to slices -/// -/// These methods are only available with the `unstable` feature enabled (requires a nightly -/// Rust compiler). -#[cfg(feature = "unstable")] -impl<'a, T, R, W, const N: usize> VolatilePtr<'a, [T; N], Access> { - /// Converts an array reference to a shared slice. - /// - /// This makes it possible to use the methods defined on slices. - /// - /// ## Example - /// - /// Copying two elements from a volatile array reference using `copy_into_slice`: - /// - /// ``` - /// # extern crate core; - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let src = [1, 2]; - /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(&src)) }; - /// let mut dst = [0, 0]; - /// - /// // convert the `Volatile<&[i32; 2]>` array reference to a `Volatile<&[i32]>` slice - /// let volatile_slice = volatile.as_slice(); - /// // we can now use the slice methods - /// volatile_slice.copy_into_slice(&mut dst); - /// - /// assert_eq!(dst, [1, 2]); - /// ``` - pub fn as_slice(self) -> VolatilePtr<'a, [T], Access> { - unsafe { - self.map(|array| { - NonNull::new(ptr::slice_from_raw_parts_mut(array.as_ptr() as *mut T, N)).unwrap() - }) - } - } - - /// Converts an array reference to a shared slice. - /// - /// This makes it possible to use the methods defined on slices. - /// - /// ## Example - /// - /// Copying two elements into a volatile array reference using `copy_from_slice`: - /// - /// ``` - /// # extern crate core; - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let src = [1, 2]; - /// let mut dst = [0, 0]; - /// let mut volatile = unsafe { VolatilePtr::new_write_only(NonNull::from(&dst)) }; - /// - /// // convert the `Volatile<[i32; 2]>` array reference to a `Volatile<[i32]>` slice - /// let mut volatile_slice = volatile.as_slice_mut(); - /// // we can now use the slice methods - /// volatile_slice.copy_from_slice(&src); - /// - /// assert_eq!(dst, [1, 2]); - /// ``` - pub fn as_slice_mut(self) -> VolatilePtr<'a, [T], Access> { - unsafe { - self.map_mut(|array| { - NonNull::new(ptr::slice_from_raw_parts_mut(array.as_ptr() as *mut T, N)).unwrap() - }) - } - } -} - -/// Methods for restricting access. -impl<'a, T, R, W> VolatilePtr<'a, T, Access> -where - T: ?Sized, -{ - /// Restricts access permissions to read-only. - /// - /// ## Example - /// - /// ``` - /// # extern crate core; - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let mut value: i16 = -4; - /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; - /// - /// let read_only = volatile.read_only(); - /// assert_eq!(read_only.read(), -4); - /// // read_only.write(10); // compile-time error - /// ``` - pub fn read_only(self) -> VolatilePtr<'a, T, Access> { - unsafe { VolatilePtr::new_generic(self.pointer) } - } - - /// Restricts access permissions to write-only. - /// - /// ## Example - /// - /// Creating a write-only reference to a struct field: - /// - /// ``` - /// # extern crate core; - /// use volatile::{VolatilePtr, map_field_mut}; - /// use core::ptr::NonNull; - /// - /// struct Example { field_1: u32, field_2: u8, } - /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; - /// - /// // construct a volatile write-only reference to `field_2` - /// let mut field_2 = map_field_mut!(volatile.field_2).write_only(); - /// field_2.write(14); - /// // field_2.read(); // compile-time error - /// ``` - pub fn write_only(self) -> VolatilePtr<'a, T, Access> { - unsafe { VolatilePtr::new_generic(self.pointer) } - } -} - -/// Unsafe access methods for references to `Copy` types -impl VolatilePtr<'_, T, Access> -where - T: Copy + ?Sized, -{ - pub unsafe fn read_unsafe(&self) -> T - where - R: access::Unsafe, - { - unsafe { ptr::read_volatile(self.pointer.as_ptr()) } - } - - pub unsafe fn write_unsafe(&mut self, value: T) - where - W: access::Unsafe, - { - unsafe { ptr::write_volatile(self.pointer.as_ptr(), value) }; - } - - pub unsafe fn update_unsafe(&mut self, f: F) - where - R: access::Unsafe, - W: access::Unsafe, - F: FnOnce(&mut T), - { - let mut value = unsafe { self.read_unsafe() }; - f(&mut value); - unsafe { self.write_unsafe(value) }; - } -} - -impl fmt::Debug for VolatilePtr<'_, T, Access> -where - T: Copy + fmt::Debug + ?Sized, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Volatile").field(&self.read()).finish() - } -} - -impl fmt::Debug for VolatilePtr<'_, T, Access> -where - T: ?Sized, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Volatile").field(&"[unsafe read]").finish() - } -} - -impl fmt::Debug for VolatilePtr<'_, T, Access> -where - T: ?Sized, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Volatile") - .field(&"[no read access]") - .finish() - } -} - -#[cfg(feature = "unstable")] -fn bounds_check(len: usize, index: impl SliceIndex<[()]>) { - const MAX_ARRAY: [(); usize::MAX] = [(); usize::MAX]; - - let bound_check_slice = &MAX_ARRAY[..len]; - &bound_check_slice[index]; -} diff --git a/src/ptr.rs b/src/ptr.rs new file mode 100644 index 0000000..5f29b66 --- /dev/null +++ b/src/ptr.rs @@ -0,0 +1,1049 @@ +//! Provides the wrapper type `Volatile`, which wraps a reference to any copy-able type and allows +//! for volatile memory access to wrapped value. Volatile memory accesses are never optimized away +//! by the compiler, and are useful in many low-level systems programming and concurrent contexts. +//! +//! The wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider +//! looking at the `Atomic` wrapper types found in `libcore` or `libstd`. + +use access_ptr::{Access, NoAccess, ReadOnly, ReadWrite, WriteOnly}; +use core::{ + fmt, + marker::PhantomData, + mem, + ptr::{self, NonNull}, +}; +#[cfg(feature = "unstable")] +use core::{ + intrinsics, + ops::{Range, RangeBounds}, + slice::{range, SliceIndex}, +}; + +use crate::access_ptr; + +/// TODO +/// +/// ## Examples +/// +/// Accessing a struct field: +/// +/// ``` +/// # extern crate core; +/// use volatile::{VolatilePtr, map_field}; +/// use core::ptr::NonNull; +/// +/// struct Example { field_1: u32, field_2: u8, } +/// let mut value = Example { field_1: 15, field_2: 255 }; +/// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; +/// +/// // construct a volatile reference to a field +/// let field_2 = map_field!(volatile.field_2); +/// assert_eq!(field_2.read(), 255); +/// ``` +/// +/// Creating `VolatilePtr`s to unaligned field in packed structs is not allowed: +/// ```compile_fail +/// # extern crate core; +/// use volatile::{VolatilePtr, map_field}; +/// use core::ptr::NonNull; +/// +/// #[repr(packed)] +/// struct Example { field_1: u8, field_2: usize, } +/// let mut value = Example { field_1: 15, field_2: 255 }; +/// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; +/// +/// // Constructing a volatile reference to an unaligned field doesn't compile. +/// let field_2 = map_field!(volatile.field_2); +/// ``` +#[macro_export] +macro_rules! map_field { + ($volatile:ident.$place:ident) => {{ + // Simulate creating a reference to the field. This is done to make + // sure that the field is not potentially unaligned. The body of the + // if statement will never be executed, so it can never cause any UB. + if false { + #[deny(unaligned_references)] + let _ref_to_field = &(unsafe { &*$volatile.as_ptr().as_ptr() }).$place; + } + + unsafe { + $volatile.map(|ptr| { + core::ptr::NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).$place)).unwrap() + }) + } + }}; +} + +#[macro_export] +macro_rules! map_field_mut { + ($volatile:ident.$place:ident) => {{ + // Simulate creating a reference to the field. This is done to make + // sure that the field is not potentially unaligned. The body of the + // if statement will never be executed, so it can never cause any UB. + if false { + #[deny(unaligned_references)] + let _ref_to_field = &(unsafe { &*$volatile.as_ptr().as_ptr() }).$place; + } + + unsafe { + $volatile.map_mut(|ptr| { + core::ptr::NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).$place)).unwrap() + }) + } + }}; +} + +/// Wraps a reference to make accesses to the referenced value volatile. +/// +/// Allows volatile reads and writes on the referenced value. The referenced value needs to +/// be `Copy` for reading and writing, as volatile reads and writes take and return copies +/// of the value. +/// +/// Since not all volatile resources (e.g. memory mapped device registers) are both readable +/// and writable, this type supports limiting the allowed access types through an optional second +/// generic parameter `A` that can be one of `ReadWrite`, `ReadOnly`, or `WriteOnly`. It defaults +/// to `ReadWrite`, which allows all operations. +/// +/// The size of this struct is the same as the size of the contained reference. +#[repr(transparent)] +pub struct VolatilePtr<'a, T, A = ReadWrite> +where + T: ?Sized, +{ + pointer: NonNull, + reference: PhantomData<&'a T>, + access: PhantomData, +} + +/// Constructor functions for creating new values +/// +/// These functions allow to construct a new `Volatile` instance from a reference type. While +/// the `new` function creates a `Volatile` instance with unrestricted access, there are also +/// functions for creating read-only or write-only instances. +impl VolatilePtr<'_, T> +where + T: ?Sized, +{ + pub unsafe fn new_read_write(pointer: NonNull) -> VolatilePtr<'static, T> { + unsafe { VolatilePtr::new_with_access(pointer, Access::read_write()) } + } + + pub const unsafe fn new_read_only(pointer: NonNull) -> VolatilePtr<'static, T, ReadOnly> { + unsafe { VolatilePtr::new_with_access(pointer, Access::read_only()) } + } + + pub const unsafe fn new_write_only(pointer: NonNull) -> VolatilePtr<'static, T, WriteOnly> { + unsafe { VolatilePtr::new_with_access(pointer, Access::write_only()) } + } + + /// Constructs a new volatile instance wrapping the given reference. + /// + /// While it is possible to construct `Volatile` instances from arbitrary values (including + /// non-reference values), most of the methods are only available when the wrapped type is + /// a reference. The only reason that we don't forbid non-reference types in the constructor + /// functions is that the Rust compiler does not support trait bounds on generic `const` + /// functions yet. When this becomes possible, we will release a new version of this library + /// with removed support for non-references. For these reasons it is recommended to use + /// the `Volatile` type only with references. + /// + /// ## Example + /// + /// ```rust + /// # extern crate core; + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut value = 0u32; + /// + /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; + /// volatile.write(1); + /// assert_eq!(volatile.read(), 1); + /// ``` + pub const unsafe fn new_with_access( + pointer: NonNull, + access: A, + ) -> VolatilePtr<'static, T, A> { + mem::forget(access); // needed because we cannot require `A: Copy` on stable Rust yet + unsafe { Self::new_generic(pointer) } + } + + pub const unsafe fn new_generic<'a, A>(pointer: NonNull) -> VolatilePtr<'a, T, A> { + VolatilePtr { + pointer, + reference: PhantomData, + access: PhantomData, + } + } + + pub fn from_ref<'a>(reference: &'a T) -> VolatilePtr<'a, T, ReadOnly> + where + T: 'a, + { + unsafe { VolatilePtr::new_generic(reference.into()) } + } + + pub fn from_mut_ref<'a>(reference: &'a mut T) -> VolatilePtr<'a, T> + where + T: 'a, + { + unsafe { VolatilePtr::new_generic(reference.into()) } + } +} + +/// Methods for references to `Copy` types +impl VolatilePtr<'_, T, Access> +where + T: Copy + ?Sized, +{ + /// Performs a volatile read of the contained value. + /// + /// Returns a copy of the read value. Volatile reads are guaranteed not to be optimized + /// away by the compiler, but by themselves do not have atomic ordering + /// guarantees. To also get atomicity, consider looking at the `Atomic` wrapper types of + /// the standard/`core` library. + /// + /// ## Examples + /// + /// ```rust + /// # extern crate core; + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let value = 42; + /// let shared_reference = unsafe { VolatilePtr::new_read_only(NonNull::from(&value)) }; + /// assert_eq!(shared_reference.read(), 42); + /// + /// let mut value = 50; + /// let mut_reference = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; + /// assert_eq!(mut_reference.read(), 50); + /// ``` + pub fn read(&self) -> T + where + R: access_ptr::Safe, + { + // UNSAFE: Safe, as ... TODO + unsafe { self.read_unsafe() } + } + + /// Performs a volatile write, setting the contained value to the given `value`. + /// + /// Volatile writes are guaranteed to not be optimized away by the compiler, but by + /// themselves do not have atomic ordering guarantees. To also get atomicity, consider + /// looking at the `Atomic` wrapper types of the standard/`core` library. + /// + /// ## Example + /// + /// ```rust + /// # extern crate core; + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut value = 42; + /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; + /// volatile.write(50); + /// + /// assert_eq!(volatile.read(), 50); + /// ``` + pub fn write(&mut self, value: T) + where + W: access_ptr::Safe, + { + // UNSAFE: Safe, as ... TODO + unsafe { self.write_unsafe(value) }; + } + + /// Updates the contained value using the given closure and volatile instructions. + /// + /// Performs a volatile read of the contained value, passes a mutable reference to it to the + /// function `f`, and then performs a volatile write of the (potentially updated) value back to + /// the contained value. + /// + /// ```rust + /// # extern crate core; + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut value = 42; + /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; + /// volatile.update(|val| *val += 1); + /// + /// assert_eq!(volatile.read(), 43); + /// ``` + pub fn update(&mut self, f: F) + where + R: access_ptr::Safe, + W: access_ptr::Safe, + F: FnOnce(&mut T), + { + unsafe { self.update_unsafe(f) } + } +} + +/// Method for extracting the wrapped value. +impl VolatilePtr<'_, T, A> +where + T: ?Sized, +{ + /// Extracts the inner value stored in the wrapper type. + /// + /// This method gives direct access to the wrapped reference and thus allows + /// non-volatile access again. This is seldom what you want since there is usually + /// a reason that a reference is wrapped in `Volatile`. However, in some cases it might + /// be required or useful to use the `read_volatile`/`write_volatile` pointer methods of + /// the standard library directly, which this method makes possible. + /// + /// Since no memory safety violation can occur when accessing the referenced value using + /// non-volatile operations, this method is safe. However, it _can_ lead to bugs at the + /// application level, so this method should be used with care. + /// + /// ## Example + /// + /// ``` + /// # extern crate core; + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut value = 42; + /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; + /// volatile.write(50); + /// let unwrapped: *mut i32 = volatile.as_ptr().as_ptr(); + /// + /// assert_eq!(unsafe { *unwrapped }, 50); // non volatile access, be careful! + /// ``` + pub fn as_ptr(&self) -> NonNull { + self.pointer + } +} + +/// Transformation methods for accessing struct fields +impl<'a, T, R, W> VolatilePtr<'a, T, Access> +where + T: ?Sized, +{ + // TODO: Add documentation + pub fn borrow(&self) -> VolatilePtr> { + unsafe { VolatilePtr::new_generic(self.pointer) } + } + + // TODO: Add documentation + pub fn borrow_mut(&mut self) -> VolatilePtr> { + unsafe { VolatilePtr::new_generic(self.pointer) } + } + + /// Constructs a new `Volatile` reference by mapping the wrapped pointer. + /// + /// This method is useful for accessing only a part of a volatile value, e.g. a subslice or + /// a struct field. For struct field access, there is also the safe [`map_field`] macro that + /// wraps this function. + /// + /// ## Examples + /// + /// Accessing a struct field: + /// + /// ``` + /// # extern crate core; + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// struct Example { field_1: u32, field_2: u8, } + /// let mut value = Example { field_1: 15, field_2: 255 }; + /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; + /// + /// // construct a volatile reference to a field + /// let field_2 = unsafe { volatile.map(|ptr| NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).field_2)).unwrap()) }; + /// assert_eq!(field_2.read(), 255); + /// ``` + /// + /// Don't misuse this method to do a non-volatile read of the referenced value: + /// + /// ``` + /// # extern crate core; + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut value = 5; + /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; + /// + /// // DON'T DO THIS: + /// let mut readout = 0; + /// unsafe { volatile.map(|value| { + /// readout = *value.as_ptr(); // non-volatile read, might lead to bugs + /// value + /// })}; + /// ``` + pub unsafe fn map(self, f: F) -> VolatilePtr<'a, U, Access> + where + F: FnOnce(NonNull) -> NonNull, + U: ?Sized, + { + unsafe { VolatilePtr::new_generic(f(self.pointer)) } + } + + #[cfg(feature = "very_unstable")] + pub const unsafe fn map_const( + self, + f: F, + ) -> VolatilePtr<'a, U, Access> + where + F: FnOnce(NonNull) -> NonNull, + U: ?Sized, + { + unsafe { VolatilePtr::new_generic(f(self.pointer)) } + } + + pub unsafe fn map_mut(self, f: F) -> VolatilePtr<'a, U, Access> + where + F: FnOnce(NonNull) -> NonNull, + U: ?Sized, + { + unsafe { VolatilePtr::new_generic(f(self.pointer)) } + } + + #[cfg(feature = "very_unstable")] + pub const unsafe fn map_mut_const(self, f: F) -> VolatilePtr<'a, U, Access> + where + F: FnOnce(NonNull) -> NonNull, + U: ?Sized, + { + unsafe { VolatilePtr::new_generic(f(self.pointer)) } + } +} + +/// Methods for volatile slices +#[cfg(feature = "unstable")] +impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { + pub fn len(&self) -> usize { + self.pointer.len() + } + + pub fn is_empty(&self) -> bool { + self.pointer.len() == 0 + } + + /// Applies the index operation on the wrapped slice. + /// + /// Returns a shared `Volatile` reference to the resulting subslice. + /// + /// This is a convenience method for the `map(|slice| slice.index(index))` operation, so it + /// has the same behavior as the indexing operation on slice (e.g. panic if index is + /// out-of-bounds). + /// + /// ## Examples + /// + /// Accessing a single slice element: + /// + /// ``` + /// # extern crate core; + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let array = [1, 2, 3]; + /// let slice = &array[..]; + /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(slice)) }; + /// assert_eq!(volatile.index(1).read(), 2); + /// ``` + /// + /// Accessing a subslice: + /// + /// ``` + /// # extern crate core; + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let array = [1, 2, 3]; + /// let slice = &array[..]; + /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(slice)) }; + /// let subslice = volatile.index(1..); + /// assert_eq!(subslice.index(0).read(), 2); + /// ``` + pub fn index( + self, + index: I, + ) -> VolatilePtr<'a, >::Output, Access> + where + I: SliceIndex<[T]> + SliceIndex<[()]> + Clone, + { + bounds_check(self.pointer.len(), index.clone()); + + unsafe { self.map(|slice| slice.get_unchecked_mut(index)) } + } + + #[cfg(feature = "very_unstable")] + pub const fn index_const( + self, + index: usize, + ) -> VolatilePtr<'a, T, Access> { + assert!(index < self.pointer.len(), "index out of bounds"); + unsafe { + self.map_const(|slice| { + NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(index)) + }) + } + } + + pub fn index_mut( + self, + index: I, + ) -> VolatilePtr<'a, >::Output, Access> + where + I: SliceIndex<[T]> + SliceIndex<[()]> + Clone, + { + bounds_check(self.pointer.len(), index.clone()); + + unsafe { self.map_mut(|slice| slice.get_unchecked_mut(index)) } + } + + #[cfg(feature = "very_unstable")] + pub const fn index_mut_const(self, index: usize) -> VolatilePtr<'a, T, Access> { + assert!(index < self.pointer.len(), "index out of bounds"); + unsafe { + self.map_mut_const(|slice| { + NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(index)) + }) + } + } + + /// Returns an iterator over the slice. + pub fn iter(self) -> impl Iterator>> { + let ptr = self.as_ptr().as_ptr() as *mut T; + let len = self.len(); + (0..len) + .map(move |i| unsafe { VolatilePtr::new_generic(NonNull::new_unchecked(ptr.add(i))) }) + } + + /// Returns an iterator that allows modifying each value. + pub fn iter_mut(self) -> impl Iterator>> { + let ptr = self.as_ptr().as_ptr() as *mut T; + let len = self.len(); + (0..len) + .map(move |i| unsafe { VolatilePtr::new_generic(NonNull::new_unchecked(ptr.add(i))) }) + } + + /// Copies all elements from `self` into `dst`, using a volatile memcpy. + /// + /// The length of `dst` must be the same as `self`. + /// + /// The method is only available with the `unstable` feature enabled (requires a nightly + /// Rust compiler). + /// + /// ## Panics + /// + /// This function will panic if the two slices have different lengths. + /// + /// ## Examples + /// + /// Copying two elements from a volatile slice: + /// + /// ``` + /// # extern crate core; + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let src = [1, 2]; + /// // the `Volatile` type does not work with arrays, so convert `src` to a slice + /// let slice = &src[..]; + /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(slice)) }; + /// let mut dst = [5, 0, 0]; + /// + /// // Because the slices have to be the same length, + /// // we slice the destination slice from three elements + /// // to two. It will panic if we don't do this. + /// volatile.copy_into_slice(&mut dst[1..]); + /// + /// assert_eq!(src, [1, 2]); + /// assert_eq!(dst, [5, 1, 2]); + /// ``` + pub fn copy_into_slice(&self, dst: &mut [T]) + where + T: Copy, + R: access_ptr::Safe, + { + let len = self.pointer.len(); + assert_eq!( + len, + dst.len(), + "destination and source slices have different lengths" + ); + unsafe { + intrinsics::volatile_copy_nonoverlapping_memory( + dst.as_mut_ptr(), + self.pointer.as_mut_ptr(), + len, + ); + } + } + + /// Copies all elements from `src` into `self`, using a volatile memcpy. + /// + /// The length of `src` must be the same as `self`. + /// + /// This method is similar to the `slice::copy_from_slice` method of the standard library. The + /// difference is that this method performs a volatile copy. + /// + /// The method is only available with the `unstable` feature enabled (requires a nightly + /// Rust compiler). + /// + /// ## Panics + /// + /// This function will panic if the two slices have different lengths. + /// + /// ## Examples + /// + /// Copying two elements from a slice into a volatile slice: + /// + /// ``` + /// # extern crate core; + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let src = [1, 2, 3, 4]; + /// let mut dst = [0, 0]; + /// // the `Volatile` type does not work with arrays, so convert `dst` to a slice + /// let slice = &mut dst[..]; + /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(slice))}; + /// + /// // Because the slices have to be the same length, + /// // we slice the source slice from four elements + /// // to two. It will panic if we don't do this. + /// volatile.copy_from_slice(&src[2..]); + /// + /// assert_eq!(src, [1, 2, 3, 4]); + /// assert_eq!(dst, [3, 4]); + /// ``` + pub fn copy_from_slice(&mut self, src: &[T]) + where + T: Copy, + W: access_ptr::Safe, + { + let len = self.pointer.len(); + assert_eq!( + len, + src.len(), + "destination and source slices have different lengths" + ); + unsafe { + intrinsics::volatile_copy_nonoverlapping_memory( + self.pointer.as_mut_ptr(), + src.as_ptr(), + len, + ); + } + } + + /// Copies elements from one part of the slice to another part of itself, using a + /// volatile `memmove`. + /// + /// `src` is the range within `self` to copy from. `dest` is the starting index of the + /// range within `self` to copy to, which will have the same length as `src`. The two ranges + /// may overlap. The ends of the two ranges must be less than or equal to `self.len()`. + /// + /// This method is similar to the `slice::copy_within` method of the standard library. The + /// difference is that this method performs a volatile copy. + /// + /// This method is only available with the `unstable` feature enabled (requires a nightly + /// Rust compiler). + /// + /// ## Panics + /// + /// This function will panic if either range exceeds the end of the slice, or if the end + /// of `src` is before the start. + /// + /// ## Examples + /// + /// Copying four bytes within a slice: + /// + /// ``` + /// extern crate core; + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut byte_array = *b"Hello, World!"; + /// let mut slice: &mut [u8] = &mut byte_array[..]; + /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(slice)) }; + /// + /// volatile.copy_within(1..5, 8); + /// + /// assert_eq!(&byte_array, b"Hello, Wello!"); + pub fn copy_within(&mut self, src: impl RangeBounds, dest: usize) + where + T: Copy, + R: access_ptr::Safe, + W: access_ptr::Safe, + { + let len = self.pointer.len(); + // implementation taken from https://github.com/rust-lang/rust/blob/683d1bcd405727fcc9209f64845bd3b9104878b8/library/core/src/slice/mod.rs#L2726-L2738 + let Range { + start: src_start, + end: src_end, + } = range(src, ..len); + let count = src_end - src_start; + assert!(dest <= len - count, "dest is out of bounds"); + // SAFETY: the conditions for `volatile_copy_memory` have all been checked above, + // as have those for `ptr::add`. + unsafe { + intrinsics::volatile_copy_memory( + self.pointer.as_mut_ptr().add(dest), + self.pointer.as_mut_ptr().add(src_start), + count, + ); + } + } + + pub fn split_at( + self, + mid: usize, + ) -> ( + VolatilePtr<'a, [T], Access>, + VolatilePtr<'a, [T], Access>, + ) { + assert!(mid <= self.pointer.len()); + // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which + // fulfills the requirements of `from_raw_parts_mut`. + unsafe { self.split_at_unchecked(mid) } + } + + pub fn split_at_mut( + self, + mid: usize, + ) -> ( + VolatilePtr<'a, [T], Access>, + VolatilePtr<'a, [T], Access>, + ) { + assert!(mid <= self.pointer.len()); + // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which + // fulfills the requirements of `from_raw_parts_mut`. + unsafe { self.split_at_mut_unchecked(mid) } + } + + unsafe fn split_at_unchecked( + self, + mid: usize, + ) -> ( + VolatilePtr<'a, [T], Access>, + VolatilePtr<'a, [T], Access>, + ) { + // SAFETY: Caller has to check that `0 <= mid <= self.len()` + unsafe { + ( + VolatilePtr::new_generic((self.pointer).get_unchecked_mut(..mid)), + VolatilePtr::new_generic((self.pointer).get_unchecked_mut(mid..)), + ) + } + } + + unsafe fn split_at_mut_unchecked( + self, + mid: usize, + ) -> ( + VolatilePtr<'a, [T], Access>, + VolatilePtr<'a, [T], Access>, + ) { + let len = self.pointer.len(); + let ptr = self.pointer.as_mut_ptr(); + + // SAFETY: Caller has to check that `0 <= mid <= self.len()`. + // + // `[ptr; mid]` and `[mid; len]` are not overlapping, so returning a mutable reference + // is fine. + unsafe { + ( + VolatilePtr::new_generic( + NonNull::new(ptr::slice_from_raw_parts_mut(ptr, mid)).unwrap(), + ), + VolatilePtr::new_generic( + NonNull::new(ptr::slice_from_raw_parts_mut(ptr.add(mid), len - mid)).unwrap(), + ), + ) + } + } + + pub fn as_chunks( + self, + ) -> ( + VolatilePtr<'a, [[T; N]], Access>, + VolatilePtr<'a, [T], Access>, + ) { + assert_ne!(N, 0); + let len = self.pointer.len() / N; + let (multiple_of_n, remainder) = self.split_at(len * N); + // SAFETY: We already panicked for zero, and ensured by construction + // that the length of the subslice is a multiple of N. + let array_slice = unsafe { multiple_of_n.as_chunks_unchecked() }; + (array_slice, remainder) + } + + pub unsafe fn as_chunks_unchecked( + self, + ) -> VolatilePtr<'a, [[T; N]], Access> { + debug_assert_ne!(N, 0); + debug_assert_eq!(self.pointer.len() % N, 0); + let new_len = + // SAFETY: Our precondition is exactly what's needed to call this + unsafe { core::intrinsics::exact_div(self.pointer.len(), N) }; + // SAFETY: We cast a slice of `new_len * N` elements into + // a slice of `new_len` many `N` elements chunks. + let pointer = NonNull::new(ptr::slice_from_raw_parts_mut( + self.pointer.as_mut_ptr().cast(), + new_len, + )) + .unwrap(); + unsafe { VolatilePtr::new_generic(pointer) } + } + + pub fn as_chunks_mut( + self, + ) -> ( + VolatilePtr<'a, [[T; N]], Access>, + VolatilePtr<'a, [T], Access>, + ) { + assert_ne!(N, 0); + let len = self.pointer.len() / N; + let (multiple_of_n, remainder) = self.split_at_mut(len * N); + // SAFETY: We already panicked for zero, and ensured by construction + // that the length of the subslice is a multiple of N. + let array_slice = unsafe { multiple_of_n.as_chunks_unchecked_mut() }; + (array_slice, remainder) + } + + pub unsafe fn as_chunks_unchecked_mut( + self, + ) -> VolatilePtr<'a, [[T; N]], Access> { + debug_assert_ne!(N, 0); + debug_assert_eq!(self.pointer.len() % N, 0); + let new_len = + // SAFETY: Our precondition is exactly what's needed to call this + unsafe { core::intrinsics::exact_div(self.pointer.len(), N) }; + // SAFETY: We cast a slice of `new_len * N` elements into + // a slice of `new_len` many `N` elements chunks. + let pointer = NonNull::new(ptr::slice_from_raw_parts_mut( + self.pointer.as_mut_ptr().cast(), + new_len, + )) + .unwrap(); + unsafe { VolatilePtr::new_generic(pointer) } + } +} + +/// Methods for volatile byte slices +#[cfg(feature = "unstable")] +impl VolatilePtr<'_, [u8], Access> { + /// Sets all elements of the byte slice to the given `value` using a volatile `memset`. + /// + /// This method is similar to the `slice::fill` method of the standard library, with the + /// difference that this method performs a volatile write operation. Another difference + /// is that this method is only available for byte slices (not general `&mut [T]` slices) + /// because there currently isn't a instrinsic function that allows non-`u8` values. + /// + /// This method is only available with the `unstable` feature enabled (requires a nightly + /// Rust compiler). + /// + /// ## Example + /// + /// ```rust + /// # extern crate core; + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut vec = vec![0; 10]; + /// let mut buf = unsafe { VolatilePtr::new_read_write(NonNull::from(vec.as_mut_slice())) }; + /// buf.fill(1); + /// assert_eq!(unsafe { buf.as_ptr().as_mut() }, &mut vec![1; 10]); + /// ``` + pub fn fill(&mut self, value: u8) + where + W: access_ptr::Safe, + { + unsafe { + intrinsics::volatile_set_memory(self.pointer.as_mut_ptr(), value, self.pointer.len()); + } + } +} + +/// Methods for converting arrays to slices +/// +/// These methods are only available with the `unstable` feature enabled (requires a nightly +/// Rust compiler). +#[cfg(feature = "unstable")] +impl<'a, T, R, W, const N: usize> VolatilePtr<'a, [T; N], Access> { + /// Converts an array reference to a shared slice. + /// + /// This makes it possible to use the methods defined on slices. + /// + /// ## Example + /// + /// Copying two elements from a volatile array reference using `copy_into_slice`: + /// + /// ``` + /// # extern crate core; + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let src = [1, 2]; + /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(&src)) }; + /// let mut dst = [0, 0]; + /// + /// // convert the `Volatile<&[i32; 2]>` array reference to a `Volatile<&[i32]>` slice + /// let volatile_slice = volatile.as_slice(); + /// // we can now use the slice methods + /// volatile_slice.copy_into_slice(&mut dst); + /// + /// assert_eq!(dst, [1, 2]); + /// ``` + pub fn as_slice(self) -> VolatilePtr<'a, [T], Access> { + unsafe { + self.map(|array| { + NonNull::new(ptr::slice_from_raw_parts_mut(array.as_ptr() as *mut T, N)).unwrap() + }) + } + } + + /// Converts an array reference to a shared slice. + /// + /// This makes it possible to use the methods defined on slices. + /// + /// ## Example + /// + /// Copying two elements into a volatile array reference using `copy_from_slice`: + /// + /// ``` + /// # extern crate core; + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let src = [1, 2]; + /// let mut dst = [0, 0]; + /// let mut volatile = unsafe { VolatilePtr::new_write_only(NonNull::from(&dst)) }; + /// + /// // convert the `Volatile<[i32; 2]>` array reference to a `Volatile<[i32]>` slice + /// let mut volatile_slice = volatile.as_slice_mut(); + /// // we can now use the slice methods + /// volatile_slice.copy_from_slice(&src); + /// + /// assert_eq!(dst, [1, 2]); + /// ``` + pub fn as_slice_mut(self) -> VolatilePtr<'a, [T], Access> { + unsafe { + self.map_mut(|array| { + NonNull::new(ptr::slice_from_raw_parts_mut(array.as_ptr() as *mut T, N)).unwrap() + }) + } + } +} + +/// Methods for restricting access. +impl<'a, T, R, W> VolatilePtr<'a, T, Access> +where + T: ?Sized, +{ + /// Restricts access permissions to read-only. + /// + /// ## Example + /// + /// ``` + /// # extern crate core; + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut value: i16 = -4; + /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; + /// + /// let read_only = volatile.read_only(); + /// assert_eq!(read_only.read(), -4); + /// // read_only.write(10); // compile-time error + /// ``` + pub fn read_only(self) -> VolatilePtr<'a, T, Access> { + unsafe { VolatilePtr::new_generic(self.pointer) } + } + + /// Restricts access permissions to write-only. + /// + /// ## Example + /// + /// Creating a write-only reference to a struct field: + /// + /// ``` + /// # extern crate core; + /// use volatile::{VolatilePtr, map_field_mut}; + /// use core::ptr::NonNull; + /// + /// struct Example { field_1: u32, field_2: u8, } + /// let mut value = Example { field_1: 15, field_2: 255 }; + /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; + /// + /// // construct a volatile write-only reference to `field_2` + /// let mut field_2 = map_field_mut!(volatile.field_2).write_only(); + /// field_2.write(14); + /// // field_2.read(); // compile-time error + /// ``` + pub fn write_only(self) -> VolatilePtr<'a, T, Access> { + unsafe { VolatilePtr::new_generic(self.pointer) } + } +} + +/// Unsafe access methods for references to `Copy` types +impl VolatilePtr<'_, T, Access> +where + T: Copy + ?Sized, +{ + pub unsafe fn read_unsafe(&self) -> T + where + R: access_ptr::Unsafe, + { + unsafe { ptr::read_volatile(self.pointer.as_ptr()) } + } + + pub unsafe fn write_unsafe(&mut self, value: T) + where + W: access_ptr::Unsafe, + { + unsafe { ptr::write_volatile(self.pointer.as_ptr(), value) }; + } + + pub unsafe fn update_unsafe(&mut self, f: F) + where + R: access_ptr::Unsafe, + W: access_ptr::Unsafe, + F: FnOnce(&mut T), + { + let mut value = unsafe { self.read_unsafe() }; + f(&mut value); + unsafe { self.write_unsafe(value) }; + } +} + +impl fmt::Debug for VolatilePtr<'_, T, Access> +where + T: Copy + fmt::Debug + ?Sized, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Volatile").field(&self.read()).finish() + } +} + +impl fmt::Debug for VolatilePtr<'_, T, Access> +where + T: ?Sized, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Volatile").field(&"[unsafe read]").finish() + } +} + +impl fmt::Debug for VolatilePtr<'_, T, Access> +where + T: ?Sized, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Volatile") + .field(&"[no read access]") + .finish() + } +} + +#[cfg(feature = "unstable")] +fn bounds_check(len: usize, index: impl SliceIndex<[()]>) { + const MAX_ARRAY: [(); usize::MAX] = [(); usize::MAX]; + + let bound_check_slice = &MAX_ARRAY[..len]; + &bound_check_slice[index]; +} diff --git a/src/tests.rs b/src/tests.rs index bc22063..16a117b 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,6 +1,6 @@ use core::ptr::NonNull; -use super::{access::*, VolatilePtr}; +use super::{access_ptr::*, ptr::VolatilePtr}; #[test] fn test_read() { From 111db0a67596d7fab7eeb835595b5a6cbfadf0bc Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 13:42:54 +0100 Subject: [PATCH 19/68] Adjust stable functions of `VolatilePtr` to simplified access types --- src/access_ptr.rs | 48 ------------ src/lib.rs | 1 - src/ptr.rs | 188 +++++++++++++--------------------------------- 3 files changed, 54 insertions(+), 183 deletions(-) delete mode 100644 src/access_ptr.rs diff --git a/src/access_ptr.rs b/src/access_ptr.rs deleted file mode 100644 index be88c83..0000000 --- a/src/access_ptr.rs +++ /dev/null @@ -1,48 +0,0 @@ -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct NoAccess; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct UnsafeAccess; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct SafeAccess; - -pub trait Unsafe {} -pub trait Safe: Unsafe {} - -impl Unsafe for UnsafeAccess {} -impl Unsafe for SafeAccess {} -impl Safe for SafeAccess {} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Access { - pub read: R, - pub write: W, -} - -impl Access { - pub const fn read_only() -> ReadOnly { - Access { - read: SafeAccess, - write: NoAccess, - } - } - - pub const fn write_only() -> WriteOnly { - Access { - read: NoAccess, - write: SafeAccess, - } - } - - pub const fn read_write() -> ReadWrite { - Access { - read: SafeAccess, - write: SafeAccess, - } - } -} - -pub type ReadOnly = Access; -pub type WriteOnly = Access; -pub type ReadWrite = Access; diff --git a/src/lib.rs b/src/lib.rs index 14b8ad3..b1fdb58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,5 @@ #![deny(unsafe_op_in_unsafe_fn)] pub mod access; -mod access_ptr; mod cell; mod ptr; diff --git a/src/ptr.rs b/src/ptr.rs index 5f29b66..5c3f0d8 100644 --- a/src/ptr.rs +++ b/src/ptr.rs @@ -5,7 +5,6 @@ //! The wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider //! looking at the `Atomic` wrapper types found in `libcore` or `libstd`. -use access_ptr::{Access, NoAccess, ReadOnly, ReadWrite, WriteOnly}; use core::{ fmt, marker::PhantomData, @@ -19,7 +18,7 @@ use core::{ slice::{range, SliceIndex}, }; -use crate::access_ptr; +use crate::access::{Access, ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; /// TODO /// @@ -93,7 +92,7 @@ macro_rules! map_field_mut { }}; } -/// Wraps a reference to make accesses to the referenced value volatile. +/// Wraps a pointer to make accesses to the referenced value volatile. /// /// Allows volatile reads and writes on the referenced value. The referenced value needs to /// be `Copy` for reading and writing, as volatile reads and writes take and return copies @@ -115,83 +114,50 @@ where access: PhantomData, } -/// Constructor functions for creating new values -/// -/// These functions allow to construct a new `Volatile` instance from a reference type. While -/// the `new` function creates a `Volatile` instance with unrestricted access, there are also -/// functions for creating read-only or write-only instances. -impl VolatilePtr<'_, T> +impl<'a, T> VolatilePtr<'a, T> where T: ?Sized, { - pub unsafe fn new_read_write(pointer: NonNull) -> VolatilePtr<'static, T> { - unsafe { VolatilePtr::new_with_access(pointer, Access::read_write()) } - } - - pub const unsafe fn new_read_only(pointer: NonNull) -> VolatilePtr<'static, T, ReadOnly> { - unsafe { VolatilePtr::new_with_access(pointer, Access::read_only()) } - } - - pub const unsafe fn new_write_only(pointer: NonNull) -> VolatilePtr<'static, T, WriteOnly> { - unsafe { VolatilePtr::new_with_access(pointer, Access::write_only()) } + pub unsafe fn new(pointer: NonNull) -> Self { + unsafe { VolatilePtr::new_restricted(ReadWrite, pointer) } } - /// Constructs a new volatile instance wrapping the given reference. - /// - /// While it is possible to construct `Volatile` instances from arbitrary values (including - /// non-reference values), most of the methods are only available when the wrapped type is - /// a reference. The only reason that we don't forbid non-reference types in the constructor - /// functions is that the Rust compiler does not support trait bounds on generic `const` - /// functions yet. When this becomes possible, we will release a new version of this library - /// with removed support for non-references. For these reasons it is recommended to use - /// the `Volatile` type only with references. - /// - /// ## Example - /// - /// ```rust - /// # extern crate core; - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let mut value = 0u32; - /// - /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; - /// volatile.write(1); - /// assert_eq!(volatile.read(), 1); - /// ``` - pub const unsafe fn new_with_access( - pointer: NonNull, - access: A, - ) -> VolatilePtr<'static, T, A> { - mem::forget(access); // needed because we cannot require `A: Copy` on stable Rust yet - unsafe { Self::new_generic(pointer) } + pub fn from_mut_ref(reference: &'a mut T) -> Self + where + T: 'a, + { + unsafe { VolatilePtr::new(reference.into()) } } +} - pub const unsafe fn new_generic<'a, A>(pointer: NonNull) -> VolatilePtr<'a, T, A> { +impl VolatilePtr<'_, T, A> +where + A: Access, + T: ?Sized, +{ + pub const unsafe fn new_restricted(access: A, pointer: NonNull) -> Self { + let _ = access; VolatilePtr { pointer, reference: PhantomData, access: PhantomData, } } - - pub fn from_ref<'a>(reference: &'a T) -> VolatilePtr<'a, T, ReadOnly> - where - T: 'a, - { - unsafe { VolatilePtr::new_generic(reference.into()) } - } - - pub fn from_mut_ref<'a>(reference: &'a mut T) -> VolatilePtr<'a, T> +} +impl<'a, T> VolatilePtr<'a, T, ReadOnly> +where + T: ?Sized, +{ + pub fn from_ref(reference: &'a T) -> Self where T: 'a, { - unsafe { VolatilePtr::new_generic(reference.into()) } + unsafe { VolatilePtr::new_restricted(ReadOnly, reference.into()) } } } /// Methods for references to `Copy` types -impl VolatilePtr<'_, T, Access> +impl VolatilePtr<'_, T, A> where T: Copy + ?Sized, { @@ -219,10 +185,10 @@ where /// ``` pub fn read(&self) -> T where - R: access_ptr::Safe, + A: Readable, { // UNSAFE: Safe, as ... TODO - unsafe { self.read_unsafe() } + unsafe { ptr::read_volatile(self.pointer.as_ptr()) } } /// Performs a volatile write, setting the contained value to the given `value`. @@ -246,10 +212,10 @@ where /// ``` pub fn write(&mut self, value: T) where - W: access_ptr::Safe, + A: Writable, { // UNSAFE: Safe, as ... TODO - unsafe { self.write_unsafe(value) }; + unsafe { ptr::write_volatile(self.pointer.as_ptr(), value) }; } /// Updates the contained value using the given closure and volatile instructions. @@ -271,11 +237,11 @@ where /// ``` pub fn update(&mut self, f: F) where - R: access_ptr::Safe, - W: access_ptr::Safe, - F: FnOnce(&mut T), + A: Readable + Writable, + F: FnOnce(T) -> T, { - unsafe { self.update_unsafe(f) } + let new = f(self.read()); + self.write(new); } } @@ -316,18 +282,21 @@ where } /// Transformation methods for accessing struct fields -impl<'a, T, R, W> VolatilePtr<'a, T, Access> +impl<'a, T, A> VolatilePtr<'a, T, A> where T: ?Sized, { // TODO: Add documentation - pub fn borrow(&self) -> VolatilePtr> { - unsafe { VolatilePtr::new_generic(self.pointer) } + pub fn borrow(&self) -> VolatilePtr { + unsafe { VolatilePtr::new_restricted(ReadOnly, self.pointer) } } // TODO: Add documentation - pub fn borrow_mut(&mut self) -> VolatilePtr> { - unsafe { VolatilePtr::new_generic(self.pointer) } + pub fn borrow_mut(&mut self) -> VolatilePtr + where + A: Access, + { + unsafe { VolatilePtr::new_restricted(A::default(), self.pointer) } } /// Constructs a new `Volatile` reference by mapping the wrapped pointer. @@ -371,12 +340,12 @@ where /// value /// })}; /// ``` - pub unsafe fn map(self, f: F) -> VolatilePtr<'a, U, Access> + pub unsafe fn map(self, f: F) -> VolatilePtr<'a, U, ReadOnly> where F: FnOnce(NonNull) -> NonNull, U: ?Sized, { - unsafe { VolatilePtr::new_generic(f(self.pointer)) } + unsafe { VolatilePtr::new_restricted(ReadOnly, f(self.pointer)) } } #[cfg(feature = "very_unstable")] @@ -391,12 +360,13 @@ where unsafe { VolatilePtr::new_generic(f(self.pointer)) } } - pub unsafe fn map_mut(self, f: F) -> VolatilePtr<'a, U, Access> + pub unsafe fn map_mut(self, f: F) -> VolatilePtr<'a, U, A> where F: FnOnce(NonNull) -> NonNull, U: ?Sized, + A: Access, { - unsafe { VolatilePtr::new_generic(f(self.pointer)) } + unsafe { VolatilePtr::new_restricted(A::default(), f(self.pointer)) } } #[cfg(feature = "very_unstable")] @@ -931,7 +901,7 @@ impl<'a, T, R, W, const N: usize> VolatilePtr<'a, [T; N], Access> { } /// Methods for restricting access. -impl<'a, T, R, W> VolatilePtr<'a, T, Access> +impl<'a, T> VolatilePtr<'a, T, ReadWrite> where T: ?Sized, { @@ -951,8 +921,8 @@ where /// assert_eq!(read_only.read(), -4); /// // read_only.write(10); // compile-time error /// ``` - pub fn read_only(self) -> VolatilePtr<'a, T, Access> { - unsafe { VolatilePtr::new_generic(self.pointer) } + pub fn read_only(self) -> VolatilePtr<'a, T, ReadOnly> { + unsafe { VolatilePtr::new_restricted(ReadOnly, self.pointer) } } /// Restricts access permissions to write-only. @@ -975,71 +945,21 @@ where /// field_2.write(14); /// // field_2.read(); // compile-time error /// ``` - pub fn write_only(self) -> VolatilePtr<'a, T, Access> { - unsafe { VolatilePtr::new_generic(self.pointer) } + pub fn write_only(self) -> VolatilePtr<'a, T, WriteOnly> { + unsafe { VolatilePtr::new_restricted(WriteOnly, self.pointer) } } } -/// Unsafe access methods for references to `Copy` types -impl VolatilePtr<'_, T, Access> -where - T: Copy + ?Sized, -{ - pub unsafe fn read_unsafe(&self) -> T - where - R: access_ptr::Unsafe, - { - unsafe { ptr::read_volatile(self.pointer.as_ptr()) } - } - - pub unsafe fn write_unsafe(&mut self, value: T) - where - W: access_ptr::Unsafe, - { - unsafe { ptr::write_volatile(self.pointer.as_ptr(), value) }; - } - - pub unsafe fn update_unsafe(&mut self, f: F) - where - R: access_ptr::Unsafe, - W: access_ptr::Unsafe, - F: FnOnce(&mut T), - { - let mut value = unsafe { self.read_unsafe() }; - f(&mut value); - unsafe { self.write_unsafe(value) }; - } -} - -impl fmt::Debug for VolatilePtr<'_, T, Access> +impl fmt::Debug for VolatilePtr<'_, T, A> where T: Copy + fmt::Debug + ?Sized, + A: Readable, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("Volatile").field(&self.read()).finish() } } -impl fmt::Debug for VolatilePtr<'_, T, Access> -where - T: ?Sized, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Volatile").field(&"[unsafe read]").finish() - } -} - -impl fmt::Debug for VolatilePtr<'_, T, Access> -where - T: ?Sized, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Volatile") - .field(&"[no read access]") - .finish() - } -} - #[cfg(feature = "unstable")] fn bounds_check(len: usize, index: impl SliceIndex<[()]>) { const MAX_ARRAY: [(); usize::MAX] = [(); usize::MAX]; From 838581870cc8139c2b83b6868dc1de5a3105687f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 13:43:27 +0100 Subject: [PATCH 20/68] Add methods for borrowing a `VolatileCell` as `VolatilePtr` --- src/cell.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/cell.rs b/src/cell.rs index 336fd93..356a05a 100644 --- a/src/cell.rs +++ b/src/cell.rs @@ -19,8 +19,11 @@ // ! and then perform operations on the pointer as usual in a volatile way. This method works as all // ! of the volatile wrapper types are the same size as their contained values. -use crate::access::{Access, ReadWrite, Readable, Writable}; -use core::{fmt, marker::PhantomData, ptr}; +use crate::{ + access::{Access, ReadOnly, ReadWrite, Readable, Writable}, + ptr::VolatilePtr, +}; +use core::{fmt, marker::PhantomData}; /// A wrapper type around a volatile variable, which allows for volatile reads and writes /// to the contained value. The stored type needs to be `Copy`, as volatile reads and writes @@ -116,6 +119,16 @@ impl VolatileCell { pub fn access(&self) -> A { A::default() } + + pub fn as_ptr(&self) -> VolatilePtr { + // UNSAFE: Safe, as we know that our internal value exists. + unsafe { VolatilePtr::new_restricted(ReadOnly, (&self.value).into()) } + } + + pub fn as_mut_ptr(&mut self) -> VolatilePtr { + // UNSAFE: Safe, as we know that our internal value exists. + unsafe { VolatilePtr::new_restricted(A::default(), (&mut self.value).into()) } + } } impl VolatileCell { From 2fbc95622e9f3abe7a376d795591c64cabd91f6c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 13:43:55 +0100 Subject: [PATCH 21/68] Use new pointer borrow methods for implementing read/write functions on `VolatileCell` --- src/cell.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/cell.rs b/src/cell.rs index 356a05a..f12e734 100644 --- a/src/cell.rs +++ b/src/cell.rs @@ -147,8 +147,7 @@ impl VolatileCell { where A: Readable, { - // UNSAFE: Safe, as we know that our internal value exists. - unsafe { ptr::read_volatile(&self.value) } + self.as_ptr().read() } /// Performs a volatile write, setting the contained value to the given value `value`. Volatile @@ -167,8 +166,7 @@ impl VolatileCell { where A: Writable, { - // UNSAFE: Safe, as we know that our internal value exists. - unsafe { ptr::write_volatile(&mut self.value, value) }; + self.as_mut_ptr().write(value) } /// Performs a volatile read of the contained value, passes a mutable reference to it to the From cf4c57cd90406bda2e54eea3dafacf89f05fb0af Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 13:47:39 +0100 Subject: [PATCH 22/68] Wrap `VolatileCell::value` in `UnsafeCell` to prevent aliasing issues --- src/cell.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/cell.rs b/src/cell.rs index f12e734..dc2138b 100644 --- a/src/cell.rs +++ b/src/cell.rs @@ -23,7 +23,7 @@ use crate::{ access::{Access, ReadOnly, ReadWrite, Readable, Writable}, ptr::VolatilePtr, }; -use core::{fmt, marker::PhantomData}; +use core::{cell::UnsafeCell, fmt, marker::PhantomData, ptr::NonNull}; /// A wrapper type around a volatile variable, which allows for volatile reads and writes /// to the contained value. The stored type needs to be `Copy`, as volatile reads and writes @@ -42,7 +42,7 @@ use core::{fmt, marker::PhantomData}; #[derive(Default)] #[repr(transparent)] pub struct VolatileCell { - value: T, + value: UnsafeCell, access: PhantomData, } @@ -111,7 +111,7 @@ impl VolatileCell { pub const fn new_restricted(access: A, value: T) -> Self { let _ = access; VolatileCell { - value, + value: UnsafeCell::new(value), access: PhantomData, } } @@ -122,12 +122,14 @@ impl VolatileCell { pub fn as_ptr(&self) -> VolatilePtr { // UNSAFE: Safe, as we know that our internal value exists. - unsafe { VolatilePtr::new_restricted(ReadOnly, (&self.value).into()) } + unsafe { VolatilePtr::new_restricted(ReadOnly, NonNull::new_unchecked(self.value.get())) } } pub fn as_mut_ptr(&mut self) -> VolatilePtr { // UNSAFE: Safe, as we know that our internal value exists. - unsafe { VolatilePtr::new_restricted(A::default(), (&mut self.value).into()) } + unsafe { + VolatilePtr::new_restricted(A::default(), NonNull::new_unchecked(self.value.get())) + } } } @@ -242,14 +244,14 @@ mod tests { fn test_write() { let mut volatile = VolatileCell::new(42); volatile.write(50); - assert_eq!(volatile.value, 50); + assert_eq!(unsafe { *volatile.value.get_mut() }, 50); } #[test] fn test_update() { let mut volatile = VolatileCell::new(42); volatile.update(|v| v + 1); - assert_eq!(volatile.value, 43); + assert_eq!(volatile.read(), 43); } #[test] From 1d2d3bfc85ddd4c04b24b46c595f950a66901726 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 13:51:10 +0100 Subject: [PATCH 23/68] Reexport `VolatileCell` and `VolatilePtr` --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index b1fdb58..440ce7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,9 @@ #![warn(missing_docs)] #![deny(unsafe_op_in_unsafe_fn)] +pub use cell::VolatileCell; +pub use ptr::VolatilePtr; + pub mod access; mod cell; mod ptr; From c3236675d79d86e4474bfa0e8a110edaadfa8c31 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 13:51:21 +0100 Subject: [PATCH 24/68] Improve generated docs --- src/ptr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ptr.rs b/src/ptr.rs index 5c3f0d8..7d1f428 100644 --- a/src/ptr.rs +++ b/src/ptr.rs @@ -114,7 +114,7 @@ where access: PhantomData, } -impl<'a, T> VolatilePtr<'a, T> +impl<'a, T> VolatilePtr<'a, T, ReadWrite> where T: ?Sized, { From 5da07e77534ace09aa18e04b027cec790f0531ef Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 13:57:38 +0100 Subject: [PATCH 25/68] Fix: Using `borrow` on a `WriteOnly` pointer should result in no access --- src/access.rs | 21 ++++++++++++++++++--- src/ptr.rs | 9 ++++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/access.rs b/src/access.rs index 3234a3a..096d43f 100644 --- a/src/access.rs +++ b/src/access.rs @@ -4,6 +4,8 @@ pub trait Access: Copy + Default { fn _private() -> _Private { _Private } + + type RestrictShared: Access; } /// Helper trait that is implemented by [`ReadWrite`] and [`ReadOnly`]. @@ -25,22 +27,35 @@ pub trait Writable: Access { /// Zero-sized marker type for allowing both read and write access. #[derive(Debug, Default, Copy, Clone)] pub struct ReadWrite; -impl Access for ReadWrite {} +impl Access for ReadWrite { + type RestrictShared = ReadOnly; +} impl Readable for ReadWrite {} impl Writable for ReadWrite {} /// Zero-sized marker type for allowing only read access. #[derive(Debug, Default, Copy, Clone)] pub struct ReadOnly; -impl Access for ReadOnly {} +impl Access for ReadOnly { + type RestrictShared = ReadOnly; +} impl Readable for ReadOnly {} /// Zero-sized marker type for allowing only write access. #[derive(Debug, Default, Copy, Clone)] pub struct WriteOnly; -impl Access for WriteOnly {} +impl Access for WriteOnly { + type RestrictShared = NoAccess; +} impl Writable for WriteOnly {} +/// Zero-sized marker type that grants no access. +#[derive(Debug, Default, Copy, Clone)] +pub struct NoAccess; +impl Access for NoAccess { + type RestrictShared = NoAccess; +} + #[non_exhaustive] #[doc(hidden)] pub struct _Private; diff --git a/src/ptr.rs b/src/ptr.rs index 7d1f428..26e2a55 100644 --- a/src/ptr.rs +++ b/src/ptr.rs @@ -287,8 +287,11 @@ where T: ?Sized, { // TODO: Add documentation - pub fn borrow(&self) -> VolatilePtr { - unsafe { VolatilePtr::new_restricted(ReadOnly, self.pointer) } + pub fn borrow(&self) -> VolatilePtr + where + A: Access, + { + unsafe { VolatilePtr::new_restricted(Default::default(), self.pointer) } } // TODO: Add documentation @@ -345,7 +348,7 @@ where F: FnOnce(NonNull) -> NonNull, U: ?Sized, { - unsafe { VolatilePtr::new_restricted(ReadOnly, f(self.pointer)) } + unsafe { VolatilePtr::new_restricted(Default::default(), f(self.pointer)) } } #[cfg(feature = "very_unstable")] From 06404ff709a5cad1328d08b546f251d7b5cc0b8f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 14:25:22 +0100 Subject: [PATCH 26/68] Move trait bounds to methods (instead of impl blocks) --- src/cell.rs | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/cell.rs b/src/cell.rs index dc2138b..bdc243b 100644 --- a/src/cell.rs +++ b/src/cell.rs @@ -46,7 +46,7 @@ pub struct VolatileCell { access: PhantomData, } -impl VolatileCell { +impl VolatileCell { /// Construct a new volatile cell wrapping the given value. /// /// The returned cell allows read and write operations. Use @@ -71,9 +71,7 @@ impl VolatileCell { pub const fn new(value: T) -> Self { VolatileCell::new_restricted(ReadWrite, value) } -} -impl VolatileCell { /// Construct a new volatile cell with restricted access, wrapping the given value. /// /// ## Examples @@ -108,15 +106,23 @@ impl VolatileCell { /// read_only.write(5); // -> compile error /// read_only.update(|v| v + 1); // -> compile error /// ``` - pub const fn new_restricted(access: A, value: T) -> Self { + pub const fn new_restricted(access: A, value: T) -> VolatileCell + where + A: Access, + { let _ = access; VolatileCell { value: UnsafeCell::new(value), access: PhantomData, } } +} - pub fn access(&self) -> A { +impl VolatileCell { + pub fn access(&self) -> A + where + A: Access, + { A::default() } @@ -125,15 +131,16 @@ impl VolatileCell { unsafe { VolatilePtr::new_restricted(ReadOnly, NonNull::new_unchecked(self.value.get())) } } - pub fn as_mut_ptr(&mut self) -> VolatilePtr { + pub fn as_mut_ptr(&mut self) -> VolatilePtr + where + A: Access, + { // UNSAFE: Safe, as we know that our internal value exists. unsafe { VolatilePtr::new_restricted(A::default(), NonNull::new_unchecked(self.value.get())) } } -} -impl VolatileCell { /// Performs a volatile read of the contained value, returning a copy /// of the read value. Volatile reads are guaranteed not to be optimized /// away by the compiler, but by themselves do not have atomic ordering @@ -148,6 +155,7 @@ impl VolatileCell { pub fn read(&self) -> T where A: Readable, + T: Copy, { self.as_ptr().read() } @@ -167,6 +175,7 @@ impl VolatileCell { pub fn write(&mut self, value: T) where A: Writable, + T: Copy, { self.as_mut_ptr().write(value) } @@ -186,6 +195,7 @@ impl VolatileCell { where F: FnOnce(T) -> T, A: Readable + Writable, + T: Copy, { let new = f(self.read()); self.write(new); From 60b2db2d04d0632b22c8780c47f34a1fcf991c9c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 14:25:31 +0100 Subject: [PATCH 27/68] Remove unneeded unsafe --- src/cell.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cell.rs b/src/cell.rs index bdc243b..f7d82dc 100644 --- a/src/cell.rs +++ b/src/cell.rs @@ -254,7 +254,7 @@ mod tests { fn test_write() { let mut volatile = VolatileCell::new(42); volatile.write(50); - assert_eq!(unsafe { *volatile.value.get_mut() }, 50); + assert_eq!(*volatile.value.get_mut(), 50); } #[test] From 9b03f611a61d20c2587a7f3ec337ef99905342a2 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 14:26:10 +0100 Subject: [PATCH 28/68] Remove unneeded extern crate statements in doc tests --- src/ptr.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/ptr.rs b/src/ptr.rs index 26e2a55..d956f92 100644 --- a/src/ptr.rs +++ b/src/ptr.rs @@ -27,7 +27,6 @@ use crate::access::{Access, ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; /// Accessing a struct field: /// /// ``` -/// # extern crate core; /// use volatile::{VolatilePtr, map_field}; /// use core::ptr::NonNull; /// @@ -42,7 +41,6 @@ use crate::access::{Access, ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; /// /// Creating `VolatilePtr`s to unaligned field in packed structs is not allowed: /// ```compile_fail -/// # extern crate core; /// use volatile::{VolatilePtr, map_field}; /// use core::ptr::NonNull; /// @@ -171,7 +169,6 @@ where /// ## Examples /// /// ```rust - /// # extern crate core; /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// @@ -200,7 +197,6 @@ where /// ## Example /// /// ```rust - /// # extern crate core; /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// @@ -225,7 +221,6 @@ where /// the contained value. /// /// ```rust - /// # extern crate core; /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// @@ -265,7 +260,6 @@ where /// ## Example /// /// ``` - /// # extern crate core; /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// @@ -313,7 +307,6 @@ where /// Accessing a struct field: /// /// ``` - /// # extern crate core; /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// @@ -329,7 +322,6 @@ where /// Don't misuse this method to do a non-volatile read of the referenced value: /// /// ``` - /// # extern crate core; /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// @@ -406,7 +398,6 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { /// Accessing a single slice element: /// /// ``` - /// # extern crate core; /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// @@ -419,7 +410,6 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { /// Accessing a subslice: /// /// ``` - /// # extern crate core; /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// @@ -508,7 +498,6 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { /// Copying two elements from a volatile slice: /// /// ``` - /// # extern crate core; /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// @@ -565,7 +554,6 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { /// Copying two elements from a slice into a volatile slice: /// /// ``` - /// # extern crate core; /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// @@ -813,7 +801,6 @@ impl VolatilePtr<'_, [u8], Access> { /// ## Example /// /// ```rust - /// # extern crate core; /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// @@ -847,7 +834,6 @@ impl<'a, T, R, W, const N: usize> VolatilePtr<'a, [T; N], Access> { /// Copying two elements from a volatile array reference using `copy_into_slice`: /// /// ``` - /// # extern crate core; /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// @@ -879,7 +865,6 @@ impl<'a, T, R, W, const N: usize> VolatilePtr<'a, [T; N], Access> { /// Copying two elements into a volatile array reference using `copy_from_slice`: /// /// ``` - /// # extern crate core; /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// From f5602e7227f73e25b0e986add63e1bac535125b6 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 14:26:40 +0100 Subject: [PATCH 29/68] Move `VolatilePtr` trait bounds to methods (instead of impl blocks) --- src/ptr.rs | 46 +++++++++++++++++----------------------------- 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/src/ptr.rs b/src/ptr.rs index d956f92..5ef4c57 100644 --- a/src/ptr.rs +++ b/src/ptr.rs @@ -112,7 +112,12 @@ where access: PhantomData, } -impl<'a, T> VolatilePtr<'a, T, ReadWrite> +/// Constructor functions. +/// +/// These functions construct new `VolatilePtr` values. While the `new` +/// function creates a `VolatilePtr` instance with unrestricted access, there +/// are also functions for creating read-only or write-only instances. +impl<'a, T> VolatilePtr<'a, T> where T: ?Sized, { @@ -126,14 +131,11 @@ where { unsafe { VolatilePtr::new(reference.into()) } } -} -impl VolatilePtr<'_, T, A> -where - A: Access, - T: ?Sized, -{ - pub const unsafe fn new_restricted(access: A, pointer: NonNull) -> Self { + pub const unsafe fn new_restricted(access: A, pointer: NonNull) -> VolatilePtr<'a, T, A> + where + A: Access, + { let _ = access; VolatilePtr { pointer, @@ -141,12 +143,8 @@ where access: PhantomData, } } -} -impl<'a, T> VolatilePtr<'a, T, ReadOnly> -where - T: ?Sized, -{ - pub fn from_ref(reference: &'a T) -> Self + + pub fn from_ref(reference: &'a T) -> VolatilePtr<'a, T, ReadOnly> where T: 'a, { @@ -154,10 +152,9 @@ where } } -/// Methods for references to `Copy` types -impl VolatilePtr<'_, T, A> +impl<'a, T, A> VolatilePtr<'a, T, A> where - T: Copy + ?Sized, + T: ?Sized, { /// Performs a volatile read of the contained value. /// @@ -182,6 +179,7 @@ where /// ``` pub fn read(&self) -> T where + T: Copy, A: Readable, { // UNSAFE: Safe, as ... TODO @@ -208,6 +206,7 @@ where /// ``` pub fn write(&mut self, value: T) where + T: Copy, A: Writable, { // UNSAFE: Safe, as ... TODO @@ -232,19 +231,14 @@ where /// ``` pub fn update(&mut self, f: F) where + T: Copy, A: Readable + Writable, F: FnOnce(T) -> T, { let new = f(self.read()); self.write(new); } -} -/// Method for extracting the wrapped value. -impl VolatilePtr<'_, T, A> -where - T: ?Sized, -{ /// Extracts the inner value stored in the wrapper type. /// /// This method gives direct access to the wrapped reference and thus allows @@ -273,13 +267,7 @@ where pub fn as_ptr(&self) -> NonNull { self.pointer } -} -/// Transformation methods for accessing struct fields -impl<'a, T, A> VolatilePtr<'a, T, A> -where - T: ?Sized, -{ // TODO: Add documentation pub fn borrow(&self) -> VolatilePtr where From 2be8bbd2e5719754443ad22d7cc740ea85491ee5 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 14:27:25 +0100 Subject: [PATCH 30/68] Reorder impl blocks (unstable features last) --- src/ptr.rs | 98 ++++++++++++++++++++++++++---------------------------- 1 file changed, 48 insertions(+), 50 deletions(-) diff --git a/src/ptr.rs b/src/ptr.rs index 5ef4c57..8ae11ce 100644 --- a/src/ptr.rs +++ b/src/ptr.rs @@ -362,6 +362,54 @@ where } } +/// Methods for restricting access. +impl<'a, T> VolatilePtr<'a, T, ReadWrite> +where + T: ?Sized, +{ + /// Restricts access permissions to read-only. + /// + /// ## Example + /// + /// ``` + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut value: i16 = -4; + /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; + /// + /// let read_only = volatile.read_only(); + /// assert_eq!(read_only.read(), -4); + /// // read_only.write(10); // compile-time error + /// ``` + pub fn read_only(self) -> VolatilePtr<'a, T, ReadOnly> { + unsafe { VolatilePtr::new_restricted(ReadOnly, self.pointer) } + } + + /// Restricts access permissions to write-only. + /// + /// ## Example + /// + /// Creating a write-only reference to a struct field: + /// + /// ``` + /// use volatile::{VolatilePtr, map_field_mut}; + /// use core::ptr::NonNull; + /// + /// struct Example { field_1: u32, field_2: u8, } + /// let mut value = Example { field_1: 15, field_2: 255 }; + /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; + /// + /// // construct a volatile write-only reference to `field_2` + /// let mut field_2 = map_field_mut!(volatile.field_2).write_only(); + /// field_2.write(14); + /// // field_2.read(); // compile-time error + /// ``` + pub fn write_only(self) -> VolatilePtr<'a, T, WriteOnly> { + unsafe { VolatilePtr::new_restricted(WriteOnly, self.pointer) } + } +} + /// Methods for volatile slices #[cfg(feature = "unstable")] impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { @@ -876,56 +924,6 @@ impl<'a, T, R, W, const N: usize> VolatilePtr<'a, [T; N], Access> { } } -/// Methods for restricting access. -impl<'a, T> VolatilePtr<'a, T, ReadWrite> -where - T: ?Sized, -{ - /// Restricts access permissions to read-only. - /// - /// ## Example - /// - /// ``` - /// # extern crate core; - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let mut value: i16 = -4; - /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; - /// - /// let read_only = volatile.read_only(); - /// assert_eq!(read_only.read(), -4); - /// // read_only.write(10); // compile-time error - /// ``` - pub fn read_only(self) -> VolatilePtr<'a, T, ReadOnly> { - unsafe { VolatilePtr::new_restricted(ReadOnly, self.pointer) } - } - - /// Restricts access permissions to write-only. - /// - /// ## Example - /// - /// Creating a write-only reference to a struct field: - /// - /// ``` - /// # extern crate core; - /// use volatile::{VolatilePtr, map_field_mut}; - /// use core::ptr::NonNull; - /// - /// struct Example { field_1: u32, field_2: u8, } - /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; - /// - /// // construct a volatile write-only reference to `field_2` - /// let mut field_2 = map_field_mut!(volatile.field_2).write_only(); - /// field_2.write(14); - /// // field_2.read(); // compile-time error - /// ``` - pub fn write_only(self) -> VolatilePtr<'a, T, WriteOnly> { - unsafe { VolatilePtr::new_restricted(WriteOnly, self.pointer) } - } -} - impl fmt::Debug for VolatilePtr<'_, T, A> where T: Copy + fmt::Debug + ?Sized, From 70fa6ca0bcaa3051c11d9b0b4892b57fd56d319c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 14:29:43 +0100 Subject: [PATCH 31/68] Use 2021 edition --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 4e8cea2..85f0312 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ keywords = ["volatile"] description = "A simple volatile wrapper type" documentation = "https://docs.rs/volatile" repository = "https://github.com/rust-osdev/volatile" +edition = "2021" [dependencies] From f3795cc2dd54a52ef1ed053b37575e9d2f3de435 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 14:29:53 +0100 Subject: [PATCH 32/68] Remove unused import --- src/ptr.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ptr.rs b/src/ptr.rs index 8ae11ce..1f8b77f 100644 --- a/src/ptr.rs +++ b/src/ptr.rs @@ -8,7 +8,6 @@ use core::{ fmt, marker::PhantomData, - mem, ptr::{self, NonNull}, }; #[cfg(feature = "unstable")] From f21ef3f222b5982928912c5680dbbc6fb3a64c4b Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 14:31:35 +0100 Subject: [PATCH 33/68] Fix doctests --- src/ptr.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/ptr.rs b/src/ptr.rs index 1f8b77f..05fc2b2 100644 --- a/src/ptr.rs +++ b/src/ptr.rs @@ -31,7 +31,7 @@ use crate::access::{Access, ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; /// /// struct Example { field_1: u32, field_2: u8, } /// let mut value = Example { field_1: 15, field_2: 255 }; -/// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; +/// let mut volatile = VolatilePtr::from_mut_ref(&mut value); /// /// // construct a volatile reference to a field /// let field_2 = map_field!(volatile.field_2); @@ -46,7 +46,7 @@ use crate::access::{Access, ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; /// #[repr(packed)] /// struct Example { field_1: u8, field_2: usize, } /// let mut value = Example { field_1: 15, field_2: 255 }; -/// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; +/// let mut volatile = VolatilePtr::from_mut_ref(&mut value); /// /// // Constructing a volatile reference to an unaligned field doesn't compile. /// let field_2 = map_field!(volatile.field_2); @@ -165,15 +165,17 @@ where /// ## Examples /// /// ```rust - /// use volatile::VolatilePtr; + /// use volatile::{VolatilePtr, access}; /// use core::ptr::NonNull; /// /// let value = 42; - /// let shared_reference = unsafe { VolatilePtr::new_read_only(NonNull::from(&value)) }; + /// let shared_reference = unsafe { + /// VolatilePtr::new_restricted(access::ReadOnly, NonNull::from(&value)) + /// }; /// assert_eq!(shared_reference.read(), 42); /// /// let mut value = 50; - /// let mut_reference = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; + /// let mut_reference = VolatilePtr::from_mut_ref(&mut value); /// assert_eq!(mut_reference.read(), 50); /// ``` pub fn read(&self) -> T @@ -198,7 +200,7 @@ where /// use core::ptr::NonNull; /// /// let mut value = 42; - /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; + /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); /// volatile.write(50); /// /// assert_eq!(volatile.read(), 50); @@ -223,8 +225,8 @@ where /// use core::ptr::NonNull; /// /// let mut value = 42; - /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; - /// volatile.update(|val| *val += 1); + /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); + /// volatile.update(|val| val + 1); /// /// assert_eq!(volatile.read(), 43); /// ``` @@ -257,7 +259,7 @@ where /// use core::ptr::NonNull; /// /// let mut value = 42; - /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; + /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); /// volatile.write(50); /// let unwrapped: *mut i32 = volatile.as_ptr().as_ptr(); /// @@ -299,7 +301,7 @@ where /// /// struct Example { field_1: u32, field_2: u8, } /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; + /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); /// /// // construct a volatile reference to a field /// let field_2 = unsafe { volatile.map(|ptr| NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).field_2)).unwrap()) }; @@ -313,7 +315,7 @@ where /// use core::ptr::NonNull; /// /// let mut value = 5; - /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; + /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); /// /// // DON'T DO THIS: /// let mut readout = 0; @@ -375,7 +377,7 @@ where /// use core::ptr::NonNull; /// /// let mut value: i16 = -4; - /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; + /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); /// /// let read_only = volatile.read_only(); /// assert_eq!(read_only.read(), -4); @@ -397,7 +399,7 @@ where /// /// struct Example { field_1: u32, field_2: u8, } /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut value)) }; + /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); /// /// // construct a volatile write-only reference to `field_2` /// let mut field_2 = map_field_mut!(volatile.field_2).write_only(); @@ -596,9 +598,8 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { /// let mut dst = [0, 0]; /// // the `Volatile` type does not work with arrays, so convert `dst` to a slice /// let slice = &mut dst[..]; - /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(slice))}; - /// - /// // Because the slices have to be the same length, + /// let mut volatile = VolatilePtr::from_mut_ref(lice); + /// /// // Because the slices have to be the same length, /// // we slice the source slice from four elements /// // to two. It will panic if we don't do this. /// volatile.copy_from_slice(&src[2..]); @@ -655,8 +656,7 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { /// /// let mut byte_array = *b"Hello, World!"; /// let mut slice: &mut [u8] = &mut byte_array[..]; - /// let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(slice)) }; - /// + /// let mut volatile = VolatilePtr::from_mut_ref(lice)) }; / /// volatile.copy_within(1..5, 8); /// /// assert_eq!(&byte_array, b"Hello, Wello!"); @@ -840,7 +840,7 @@ impl VolatilePtr<'_, [u8], Access> { /// use core::ptr::NonNull; /// /// let mut vec = vec![0; 10]; - /// let mut buf = unsafe { VolatilePtr::new_read_write(NonNull::from(vec.as_mut_slice())) }; + /// let mut buf = VolatilePtr::from_mut_ref(ec.as_mut_slice()); /// buf.fill(1); /// assert_eq!(unsafe { buf.as_ptr().as_mut() }, &mut vec![1; 10]); /// ``` From cb0fc46bdffcf89a517a5ab54c673e9a77516f55 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 14:47:21 +0100 Subject: [PATCH 34/68] Add a `new_read_only` convenience function --- src/ptr.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ptr.rs b/src/ptr.rs index 05fc2b2..5d7c503 100644 --- a/src/ptr.rs +++ b/src/ptr.rs @@ -131,6 +131,10 @@ where unsafe { VolatilePtr::new(reference.into()) } } + pub const unsafe fn new_read_only(pointer: NonNull) -> VolatilePtr<'a, T, ReadOnly> { + unsafe { Self::new_restricted(ReadOnly, pointer) } + } + pub const unsafe fn new_restricted(access: A, pointer: NonNull) -> VolatilePtr<'a, T, A> where A: Access, From 4a8c18be8f29a9a3880c6c83eaee69c41df4aa69 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 14:47:38 +0100 Subject: [PATCH 35/68] Create an internal `new_generic` constructor --- src/ptr.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/ptr.rs b/src/ptr.rs index 5d7c503..ab09a01 100644 --- a/src/ptr.rs +++ b/src/ptr.rs @@ -140,11 +140,7 @@ where A: Access, { let _ = access; - VolatilePtr { - pointer, - reference: PhantomData, - access: PhantomData, - } + unsafe { Self::new_generic(pointer) } } pub fn from_ref(reference: &'a T) -> VolatilePtr<'a, T, ReadOnly> @@ -153,6 +149,14 @@ where { unsafe { VolatilePtr::new_restricted(ReadOnly, reference.into()) } } + + const unsafe fn new_generic(pointer: NonNull) -> VolatilePtr<'a, T, A> { + VolatilePtr { + pointer, + reference: PhantomData, + access: PhantomData, + } + } } impl<'a, T, A> VolatilePtr<'a, T, A> From d0a237af9be575b0cb4f919a8ba116dc93932d69 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 14:48:11 +0100 Subject: [PATCH 36/68] Update unstable methods and their doc tests --- src/ptr.rs | 119 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 67 insertions(+), 52 deletions(-) diff --git a/src/ptr.rs b/src/ptr.rs index ab09a01..a4f6c74 100644 --- a/src/ptr.rs +++ b/src/ptr.rs @@ -332,9 +332,10 @@ where /// value /// })}; /// ``` - pub unsafe fn map(self, f: F) -> VolatilePtr<'a, U, ReadOnly> + pub unsafe fn map(self, f: F) -> VolatilePtr<'a, U, A::RestrictShared> where F: FnOnce(NonNull) -> NonNull, + A: Access, U: ?Sized, { unsafe { VolatilePtr::new_restricted(Default::default(), f(self.pointer)) } @@ -421,7 +422,7 @@ where /// Methods for volatile slices #[cfg(feature = "unstable")] -impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { +impl<'a, T, A> VolatilePtr<'a, [T], A> { pub fn len(&self) -> usize { self.pointer.len() } @@ -467,9 +468,10 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { pub fn index( self, index: I, - ) -> VolatilePtr<'a, >::Output, Access> + ) -> VolatilePtr<'a, >::Output, A::RestrictShared> where I: SliceIndex<[T]> + SliceIndex<[()]> + Clone, + A: Access, { bounds_check(self.pointer.len(), index.clone()); @@ -489,12 +491,10 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { } } - pub fn index_mut( - self, - index: I, - ) -> VolatilePtr<'a, >::Output, Access> + pub fn index_mut(self, index: I) -> VolatilePtr<'a, >::Output, A> where I: SliceIndex<[T]> + SliceIndex<[()]> + Clone, + A: Access, { bounds_check(self.pointer.len(), index.clone()); @@ -512,7 +512,10 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { } /// Returns an iterator over the slice. - pub fn iter(self) -> impl Iterator>> { + pub fn iter(self) -> impl Iterator> + where + A: Access, + { let ptr = self.as_ptr().as_ptr() as *mut T; let len = self.len(); (0..len) @@ -520,7 +523,7 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { } /// Returns an iterator that allows modifying each value. - pub fn iter_mut(self) -> impl Iterator>> { + pub fn iter_mut(self) -> impl Iterator> { let ptr = self.as_ptr().as_ptr() as *mut T; let len = self.len(); (0..len) @@ -563,7 +566,7 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { pub fn copy_into_slice(&self, dst: &mut [T]) where T: Copy, - R: access_ptr::Safe, + A: Readable, { let len = self.pointer.len(); assert_eq!( @@ -606,7 +609,7 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { /// let mut dst = [0, 0]; /// // the `Volatile` type does not work with arrays, so convert `dst` to a slice /// let slice = &mut dst[..]; - /// let mut volatile = VolatilePtr::from_mut_ref(lice); + /// let mut volatile = VolatilePtr::from_mut_ref(slice); /// /// // Because the slices have to be the same length, /// // we slice the source slice from four elements /// // to two. It will panic if we don't do this. @@ -618,7 +621,7 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { pub fn copy_from_slice(&mut self, src: &[T]) where T: Copy, - W: access_ptr::Safe, + A: Writable, { let len = self.pointer.len(); assert_eq!( @@ -664,15 +667,14 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { /// /// let mut byte_array = *b"Hello, World!"; /// let mut slice: &mut [u8] = &mut byte_array[..]; - /// let mut volatile = VolatilePtr::from_mut_ref(lice)) }; / + /// let mut volatile = VolatilePtr::from_mut_ref(slice); /// volatile.copy_within(1..5, 8); /// /// assert_eq!(&byte_array, b"Hello, Wello!"); pub fn copy_within(&mut self, src: impl RangeBounds, dest: usize) where T: Copy, - R: access_ptr::Safe, - W: access_ptr::Safe, + A: Readable + Writable, { let len = self.pointer.len(); // implementation taken from https://github.com/rust-lang/rust/blob/683d1bcd405727fcc9209f64845bd3b9104878b8/library/core/src/slice/mod.rs#L2726-L2738 @@ -697,22 +699,22 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { self, mid: usize, ) -> ( - VolatilePtr<'a, [T], Access>, - VolatilePtr<'a, [T], Access>, - ) { + VolatilePtr<'a, [T], A::RestrictShared>, + VolatilePtr<'a, [T], A::RestrictShared>, + ) + where + A: Access, + { assert!(mid <= self.pointer.len()); // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which // fulfills the requirements of `from_raw_parts_mut`. unsafe { self.split_at_unchecked(mid) } } - pub fn split_at_mut( - self, - mid: usize, - ) -> ( - VolatilePtr<'a, [T], Access>, - VolatilePtr<'a, [T], Access>, - ) { + pub fn split_at_mut(self, mid: usize) -> (VolatilePtr<'a, [T], A>, VolatilePtr<'a, [T], A>) + where + A: Access, + { assert!(mid <= self.pointer.len()); // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which // fulfills the requirements of `from_raw_parts_mut`. @@ -723,9 +725,12 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { self, mid: usize, ) -> ( - VolatilePtr<'a, [T], Access>, - VolatilePtr<'a, [T], Access>, - ) { + VolatilePtr<'a, [T], A::RestrictShared>, + VolatilePtr<'a, [T], A::RestrictShared>, + ) + where + A: Access, + { // SAFETY: Caller has to check that `0 <= mid <= self.len()` unsafe { ( @@ -738,10 +743,10 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { unsafe fn split_at_mut_unchecked( self, mid: usize, - ) -> ( - VolatilePtr<'a, [T], Access>, - VolatilePtr<'a, [T], Access>, - ) { + ) -> (VolatilePtr<'a, [T], A>, VolatilePtr<'a, [T], A>) + where + A: Access, + { let len = self.pointer.len(); let ptr = self.pointer.as_mut_ptr(); @@ -764,9 +769,12 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { pub fn as_chunks( self, ) -> ( - VolatilePtr<'a, [[T; N]], Access>, - VolatilePtr<'a, [T], Access>, - ) { + VolatilePtr<'a, [[T; N]], ::RestrictShared>, + VolatilePtr<'a, [T], A::RestrictShared>, + ) + where + A: Access, + { assert_ne!(N, 0); let len = self.pointer.len() / N; let (multiple_of_n, remainder) = self.split_at(len * N); @@ -778,7 +786,10 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { pub unsafe fn as_chunks_unchecked( self, - ) -> VolatilePtr<'a, [[T; N]], Access> { + ) -> VolatilePtr<'a, [[T; N]], A::RestrictShared> + where + A: Access, + { debug_assert_ne!(N, 0); debug_assert_eq!(self.pointer.len() % N, 0); let new_len = @@ -796,10 +807,10 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { pub fn as_chunks_mut( self, - ) -> ( - VolatilePtr<'a, [[T; N]], Access>, - VolatilePtr<'a, [T], Access>, - ) { + ) -> (VolatilePtr<'a, [[T; N]], A>, VolatilePtr<'a, [T], A>) + where + A: Access, + { assert_ne!(N, 0); let len = self.pointer.len() / N; let (multiple_of_n, remainder) = self.split_at_mut(len * N); @@ -809,9 +820,7 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { (array_slice, remainder) } - pub unsafe fn as_chunks_unchecked_mut( - self, - ) -> VolatilePtr<'a, [[T; N]], Access> { + pub unsafe fn as_chunks_unchecked_mut(self) -> VolatilePtr<'a, [[T; N]], A> { debug_assert_ne!(N, 0); debug_assert_eq!(self.pointer.len() % N, 0); let new_len = @@ -830,7 +839,7 @@ impl<'a, T, R, W> VolatilePtr<'a, [T], Access> { /// Methods for volatile byte slices #[cfg(feature = "unstable")] -impl VolatilePtr<'_, [u8], Access> { +impl VolatilePtr<'_, [u8], A> { /// Sets all elements of the byte slice to the given `value` using a volatile `memset`. /// /// This method is similar to the `slice::fill` method of the standard library, with the @@ -848,13 +857,13 @@ impl VolatilePtr<'_, [u8], Access> { /// use core::ptr::NonNull; /// /// let mut vec = vec![0; 10]; - /// let mut buf = VolatilePtr::from_mut_ref(ec.as_mut_slice()); + /// let mut buf = VolatilePtr::from_mut_ref(vec.as_mut_slice()); /// buf.fill(1); /// assert_eq!(unsafe { buf.as_ptr().as_mut() }, &mut vec![1; 10]); /// ``` pub fn fill(&mut self, value: u8) where - W: access_ptr::Safe, + A: Writable, { unsafe { intrinsics::volatile_set_memory(self.pointer.as_mut_ptr(), value, self.pointer.len()); @@ -867,7 +876,7 @@ impl VolatilePtr<'_, [u8], Access> { /// These methods are only available with the `unstable` feature enabled (requires a nightly /// Rust compiler). #[cfg(feature = "unstable")] -impl<'a, T, R, W, const N: usize> VolatilePtr<'a, [T; N], Access> { +impl<'a, T, A, const N: usize> VolatilePtr<'a, [T; N], A> { /// Converts an array reference to a shared slice. /// /// This makes it possible to use the methods defined on slices. @@ -891,7 +900,10 @@ impl<'a, T, R, W, const N: usize> VolatilePtr<'a, [T; N], Access> { /// /// assert_eq!(dst, [1, 2]); /// ``` - pub fn as_slice(self) -> VolatilePtr<'a, [T], Access> { + pub fn as_slice(self) -> VolatilePtr<'a, [T], A::RestrictShared> + where + A: Access, + { unsafe { self.map(|array| { NonNull::new(ptr::slice_from_raw_parts_mut(array.as_ptr() as *mut T, N)).unwrap() @@ -908,12 +920,12 @@ impl<'a, T, R, W, const N: usize> VolatilePtr<'a, [T; N], Access> { /// Copying two elements into a volatile array reference using `copy_from_slice`: /// /// ``` - /// use volatile::VolatilePtr; + /// use volatile::{access, VolatilePtr}; /// use core::ptr::NonNull; /// /// let src = [1, 2]; /// let mut dst = [0, 0]; - /// let mut volatile = unsafe { VolatilePtr::new_write_only(NonNull::from(&dst)) }; + /// let mut volatile = unsafe { VolatilePtr::new_restricted(access::WriteOnly, NonNull::from(&dst)) }; /// /// // convert the `Volatile<[i32; 2]>` array reference to a `Volatile<[i32]>` slice /// let mut volatile_slice = volatile.as_slice_mut(); @@ -922,7 +934,10 @@ impl<'a, T, R, W, const N: usize> VolatilePtr<'a, [T; N], Access> { /// /// assert_eq!(dst, [1, 2]); /// ``` - pub fn as_slice_mut(self) -> VolatilePtr<'a, [T], Access> { + pub fn as_slice_mut(self) -> VolatilePtr<'a, [T], A> + where + A: Access, + { unsafe { self.map_mut(|array| { NonNull::new(ptr::slice_from_raw_parts_mut(array.as_ptr() as *mut T, N)).unwrap() @@ -946,5 +961,5 @@ fn bounds_check(len: usize, index: impl SliceIndex<[()]>) { const MAX_ARRAY: [(); usize::MAX] = [(); usize::MAX]; let bound_check_slice = &MAX_ARRAY[..len]; - &bound_check_slice[index]; + let _ = &bound_check_slice[index]; } From bbb23f4afa24eaa445be35469a41e4eb3b17a850 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 14:48:40 +0100 Subject: [PATCH 37/68] Remove stabilized features --- src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 440ce7a..e159e4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,9 +4,6 @@ #![cfg_attr(feature = "unstable", feature(slice_ptr_get))] #![cfg_attr(feature = "unstable", feature(slice_ptr_len))] #![cfg_attr(feature = "very_unstable", feature(const_slice_ptr_len))] -#![cfg_attr(feature = "very_unstable", feature(const_panic))] -#![cfg_attr(feature = "very_unstable", feature(const_fn_trait_bound))] -#![cfg_attr(feature = "very_unstable", feature(const_fn_fn_ptr_basics))] #![cfg_attr(feature = "very_unstable", feature(const_trait_impl))] #![cfg_attr(feature = "very_unstable", feature(const_mut_refs))] #![cfg_attr(feature = "very_unstable", allow(incomplete_features))] From a143e0bbfa72ac2f3a2426a95d819f7d28533159 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 15:04:18 +0100 Subject: [PATCH 38/68] Update `very_unstable` to work with new access types and latest nightly --- src/lib.rs | 4 +++- src/ptr.rs | 54 ++++++++++++++++++++++++++++++++++-------------------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e159e4f..ecefbb4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,9 @@ #![cfg_attr(feature = "very_unstable", feature(const_slice_ptr_len))] #![cfg_attr(feature = "very_unstable", feature(const_trait_impl))] #![cfg_attr(feature = "very_unstable", feature(const_mut_refs))] -#![cfg_attr(feature = "very_unstable", allow(incomplete_features))] +#![cfg_attr(feature = "very_unstable", feature(inline_const))] +#![cfg_attr(feature = "very_unstable", feature(unboxed_closures))] +#![cfg_attr(feature = "very_unstable", feature(fn_traits))] #![cfg_attr(all(feature = "unstable", test), feature(slice_as_chunks))] #![warn(missing_docs)] #![deny(unsafe_op_in_unsafe_fn)] diff --git a/src/ptr.rs b/src/ptr.rs index a4f6c74..65abff2 100644 --- a/src/ptr.rs +++ b/src/ptr.rs @@ -342,12 +342,10 @@ where } #[cfg(feature = "very_unstable")] - pub const unsafe fn map_const( - self, - f: F, - ) -> VolatilePtr<'a, U, Access> + pub const unsafe fn map_const(self, f: F) -> VolatilePtr<'a, U, A::RestrictShared> where - F: FnOnce(NonNull) -> NonNull, + F: ~const FnOnce(NonNull) -> NonNull, + A: Access, U: ?Sized, { unsafe { VolatilePtr::new_generic(f(self.pointer)) } @@ -363,9 +361,9 @@ where } #[cfg(feature = "very_unstable")] - pub const unsafe fn map_mut_const(self, f: F) -> VolatilePtr<'a, U, Access> + pub const unsafe fn map_mut_const(self, f: F) -> VolatilePtr<'a, U, A> where - F: FnOnce(NonNull) -> NonNull, + F: ~const FnOnce(NonNull) -> NonNull, U: ?Sized, { unsafe { VolatilePtr::new_generic(f(self.pointer)) } @@ -479,16 +477,24 @@ impl<'a, T, A> VolatilePtr<'a, [T], A> { } #[cfg(feature = "very_unstable")] - pub const fn index_const( - self, - index: usize, - ) -> VolatilePtr<'a, T, Access> { + pub const fn index_const(self, index: usize) -> VolatilePtr<'a, T, A::RestrictShared> + where + A: Access, + { assert!(index < self.pointer.len(), "index out of bounds"); - unsafe { - self.map_const(|slice| { - NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(index)) - }) + + struct Mapper { + index: usize, + } + impl const FnOnce<(NonNull<[T]>,)> for Mapper { + type Output = NonNull; + + extern "rust-call" fn call_once(self, (slice,): (NonNull<[T]>,)) -> Self::Output { + unsafe { NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(self.index)) } + } } + + unsafe { self.map_const(Mapper { index }) } } pub fn index_mut(self, index: I) -> VolatilePtr<'a, >::Output, A> @@ -502,13 +508,21 @@ impl<'a, T, A> VolatilePtr<'a, [T], A> { } #[cfg(feature = "very_unstable")] - pub const fn index_mut_const(self, index: usize) -> VolatilePtr<'a, T, Access> { + pub const fn index_mut_const(self, index: usize) -> VolatilePtr<'a, T, A> { assert!(index < self.pointer.len(), "index out of bounds"); - unsafe { - self.map_mut_const(|slice| { - NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(index)) - }) + + struct Mapper { + index: usize, } + impl const FnOnce<(NonNull<[T]>,)> for Mapper { + type Output = NonNull; + + extern "rust-call" fn call_once(self, (slice,): (NonNull<[T]>,)) -> Self::Output { + unsafe { NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(self.index)) } + } + } + + unsafe { self.map_mut_const(Mapper { index }) } } /// Returns an iterator over the slice. From b7cb879c5835f5821af8d8be969e400fbff77eb7 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 15:55:05 +0100 Subject: [PATCH 39/68] Add an alternative `VolatilePtrCopy` variant that implement Copy --- src/lib.rs | 2 + src/ptr_copy.rs | 917 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 919 insertions(+) create mode 100644 src/ptr_copy.rs diff --git a/src/lib.rs b/src/lib.rs index ecefbb4..c56ded2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,9 @@ pub use cell::VolatileCell; pub use ptr::VolatilePtr; +pub use ptr_copy::VolatilePtrCopy; pub mod access; mod cell; mod ptr; +mod ptr_copy; diff --git a/src/ptr_copy.rs b/src/ptr_copy.rs new file mode 100644 index 0000000..6b42802 --- /dev/null +++ b/src/ptr_copy.rs @@ -0,0 +1,917 @@ +//! Provides the wrapper type `Volatile`, which wraps a reference to any copy-able type and allows +//! for volatile memory access to wrapped value. Volatile memory accesses are never optimized away +//! by the compiler, and are useful in many low-level systems programming and concurrent contexts. +//! +//! The wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider +//! looking at the `Atomic` wrapper types found in `libcore` or `libstd`. + +use core::{ + fmt, + marker::PhantomData, + ptr::{self, NonNull}, +}; +#[cfg(feature = "unstable")] +use core::{ + intrinsics, + ops::{Range, RangeBounds}, + slice::{range, SliceIndex}, +}; + +use crate::access::{Access, ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; + +/// Wraps a pointer to make accesses to the referenced value volatile. +/// +/// Allows volatile reads and writes on the referenced value. The referenced value needs to +/// be `Copy` for reading and writing, as volatile reads and writes take and return copies +/// of the value. +/// +/// Since not all volatile resources (e.g. memory mapped device registers) are both readable +/// and writable, this type supports limiting the allowed access types through an optional second +/// generic parameter `A` that can be one of `ReadWrite`, `ReadOnly`, or `WriteOnly`. It defaults +/// to `ReadWrite`, which allows all operations. +/// +/// The size of this struct is the same as the size of the contained reference. +#[repr(transparent)] +pub struct VolatilePtrCopy<'a, T, A = ReadWrite> +where + T: ?Sized, +{ + pointer: NonNull, + reference: PhantomData<&'a T>, + access: PhantomData, +} + +impl<'a, T, A> Copy for VolatilePtrCopy<'a, T, A> where T: ?Sized {} + +impl Clone for VolatilePtrCopy<'_, T, A> +where + T: ?Sized, +{ + fn clone(&self) -> Self { + *self + } +} + +/// Constructor functions. +/// +/// These functions construct new `VolatilePtr` values. While the `new` +/// function creates a `VolatilePtr` instance with unrestricted access, there +/// are also functions for creating read-only or write-only instances. +impl<'a, T> VolatilePtrCopy<'a, T> +where + T: ?Sized, +{ + pub unsafe fn new(pointer: NonNull) -> Self { + unsafe { VolatilePtrCopy::new_restricted(ReadWrite, pointer) } + } + + pub fn from_mut_ref(reference: &'a mut T) -> Self + where + T: 'a, + { + unsafe { VolatilePtrCopy::new(reference.into()) } + } + + pub const unsafe fn new_read_only(pointer: NonNull) -> VolatilePtrCopy<'a, T, ReadOnly> { + unsafe { Self::new_restricted(ReadOnly, pointer) } + } + + pub const unsafe fn new_restricted( + access: A, + pointer: NonNull, + ) -> VolatilePtrCopy<'a, T, A> + where + A: Access, + { + let _ = access; + unsafe { Self::new_generic(pointer) } + } + + pub fn from_ref(reference: &'a T) -> VolatilePtrCopy<'a, T, ReadOnly> + where + T: 'a, + { + unsafe { VolatilePtrCopy::new_restricted(ReadOnly, reference.into()) } + } + + const unsafe fn new_generic(pointer: NonNull) -> VolatilePtrCopy<'a, T, A> { + VolatilePtrCopy { + pointer, + reference: PhantomData, + access: PhantomData, + } + } +} + +impl<'a, T, A> VolatilePtrCopy<'a, T, A> +where + T: ?Sized, +{ + /// Performs a volatile read of the contained value. + /// + /// Returns a copy of the read value. Volatile reads are guaranteed not to be optimized + /// away by the compiler, but by themselves do not have atomic ordering + /// guarantees. To also get atomicity, consider looking at the `Atomic` wrapper types of + /// the standard/`core` library. + /// + /// ## Examples + /// + /// ```rust + /// use volatile::{VolatilePtrCopy, access}; + /// use core::ptr::NonNull; + /// + /// let value = 42; + /// let shared_reference = unsafe { + /// VolatilePtrCopy::new_restricted(access::ReadOnly, NonNull::from(&value)) + /// }; + /// assert_eq!(shared_reference.read(), 42); + /// + /// let mut value = 50; + /// let mut_reference = VolatilePtrCopy::from_mut_ref(&mut value); + /// assert_eq!(mut_reference.read(), 50); + /// ``` + pub fn read(self) -> T + where + T: Copy, + A: Readable, + { + // UNSAFE: Safe, as ... TODO + unsafe { ptr::read_volatile(self.pointer.as_ptr()) } + } + + /// Performs a volatile write, setting the contained value to the given `value`. + /// + /// Volatile writes are guaranteed to not be optimized away by the compiler, but by + /// themselves do not have atomic ordering guarantees. To also get atomicity, consider + /// looking at the `Atomic` wrapper types of the standard/`core` library. + /// + /// ## Example + /// + /// ```rust + /// use volatile::VolatilePtrCopy; + /// use core::ptr::NonNull; + /// + /// let mut value = 42; + /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); + /// volatile.write(50); + /// + /// assert_eq!(volatile.read(), 50); + /// ``` + pub fn write(self, value: T) + where + T: Copy, + A: Writable, + { + // UNSAFE: Safe, as ... TODO + unsafe { ptr::write_volatile(self.pointer.as_ptr(), value) }; + } + + /// Updates the contained value using the given closure and volatile instructions. + /// + /// Performs a volatile read of the contained value, passes a mutable reference to it to the + /// function `f`, and then performs a volatile write of the (potentially updated) value back to + /// the contained value. + /// + /// ```rust + /// use volatile::VolatilePtrCopy; + /// use core::ptr::NonNull; + /// + /// let mut value = 42; + /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); + /// volatile.update(|val| val + 1); + /// + /// assert_eq!(volatile.read(), 43); + /// ``` + pub fn update(self, f: F) + where + T: Copy, + A: Readable + Writable, + F: FnOnce(T) -> T, + { + let new = f(self.read()); + self.write(new); + } + + /// Extracts the inner value stored in the wrapper type. + /// + /// This method gives direct access to the wrapped reference and thus allows + /// non-volatile access again. This is seldom what you want since there is usually + /// a reason that a reference is wrapped in `Volatile`. However, in some cases it might + /// be required or useful to use the `read_volatile`/`write_volatile` pointer methods of + /// the standard library directly, which this method makes possible. + /// + /// Since no memory safety violation can occur when accessing the referenced value using + /// non-volatile operations, this method is safe. However, it _can_ lead to bugs at the + /// application level, so this method should be used with care. + /// + /// ## Example + /// + /// ``` + /// use volatile::VolatilePtrCopy; + /// use core::ptr::NonNull; + /// + /// let mut value = 42; + /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); + /// volatile.write(50); + /// let unwrapped: *mut i32 = volatile.as_ptr().as_ptr(); + /// + /// assert_eq!(unsafe { *unwrapped }, 50); // non volatile access, be careful! + /// ``` + pub fn as_ptr(self) -> NonNull { + self.pointer + } + + /// Constructs a new `Volatile` reference by mapping the wrapped pointer. + /// + /// This method is useful for accessing only a part of a volatile value, e.g. a subslice or + /// a struct field. For struct field access, there is also the safe [`map_field`] macro that + /// wraps this function. + /// + /// ## Examples + /// + /// Accessing a struct field: + /// + /// ``` + /// use volatile::VolatilePtrCopy; + /// use core::ptr::NonNull; + /// + /// struct Example { field_1: u32, field_2: u8, } + /// let mut value = Example { field_1: 15, field_2: 255 }; + /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); + /// + /// // construct a volatile reference to a field + /// let field_2 = unsafe { volatile.map(|ptr| NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).field_2)).unwrap()) }; + /// assert_eq!(field_2.read(), 255); + /// ``` + /// + /// Don't misuse this method to do a non-volatile read of the referenced value: + /// + /// ``` + /// use volatile::VolatilePtrCopy; + /// use core::ptr::NonNull; + /// + /// let mut value = 5; + /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); + /// + /// // DON'T DO THIS: + /// let mut readout = 0; + /// unsafe { volatile.map(|value| { + /// readout = *value.as_ptr(); // non-volatile read, might lead to bugs + /// value + /// })}; + /// ``` + pub unsafe fn map(self, f: F) -> VolatilePtrCopy<'a, U, A::RestrictShared> + where + F: FnOnce(NonNull) -> NonNull, + A: Access, + U: ?Sized, + { + unsafe { VolatilePtrCopy::new_restricted(Default::default(), f(self.pointer)) } + } + + #[cfg(feature = "very_unstable")] + pub const unsafe fn map_const(self, f: F) -> VolatilePtrCopy<'a, U, A::RestrictShared> + where + F: ~const FnOnce(NonNull) -> NonNull, + A: Access, + U: ?Sized, + { + unsafe { VolatilePtrCopy::new_generic(f(self.pointer)) } + } + + pub unsafe fn map_mut(self, f: F) -> VolatilePtrCopy<'a, U, A> + where + F: FnOnce(NonNull) -> NonNull, + U: ?Sized, + A: Access, + { + unsafe { VolatilePtrCopy::new_restricted(A::default(), f(self.pointer)) } + } + + #[cfg(feature = "very_unstable")] + pub const unsafe fn map_mut_const(self, f: F) -> VolatilePtrCopy<'a, U, A> + where + F: ~const FnOnce(NonNull) -> NonNull, + U: ?Sized, + { + unsafe { VolatilePtrCopy::new_generic(f(self.pointer)) } + } +} + +/// Methods for restricting access. +impl<'a, T> VolatilePtrCopy<'a, T, ReadWrite> +where + T: ?Sized, +{ + /// Restricts access permissions to read-only. + /// + /// ## Example + /// + /// ``` + /// use volatile::VolatilePtrCopy; + /// use core::ptr::NonNull; + /// + /// let mut value: i16 = -4; + /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); + /// + /// let read_only = volatile.read_only(); + /// assert_eq!(read_only.read(), -4); + /// // read_only.write(10); // compile-time error + /// ``` + pub fn read_only(self) -> VolatilePtrCopy<'a, T, ReadOnly> { + unsafe { VolatilePtrCopy::new_restricted(ReadOnly, self.pointer) } + } + + /// Restricts access permissions to write-only. + /// + /// ## Example + /// + /// Creating a write-only reference to a struct field: + /// + /// ``` + /// use volatile::{VolatilePtrCopy, map_field_mut}; + /// use core::ptr::NonNull; + /// + /// struct Example { field_1: u32, field_2: u8, } + /// let mut value = Example { field_1: 15, field_2: 255 }; + /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); + /// + /// // construct a volatile write-only reference to `field_2` + /// let mut field_2 = map_field_mut!(volatile.field_2).write_only(); + /// field_2.write(14); + /// // field_2.read(); // compile-time error + /// ``` + pub fn write_only(self) -> VolatilePtrCopy<'a, T, WriteOnly> { + unsafe { VolatilePtrCopy::new_restricted(WriteOnly, self.pointer) } + } +} + +/// Methods for volatile slices +#[cfg(feature = "unstable")] +impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { + pub fn len(self) -> usize { + self.pointer.len() + } + + pub fn is_empty(self) -> bool { + self.pointer.len() == 0 + } + + /// Applies the index operation on the wrapped slice. + /// + /// Returns a shared `Volatile` reference to the resulting subslice. + /// + /// This is a convenience method for the `map(|slice| slice.index(index))` operation, so it + /// has the same behavior as the indexing operation on slice (e.g. panic if index is + /// out-of-bounds). + /// + /// ## Examples + /// + /// Accessing a single slice element: + /// + /// ``` + /// use volatile::VolatilePtrCopy; + /// use core::ptr::NonNull; + /// + /// let array = [1, 2, 3]; + /// let slice = &array[..]; + /// let volatile = unsafe { VolatilePtrCopy::new_read_only(NonNull::from(slice)) }; + /// assert_eq!(volatile.index(1).read(), 2); + /// ``` + /// + /// Accessing a subslice: + /// + /// ``` + /// use volatile::VolatilePtrCopy; + /// use core::ptr::NonNull; + /// + /// let array = [1, 2, 3]; + /// let slice = &array[..]; + /// let volatile = unsafe { VolatilePtrCopy::new_read_only(NonNull::from(slice)) }; + /// let subslice = volatile.index(1..); + /// assert_eq!(subslice.index(0).read(), 2); + /// ``` + pub fn index( + self, + index: I, + ) -> VolatilePtrCopy<'a, >::Output, A::RestrictShared> + where + I: SliceIndex<[T]> + SliceIndex<[()]> + Clone, + A: Access, + { + bounds_check(self.pointer.len(), index.clone()); + + unsafe { self.map(|slice| slice.get_unchecked_mut(index)) } + } + + #[cfg(feature = "very_unstable")] + pub const fn index_const(self, index: usize) -> VolatilePtrCopy<'a, T, A::RestrictShared> + where + A: Access, + { + assert!(index < self.pointer.len(), "index out of bounds"); + + struct Mapper { + index: usize, + } + impl const FnOnce<(NonNull<[T]>,)> for Mapper { + type Output = NonNull; + + extern "rust-call" fn call_once(self, (slice,): (NonNull<[T]>,)) -> Self::Output { + unsafe { NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(self.index)) } + } + } + + unsafe { self.map_const(Mapper { index }) } + } + + pub fn index_mut(self, index: I) -> VolatilePtrCopy<'a, >::Output, A> + where + I: SliceIndex<[T]> + SliceIndex<[()]> + Clone, + A: Access, + { + bounds_check(self.pointer.len(), index.clone()); + + unsafe { self.map_mut(|slice| slice.get_unchecked_mut(index)) } + } + + #[cfg(feature = "very_unstable")] + pub const fn index_mut_const(self, index: usize) -> VolatilePtrCopy<'a, T, A> { + assert!(index < self.pointer.len(), "index out of bounds"); + + struct Mapper { + index: usize, + } + impl const FnOnce<(NonNull<[T]>,)> for Mapper { + type Output = NonNull; + + extern "rust-call" fn call_once(self, (slice,): (NonNull<[T]>,)) -> Self::Output { + unsafe { NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(self.index)) } + } + } + + unsafe { self.map_mut_const(Mapper { index }) } + } + + /// Returns an iterator over the slice. + pub fn iter(self) -> impl Iterator> + where + A: Access, + { + let ptr = self.as_ptr().as_ptr() as *mut T; + let len = self.len(); + (0..len).map(move |i| unsafe { + VolatilePtrCopy::new_generic(NonNull::new_unchecked(ptr.add(i))) + }) + } + + /// Returns an iterator that allows modifying each value. + pub fn iter_mut(self) -> impl Iterator> { + let ptr = self.as_ptr().as_ptr() as *mut T; + let len = self.len(); + (0..len).map(move |i| unsafe { + VolatilePtrCopy::new_generic(NonNull::new_unchecked(ptr.add(i))) + }) + } + + /// Copies all elements from `self` into `dst`, using a volatile memcpy. + /// + /// The length of `dst` must be the same as `self`. + /// + /// The method is only available with the `unstable` feature enabled (requires a nightly + /// Rust compiler). + /// + /// ## Panics + /// + /// This function will panic if the two slices have different lengths. + /// + /// ## Examples + /// + /// Copying two elements from a volatile slice: + /// + /// ``` + /// use volatile::VolatilePtrCopy; + /// use core::ptr::NonNull; + /// + /// let src = [1, 2]; + /// // the `Volatile` type does not work with arrays, so convert `src` to a slice + /// let slice = &src[..]; + /// let volatile = unsafe { VolatilePtrCopy::new_read_only(NonNull::from(slice)) }; + /// let mut dst = [5, 0, 0]; + /// + /// // Because the slices have to be the same length, + /// // we slice the destination slice from three elements + /// // to two. It will panic if we don't do this. + /// volatile.copy_into_slice(&mut dst[1..]); + /// + /// assert_eq!(src, [1, 2]); + /// assert_eq!(dst, [5, 1, 2]); + /// ``` + pub fn copy_into_slice(self, dst: &mut [T]) + where + T: Copy, + A: Readable, + { + let len = self.pointer.len(); + assert_eq!( + len, + dst.len(), + "destination and source slices have different lengths" + ); + unsafe { + intrinsics::volatile_copy_nonoverlapping_memory( + dst.as_mut_ptr(), + self.pointer.as_mut_ptr(), + len, + ); + } + } + + /// Copies all elements from `src` into `self`, using a volatile memcpy. + /// + /// The length of `src` must be the same as `self`. + /// + /// This method is similar to the `slice::copy_from_slice` method of the standard library. The + /// difference is that this method performs a volatile copy. + /// + /// The method is only available with the `unstable` feature enabled (requires a nightly + /// Rust compiler). + /// + /// ## Panics + /// + /// This function will panic if the two slices have different lengths. + /// + /// ## Examples + /// + /// Copying two elements from a slice into a volatile slice: + /// + /// ``` + /// use volatile::VolatilePtrCopy; + /// use core::ptr::NonNull; + /// + /// let src = [1, 2, 3, 4]; + /// let mut dst = [0, 0]; + /// // the `Volatile` type does not work with arrays, so convert `dst` to a slice + /// let slice = &mut dst[..]; + /// let mut volatile = VolatilePtrCopy::from_mut_ref(slice); + /// /// // Because the slices have to be the same length, + /// // we slice the source slice from four elements + /// // to two. It will panic if we don't do this. + /// volatile.copy_from_slice(&src[2..]); + /// + /// assert_eq!(src, [1, 2, 3, 4]); + /// assert_eq!(dst, [3, 4]); + /// ``` + pub fn copy_from_slice(self, src: &[T]) + where + T: Copy, + A: Writable, + { + let len = self.pointer.len(); + assert_eq!( + len, + src.len(), + "destination and source slices have different lengths" + ); + unsafe { + intrinsics::volatile_copy_nonoverlapping_memory( + self.pointer.as_mut_ptr(), + src.as_ptr(), + len, + ); + } + } + + /// Copies elements from one part of the slice to another part of itself, using a + /// volatile `memmove`. + /// + /// `src` is the range within `self` to copy from. `dest` is the starting index of the + /// range within `self` to copy to, which will have the same length as `src`. The two ranges + /// may overlap. The ends of the two ranges must be less than or equal to `self.len()`. + /// + /// This method is similar to the `slice::copy_within` method of the standard library. The + /// difference is that this method performs a volatile copy. + /// + /// This method is only available with the `unstable` feature enabled (requires a nightly + /// Rust compiler). + /// + /// ## Panics + /// + /// This function will panic if either range exceeds the end of the slice, or if the end + /// of `src` is before the start. + /// + /// ## Examples + /// + /// Copying four bytes within a slice: + /// + /// ``` + /// extern crate core; + /// use volatile::VolatilePtrCopy; + /// use core::ptr::NonNull; + /// + /// let mut byte_array = *b"Hello, World!"; + /// let mut slice: &mut [u8] = &mut byte_array[..]; + /// let mut volatile = VolatilePtrCopy::from_mut_ref(slice); + /// volatile.copy_within(1..5, 8); + /// + /// assert_eq!(&byte_array, b"Hello, Wello!"); + pub fn copy_within(self, src: impl RangeBounds, dest: usize) + where + T: Copy, + A: Readable + Writable, + { + let len = self.pointer.len(); + // implementation taken from https://github.com/rust-lang/rust/blob/683d1bcd405727fcc9209f64845bd3b9104878b8/library/core/src/slice/mod.rs#L2726-L2738 + let Range { + start: src_start, + end: src_end, + } = range(src, ..len); + let count = src_end - src_start; + assert!(dest <= len - count, "dest is out of bounds"); + // SAFETY: the conditions for `volatile_copy_memory` have all been checked above, + // as have those for `ptr::add`. + unsafe { + intrinsics::volatile_copy_memory( + self.pointer.as_mut_ptr().add(dest), + self.pointer.as_mut_ptr().add(src_start), + count, + ); + } + } + + pub fn split_at( + self, + mid: usize, + ) -> ( + VolatilePtrCopy<'a, [T], A::RestrictShared>, + VolatilePtrCopy<'a, [T], A::RestrictShared>, + ) + where + A: Access, + { + assert!(mid <= self.pointer.len()); + // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which + // fulfills the requirements of `from_raw_parts_mut`. + unsafe { self.split_at_unchecked(mid) } + } + + pub fn split_at_mut( + self, + mid: usize, + ) -> (VolatilePtrCopy<'a, [T], A>, VolatilePtrCopy<'a, [T], A>) + where + A: Access, + { + assert!(mid <= self.pointer.len()); + // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which + // fulfills the requirements of `from_raw_parts_mut`. + unsafe { self.split_at_mut_unchecked(mid) } + } + + unsafe fn split_at_unchecked( + self, + mid: usize, + ) -> ( + VolatilePtrCopy<'a, [T], A::RestrictShared>, + VolatilePtrCopy<'a, [T], A::RestrictShared>, + ) + where + A: Access, + { + // SAFETY: Caller has to check that `0 <= mid <= self.len()` + unsafe { + ( + VolatilePtrCopy::new_generic((self.pointer).get_unchecked_mut(..mid)), + VolatilePtrCopy::new_generic((self.pointer).get_unchecked_mut(mid..)), + ) + } + } + + unsafe fn split_at_mut_unchecked( + self, + mid: usize, + ) -> (VolatilePtrCopy<'a, [T], A>, VolatilePtrCopy<'a, [T], A>) + where + A: Access, + { + let len = self.pointer.len(); + let ptr = self.pointer.as_mut_ptr(); + + // SAFETY: Caller has to check that `0 <= mid <= self.len()`. + // + // `[ptr; mid]` and `[mid; len]` are not overlapping, so returning a mutable reference + // is fine. + unsafe { + ( + VolatilePtrCopy::new_generic( + NonNull::new(ptr::slice_from_raw_parts_mut(ptr, mid)).unwrap(), + ), + VolatilePtrCopy::new_generic( + NonNull::new(ptr::slice_from_raw_parts_mut(ptr.add(mid), len - mid)).unwrap(), + ), + ) + } + } + + pub fn as_chunks( + self, + ) -> ( + VolatilePtrCopy<'a, [[T; N]], ::RestrictShared>, + VolatilePtrCopy<'a, [T], A::RestrictShared>, + ) + where + A: Access, + { + assert_ne!(N, 0); + let len = self.pointer.len() / N; + let (multiple_of_n, remainder) = self.split_at(len * N); + // SAFETY: We already panicked for zero, and ensured by construction + // that the length of the subslice is a multiple of N. + let array_slice = unsafe { multiple_of_n.as_chunks_unchecked() }; + (array_slice, remainder) + } + + pub unsafe fn as_chunks_unchecked( + self, + ) -> VolatilePtrCopy<'a, [[T; N]], A::RestrictShared> + where + A: Access, + { + debug_assert_ne!(N, 0); + debug_assert_eq!(self.pointer.len() % N, 0); + let new_len = + // SAFETY: Our precondition is exactly what's needed to call this + unsafe { core::intrinsics::exact_div(self.pointer.len(), N) }; + // SAFETY: We cast a slice of `new_len * N` elements into + // a slice of `new_len` many `N` elements chunks. + let pointer = NonNull::new(ptr::slice_from_raw_parts_mut( + self.pointer.as_mut_ptr().cast(), + new_len, + )) + .unwrap(); + unsafe { VolatilePtrCopy::new_generic(pointer) } + } + + pub fn as_chunks_mut( + self, + ) -> ( + VolatilePtrCopy<'a, [[T; N]], A>, + VolatilePtrCopy<'a, [T], A>, + ) + where + A: Access, + { + assert_ne!(N, 0); + let len = self.pointer.len() / N; + let (multiple_of_n, remainder) = self.split_at_mut(len * N); + // SAFETY: We already panicked for zero, and ensured by construction + // that the length of the subslice is a multiple of N. + let array_slice = unsafe { multiple_of_n.as_chunks_unchecked_mut() }; + (array_slice, remainder) + } + + pub unsafe fn as_chunks_unchecked_mut( + self, + ) -> VolatilePtrCopy<'a, [[T; N]], A> { + debug_assert_ne!(N, 0); + debug_assert_eq!(self.pointer.len() % N, 0); + let new_len = + // SAFETY: Our precondition is exactly what's needed to call this + unsafe { core::intrinsics::exact_div(self.pointer.len(), N) }; + // SAFETY: We cast a slice of `new_len * N` elements into + // a slice of `new_len` many `N` elements chunks. + let pointer = NonNull::new(ptr::slice_from_raw_parts_mut( + self.pointer.as_mut_ptr().cast(), + new_len, + )) + .unwrap(); + unsafe { VolatilePtrCopy::new_generic(pointer) } + } +} + +/// Methods for volatile byte slices +#[cfg(feature = "unstable")] +impl VolatilePtrCopy<'_, [u8], A> { + /// Sets all elements of the byte slice to the given `value` using a volatile `memset`. + /// + /// This method is similar to the `slice::fill` method of the standard library, with the + /// difference that this method performs a volatile write operation. Another difference + /// is that this method is only available for byte slices (not general `&mut [T]` slices) + /// because there currently isn't a instrinsic function that allows non-`u8` values. + /// + /// This method is only available with the `unstable` feature enabled (requires a nightly + /// Rust compiler). + /// + /// ## Example + /// + /// ```rust + /// use volatile::VolatilePtrCopy; + /// use core::ptr::NonNull; + /// + /// let mut vec = vec![0; 10]; + /// let mut buf = VolatilePtrCopy::from_mut_ref(vec.as_mut_slice()); + /// buf.fill(1); + /// assert_eq!(unsafe { buf.as_ptr().as_mut() }, &mut vec![1; 10]); + /// ``` + pub fn fill(self, value: u8) + where + A: Writable, + { + unsafe { + intrinsics::volatile_set_memory(self.pointer.as_mut_ptr(), value, self.pointer.len()); + } + } +} + +/// Methods for converting arrays to slices +/// +/// These methods are only available with the `unstable` feature enabled (requires a nightly +/// Rust compiler). +#[cfg(feature = "unstable")] +impl<'a, T, A, const N: usize> VolatilePtrCopy<'a, [T; N], A> { + /// Converts an array reference to a shared slice. + /// + /// This makes it possible to use the methods defined on slices. + /// + /// ## Example + /// + /// Copying two elements from a volatile array reference using `copy_into_slice`: + /// + /// ``` + /// use volatile::VolatilePtrCopy; + /// use core::ptr::NonNull; + /// + /// let src = [1, 2]; + /// let volatile = unsafe { VolatilePtrCopy::new_read_only(NonNull::from(&src)) }; + /// let mut dst = [0, 0]; + /// + /// // convert the `Volatile<&[i32; 2]>` array reference to a `Volatile<&[i32]>` slice + /// let volatile_slice = volatile.as_slice(); + /// // we can now use the slice methods + /// volatile_slice.copy_into_slice(&mut dst); + /// + /// assert_eq!(dst, [1, 2]); + /// ``` + pub fn as_slice(self) -> VolatilePtrCopy<'a, [T], A::RestrictShared> + where + A: Access, + { + unsafe { + self.map(|array| { + NonNull::new(ptr::slice_from_raw_parts_mut(array.as_ptr() as *mut T, N)).unwrap() + }) + } + } + + /// Converts an array reference to a shared slice. + /// + /// This makes it possible to use the methods defined on slices. + /// + /// ## Example + /// + /// Copying two elements into a volatile array reference using `copy_from_slice`: + /// + /// ``` + /// use volatile::{access, VolatilePtrCopy}; + /// use core::ptr::NonNull; + /// + /// let src = [1, 2]; + /// let mut dst = [0, 0]; + /// let mut volatile = unsafe { VolatilePtrCopy::new_restricted(access::WriteOnly, NonNull::from(&dst)) }; + /// + /// // convert the `Volatile<[i32; 2]>` array reference to a `Volatile<[i32]>` slice + /// let mut volatile_slice = volatile.as_slice_mut(); + /// // we can now use the slice methods + /// volatile_slice.copy_from_slice(&src); + /// + /// assert_eq!(dst, [1, 2]); + /// ``` + pub fn as_slice_mut(self) -> VolatilePtrCopy<'a, [T], A> + where + A: Access, + { + unsafe { + self.map_mut(|array| { + NonNull::new(ptr::slice_from_raw_parts_mut(array.as_ptr() as *mut T, N)).unwrap() + }) + } + } +} + +impl fmt::Debug for VolatilePtrCopy<'_, T, A> +where + T: Copy + fmt::Debug + ?Sized, + A: Readable, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Volatile").field(&self.read()).finish() + } +} + +#[cfg(feature = "unstable")] +fn bounds_check(len: usize, index: impl SliceIndex<[()]>) { + const MAX_ARRAY: [(); usize::MAX] = [(); usize::MAX]; + + let bound_check_slice = &MAX_ARRAY[..len]; + let _ = &bound_check_slice[index]; +} From 99e332973cda99ca7277f73322b3596cfbfe2a7b Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 15:57:06 +0100 Subject: [PATCH 40/68] Implement Send/Sync for `VolatilePtr` Possible because of reference-based design. --- src/ptr.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ptr.rs b/src/ptr.rs index 65abff2..82aee9f 100644 --- a/src/ptr.rs +++ b/src/ptr.rs @@ -960,6 +960,9 @@ impl<'a, T, A, const N: usize> VolatilePtr<'a, [T; N], A> { } } +unsafe impl Send for VolatilePtr<'_, T, A> where T: Sync {} +unsafe impl Sync for VolatilePtr<'_, T, A> where T: Sync {} + impl fmt::Debug for VolatilePtr<'_, T, A> where T: Copy + fmt::Debug + ?Sized, From c948890294787f0294806f14feb7953b37d661ab Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 16:44:57 +0100 Subject: [PATCH 41/68] Refactor macros and unstable functions into submodules --- src/lib.rs | 1 + src/macros.rs | 69 ++++ src/ptr/mod.rs | 341 ++++++++++++++++ src/{ptr.rs => ptr/unstable.rs} | 474 +--------------------- src/ptr/very_unstable.rs | 66 +++ src/ptr_copy/mod.rs | 337 +++++++++++++++ src/{ptr_copy.rs => ptr_copy/unstable.rs} | 400 +----------------- src/ptr_copy/very_unstable.rs | 66 +++ 8 files changed, 890 insertions(+), 864 deletions(-) create mode 100644 src/macros.rs create mode 100644 src/ptr/mod.rs rename src/{ptr.rs => ptr/unstable.rs} (52%) create mode 100644 src/ptr/very_unstable.rs create mode 100644 src/ptr_copy/mod.rs rename src/{ptr_copy.rs => ptr_copy/unstable.rs} (57%) create mode 100644 src/ptr_copy/very_unstable.rs diff --git a/src/lib.rs b/src/lib.rs index c56ded2..8de2062 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,5 +19,6 @@ pub use ptr_copy::VolatilePtrCopy; pub mod access; mod cell; +mod macros; mod ptr; mod ptr_copy; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..9f06237 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,69 @@ +/// TODO +/// +/// ## Examples +/// +/// Accessing a struct field: +/// +/// ``` +/// use volatile::{VolatilePtr, map_field}; +/// use core::ptr::NonNull; +/// +/// struct Example { field_1: u32, field_2: u8, } +/// let mut value = Example { field_1: 15, field_2: 255 }; +/// let mut volatile = VolatilePtr::from_mut_ref(&mut value); +/// +/// // construct a volatile reference to a field +/// let field_2 = map_field!(volatile.field_2); +/// assert_eq!(field_2.read(), 255); +/// ``` +/// +/// Creating `VolatilePtr`s to unaligned field in packed structs is not allowed: +/// ```compile_fail +/// use volatile::{VolatilePtr, map_field}; +/// use core::ptr::NonNull; +/// +/// #[repr(packed)] +/// struct Example { field_1: u8, field_2: usize, } +/// let mut value = Example { field_1: 15, field_2: 255 }; +/// let mut volatile = VolatilePtr::from_mut_ref(&mut value); +/// +/// // Constructing a volatile reference to an unaligned field doesn't compile. +/// let field_2 = map_field!(volatile.field_2); +/// ``` +#[macro_export] +macro_rules! map_field { + ($volatile:ident.$place:ident) => {{ + // Simulate creating a reference to the field. This is done to make + // sure that the field is not potentially unaligned. The body of the + // if statement will never be executed, so it can never cause any UB. + if false { + #[deny(unaligned_references)] + let _ref_to_field = &(unsafe { &*$volatile.as_ptr().as_ptr() }).$place; + } + + unsafe { + $volatile.map(|ptr| { + core::ptr::NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).$place)).unwrap() + }) + } + }}; +} + +#[macro_export] +macro_rules! map_field_mut { + ($volatile:ident.$place:ident) => {{ + // Simulate creating a reference to the field. This is done to make + // sure that the field is not potentially unaligned. The body of the + // if statement will never be executed, so it can never cause any UB. + if false { + #[deny(unaligned_references)] + let _ref_to_field = &(unsafe { &*$volatile.as_ptr().as_ptr() }).$place; + } + + unsafe { + $volatile.map_mut(|ptr| { + core::ptr::NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).$place)).unwrap() + }) + } + }}; +} diff --git a/src/ptr/mod.rs b/src/ptr/mod.rs new file mode 100644 index 0000000..a03c787 --- /dev/null +++ b/src/ptr/mod.rs @@ -0,0 +1,341 @@ +//! Provides the wrapper type `Volatile`, which wraps a reference to any copy-able type and allows +//! for volatile memory access to wrapped value. Volatile memory accesses are never optimized away +//! by the compiler, and are useful in many low-level systems programming and concurrent contexts. +//! +//! The wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider +//! looking at the `Atomic` wrapper types found in `libcore` or `libstd`. + +use crate::access::{Access, ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; +use core::{ + fmt, + marker::PhantomData, + ptr::{self, NonNull}, +}; + +#[cfg(feature = "unstable")] +mod unstable; +#[cfg(feature = "very_unstable")] +mod very_unstable; + +/// Wraps a pointer to make accesses to the referenced value volatile. +/// +/// Allows volatile reads and writes on the referenced value. The referenced value needs to +/// be `Copy` for reading and writing, as volatile reads and writes take and return copies +/// of the value. +/// +/// Since not all volatile resources (e.g. memory mapped device registers) are both readable +/// and writable, this type supports limiting the allowed access types through an optional second +/// generic parameter `A` that can be one of `ReadWrite`, `ReadOnly`, or `WriteOnly`. It defaults +/// to `ReadWrite`, which allows all operations. +/// +/// The size of this struct is the same as the size of the contained reference. +#[repr(transparent)] +pub struct VolatilePtr<'a, T, A = ReadWrite> +where + T: ?Sized, +{ + pointer: NonNull, + reference: PhantomData<&'a T>, + access: PhantomData, +} + +/// Constructor functions. +/// +/// These functions construct new `VolatilePtr` values. While the `new` +/// function creates a `VolatilePtr` instance with unrestricted access, there +/// are also functions for creating read-only or write-only instances. +impl<'a, T> VolatilePtr<'a, T> +where + T: ?Sized, +{ + pub unsafe fn new(pointer: NonNull) -> Self { + unsafe { VolatilePtr::new_restricted(ReadWrite, pointer) } + } + + pub fn from_mut_ref(reference: &'a mut T) -> Self + where + T: 'a, + { + unsafe { VolatilePtr::new(reference.into()) } + } + + pub const unsafe fn new_read_only(pointer: NonNull) -> VolatilePtr<'a, T, ReadOnly> { + unsafe { Self::new_restricted(ReadOnly, pointer) } + } + + pub const unsafe fn new_restricted(access: A, pointer: NonNull) -> VolatilePtr<'a, T, A> + where + A: Access, + { + let _ = access; + unsafe { Self::new_generic(pointer) } + } + + pub fn from_ref(reference: &'a T) -> VolatilePtr<'a, T, ReadOnly> + where + T: 'a, + { + unsafe { VolatilePtr::new_restricted(ReadOnly, reference.into()) } + } + + const unsafe fn new_generic(pointer: NonNull) -> VolatilePtr<'a, T, A> { + VolatilePtr { + pointer, + reference: PhantomData, + access: PhantomData, + } + } +} + +impl<'a, T, A> VolatilePtr<'a, T, A> +where + T: ?Sized, +{ + /// Performs a volatile read of the contained value. + /// + /// Returns a copy of the read value. Volatile reads are guaranteed not to be optimized + /// away by the compiler, but by themselves do not have atomic ordering + /// guarantees. To also get atomicity, consider looking at the `Atomic` wrapper types of + /// the standard/`core` library. + /// + /// ## Examples + /// + /// ```rust + /// use volatile::{VolatilePtr, access}; + /// use core::ptr::NonNull; + /// + /// let value = 42; + /// let shared_reference = unsafe { + /// VolatilePtr::new_restricted(access::ReadOnly, NonNull::from(&value)) + /// }; + /// assert_eq!(shared_reference.read(), 42); + /// + /// let mut value = 50; + /// let mut_reference = VolatilePtr::from_mut_ref(&mut value); + /// assert_eq!(mut_reference.read(), 50); + /// ``` + pub fn read(&self) -> T + where + T: Copy, + A: Readable, + { + // UNSAFE: Safe, as ... TODO + unsafe { ptr::read_volatile(self.pointer.as_ptr()) } + } + + /// Performs a volatile write, setting the contained value to the given `value`. + /// + /// Volatile writes are guaranteed to not be optimized away by the compiler, but by + /// themselves do not have atomic ordering guarantees. To also get atomicity, consider + /// looking at the `Atomic` wrapper types of the standard/`core` library. + /// + /// ## Example + /// + /// ```rust + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut value = 42; + /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); + /// volatile.write(50); + /// + /// assert_eq!(volatile.read(), 50); + /// ``` + pub fn write(&mut self, value: T) + where + T: Copy, + A: Writable, + { + // UNSAFE: Safe, as ... TODO + unsafe { ptr::write_volatile(self.pointer.as_ptr(), value) }; + } + + /// Updates the contained value using the given closure and volatile instructions. + /// + /// Performs a volatile read of the contained value, passes a mutable reference to it to the + /// function `f`, and then performs a volatile write of the (potentially updated) value back to + /// the contained value. + /// + /// ```rust + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut value = 42; + /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); + /// volatile.update(|val| val + 1); + /// + /// assert_eq!(volatile.read(), 43); + /// ``` + pub fn update(&mut self, f: F) + where + T: Copy, + A: Readable + Writable, + F: FnOnce(T) -> T, + { + let new = f(self.read()); + self.write(new); + } + + /// Extracts the inner value stored in the wrapper type. + /// + /// This method gives direct access to the wrapped reference and thus allows + /// non-volatile access again. This is seldom what you want since there is usually + /// a reason that a reference is wrapped in `Volatile`. However, in some cases it might + /// be required or useful to use the `read_volatile`/`write_volatile` pointer methods of + /// the standard library directly, which this method makes possible. + /// + /// Since no memory safety violation can occur when accessing the referenced value using + /// non-volatile operations, this method is safe. However, it _can_ lead to bugs at the + /// application level, so this method should be used with care. + /// + /// ## Example + /// + /// ``` + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut value = 42; + /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); + /// volatile.write(50); + /// let unwrapped: *mut i32 = volatile.as_ptr().as_ptr(); + /// + /// assert_eq!(unsafe { *unwrapped }, 50); // non volatile access, be careful! + /// ``` + pub fn as_ptr(&self) -> NonNull { + self.pointer + } + + // TODO: Add documentation + pub fn borrow(&self) -> VolatilePtr + where + A: Access, + { + unsafe { VolatilePtr::new_restricted(Default::default(), self.pointer) } + } + + // TODO: Add documentation + pub fn borrow_mut(&mut self) -> VolatilePtr + where + A: Access, + { + unsafe { VolatilePtr::new_restricted(A::default(), self.pointer) } + } + + /// Constructs a new `Volatile` reference by mapping the wrapped pointer. + /// + /// This method is useful for accessing only a part of a volatile value, e.g. a subslice or + /// a struct field. For struct field access, there is also the safe [`map_field`] macro that + /// wraps this function. + /// + /// ## Examples + /// + /// Accessing a struct field: + /// + /// ``` + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// struct Example { field_1: u32, field_2: u8, } + /// let mut value = Example { field_1: 15, field_2: 255 }; + /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); + /// + /// // construct a volatile reference to a field + /// let field_2 = unsafe { volatile.map(|ptr| NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).field_2)).unwrap()) }; + /// assert_eq!(field_2.read(), 255); + /// ``` + /// + /// Don't misuse this method to do a non-volatile read of the referenced value: + /// + /// ``` + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut value = 5; + /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); + /// + /// // DON'T DO THIS: + /// let mut readout = 0; + /// unsafe { volatile.map(|value| { + /// readout = *value.as_ptr(); // non-volatile read, might lead to bugs + /// value + /// })}; + /// ``` + pub unsafe fn map(self, f: F) -> VolatilePtr<'a, U, A::RestrictShared> + where + F: FnOnce(NonNull) -> NonNull, + A: Access, + U: ?Sized, + { + unsafe { VolatilePtr::new_restricted(Default::default(), f(self.pointer)) } + } + + pub unsafe fn map_mut(self, f: F) -> VolatilePtr<'a, U, A> + where + F: FnOnce(NonNull) -> NonNull, + U: ?Sized, + A: Access, + { + unsafe { VolatilePtr::new_restricted(A::default(), f(self.pointer)) } + } +} + +/// Methods for restricting access. +impl<'a, T> VolatilePtr<'a, T, ReadWrite> +where + T: ?Sized, +{ + /// Restricts access permissions to read-only. + /// + /// ## Example + /// + /// ``` + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut value: i16 = -4; + /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); + /// + /// let read_only = volatile.read_only(); + /// assert_eq!(read_only.read(), -4); + /// // read_only.write(10); // compile-time error + /// ``` + pub fn read_only(self) -> VolatilePtr<'a, T, ReadOnly> { + unsafe { VolatilePtr::new_restricted(ReadOnly, self.pointer) } + } + + /// Restricts access permissions to write-only. + /// + /// ## Example + /// + /// Creating a write-only reference to a struct field: + /// + /// ``` + /// use volatile::{VolatilePtr, map_field_mut}; + /// use core::ptr::NonNull; + /// + /// struct Example { field_1: u32, field_2: u8, } + /// let mut value = Example { field_1: 15, field_2: 255 }; + /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); + /// + /// // construct a volatile write-only reference to `field_2` + /// let mut field_2 = map_field_mut!(volatile.field_2).write_only(); + /// field_2.write(14); + /// // field_2.read(); // compile-time error + /// ``` + pub fn write_only(self) -> VolatilePtr<'a, T, WriteOnly> { + unsafe { VolatilePtr::new_restricted(WriteOnly, self.pointer) } + } +} + +unsafe impl Send for VolatilePtr<'_, T, A> where T: Sync {} +unsafe impl Sync for VolatilePtr<'_, T, A> where T: Sync {} + +impl fmt::Debug for VolatilePtr<'_, T, A> +where + T: Copy + fmt::Debug + ?Sized, + A: Readable, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Volatile").field(&self.read()).finish() + } +} diff --git a/src/ptr.rs b/src/ptr/unstable.rs similarity index 52% rename from src/ptr.rs rename to src/ptr/unstable.rs index 82aee9f..2246c59 100644 --- a/src/ptr.rs +++ b/src/ptr/unstable.rs @@ -1,425 +1,16 @@ -//! Provides the wrapper type `Volatile`, which wraps a reference to any copy-able type and allows -//! for volatile memory access to wrapped value. Volatile memory accesses are never optimized away -//! by the compiler, and are useful in many low-level systems programming and concurrent contexts. -//! -//! The wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider -//! looking at the `Atomic` wrapper types found in `libcore` or `libstd`. - -use core::{ - fmt, - marker::PhantomData, - ptr::{self, NonNull}, -}; -#[cfg(feature = "unstable")] use core::{ intrinsics, ops::{Range, RangeBounds}, + ptr::{self, NonNull}, slice::{range, SliceIndex}, }; -use crate::access::{Access, ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; - -/// TODO -/// -/// ## Examples -/// -/// Accessing a struct field: -/// -/// ``` -/// use volatile::{VolatilePtr, map_field}; -/// use core::ptr::NonNull; -/// -/// struct Example { field_1: u32, field_2: u8, } -/// let mut value = Example { field_1: 15, field_2: 255 }; -/// let mut volatile = VolatilePtr::from_mut_ref(&mut value); -/// -/// // construct a volatile reference to a field -/// let field_2 = map_field!(volatile.field_2); -/// assert_eq!(field_2.read(), 255); -/// ``` -/// -/// Creating `VolatilePtr`s to unaligned field in packed structs is not allowed: -/// ```compile_fail -/// use volatile::{VolatilePtr, map_field}; -/// use core::ptr::NonNull; -/// -/// #[repr(packed)] -/// struct Example { field_1: u8, field_2: usize, } -/// let mut value = Example { field_1: 15, field_2: 255 }; -/// let mut volatile = VolatilePtr::from_mut_ref(&mut value); -/// -/// // Constructing a volatile reference to an unaligned field doesn't compile. -/// let field_2 = map_field!(volatile.field_2); -/// ``` -#[macro_export] -macro_rules! map_field { - ($volatile:ident.$place:ident) => {{ - // Simulate creating a reference to the field. This is done to make - // sure that the field is not potentially unaligned. The body of the - // if statement will never be executed, so it can never cause any UB. - if false { - #[deny(unaligned_references)] - let _ref_to_field = &(unsafe { &*$volatile.as_ptr().as_ptr() }).$place; - } - - unsafe { - $volatile.map(|ptr| { - core::ptr::NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).$place)).unwrap() - }) - } - }}; -} - -#[macro_export] -macro_rules! map_field_mut { - ($volatile:ident.$place:ident) => {{ - // Simulate creating a reference to the field. This is done to make - // sure that the field is not potentially unaligned. The body of the - // if statement will never be executed, so it can never cause any UB. - if false { - #[deny(unaligned_references)] - let _ref_to_field = &(unsafe { &*$volatile.as_ptr().as_ptr() }).$place; - } - - unsafe { - $volatile.map_mut(|ptr| { - core::ptr::NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).$place)).unwrap() - }) - } - }}; -} - -/// Wraps a pointer to make accesses to the referenced value volatile. -/// -/// Allows volatile reads and writes on the referenced value. The referenced value needs to -/// be `Copy` for reading and writing, as volatile reads and writes take and return copies -/// of the value. -/// -/// Since not all volatile resources (e.g. memory mapped device registers) are both readable -/// and writable, this type supports limiting the allowed access types through an optional second -/// generic parameter `A` that can be one of `ReadWrite`, `ReadOnly`, or `WriteOnly`. It defaults -/// to `ReadWrite`, which allows all operations. -/// -/// The size of this struct is the same as the size of the contained reference. -#[repr(transparent)] -pub struct VolatilePtr<'a, T, A = ReadWrite> -where - T: ?Sized, -{ - pointer: NonNull, - reference: PhantomData<&'a T>, - access: PhantomData, -} - -/// Constructor functions. -/// -/// These functions construct new `VolatilePtr` values. While the `new` -/// function creates a `VolatilePtr` instance with unrestricted access, there -/// are also functions for creating read-only or write-only instances. -impl<'a, T> VolatilePtr<'a, T> -where - T: ?Sized, -{ - pub unsafe fn new(pointer: NonNull) -> Self { - unsafe { VolatilePtr::new_restricted(ReadWrite, pointer) } - } - - pub fn from_mut_ref(reference: &'a mut T) -> Self - where - T: 'a, - { - unsafe { VolatilePtr::new(reference.into()) } - } - - pub const unsafe fn new_read_only(pointer: NonNull) -> VolatilePtr<'a, T, ReadOnly> { - unsafe { Self::new_restricted(ReadOnly, pointer) } - } - - pub const unsafe fn new_restricted(access: A, pointer: NonNull) -> VolatilePtr<'a, T, A> - where - A: Access, - { - let _ = access; - unsafe { Self::new_generic(pointer) } - } - - pub fn from_ref(reference: &'a T) -> VolatilePtr<'a, T, ReadOnly> - where - T: 'a, - { - unsafe { VolatilePtr::new_restricted(ReadOnly, reference.into()) } - } - - const unsafe fn new_generic(pointer: NonNull) -> VolatilePtr<'a, T, A> { - VolatilePtr { - pointer, - reference: PhantomData, - access: PhantomData, - } - } -} - -impl<'a, T, A> VolatilePtr<'a, T, A> -where - T: ?Sized, -{ - /// Performs a volatile read of the contained value. - /// - /// Returns a copy of the read value. Volatile reads are guaranteed not to be optimized - /// away by the compiler, but by themselves do not have atomic ordering - /// guarantees. To also get atomicity, consider looking at the `Atomic` wrapper types of - /// the standard/`core` library. - /// - /// ## Examples - /// - /// ```rust - /// use volatile::{VolatilePtr, access}; - /// use core::ptr::NonNull; - /// - /// let value = 42; - /// let shared_reference = unsafe { - /// VolatilePtr::new_restricted(access::ReadOnly, NonNull::from(&value)) - /// }; - /// assert_eq!(shared_reference.read(), 42); - /// - /// let mut value = 50; - /// let mut_reference = VolatilePtr::from_mut_ref(&mut value); - /// assert_eq!(mut_reference.read(), 50); - /// ``` - pub fn read(&self) -> T - where - T: Copy, - A: Readable, - { - // UNSAFE: Safe, as ... TODO - unsafe { ptr::read_volatile(self.pointer.as_ptr()) } - } - - /// Performs a volatile write, setting the contained value to the given `value`. - /// - /// Volatile writes are guaranteed to not be optimized away by the compiler, but by - /// themselves do not have atomic ordering guarantees. To also get atomicity, consider - /// looking at the `Atomic` wrapper types of the standard/`core` library. - /// - /// ## Example - /// - /// ```rust - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let mut value = 42; - /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); - /// volatile.write(50); - /// - /// assert_eq!(volatile.read(), 50); - /// ``` - pub fn write(&mut self, value: T) - where - T: Copy, - A: Writable, - { - // UNSAFE: Safe, as ... TODO - unsafe { ptr::write_volatile(self.pointer.as_ptr(), value) }; - } - - /// Updates the contained value using the given closure and volatile instructions. - /// - /// Performs a volatile read of the contained value, passes a mutable reference to it to the - /// function `f`, and then performs a volatile write of the (potentially updated) value back to - /// the contained value. - /// - /// ```rust - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let mut value = 42; - /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); - /// volatile.update(|val| val + 1); - /// - /// assert_eq!(volatile.read(), 43); - /// ``` - pub fn update(&mut self, f: F) - where - T: Copy, - A: Readable + Writable, - F: FnOnce(T) -> T, - { - let new = f(self.read()); - self.write(new); - } - - /// Extracts the inner value stored in the wrapper type. - /// - /// This method gives direct access to the wrapped reference and thus allows - /// non-volatile access again. This is seldom what you want since there is usually - /// a reason that a reference is wrapped in `Volatile`. However, in some cases it might - /// be required or useful to use the `read_volatile`/`write_volatile` pointer methods of - /// the standard library directly, which this method makes possible. - /// - /// Since no memory safety violation can occur when accessing the referenced value using - /// non-volatile operations, this method is safe. However, it _can_ lead to bugs at the - /// application level, so this method should be used with care. - /// - /// ## Example - /// - /// ``` - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let mut value = 42; - /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); - /// volatile.write(50); - /// let unwrapped: *mut i32 = volatile.as_ptr().as_ptr(); - /// - /// assert_eq!(unsafe { *unwrapped }, 50); // non volatile access, be careful! - /// ``` - pub fn as_ptr(&self) -> NonNull { - self.pointer - } - - // TODO: Add documentation - pub fn borrow(&self) -> VolatilePtr - where - A: Access, - { - unsafe { VolatilePtr::new_restricted(Default::default(), self.pointer) } - } - - // TODO: Add documentation - pub fn borrow_mut(&mut self) -> VolatilePtr - where - A: Access, - { - unsafe { VolatilePtr::new_restricted(A::default(), self.pointer) } - } - - /// Constructs a new `Volatile` reference by mapping the wrapped pointer. - /// - /// This method is useful for accessing only a part of a volatile value, e.g. a subslice or - /// a struct field. For struct field access, there is also the safe [`map_field`] macro that - /// wraps this function. - /// - /// ## Examples - /// - /// Accessing a struct field: - /// - /// ``` - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// struct Example { field_1: u32, field_2: u8, } - /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); - /// - /// // construct a volatile reference to a field - /// let field_2 = unsafe { volatile.map(|ptr| NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).field_2)).unwrap()) }; - /// assert_eq!(field_2.read(), 255); - /// ``` - /// - /// Don't misuse this method to do a non-volatile read of the referenced value: - /// - /// ``` - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let mut value = 5; - /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); - /// - /// // DON'T DO THIS: - /// let mut readout = 0; - /// unsafe { volatile.map(|value| { - /// readout = *value.as_ptr(); // non-volatile read, might lead to bugs - /// value - /// })}; - /// ``` - pub unsafe fn map(self, f: F) -> VolatilePtr<'a, U, A::RestrictShared> - where - F: FnOnce(NonNull) -> NonNull, - A: Access, - U: ?Sized, - { - unsafe { VolatilePtr::new_restricted(Default::default(), f(self.pointer)) } - } - - #[cfg(feature = "very_unstable")] - pub const unsafe fn map_const(self, f: F) -> VolatilePtr<'a, U, A::RestrictShared> - where - F: ~const FnOnce(NonNull) -> NonNull, - A: Access, - U: ?Sized, - { - unsafe { VolatilePtr::new_generic(f(self.pointer)) } - } - - pub unsafe fn map_mut(self, f: F) -> VolatilePtr<'a, U, A> - where - F: FnOnce(NonNull) -> NonNull, - U: ?Sized, - A: Access, - { - unsafe { VolatilePtr::new_restricted(A::default(), f(self.pointer)) } - } - - #[cfg(feature = "very_unstable")] - pub const unsafe fn map_mut_const(self, f: F) -> VolatilePtr<'a, U, A> - where - F: ~const FnOnce(NonNull) -> NonNull, - U: ?Sized, - { - unsafe { VolatilePtr::new_generic(f(self.pointer)) } - } -} - -/// Methods for restricting access. -impl<'a, T> VolatilePtr<'a, T, ReadWrite> -where - T: ?Sized, -{ - /// Restricts access permissions to read-only. - /// - /// ## Example - /// - /// ``` - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let mut value: i16 = -4; - /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); - /// - /// let read_only = volatile.read_only(); - /// assert_eq!(read_only.read(), -4); - /// // read_only.write(10); // compile-time error - /// ``` - pub fn read_only(self) -> VolatilePtr<'a, T, ReadOnly> { - unsafe { VolatilePtr::new_restricted(ReadOnly, self.pointer) } - } - - /// Restricts access permissions to write-only. - /// - /// ## Example - /// - /// Creating a write-only reference to a struct field: - /// - /// ``` - /// use volatile::{VolatilePtr, map_field_mut}; - /// use core::ptr::NonNull; - /// - /// struct Example { field_1: u32, field_2: u8, } - /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); - /// - /// // construct a volatile write-only reference to `field_2` - /// let mut field_2 = map_field_mut!(volatile.field_2).write_only(); - /// field_2.write(14); - /// // field_2.read(); // compile-time error - /// ``` - pub fn write_only(self) -> VolatilePtr<'a, T, WriteOnly> { - unsafe { VolatilePtr::new_restricted(WriteOnly, self.pointer) } - } -} +use crate::{ + access::{Access, Readable, Writable}, + VolatilePtr, +}; /// Methods for volatile slices -#[cfg(feature = "unstable")] impl<'a, T, A> VolatilePtr<'a, [T], A> { pub fn len(&self) -> usize { self.pointer.len() @@ -476,27 +67,6 @@ impl<'a, T, A> VolatilePtr<'a, [T], A> { unsafe { self.map(|slice| slice.get_unchecked_mut(index)) } } - #[cfg(feature = "very_unstable")] - pub const fn index_const(self, index: usize) -> VolatilePtr<'a, T, A::RestrictShared> - where - A: Access, - { - assert!(index < self.pointer.len(), "index out of bounds"); - - struct Mapper { - index: usize, - } - impl const FnOnce<(NonNull<[T]>,)> for Mapper { - type Output = NonNull; - - extern "rust-call" fn call_once(self, (slice,): (NonNull<[T]>,)) -> Self::Output { - unsafe { NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(self.index)) } - } - } - - unsafe { self.map_const(Mapper { index }) } - } - pub fn index_mut(self, index: I) -> VolatilePtr<'a, >::Output, A> where I: SliceIndex<[T]> + SliceIndex<[()]> + Clone, @@ -507,24 +77,6 @@ impl<'a, T, A> VolatilePtr<'a, [T], A> { unsafe { self.map_mut(|slice| slice.get_unchecked_mut(index)) } } - #[cfg(feature = "very_unstable")] - pub const fn index_mut_const(self, index: usize) -> VolatilePtr<'a, T, A> { - assert!(index < self.pointer.len(), "index out of bounds"); - - struct Mapper { - index: usize, - } - impl const FnOnce<(NonNull<[T]>,)> for Mapper { - type Output = NonNull; - - extern "rust-call" fn call_once(self, (slice,): (NonNull<[T]>,)) -> Self::Output { - unsafe { NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(self.index)) } - } - } - - unsafe { self.map_mut_const(Mapper { index }) } - } - /// Returns an iterator over the slice. pub fn iter(self) -> impl Iterator> where @@ -852,7 +404,6 @@ impl<'a, T, A> VolatilePtr<'a, [T], A> { } /// Methods for volatile byte slices -#[cfg(feature = "unstable")] impl VolatilePtr<'_, [u8], A> { /// Sets all elements of the byte slice to the given `value` using a volatile `memset`. /// @@ -889,7 +440,6 @@ impl VolatilePtr<'_, [u8], A> { /// /// These methods are only available with the `unstable` feature enabled (requires a nightly /// Rust compiler). -#[cfg(feature = "unstable")] impl<'a, T, A, const N: usize> VolatilePtr<'a, [T; N], A> { /// Converts an array reference to a shared slice. /// @@ -960,20 +510,6 @@ impl<'a, T, A, const N: usize> VolatilePtr<'a, [T; N], A> { } } -unsafe impl Send for VolatilePtr<'_, T, A> where T: Sync {} -unsafe impl Sync for VolatilePtr<'_, T, A> where T: Sync {} - -impl fmt::Debug for VolatilePtr<'_, T, A> -where - T: Copy + fmt::Debug + ?Sized, - A: Readable, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Volatile").field(&self.read()).finish() - } -} - -#[cfg(feature = "unstable")] fn bounds_check(len: usize, index: impl SliceIndex<[()]>) { const MAX_ARRAY: [(); usize::MAX] = [(); usize::MAX]; diff --git a/src/ptr/very_unstable.rs b/src/ptr/very_unstable.rs new file mode 100644 index 0000000..f7bddcb --- /dev/null +++ b/src/ptr/very_unstable.rs @@ -0,0 +1,66 @@ +use core::ptr::NonNull; + +use crate::{access::Access, VolatilePtr}; + +impl<'a, T, A> VolatilePtr<'a, T, A> +where + T: ?Sized, +{ + pub const unsafe fn map_const(self, f: F) -> VolatilePtr<'a, U, A::RestrictShared> + where + F: ~const FnOnce(NonNull) -> NonNull, + A: Access, + U: ?Sized, + { + unsafe { VolatilePtr::new_generic(f(self.pointer)) } + } + + pub const unsafe fn map_mut_const(self, f: F) -> VolatilePtr<'a, U, A> + where + F: ~const FnOnce(NonNull) -> NonNull, + U: ?Sized, + { + unsafe { VolatilePtr::new_generic(f(self.pointer)) } + } +} + +/// Methods for volatile slices +#[cfg(feature = "unstable")] +impl<'a, T, A> VolatilePtr<'a, [T], A> { + pub const fn index_const(self, index: usize) -> VolatilePtr<'a, T, A::RestrictShared> + where + A: Access, + { + assert!(index < self.pointer.len(), "index out of bounds"); + + struct Mapper { + index: usize, + } + impl const FnOnce<(NonNull<[T]>,)> for Mapper { + type Output = NonNull; + + extern "rust-call" fn call_once(self, (slice,): (NonNull<[T]>,)) -> Self::Output { + unsafe { NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(self.index)) } + } + } + + unsafe { self.map_const(Mapper { index }) } + } + + pub const fn index_mut_const(self, index: usize) -> VolatilePtr<'a, T, A> { + assert!(index < self.pointer.len(), "index out of bounds"); + + struct Mapper { + index: usize, + } + impl const FnOnce<(NonNull<[T]>,)> for Mapper { + type Output = NonNull; + + extern "rust-call" fn call_once(self, (slice,): (NonNull<[T]>,)) -> Self::Output { + unsafe { NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(self.index)) } + } + } + + unsafe { self.map_mut_const(Mapper { index }) } + } +} diff --git a/src/ptr_copy/mod.rs b/src/ptr_copy/mod.rs new file mode 100644 index 0000000..5ed68d9 --- /dev/null +++ b/src/ptr_copy/mod.rs @@ -0,0 +1,337 @@ +//! Provides the wrapper type `Volatile`, which wraps a reference to any copy-able type and allows +//! for volatile memory access to wrapped value. Volatile memory accesses are never optimized away +//! by the compiler, and are useful in many low-level systems programming and concurrent contexts. +//! +//! The wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider +//! looking at the `Atomic` wrapper types found in `libcore` or `libstd`. + +use core::{ + fmt, + marker::PhantomData, + ptr::{self, NonNull}, +}; + +use crate::access::{Access, ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; + +#[cfg(feature = "unstable")] +mod unstable; +#[cfg(feature = "very_unstable")] +mod very_unstable; + +/// Wraps a pointer to make accesses to the referenced value volatile. +/// +/// Allows volatile reads and writes on the referenced value. The referenced value needs to +/// be `Copy` for reading and writing, as volatile reads and writes take and return copies +/// of the value. +/// +/// Since not all volatile resources (e.g. memory mapped device registers) are both readable +/// and writable, this type supports limiting the allowed access types through an optional second +/// generic parameter `A` that can be one of `ReadWrite`, `ReadOnly`, or `WriteOnly`. It defaults +/// to `ReadWrite`, which allows all operations. +/// +/// The size of this struct is the same as the size of the contained reference. +#[repr(transparent)] +pub struct VolatilePtrCopy<'a, T, A = ReadWrite> +where + T: ?Sized, +{ + pointer: NonNull, + reference: PhantomData<&'a T>, + access: PhantomData, +} + +impl<'a, T, A> Copy for VolatilePtrCopy<'a, T, A> where T: ?Sized {} + +impl Clone for VolatilePtrCopy<'_, T, A> +where + T: ?Sized, +{ + fn clone(&self) -> Self { + *self + } +} + +/// Constructor functions. +/// +/// These functions construct new `VolatilePtr` values. While the `new` +/// function creates a `VolatilePtr` instance with unrestricted access, there +/// are also functions for creating read-only or write-only instances. +impl<'a, T> VolatilePtrCopy<'a, T> +where + T: ?Sized, +{ + pub unsafe fn new(pointer: NonNull) -> Self { + unsafe { VolatilePtrCopy::new_restricted(ReadWrite, pointer) } + } + + pub fn from_mut_ref(reference: &'a mut T) -> Self + where + T: 'a, + { + unsafe { VolatilePtrCopy::new(reference.into()) } + } + + pub const unsafe fn new_read_only(pointer: NonNull) -> VolatilePtrCopy<'a, T, ReadOnly> { + unsafe { Self::new_restricted(ReadOnly, pointer) } + } + + pub const unsafe fn new_restricted( + access: A, + pointer: NonNull, + ) -> VolatilePtrCopy<'a, T, A> + where + A: Access, + { + let _ = access; + unsafe { Self::new_generic(pointer) } + } + + pub fn from_ref(reference: &'a T) -> VolatilePtrCopy<'a, T, ReadOnly> + where + T: 'a, + { + unsafe { VolatilePtrCopy::new_restricted(ReadOnly, reference.into()) } + } + + const unsafe fn new_generic(pointer: NonNull) -> VolatilePtrCopy<'a, T, A> { + VolatilePtrCopy { + pointer, + reference: PhantomData, + access: PhantomData, + } + } +} + +impl<'a, T, A> VolatilePtrCopy<'a, T, A> +where + T: ?Sized, +{ + /// Performs a volatile read of the contained value. + /// + /// Returns a copy of the read value. Volatile reads are guaranteed not to be optimized + /// away by the compiler, but by themselves do not have atomic ordering + /// guarantees. To also get atomicity, consider looking at the `Atomic` wrapper types of + /// the standard/`core` library. + /// + /// ## Examples + /// + /// ```rust + /// use volatile::{VolatilePtrCopy, access}; + /// use core::ptr::NonNull; + /// + /// let value = 42; + /// let shared_reference = unsafe { + /// VolatilePtrCopy::new_restricted(access::ReadOnly, NonNull::from(&value)) + /// }; + /// assert_eq!(shared_reference.read(), 42); + /// + /// let mut value = 50; + /// let mut_reference = VolatilePtrCopy::from_mut_ref(&mut value); + /// assert_eq!(mut_reference.read(), 50); + /// ``` + pub fn read(self) -> T + where + T: Copy, + A: Readable, + { + // UNSAFE: Safe, as ... TODO + unsafe { ptr::read_volatile(self.pointer.as_ptr()) } + } + + /// Performs a volatile write, setting the contained value to the given `value`. + /// + /// Volatile writes are guaranteed to not be optimized away by the compiler, but by + /// themselves do not have atomic ordering guarantees. To also get atomicity, consider + /// looking at the `Atomic` wrapper types of the standard/`core` library. + /// + /// ## Example + /// + /// ```rust + /// use volatile::VolatilePtrCopy; + /// use core::ptr::NonNull; + /// + /// let mut value = 42; + /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); + /// volatile.write(50); + /// + /// assert_eq!(volatile.read(), 50); + /// ``` + pub fn write(self, value: T) + where + T: Copy, + A: Writable, + { + // UNSAFE: Safe, as ... TODO + unsafe { ptr::write_volatile(self.pointer.as_ptr(), value) }; + } + + /// Updates the contained value using the given closure and volatile instructions. + /// + /// Performs a volatile read of the contained value, passes a mutable reference to it to the + /// function `f`, and then performs a volatile write of the (potentially updated) value back to + /// the contained value. + /// + /// ```rust + /// use volatile::VolatilePtrCopy; + /// use core::ptr::NonNull; + /// + /// let mut value = 42; + /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); + /// volatile.update(|val| val + 1); + /// + /// assert_eq!(volatile.read(), 43); + /// ``` + pub fn update(self, f: F) + where + T: Copy, + A: Readable + Writable, + F: FnOnce(T) -> T, + { + let new = f(self.read()); + self.write(new); + } + + /// Extracts the inner value stored in the wrapper type. + /// + /// This method gives direct access to the wrapped reference and thus allows + /// non-volatile access again. This is seldom what you want since there is usually + /// a reason that a reference is wrapped in `Volatile`. However, in some cases it might + /// be required or useful to use the `read_volatile`/`write_volatile` pointer methods of + /// the standard library directly, which this method makes possible. + /// + /// Since no memory safety violation can occur when accessing the referenced value using + /// non-volatile operations, this method is safe. However, it _can_ lead to bugs at the + /// application level, so this method should be used with care. + /// + /// ## Example + /// + /// ``` + /// use volatile::VolatilePtrCopy; + /// use core::ptr::NonNull; + /// + /// let mut value = 42; + /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); + /// volatile.write(50); + /// let unwrapped: *mut i32 = volatile.as_ptr().as_ptr(); + /// + /// assert_eq!(unsafe { *unwrapped }, 50); // non volatile access, be careful! + /// ``` + pub fn as_ptr(self) -> NonNull { + self.pointer + } + + /// Constructs a new `Volatile` reference by mapping the wrapped pointer. + /// + /// This method is useful for accessing only a part of a volatile value, e.g. a subslice or + /// a struct field. For struct field access, there is also the safe [`map_field`] macro that + /// wraps this function. + /// + /// ## Examples + /// + /// Accessing a struct field: + /// + /// ``` + /// use volatile::VolatilePtrCopy; + /// use core::ptr::NonNull; + /// + /// struct Example { field_1: u32, field_2: u8, } + /// let mut value = Example { field_1: 15, field_2: 255 }; + /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); + /// + /// // construct a volatile reference to a field + /// let field_2 = unsafe { volatile.map(|ptr| NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).field_2)).unwrap()) }; + /// assert_eq!(field_2.read(), 255); + /// ``` + /// + /// Don't misuse this method to do a non-volatile read of the referenced value: + /// + /// ``` + /// use volatile::VolatilePtrCopy; + /// use core::ptr::NonNull; + /// + /// let mut value = 5; + /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); + /// + /// // DON'T DO THIS: + /// let mut readout = 0; + /// unsafe { volatile.map(|value| { + /// readout = *value.as_ptr(); // non-volatile read, might lead to bugs + /// value + /// })}; + /// ``` + pub unsafe fn map(self, f: F) -> VolatilePtrCopy<'a, U, A::RestrictShared> + where + F: FnOnce(NonNull) -> NonNull, + A: Access, + U: ?Sized, + { + unsafe { VolatilePtrCopy::new_restricted(Default::default(), f(self.pointer)) } + } + + pub unsafe fn map_mut(self, f: F) -> VolatilePtrCopy<'a, U, A> + where + F: FnOnce(NonNull) -> NonNull, + U: ?Sized, + A: Access, + { + unsafe { VolatilePtrCopy::new_restricted(A::default(), f(self.pointer)) } + } +} + +/// Methods for restricting access. +impl<'a, T> VolatilePtrCopy<'a, T, ReadWrite> +where + T: ?Sized, +{ + /// Restricts access permissions to read-only. + /// + /// ## Example + /// + /// ``` + /// use volatile::VolatilePtrCopy; + /// use core::ptr::NonNull; + /// + /// let mut value: i16 = -4; + /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); + /// + /// let read_only = volatile.read_only(); + /// assert_eq!(read_only.read(), -4); + /// // read_only.write(10); // compile-time error + /// ``` + pub fn read_only(self) -> VolatilePtrCopy<'a, T, ReadOnly> { + unsafe { VolatilePtrCopy::new_restricted(ReadOnly, self.pointer) } + } + + /// Restricts access permissions to write-only. + /// + /// ## Example + /// + /// Creating a write-only reference to a struct field: + /// + /// ``` + /// use volatile::{VolatilePtrCopy, map_field_mut}; + /// use core::ptr::NonNull; + /// + /// struct Example { field_1: u32, field_2: u8, } + /// let mut value = Example { field_1: 15, field_2: 255 }; + /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); + /// + /// // construct a volatile write-only reference to `field_2` + /// let mut field_2 = map_field_mut!(volatile.field_2).write_only(); + /// field_2.write(14); + /// // field_2.read(); // compile-time error + /// ``` + pub fn write_only(self) -> VolatilePtrCopy<'a, T, WriteOnly> { + unsafe { VolatilePtrCopy::new_restricted(WriteOnly, self.pointer) } + } +} + +impl fmt::Debug for VolatilePtrCopy<'_, T, A> +where + T: Copy + fmt::Debug + ?Sized, + A: Readable, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Volatile").field(&self.read()).finish() + } +} diff --git a/src/ptr_copy.rs b/src/ptr_copy/unstable.rs similarity index 57% rename from src/ptr_copy.rs rename to src/ptr_copy/unstable.rs index 6b42802..e819b06 100644 --- a/src/ptr_copy.rs +++ b/src/ptr_copy/unstable.rs @@ -1,353 +1,15 @@ -//! Provides the wrapper type `Volatile`, which wraps a reference to any copy-able type and allows -//! for volatile memory access to wrapped value. Volatile memory accesses are never optimized away -//! by the compiler, and are useful in many low-level systems programming and concurrent contexts. -//! -//! The wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider -//! looking at the `Atomic` wrapper types found in `libcore` or `libstd`. - -use core::{ - fmt, - marker::PhantomData, - ptr::{self, NonNull}, -}; -#[cfg(feature = "unstable")] use core::{ intrinsics, ops::{Range, RangeBounds}, + ptr::{self, NonNull}, slice::{range, SliceIndex}, }; -use crate::access::{Access, ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; - -/// Wraps a pointer to make accesses to the referenced value volatile. -/// -/// Allows volatile reads and writes on the referenced value. The referenced value needs to -/// be `Copy` for reading and writing, as volatile reads and writes take and return copies -/// of the value. -/// -/// Since not all volatile resources (e.g. memory mapped device registers) are both readable -/// and writable, this type supports limiting the allowed access types through an optional second -/// generic parameter `A` that can be one of `ReadWrite`, `ReadOnly`, or `WriteOnly`. It defaults -/// to `ReadWrite`, which allows all operations. -/// -/// The size of this struct is the same as the size of the contained reference. -#[repr(transparent)] -pub struct VolatilePtrCopy<'a, T, A = ReadWrite> -where - T: ?Sized, -{ - pointer: NonNull, - reference: PhantomData<&'a T>, - access: PhantomData, -} - -impl<'a, T, A> Copy for VolatilePtrCopy<'a, T, A> where T: ?Sized {} - -impl Clone for VolatilePtrCopy<'_, T, A> -where - T: ?Sized, -{ - fn clone(&self) -> Self { - *self - } -} - -/// Constructor functions. -/// -/// These functions construct new `VolatilePtr` values. While the `new` -/// function creates a `VolatilePtr` instance with unrestricted access, there -/// are also functions for creating read-only or write-only instances. -impl<'a, T> VolatilePtrCopy<'a, T> -where - T: ?Sized, -{ - pub unsafe fn new(pointer: NonNull) -> Self { - unsafe { VolatilePtrCopy::new_restricted(ReadWrite, pointer) } - } - - pub fn from_mut_ref(reference: &'a mut T) -> Self - where - T: 'a, - { - unsafe { VolatilePtrCopy::new(reference.into()) } - } - - pub const unsafe fn new_read_only(pointer: NonNull) -> VolatilePtrCopy<'a, T, ReadOnly> { - unsafe { Self::new_restricted(ReadOnly, pointer) } - } - - pub const unsafe fn new_restricted( - access: A, - pointer: NonNull, - ) -> VolatilePtrCopy<'a, T, A> - where - A: Access, - { - let _ = access; - unsafe { Self::new_generic(pointer) } - } - - pub fn from_ref(reference: &'a T) -> VolatilePtrCopy<'a, T, ReadOnly> - where - T: 'a, - { - unsafe { VolatilePtrCopy::new_restricted(ReadOnly, reference.into()) } - } - - const unsafe fn new_generic(pointer: NonNull) -> VolatilePtrCopy<'a, T, A> { - VolatilePtrCopy { - pointer, - reference: PhantomData, - access: PhantomData, - } - } -} - -impl<'a, T, A> VolatilePtrCopy<'a, T, A> -where - T: ?Sized, -{ - /// Performs a volatile read of the contained value. - /// - /// Returns a copy of the read value. Volatile reads are guaranteed not to be optimized - /// away by the compiler, but by themselves do not have atomic ordering - /// guarantees. To also get atomicity, consider looking at the `Atomic` wrapper types of - /// the standard/`core` library. - /// - /// ## Examples - /// - /// ```rust - /// use volatile::{VolatilePtrCopy, access}; - /// use core::ptr::NonNull; - /// - /// let value = 42; - /// let shared_reference = unsafe { - /// VolatilePtrCopy::new_restricted(access::ReadOnly, NonNull::from(&value)) - /// }; - /// assert_eq!(shared_reference.read(), 42); - /// - /// let mut value = 50; - /// let mut_reference = VolatilePtrCopy::from_mut_ref(&mut value); - /// assert_eq!(mut_reference.read(), 50); - /// ``` - pub fn read(self) -> T - where - T: Copy, - A: Readable, - { - // UNSAFE: Safe, as ... TODO - unsafe { ptr::read_volatile(self.pointer.as_ptr()) } - } - - /// Performs a volatile write, setting the contained value to the given `value`. - /// - /// Volatile writes are guaranteed to not be optimized away by the compiler, but by - /// themselves do not have atomic ordering guarantees. To also get atomicity, consider - /// looking at the `Atomic` wrapper types of the standard/`core` library. - /// - /// ## Example - /// - /// ```rust - /// use volatile::VolatilePtrCopy; - /// use core::ptr::NonNull; - /// - /// let mut value = 42; - /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); - /// volatile.write(50); - /// - /// assert_eq!(volatile.read(), 50); - /// ``` - pub fn write(self, value: T) - where - T: Copy, - A: Writable, - { - // UNSAFE: Safe, as ... TODO - unsafe { ptr::write_volatile(self.pointer.as_ptr(), value) }; - } - - /// Updates the contained value using the given closure and volatile instructions. - /// - /// Performs a volatile read of the contained value, passes a mutable reference to it to the - /// function `f`, and then performs a volatile write of the (potentially updated) value back to - /// the contained value. - /// - /// ```rust - /// use volatile::VolatilePtrCopy; - /// use core::ptr::NonNull; - /// - /// let mut value = 42; - /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); - /// volatile.update(|val| val + 1); - /// - /// assert_eq!(volatile.read(), 43); - /// ``` - pub fn update(self, f: F) - where - T: Copy, - A: Readable + Writable, - F: FnOnce(T) -> T, - { - let new = f(self.read()); - self.write(new); - } - - /// Extracts the inner value stored in the wrapper type. - /// - /// This method gives direct access to the wrapped reference and thus allows - /// non-volatile access again. This is seldom what you want since there is usually - /// a reason that a reference is wrapped in `Volatile`. However, in some cases it might - /// be required or useful to use the `read_volatile`/`write_volatile` pointer methods of - /// the standard library directly, which this method makes possible. - /// - /// Since no memory safety violation can occur when accessing the referenced value using - /// non-volatile operations, this method is safe. However, it _can_ lead to bugs at the - /// application level, so this method should be used with care. - /// - /// ## Example - /// - /// ``` - /// use volatile::VolatilePtrCopy; - /// use core::ptr::NonNull; - /// - /// let mut value = 42; - /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); - /// volatile.write(50); - /// let unwrapped: *mut i32 = volatile.as_ptr().as_ptr(); - /// - /// assert_eq!(unsafe { *unwrapped }, 50); // non volatile access, be careful! - /// ``` - pub fn as_ptr(self) -> NonNull { - self.pointer - } - - /// Constructs a new `Volatile` reference by mapping the wrapped pointer. - /// - /// This method is useful for accessing only a part of a volatile value, e.g. a subslice or - /// a struct field. For struct field access, there is also the safe [`map_field`] macro that - /// wraps this function. - /// - /// ## Examples - /// - /// Accessing a struct field: - /// - /// ``` - /// use volatile::VolatilePtrCopy; - /// use core::ptr::NonNull; - /// - /// struct Example { field_1: u32, field_2: u8, } - /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); - /// - /// // construct a volatile reference to a field - /// let field_2 = unsafe { volatile.map(|ptr| NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).field_2)).unwrap()) }; - /// assert_eq!(field_2.read(), 255); - /// ``` - /// - /// Don't misuse this method to do a non-volatile read of the referenced value: - /// - /// ``` - /// use volatile::VolatilePtrCopy; - /// use core::ptr::NonNull; - /// - /// let mut value = 5; - /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); - /// - /// // DON'T DO THIS: - /// let mut readout = 0; - /// unsafe { volatile.map(|value| { - /// readout = *value.as_ptr(); // non-volatile read, might lead to bugs - /// value - /// })}; - /// ``` - pub unsafe fn map(self, f: F) -> VolatilePtrCopy<'a, U, A::RestrictShared> - where - F: FnOnce(NonNull) -> NonNull, - A: Access, - U: ?Sized, - { - unsafe { VolatilePtrCopy::new_restricted(Default::default(), f(self.pointer)) } - } - - #[cfg(feature = "very_unstable")] - pub const unsafe fn map_const(self, f: F) -> VolatilePtrCopy<'a, U, A::RestrictShared> - where - F: ~const FnOnce(NonNull) -> NonNull, - A: Access, - U: ?Sized, - { - unsafe { VolatilePtrCopy::new_generic(f(self.pointer)) } - } - - pub unsafe fn map_mut(self, f: F) -> VolatilePtrCopy<'a, U, A> - where - F: FnOnce(NonNull) -> NonNull, - U: ?Sized, - A: Access, - { - unsafe { VolatilePtrCopy::new_restricted(A::default(), f(self.pointer)) } - } - - #[cfg(feature = "very_unstable")] - pub const unsafe fn map_mut_const(self, f: F) -> VolatilePtrCopy<'a, U, A> - where - F: ~const FnOnce(NonNull) -> NonNull, - U: ?Sized, - { - unsafe { VolatilePtrCopy::new_generic(f(self.pointer)) } - } -} - -/// Methods for restricting access. -impl<'a, T> VolatilePtrCopy<'a, T, ReadWrite> -where - T: ?Sized, -{ - /// Restricts access permissions to read-only. - /// - /// ## Example - /// - /// ``` - /// use volatile::VolatilePtrCopy; - /// use core::ptr::NonNull; - /// - /// let mut value: i16 = -4; - /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); - /// - /// let read_only = volatile.read_only(); - /// assert_eq!(read_only.read(), -4); - /// // read_only.write(10); // compile-time error - /// ``` - pub fn read_only(self) -> VolatilePtrCopy<'a, T, ReadOnly> { - unsafe { VolatilePtrCopy::new_restricted(ReadOnly, self.pointer) } - } - - /// Restricts access permissions to write-only. - /// - /// ## Example - /// - /// Creating a write-only reference to a struct field: - /// - /// ``` - /// use volatile::{VolatilePtrCopy, map_field_mut}; - /// use core::ptr::NonNull; - /// - /// struct Example { field_1: u32, field_2: u8, } - /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); - /// - /// // construct a volatile write-only reference to `field_2` - /// let mut field_2 = map_field_mut!(volatile.field_2).write_only(); - /// field_2.write(14); - /// // field_2.read(); // compile-time error - /// ``` - pub fn write_only(self) -> VolatilePtrCopy<'a, T, WriteOnly> { - unsafe { VolatilePtrCopy::new_restricted(WriteOnly, self.pointer) } - } -} +use crate::{ + access::{Access, Readable, Writable}, + VolatilePtrCopy, +}; -/// Methods for volatile slices -#[cfg(feature = "unstable")] impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { pub fn len(self) -> usize { self.pointer.len() @@ -404,27 +66,6 @@ impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { unsafe { self.map(|slice| slice.get_unchecked_mut(index)) } } - #[cfg(feature = "very_unstable")] - pub const fn index_const(self, index: usize) -> VolatilePtrCopy<'a, T, A::RestrictShared> - where - A: Access, - { - assert!(index < self.pointer.len(), "index out of bounds"); - - struct Mapper { - index: usize, - } - impl const FnOnce<(NonNull<[T]>,)> for Mapper { - type Output = NonNull; - - extern "rust-call" fn call_once(self, (slice,): (NonNull<[T]>,)) -> Self::Output { - unsafe { NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(self.index)) } - } - } - - unsafe { self.map_const(Mapper { index }) } - } - pub fn index_mut(self, index: I) -> VolatilePtrCopy<'a, >::Output, A> where I: SliceIndex<[T]> + SliceIndex<[()]> + Clone, @@ -435,24 +76,6 @@ impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { unsafe { self.map_mut(|slice| slice.get_unchecked_mut(index)) } } - #[cfg(feature = "very_unstable")] - pub const fn index_mut_const(self, index: usize) -> VolatilePtrCopy<'a, T, A> { - assert!(index < self.pointer.len(), "index out of bounds"); - - struct Mapper { - index: usize, - } - impl const FnOnce<(NonNull<[T]>,)> for Mapper { - type Output = NonNull; - - extern "rust-call" fn call_once(self, (slice,): (NonNull<[T]>,)) -> Self::Output { - unsafe { NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(self.index)) } - } - } - - unsafe { self.map_mut_const(Mapper { index }) } - } - /// Returns an iterator over the slice. pub fn iter(self) -> impl Iterator> where @@ -790,7 +413,6 @@ impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { } /// Methods for volatile byte slices -#[cfg(feature = "unstable")] impl VolatilePtrCopy<'_, [u8], A> { /// Sets all elements of the byte slice to the given `value` using a volatile `memset`. /// @@ -827,7 +449,6 @@ impl VolatilePtrCopy<'_, [u8], A> { /// /// These methods are only available with the `unstable` feature enabled (requires a nightly /// Rust compiler). -#[cfg(feature = "unstable")] impl<'a, T, A, const N: usize> VolatilePtrCopy<'a, [T; N], A> { /// Converts an array reference to a shared slice. /// @@ -898,17 +519,6 @@ impl<'a, T, A, const N: usize> VolatilePtrCopy<'a, [T; N], A> { } } -impl fmt::Debug for VolatilePtrCopy<'_, T, A> -where - T: Copy + fmt::Debug + ?Sized, - A: Readable, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Volatile").field(&self.read()).finish() - } -} - -#[cfg(feature = "unstable")] fn bounds_check(len: usize, index: impl SliceIndex<[()]>) { const MAX_ARRAY: [(); usize::MAX] = [(); usize::MAX]; diff --git a/src/ptr_copy/very_unstable.rs b/src/ptr_copy/very_unstable.rs new file mode 100644 index 0000000..c3c12a2 --- /dev/null +++ b/src/ptr_copy/very_unstable.rs @@ -0,0 +1,66 @@ +use core::ptr::NonNull; + +use crate::{access::Access, VolatilePtrCopy}; + +impl<'a, T, A> VolatilePtrCopy<'a, T, A> +where + T: ?Sized, +{ + pub const unsafe fn map_const(self, f: F) -> VolatilePtrCopy<'a, U, A::RestrictShared> + where + F: ~const FnOnce(NonNull) -> NonNull, + A: Access, + U: ?Sized, + { + unsafe { VolatilePtrCopy::new_generic(f(self.pointer)) } + } + + pub const unsafe fn map_mut_const(self, f: F) -> VolatilePtrCopy<'a, U, A> + where + F: ~const FnOnce(NonNull) -> NonNull, + U: ?Sized, + { + unsafe { VolatilePtrCopy::new_generic(f(self.pointer)) } + } +} + +/// Methods for volatile slices +#[cfg(feature = "unstable")] +impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { + pub const fn index_const(self, index: usize) -> VolatilePtrCopy<'a, T, A::RestrictShared> + where + A: Access, + { + assert!(index < self.pointer.len(), "index out of bounds"); + + struct Mapper { + index: usize, + } + impl const FnOnce<(NonNull<[T]>,)> for Mapper { + type Output = NonNull; + + extern "rust-call" fn call_once(self, (slice,): (NonNull<[T]>,)) -> Self::Output { + unsafe { NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(self.index)) } + } + } + + unsafe { self.map_const(Mapper { index }) } + } + + pub const fn index_mut_const(self, index: usize) -> VolatilePtrCopy<'a, T, A> { + assert!(index < self.pointer.len(), "index out of bounds"); + + struct Mapper { + index: usize, + } + impl const FnOnce<(NonNull<[T]>,)> for Mapper { + type Output = NonNull; + + extern "rust-call" fn call_once(self, (slice,): (NonNull<[T]>,)) -> Self::Output { + unsafe { NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(self.index)) } + } + } + + unsafe { self.map_mut_const(Mapper { index }) } + } +} From 01cbbdce98f8280dfc4b8187a13b939e0ab956d6 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 16:57:04 +0100 Subject: [PATCH 42/68] Adjust and enable `VolatilePtr` tests --- src/ptr/mod.rs | 2 + src/ptr/tests.rs | 189 ++++++++++++++++++++++++++++++++++ src/tests.rs | 261 ----------------------------------------------- 3 files changed, 191 insertions(+), 261 deletions(-) create mode 100644 src/ptr/tests.rs delete mode 100644 src/tests.rs diff --git a/src/ptr/mod.rs b/src/ptr/mod.rs index a03c787..24b5b00 100644 --- a/src/ptr/mod.rs +++ b/src/ptr/mod.rs @@ -12,6 +12,8 @@ use core::{ ptr::{self, NonNull}, }; +#[cfg(test)] +mod tests; #[cfg(feature = "unstable")] mod unstable; #[cfg(feature = "very_unstable")] diff --git a/src/ptr/tests.rs b/src/ptr/tests.rs new file mode 100644 index 0000000..433581e --- /dev/null +++ b/src/ptr/tests.rs @@ -0,0 +1,189 @@ +use crate::{ + access::{ReadOnly, ReadWrite, WriteOnly}, + map_field_mut, VolatilePtr, +}; +use core::ptr::NonNull; + +#[test] +fn test_read() { + let val = 42; + assert_eq!( + unsafe { VolatilePtr::new_read_only(NonNull::from(&val)) }.read(), + 42 + ); +} + +#[test] +fn test_write() { + let mut val = 50; + let mut volatile = unsafe { VolatilePtr::new(NonNull::from(&mut val)) }; + volatile.write(50); + assert_eq!(val, 50); +} + +#[test] +fn test_update() { + let mut val = 42; + let mut volatile = unsafe { VolatilePtr::new(NonNull::from(&mut val)) }; + volatile.update(|v| v + 1); + assert_eq!(val, 43); +} + +#[test] +fn test_access() { + let mut val: i64 = 42; + + // ReadWrite + assert_eq!( + unsafe { VolatilePtr::new_restricted(ReadWrite, NonNull::from(&mut val)) }.read(), + 42 + ); + unsafe { VolatilePtr::new_restricted(ReadWrite, NonNull::from(&mut val)) }.write(50); + assert_eq!(val, 50); + unsafe { VolatilePtr::new_restricted(ReadWrite, NonNull::from(&mut val)) }.update(|i| i + 1); + assert_eq!(val, 51); + + // ReadOnly and WriteOnly + assert_eq!( + unsafe { VolatilePtr::new_restricted(ReadOnly, NonNull::from(&mut val)) }.read(), + 51 + ); + unsafe { VolatilePtr::new_restricted(WriteOnly, NonNull::from(&mut val)) }.write(12); + assert_eq!(val, 12); +} + +#[test] +fn test_struct() { + #[derive(Debug, PartialEq)] + struct S { + field_1: u32, + field_2: bool, + } + + let mut val = S { + field_1: 60, + field_2: true, + }; + let mut volatile = unsafe { VolatilePtr::new(NonNull::from(&mut val)) }; + unsafe { + volatile + .borrow_mut() + .map_mut(|s| NonNull::new(core::ptr::addr_of_mut!((*s.as_ptr()).field_1)).unwrap()) + } + .update(|v| v + 1); + let mut field_2 = unsafe { + volatile.map_mut(|s| NonNull::new(core::ptr::addr_of_mut!((*s.as_ptr()).field_2)).unwrap()) + }; + assert!(field_2.read()); + field_2.write(false); + assert_eq!( + val, + S { + field_1: 61, + field_2: false + } + ); +} + +#[test] +fn test_struct_macro() { + #[derive(Debug, PartialEq)] + struct S { + field_1: u32, + field_2: bool, + } + + let mut val = S { + field_1: 60, + field_2: true, + }; + let mut volatile = unsafe { VolatilePtr::new(NonNull::from(&mut val)) }; + let volatile_borrowed = volatile.borrow_mut(); + let mut field_1 = map_field_mut!(volatile_borrowed.field_1); + field_1.update(|v| v + 1); + let mut field_2 = map_field_mut!(volatile.field_2); + assert!(field_2.read()); + field_2.write(false); + assert_eq!( + val, + S { + field_1: 61, + field_2: false + } + ); +} + +#[cfg(feature = "unstable")] +#[test] +fn test_slice() { + let val: &mut [u32] = &mut [1, 2, 3]; + let mut volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; + volatile.borrow_mut().index_mut(0).update(|v| v + 1); + + let mut dst = [0; 3]; + volatile.copy_into_slice(&mut dst); + assert_eq!(dst, [2, 2, 3]); +} + +#[cfg(feature = "unstable")] +#[test] +#[should_panic] +fn test_bounds_check_1() { + let val: &mut [u32] = &mut [1, 2, 3]; + let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; + volatile.index_mut(3); +} + +#[cfg(feature = "unstable")] +#[test] +#[should_panic] +fn test_bounds_check_2() { + let val: &mut [u32] = &mut [1, 2, 3]; + let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; + volatile.index_mut(2..1); +} + +#[cfg(feature = "unstable")] +#[test] +#[should_panic] +fn test_bounds_check_3() { + let val: &mut [u32] = &mut [1, 2, 3]; + let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; + volatile.index_mut(4..); // `3..` is is still ok (see next test) +} + +#[cfg(feature = "unstable")] +#[test] +fn test_bounds_check_4() { + let val: &mut [u32] = &mut [1, 2, 3]; + let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; + assert_eq!(volatile.index_mut(3..).len(), 0); +} + +#[cfg(feature = "unstable")] +#[test] +#[should_panic] +fn test_bounds_check_5() { + let val: &mut [u32] = &mut [1, 2, 3]; + let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; + volatile.index_mut(..4); +} + +#[cfg(feature = "unstable")] +#[test] +fn test_chunks() { + let val: &mut [u32] = &mut [1, 2, 3, 4, 5, 6]; + let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; + let mut chunks = volatile.as_chunks_mut().0; + chunks.borrow_mut().index_mut(1).write([10, 11, 12]); + assert_eq!(chunks.borrow().index(0).read(), [1, 2, 3]); + assert_eq!(chunks.index(1).read(), [10, 11, 12]); +} + +#[test] +fn test_lifetime() { + let mut val = 50; + let mut volatile = VolatilePtr::from_mut_ref(&mut val); + volatile.write(50); + assert_eq!(val, 50); +} diff --git a/src/tests.rs b/src/tests.rs deleted file mode 100644 index 16a117b..0000000 --- a/src/tests.rs +++ /dev/null @@ -1,261 +0,0 @@ -use core::ptr::NonNull; - -use super::{access_ptr::*, ptr::VolatilePtr}; - -#[test] -fn test_read() { - let val = 42; - assert_eq!( - unsafe { VolatilePtr::new_read_only(NonNull::from(&val)) }.read(), - 42 - ); -} - -#[test] -fn test_write() { - let mut val = 50; - let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut val)) }; - volatile.write(50); - assert_eq!(val, 50); -} - -#[test] -fn test_update() { - let mut val = 42; - let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut val)) }; - volatile.update(|v| *v += 1); - assert_eq!(val, 43); -} - -#[test] -fn test_access() { - let mut val: i64 = 42; - - // ReadWrite - assert_eq!( - unsafe { VolatilePtr::new_with_access(NonNull::from(&mut val), Access::read_write()) } - .read(), - 42 - ); - unsafe { VolatilePtr::new_with_access(NonNull::from(&mut val), Access::read_write()) } - .write(50); - assert_eq!(val, 50); - unsafe { VolatilePtr::new_with_access(NonNull::from(&mut val), Access::read_write()) } - .update(|i| *i += 1); - assert_eq!(val, 51); - - // ReadOnly and WriteOnly - assert_eq!( - unsafe { VolatilePtr::new_with_access(NonNull::from(&mut val), Access::read_only()) } - .read(), - 51 - ); - unsafe { VolatilePtr::new_with_access(NonNull::from(&mut val), Access::write_only()) } - .write(12); - assert_eq!(val, 12); - - // Custom: safe read + safe write - { - let access = Access { - read: SafeAccess, - write: SafeAccess, - }; - let mut volatile = unsafe { VolatilePtr::new_with_access(NonNull::from(&mut val), access) }; - let random: i32 = rand::random(); - volatile.write(i64::from(random)); - assert_eq!(volatile.read(), i64::from(random)); - let random2: i32 = rand::random(); - volatile.update(|i| *i += i64::from(random2)); - assert_eq!(volatile.read(), i64::from(random) + i64::from(random2)); - } - - // Custom: safe read + unsafe write - { - let access = Access { - read: SafeAccess, - write: UnsafeAccess, - }; - let mut volatile = unsafe { VolatilePtr::new_with_access(NonNull::from(&mut val), access) }; - let random: i32 = rand::random(); - unsafe { volatile.write_unsafe(i64::from(random)) }; - assert_eq!(volatile.read(), i64::from(random)); - let random2: i32 = rand::random(); - unsafe { volatile.update_unsafe(|i| *i += i64::from(random2)) }; - assert_eq!(volatile.read(), i64::from(random) + i64::from(random2)); - } - - // Custom: safe read + no write - { - let access = Access { - read: SafeAccess, - write: NoAccess, - }; - let random = rand::random(); - val = random; - let volatile = unsafe { VolatilePtr::new_with_access(NonNull::from(&mut val), access) }; - assert_eq!(volatile.read(), i64::from(random)); - } - - // Custom: unsafe read + safe write - { - let access = Access { - read: UnsafeAccess, - write: SafeAccess, - }; - let mut volatile = unsafe { VolatilePtr::new_with_access(NonNull::from(&mut val), access) }; - let random: i32 = rand::random(); - volatile.write(i64::from(random)); - assert_eq!(unsafe { volatile.read_unsafe() }, i64::from(random)); - let random2: i32 = rand::random(); - unsafe { volatile.update_unsafe(|i| *i += i64::from(random2)) }; - assert_eq!( - unsafe { volatile.read_unsafe() }, - i64::from(random) + i64::from(random2) - ); - } - - // Todo: Custom: unsafe read + unsafe write - // Todo: Custom: unsafe read + no write - // Todo: Custom: no read + safe write - // Todo: Custom: no read + unsafe write - // Todo: Custom: no read + no write - - // Todo: is there a way to check that a compile error occurs when trying to use - // unavailable methods (e.g. `write` when write permission is `UnsafeAccess`)? -} - -#[test] -fn test_struct() { - #[derive(Debug, PartialEq)] - struct S { - field_1: u32, - field_2: bool, - } - - let mut val = S { - field_1: 60, - field_2: true, - }; - let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut val)) }; - unsafe { - volatile - .borrow_mut() - .map_mut(|s| NonNull::new(core::ptr::addr_of_mut!((*s.as_ptr()).field_1)).unwrap()) - } - .update(|v| *v += 1); - let mut field_2 = unsafe { - volatile.map_mut(|s| NonNull::new(core::ptr::addr_of_mut!((*s.as_ptr()).field_2)).unwrap()) - }; - assert!(field_2.read()); - field_2.write(false); - assert_eq!( - val, - S { - field_1: 61, - field_2: false - } - ); -} - -#[test] -fn test_struct_macro() { - #[derive(Debug, PartialEq)] - struct S { - field_1: u32, - field_2: bool, - } - - let mut val = S { - field_1: 60, - field_2: true, - }; - let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(&mut val)) }; - let volatile_borrowed = volatile.borrow_mut(); - let mut field_1 = map_field_mut!(volatile_borrowed.field_1); - field_1.update(|v| *v += 1); - let mut field_2 = map_field_mut!(volatile.field_2); - assert!(field_2.read()); - field_2.write(false); - assert_eq!( - val, - S { - field_1: 61, - field_2: false - } - ); -} - -#[cfg(feature = "unstable")] -#[test] -fn test_slice() { - let val: &mut [u32] = &mut [1, 2, 3]; - let mut volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; - volatile.borrow_mut().index_mut(0).update(|v| *v += 1); - - let mut dst = [0; 3]; - volatile.copy_into_slice(&mut dst); - assert_eq!(dst, [2, 2, 3]); -} - -#[cfg(feature = "unstable")] -#[test] -#[should_panic] -fn test_bounds_check_1() { - let val: &mut [u32] = &mut [1, 2, 3]; - let volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; - volatile.index_mut(3); -} - -#[cfg(feature = "unstable")] -#[test] -#[should_panic] -fn test_bounds_check_2() { - let val: &mut [u32] = &mut [1, 2, 3]; - let volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; - volatile.index_mut(2..1); -} - -#[cfg(feature = "unstable")] -#[test] -#[should_panic] -fn test_bounds_check_3() { - let val: &mut [u32] = &mut [1, 2, 3]; - let volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; - volatile.index_mut(4..); // `3..` is is still ok (see next test) -} - -#[cfg(feature = "unstable")] -#[test] -fn test_bounds_check_4() { - let val: &mut [u32] = &mut [1, 2, 3]; - let volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; - assert_eq!(volatile.index_mut(3..).len(), 0); -} - -#[cfg(feature = "unstable")] -#[test] -#[should_panic] -fn test_bounds_check_5() { - let val: &mut [u32] = &mut [1, 2, 3]; - let volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; - volatile.index_mut(..4); -} - -#[cfg(feature = "unstable")] -#[test] -fn test_chunks() { - let val: &mut [u32] = &mut [1, 2, 3, 4, 5, 6]; - let volatile = unsafe { VolatilePtr::new_read_write(NonNull::from(val)) }; - let mut chunks = volatile.as_chunks_mut().0; - chunks.borrow_mut().index_mut(1).write([10, 11, 12]); - assert_eq!(chunks.borrow().index(0).read(), [1, 2, 3]); - assert_eq!(chunks.index(1).read(), [10, 11, 12]); -} - -#[test] -fn test_lifetime() { - let mut val = 50; - let mut volatile = VolatilePtr::from_mut_ref(&mut val); - volatile.write(50); - assert_eq!(val, 50); -} From e1578af60948021350438321683e753af8ede7e6 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 16:57:44 +0100 Subject: [PATCH 43/68] Implement test suite for `VolatilePtrCopy` too --- src/ptr_copy/mod.rs | 2 + src/ptr_copy/tests.rs | 187 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 src/ptr_copy/tests.rs diff --git a/src/ptr_copy/mod.rs b/src/ptr_copy/mod.rs index 5ed68d9..802e905 100644 --- a/src/ptr_copy/mod.rs +++ b/src/ptr_copy/mod.rs @@ -13,6 +13,8 @@ use core::{ use crate::access::{Access, ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; +#[cfg(test)] +mod tests; #[cfg(feature = "unstable")] mod unstable; #[cfg(feature = "very_unstable")] diff --git a/src/ptr_copy/tests.rs b/src/ptr_copy/tests.rs new file mode 100644 index 0000000..ffe2759 --- /dev/null +++ b/src/ptr_copy/tests.rs @@ -0,0 +1,187 @@ +use crate::{ + access::{ReadOnly, ReadWrite, WriteOnly}, + map_field_mut, VolatilePtrCopy, +}; +use core::ptr::NonNull; + +#[test] +fn test_read() { + let val = 42; + assert_eq!( + unsafe { VolatilePtrCopy::new_read_only(NonNull::from(&val)) }.read(), + 42 + ); +} + +#[test] +fn test_write() { + let mut val = 50; + let volatile = unsafe { VolatilePtrCopy::new(NonNull::from(&mut val)) }; + volatile.write(50); + assert_eq!(val, 50); +} + +#[test] +fn test_update() { + let mut val = 42; + let volatile = unsafe { VolatilePtrCopy::new(NonNull::from(&mut val)) }; + volatile.update(|v| v + 1); + assert_eq!(val, 43); +} + +#[test] +fn test_access() { + let mut val: i64 = 42; + + // ReadWrite + assert_eq!( + unsafe { VolatilePtrCopy::new_restricted(ReadWrite, NonNull::from(&mut val)) }.read(), + 42 + ); + unsafe { VolatilePtrCopy::new_restricted(ReadWrite, NonNull::from(&mut val)) }.write(50); + assert_eq!(val, 50); + unsafe { VolatilePtrCopy::new_restricted(ReadWrite, NonNull::from(&mut val)) } + .update(|i| i + 1); + assert_eq!(val, 51); + + // ReadOnly and WriteOnly + assert_eq!( + unsafe { VolatilePtrCopy::new_restricted(ReadOnly, NonNull::from(&mut val)) }.read(), + 51 + ); + unsafe { VolatilePtrCopy::new_restricted(WriteOnly, NonNull::from(&mut val)) }.write(12); + assert_eq!(val, 12); +} + +#[test] +fn test_struct() { + #[derive(Debug, PartialEq)] + struct S { + field_1: u32, + field_2: bool, + } + + let mut val = S { + field_1: 60, + field_2: true, + }; + let volatile = unsafe { VolatilePtrCopy::new(NonNull::from(&mut val)) }; + unsafe { + volatile.map_mut(|s| NonNull::new(core::ptr::addr_of_mut!((*s.as_ptr()).field_1)).unwrap()) + } + .update(|v| v + 1); + let field_2 = unsafe { + volatile.map_mut(|s| NonNull::new(core::ptr::addr_of_mut!((*s.as_ptr()).field_2)).unwrap()) + }; + assert!(field_2.read()); + field_2.write(false); + assert_eq!( + val, + S { + field_1: 61, + field_2: false + } + ); +} + +#[test] +fn test_struct_macro() { + #[derive(Debug, PartialEq)] + struct S { + field_1: u32, + field_2: bool, + } + + let mut val = S { + field_1: 60, + field_2: true, + }; + let volatile = unsafe { VolatilePtrCopy::new(NonNull::from(&mut val)) }; + let field_1 = map_field_mut!(volatile.field_1); + field_1.update(|v| v + 1); + let field_2 = map_field_mut!(volatile.field_2); + assert!(field_2.read()); + field_2.write(false); + assert_eq!( + val, + S { + field_1: 61, + field_2: false + } + ); +} + +#[cfg(feature = "unstable")] +#[test] +fn test_slice() { + let val: &mut [u32] = &mut [1, 2, 3]; + let volatile = unsafe { VolatilePtrCopy::new(NonNull::from(val)) }; + volatile.index_mut(0).update(|v| v + 1); + + let mut dst = [0; 3]; + volatile.copy_into_slice(&mut dst); + assert_eq!(dst, [2, 2, 3]); +} + +#[cfg(feature = "unstable")] +#[test] +#[should_panic] +fn test_bounds_check_1() { + let val: &mut [u32] = &mut [1, 2, 3]; + let volatile = unsafe { VolatilePtrCopy::new(NonNull::from(val)) }; + volatile.index_mut(3); +} + +#[cfg(feature = "unstable")] +#[test] +#[should_panic] +fn test_bounds_check_2() { + let val: &mut [u32] = &mut [1, 2, 3]; + let volatile = unsafe { VolatilePtrCopy::new(NonNull::from(val)) }; + volatile.index_mut(2..1); +} + +#[cfg(feature = "unstable")] +#[test] +#[should_panic] +fn test_bounds_check_3() { + let val: &mut [u32] = &mut [1, 2, 3]; + let volatile = unsafe { VolatilePtrCopy::new(NonNull::from(val)) }; + volatile.index_mut(4..); // `3..` is is still ok (see next test) +} + +#[cfg(feature = "unstable")] +#[test] +fn test_bounds_check_4() { + let val: &mut [u32] = &mut [1, 2, 3]; + let volatile = unsafe { VolatilePtrCopy::new(NonNull::from(val)) }; + assert_eq!(volatile.index_mut(3..).len(), 0); +} + +#[cfg(feature = "unstable")] +#[test] +#[should_panic] +fn test_bounds_check_5() { + let val: &mut [u32] = &mut [1, 2, 3]; + let volatile = unsafe { VolatilePtrCopy::new(NonNull::from(val)) }; + volatile.index_mut(..4); +} + +#[cfg(feature = "unstable")] +#[test] +fn test_chunks() { + let val: &mut [u32] = &mut [1, 2, 3, 4, 5, 6]; + let volatile = unsafe { VolatilePtrCopy::new(NonNull::from(val)) }; + let chunks = volatile.as_chunks_mut().0; + chunks.index_mut(1).write([10, 11, 12]); + assert_eq!(chunks.index(0).read(), [1, 2, 3]); + assert_eq!(chunks.index(1).read(), [10, 11, 12]); +} + +#[test] +fn test_lifetime() { + let mut val = 50; + let volatile = VolatilePtrCopy::from_mut_ref(&mut val); + volatile.write(50); + assert_eq!(val, 50); +} From 8201fa5fc75b9cb14458157c2c5b6850601197aa Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 17:00:40 +0100 Subject: [PATCH 44/68] Rename `VolatilePtr` to `VolatilePtrSend` --- src/cell.rs | 2 +- src/lib.rs | 4 ++-- src/{ptr => ptr_send}/mod.rs | 0 src/{ptr => ptr_send}/tests.rs | 0 src/{ptr => ptr_send}/unstable.rs | 0 src/{ptr => ptr_send}/very_unstable.rs | 0 6 files changed, 3 insertions(+), 3 deletions(-) rename src/{ptr => ptr_send}/mod.rs (100%) rename src/{ptr => ptr_send}/tests.rs (100%) rename src/{ptr => ptr_send}/unstable.rs (100%) rename src/{ptr => ptr_send}/very_unstable.rs (100%) diff --git a/src/cell.rs b/src/cell.rs index f7d82dc..704b670 100644 --- a/src/cell.rs +++ b/src/cell.rs @@ -21,7 +21,7 @@ use crate::{ access::{Access, ReadOnly, ReadWrite, Readable, Writable}, - ptr::VolatilePtr, + ptr_send::VolatilePtr, }; use core::{cell::UnsafeCell, fmt, marker::PhantomData, ptr::NonNull}; diff --git a/src/lib.rs b/src/lib.rs index 8de2062..17837ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,11 +14,11 @@ #![deny(unsafe_op_in_unsafe_fn)] pub use cell::VolatileCell; -pub use ptr::VolatilePtr; pub use ptr_copy::VolatilePtrCopy; +pub use ptr_send::VolatilePtr; pub mod access; mod cell; mod macros; -mod ptr; mod ptr_copy; +mod ptr_send; diff --git a/src/ptr/mod.rs b/src/ptr_send/mod.rs similarity index 100% rename from src/ptr/mod.rs rename to src/ptr_send/mod.rs diff --git a/src/ptr/tests.rs b/src/ptr_send/tests.rs similarity index 100% rename from src/ptr/tests.rs rename to src/ptr_send/tests.rs diff --git a/src/ptr/unstable.rs b/src/ptr_send/unstable.rs similarity index 100% rename from src/ptr/unstable.rs rename to src/ptr_send/unstable.rs diff --git a/src/ptr/very_unstable.rs b/src/ptr_send/very_unstable.rs similarity index 100% rename from src/ptr/very_unstable.rs rename to src/ptr_send/very_unstable.rs From 8e7184549f9bbd522058d68456985328108d1949 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 17 Jan 2023 17:58:49 +0100 Subject: [PATCH 45/68] Remove the `VolatileCell` type for now --- src/cell.rs | 281 ---------------------------------------------------- src/lib.rs | 2 - 2 files changed, 283 deletions(-) delete mode 100644 src/cell.rs diff --git a/src/cell.rs b/src/cell.rs deleted file mode 100644 index 704b670..0000000 --- a/src/cell.rs +++ /dev/null @@ -1,281 +0,0 @@ -// ! Provides the wrapper type `VolatileCell`, which wraps any copy-able type and allows for -// ! volatile memory access to wrapped value. Volatile memory accesses are never optimized away by -// ! the compiler, and are useful in many low-level systems programming and concurrent contexts. -// ! -// ! # Dealing with Volatile Pointers -// ! -// ! Frequently, one may have to deal with volatile pointers, eg, writes to specific memory -// ! locations. The canonical way to solve this is to cast the pointer to a volatile wrapper -// ! directly, eg: -// ! -// ! ```rust -// ! use volatile::VolatileCell; -// ! -// ! let mut_ptr = 0xFEE00000 as *mut u32; -// ! -// ! let volatile_ptr = mut_ptr as *mut VolatileCell; -// ! ``` -// ! -// ! and then perform operations on the pointer as usual in a volatile way. This method works as all -// ! of the volatile wrapper types are the same size as their contained values. - -use crate::{ - access::{Access, ReadOnly, ReadWrite, Readable, Writable}, - ptr_send::VolatilePtr, -}; -use core::{cell::UnsafeCell, fmt, marker::PhantomData, ptr::NonNull}; - -/// A wrapper type around a volatile variable, which allows for volatile reads and writes -/// to the contained value. The stored type needs to be `Copy`, as volatile reads and writes -/// take and return copies of the value. -/// -/// Volatile operations instruct the compiler to skip certain optimizations for these -/// operations. For example, the compiler will not optimize them away even if it thinks -/// that the operations have no observable effect. This is for example desirable when -/// the value is stored in a special memory region that has side effects, such as -/// memory-mapped device registers. -/// -/// Note that this wrapper types *does not* enforce any atomicity guarantees. To get atomicity, -/// use the [`core::sync::atomic`] module. -/// -/// The size of this struct is the same as the size of the contained type. -#[derive(Default)] -#[repr(transparent)] -pub struct VolatileCell { - value: UnsafeCell, - access: PhantomData, -} - -impl VolatileCell { - /// Construct a new volatile cell wrapping the given value. - /// - /// The returned cell allows read and write operations. Use - /// [`new_restricted`][VolatileCell::new_restricted] to create read-only - /// or write-only cells. - /// - /// Calling `VolatileCell::new(v)` is equivalent to calling - /// `VolatileCell::new_restricted(access::ReadWrite, v)`. - /// - /// ## Example - /// - /// ```rust - /// use volatile::VolatileCell; - /// - /// let mut value = VolatileCell::new(0u32); - /// assert_eq!(value.read(), 0); - /// value.write(42); - /// assert_eq!(value.read(), 42); - /// value.update(|v| v + 2 ); - /// assert_eq!(value.read(), 44); - /// ``` - pub const fn new(value: T) -> Self { - VolatileCell::new_restricted(ReadWrite, value) - } - - /// Construct a new volatile cell with restricted access, wrapping the given value. - /// - /// ## Examples - /// - /// ``` - /// use volatile::{VolatileCell, access}; - /// - /// let mut read_write = VolatileCell::new_restricted(access::ReadWrite, 0u32); - /// read_write.write(100); - /// read_write.update(|v| v / 2); - /// assert_eq!(read_write.read(), 50); - /// - /// let read_only = VolatileCell::new_restricted(access::ReadOnly, 0u32); - /// assert_eq!(read_only.read(), 0); - /// - /// let mut write_only = VolatileCell::new_restricted(access::WriteOnly, 0u32); - /// write_only.write(1); - /// ``` - /// - /// ```compile_fail - /// # use volatile::{VolatileCell, access}; - /// // reading or updating a write-only value is not allowed - /// let write_only = VolatileCell::new_restricted(access::WriteOnly, 0u32); - /// write_only.read(); // -> compile error - /// write_only.update(|v| v + 1); // -> compile error - /// ``` - /// - /// ```compile_fail - /// # use volatile::{VolatileCell, access}; - /// // writing or updating a write-only value is not allowed - /// let read_only = VolatileCell::new_restricted(access::ReadOnly, 0u32); - /// read_only.write(5); // -> compile error - /// read_only.update(|v| v + 1); // -> compile error - /// ``` - pub const fn new_restricted(access: A, value: T) -> VolatileCell - where - A: Access, - { - let _ = access; - VolatileCell { - value: UnsafeCell::new(value), - access: PhantomData, - } - } -} - -impl VolatileCell { - pub fn access(&self) -> A - where - A: Access, - { - A::default() - } - - pub fn as_ptr(&self) -> VolatilePtr { - // UNSAFE: Safe, as we know that our internal value exists. - unsafe { VolatilePtr::new_restricted(ReadOnly, NonNull::new_unchecked(self.value.get())) } - } - - pub fn as_mut_ptr(&mut self) -> VolatilePtr - where - A: Access, - { - // UNSAFE: Safe, as we know that our internal value exists. - unsafe { - VolatilePtr::new_restricted(A::default(), NonNull::new_unchecked(self.value.get())) - } - } - - /// Performs a volatile read of the contained value, returning a copy - /// of the read value. Volatile reads are guaranteed not to be optimized - /// away by the compiler, but by themselves do not have atomic ordering - /// guarantees. To also get atomicity, consider looking at the `Atomic` wrapper type. - /// - /// ```rust - /// use volatile::VolatileCell; - /// - /// let value = VolatileCell::new(42u32); - /// assert_eq!(value.read(), 42u32); - /// ``` - pub fn read(&self) -> T - where - A: Readable, - T: Copy, - { - self.as_ptr().read() - } - - /// Performs a volatile write, setting the contained value to the given value `value`. Volatile - /// writes are guaranteed to not be optimized away by the compiler, but by themselves do not - /// have atomic ordering guarantees. To also get atomicity, consider looking at the `Atomic` - /// wrapper type. - /// - /// ```rust - /// use volatile::VolatileCell; - /// - /// let mut value = VolatileCell::new(0u32); - /// value.write(42u32); - /// assert_eq!(value.read(), 42u32); - /// ``` - pub fn write(&mut self, value: T) - where - A: Writable, - T: Copy, - { - self.as_mut_ptr().write(value) - } - - /// Performs a volatile read of the contained value, passes a mutable reference to it to the - /// function `f`, and then performs a volatile write of the (potentially updated) value back to - /// the contained value. - /// - /// ```rust - /// use volatile::VolatileCell; - /// - /// let mut value = VolatileCell::new(21u32); - /// value.update(|val| val * 2); - /// assert_eq!(value.read(), 42u32); - /// ``` - pub fn update(&mut self, f: F) - where - F: FnOnce(T) -> T, - A: Readable + Writable, - T: Copy, - { - let new = f(self.read()); - self.write(new); - } -} - -/// Create a clone of the `VolatileCell`. -/// -/// A `VolatileCell` is clonable only if the cell is marked as readable. -/// -/// Note that using a `VolatileCell` only makes sense if the backing memory is -/// actually volatile. Stack memory is not volatile normally, so this clone -/// implementation is not needed in most situations. Instead, it is recommended -/// to read out the wrapped value instead. -/// -/// Cloning a `VolatileCell` is equivalent to: -/// -/// ```rust -/// # use volatile::VolatileCell; -/// # let volatile_cell = VolatileCell::new(0u32); -/// VolatileCell::new_restricted(volatile_cell.access(), volatile_cell.read()) -/// # ; -/// ``` -impl Clone for VolatileCell -where - T: Copy, - A: Readable, -{ - fn clone(&self) -> Self { - VolatileCell::new_restricted(self.access(), self.read()) - } -} - -/// This `Debug` implementation only applies to cells that are [`Readable`] -/// because it includes the wrapped value. -impl fmt::Debug for VolatileCell -where - T: Copy + fmt::Debug, - A: Readable, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("VolatileCell").field(&self.read()).finish() - } -} - -#[cfg(test)] -mod tests { - use super::VolatileCell; - - #[test] - fn test_read() { - assert_eq!(VolatileCell::new(42).read(), 42); - } - - #[test] - fn test_write() { - let mut volatile = VolatileCell::new(42); - volatile.write(50); - assert_eq!(*volatile.value.get_mut(), 50); - } - - #[test] - fn test_update() { - let mut volatile = VolatileCell::new(42); - volatile.update(|v| v + 1); - assert_eq!(volatile.read(), 43); - } - - #[test] - fn test_pointer_recast() { - let mut target_value = 0u32; - - let target_ptr: *mut u32 = &mut target_value; - let volatile_ptr = target_ptr as *mut VolatileCell; - - // UNSAFE: Safe, as we know the value exists on the stack. - unsafe { - (*volatile_ptr).write(42u32); - } - - assert_eq!(target_value, 42u32); - } -} diff --git a/src/lib.rs b/src/lib.rs index 17837ae..1f96cd8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,12 +13,10 @@ #![warn(missing_docs)] #![deny(unsafe_op_in_unsafe_fn)] -pub use cell::VolatileCell; pub use ptr_copy::VolatilePtrCopy; pub use ptr_send::VolatilePtr; pub mod access; -mod cell; mod macros; mod ptr_copy; mod ptr_send; From dd5cfaf2b5c0c6ce7b94cd70c41c38699fb76599 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 20 Jan 2023 11:11:34 +0100 Subject: [PATCH 46/68] Provide a default `Access` implementation for `Readable` Specifies that readable access stay readable when shared. --- src/access.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/access.rs b/src/access.rs index 096d43f..4c4dbd3 100644 --- a/src/access.rs +++ b/src/access.rs @@ -9,13 +9,22 @@ pub trait Access: Copy + Default { } /// Helper trait that is implemented by [`ReadWrite`] and [`ReadOnly`]. -pub trait Readable: Access { +pub trait Readable: Copy + Default { + type RestrictShared: Readable + Access; + /// Ensures that this trait cannot be implemented outside of this crate. fn _private() -> _Private { _Private } } +impl Access for T +where + T: Readable + Default + Copy, +{ + type RestrictShared = ::RestrictShared; +} + /// Helper trait that is implemented by [`ReadWrite`] and [`WriteOnly`]. pub trait Writable: Access { /// Ensures that this trait cannot be implemented outside of this crate. @@ -27,19 +36,17 @@ pub trait Writable: Access { /// Zero-sized marker type for allowing both read and write access. #[derive(Debug, Default, Copy, Clone)] pub struct ReadWrite; -impl Access for ReadWrite { +impl Readable for ReadWrite { type RestrictShared = ReadOnly; } -impl Readable for ReadWrite {} impl Writable for ReadWrite {} /// Zero-sized marker type for allowing only read access. #[derive(Debug, Default, Copy, Clone)] pub struct ReadOnly; -impl Access for ReadOnly { +impl Readable for ReadOnly { type RestrictShared = ReadOnly; } -impl Readable for ReadOnly {} /// Zero-sized marker type for allowing only write access. #[derive(Debug, Default, Copy, Clone)] From 42fe49deb12b1fd21fbb8244335fbf39305dbc6e Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 20 Jan 2023 11:13:27 +0100 Subject: [PATCH 47/68] Add conversion methods from `VolatilePtrSend` to `VolatilePtrCopy` This makes the `VolatilePtrSend` type a wrapper type for `VolatilePtrCopy` that prevents shared mutable aliasing and can thus safely provide a `Send` implementation. --- src/ptr_send/mod.rs | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/ptr_send/mod.rs b/src/ptr_send/mod.rs index 24b5b00..2061db5 100644 --- a/src/ptr_send/mod.rs +++ b/src/ptr_send/mod.rs @@ -5,12 +5,11 @@ //! The wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider //! looking at the `Atomic` wrapper types found in `libcore` or `libstd`. -use crate::access::{Access, ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; -use core::{ - fmt, - marker::PhantomData, - ptr::{self, NonNull}, +use crate::{ + access::{Access, ReadOnly, ReadWrite, Readable, Writable, WriteOnly}, + ptr_copy, }; +use core::{fmt, marker::PhantomData, ptr::NonNull}; #[cfg(test)] mod tests; @@ -121,8 +120,7 @@ where T: Copy, A: Readable, { - // UNSAFE: Safe, as ... TODO - unsafe { ptr::read_volatile(self.pointer.as_ptr()) } + self.as_ref().read() } /// Performs a volatile write, setting the contained value to the given `value`. @@ -148,8 +146,7 @@ where T: Copy, A: Writable, { - // UNSAFE: Safe, as ... TODO - unsafe { ptr::write_volatile(self.pointer.as_ptr(), value) }; + self.as_mut().write(value) } /// Updates the contained value using the given closure and volatile instructions. @@ -174,8 +171,7 @@ where A: Readable + Writable, F: FnOnce(T) -> T, { - let new = f(self.read()); - self.write(new); + self.as_mut().update(f); } /// Extracts the inner value stored in the wrapper type. @@ -279,6 +275,20 @@ where { unsafe { VolatilePtr::new_restricted(A::default(), f(self.pointer)) } } + + pub fn as_ref<'b>(&'b self) -> ptr_copy::VolatilePtrCopy<'b, T, A::RestrictShared> + where + A: Access, + { + unsafe { ptr_copy::VolatilePtrCopy::new_restricted(Default::default(), self.pointer) } + } + + pub fn as_mut<'b>(&'b mut self) -> ptr_copy::VolatilePtrCopy<'b, T, A> + where + A: Access, + { + unsafe { ptr_copy::VolatilePtrCopy::new_restricted(Default::default(), self.pointer) } + } } /// Methods for restricting access. From 6fd4ea15bfabc6b3e5f6b068e63e10b1155b71fd Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 28 Apr 2023 20:41:15 +0200 Subject: [PATCH 48/68] Rename non-copy pointer type to `VolatileRef` --- src/lib.rs | 4 +- src/{ptr_send => volatile_ref}/mod.rs | 54 +++++++++---------- src/{ptr_send => volatile_ref}/tests.rs | 38 ++++++------- src/{ptr_send => volatile_ref}/unstable.rs | 0 .../very_unstable.rs | 0 5 files changed, 48 insertions(+), 48 deletions(-) rename src/{ptr_send => volatile_ref}/mod.rs (87%) rename src/{ptr_send => volatile_ref}/tests.rs (75%) rename src/{ptr_send => volatile_ref}/unstable.rs (100%) rename src/{ptr_send => volatile_ref}/very_unstable.rs (100%) diff --git a/src/lib.rs b/src/lib.rs index 1f96cd8..f4625db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,9 +14,9 @@ #![deny(unsafe_op_in_unsafe_fn)] pub use ptr_copy::VolatilePtrCopy; -pub use ptr_send::VolatilePtr; +pub use volatile_ref::VolatileRef; pub mod access; mod macros; mod ptr_copy; -mod ptr_send; +mod volatile_ref; diff --git a/src/ptr_send/mod.rs b/src/volatile_ref/mod.rs similarity index 87% rename from src/ptr_send/mod.rs rename to src/volatile_ref/mod.rs index 2061db5..753cad2 100644 --- a/src/ptr_send/mod.rs +++ b/src/volatile_ref/mod.rs @@ -31,7 +31,7 @@ mod very_unstable; /// /// The size of this struct is the same as the size of the contained reference. #[repr(transparent)] -pub struct VolatilePtr<'a, T, A = ReadWrite> +pub struct VolatileRef<'a, T, A = ReadWrite> where T: ?Sized, { @@ -45,26 +45,26 @@ where /// These functions construct new `VolatilePtr` values. While the `new` /// function creates a `VolatilePtr` instance with unrestricted access, there /// are also functions for creating read-only or write-only instances. -impl<'a, T> VolatilePtr<'a, T> +impl<'a, T> VolatileRef<'a, T> where T: ?Sized, { pub unsafe fn new(pointer: NonNull) -> Self { - unsafe { VolatilePtr::new_restricted(ReadWrite, pointer) } + unsafe { VolatileRef::new_restricted(ReadWrite, pointer) } } pub fn from_mut_ref(reference: &'a mut T) -> Self where T: 'a, { - unsafe { VolatilePtr::new(reference.into()) } + unsafe { VolatileRef::new(reference.into()) } } - pub const unsafe fn new_read_only(pointer: NonNull) -> VolatilePtr<'a, T, ReadOnly> { + pub const unsafe fn new_read_only(pointer: NonNull) -> VolatileRef<'a, T, ReadOnly> { unsafe { Self::new_restricted(ReadOnly, pointer) } } - pub const unsafe fn new_restricted(access: A, pointer: NonNull) -> VolatilePtr<'a, T, A> + pub const unsafe fn new_restricted(access: A, pointer: NonNull) -> VolatileRef<'a, T, A> where A: Access, { @@ -72,15 +72,15 @@ where unsafe { Self::new_generic(pointer) } } - pub fn from_ref(reference: &'a T) -> VolatilePtr<'a, T, ReadOnly> + pub fn from_ref(reference: &'a T) -> VolatileRef<'a, T, ReadOnly> where T: 'a, { - unsafe { VolatilePtr::new_restricted(ReadOnly, reference.into()) } + unsafe { VolatileRef::new_restricted(ReadOnly, reference.into()) } } - const unsafe fn new_generic(pointer: NonNull) -> VolatilePtr<'a, T, A> { - VolatilePtr { + const unsafe fn new_generic(pointer: NonNull) -> VolatileRef<'a, T, A> { + VolatileRef { pointer, reference: PhantomData, access: PhantomData, @@ -88,7 +88,7 @@ where } } -impl<'a, T, A> VolatilePtr<'a, T, A> +impl<'a, T, A> VolatileRef<'a, T, A> where T: ?Sized, { @@ -204,19 +204,19 @@ where } // TODO: Add documentation - pub fn borrow(&self) -> VolatilePtr + pub fn borrow(&self) -> VolatileRef where A: Access, { - unsafe { VolatilePtr::new_restricted(Default::default(), self.pointer) } + unsafe { VolatileRef::new_restricted(Default::default(), self.pointer) } } // TODO: Add documentation - pub fn borrow_mut(&mut self) -> VolatilePtr + pub fn borrow_mut(&mut self) -> VolatileRef where A: Access, { - unsafe { VolatilePtr::new_restricted(A::default(), self.pointer) } + unsafe { VolatileRef::new_restricted(A::default(), self.pointer) } } /// Constructs a new `Volatile` reference by mapping the wrapped pointer. @@ -258,22 +258,22 @@ where /// value /// })}; /// ``` - pub unsafe fn map(self, f: F) -> VolatilePtr<'a, U, A::RestrictShared> + pub unsafe fn map(self, f: F) -> VolatileRef<'a, U, A::RestrictShared> where F: FnOnce(NonNull) -> NonNull, A: Access, U: ?Sized, { - unsafe { VolatilePtr::new_restricted(Default::default(), f(self.pointer)) } + unsafe { VolatileRef::new_restricted(Default::default(), f(self.pointer)) } } - pub unsafe fn map_mut(self, f: F) -> VolatilePtr<'a, U, A> + pub unsafe fn map_mut(self, f: F) -> VolatileRef<'a, U, A> where F: FnOnce(NonNull) -> NonNull, U: ?Sized, A: Access, { - unsafe { VolatilePtr::new_restricted(A::default(), f(self.pointer)) } + unsafe { VolatileRef::new_restricted(A::default(), f(self.pointer)) } } pub fn as_ref<'b>(&'b self) -> ptr_copy::VolatilePtrCopy<'b, T, A::RestrictShared> @@ -292,7 +292,7 @@ where } /// Methods for restricting access. -impl<'a, T> VolatilePtr<'a, T, ReadWrite> +impl<'a, T> VolatileRef<'a, T, ReadWrite> where T: ?Sized, { @@ -311,8 +311,8 @@ where /// assert_eq!(read_only.read(), -4); /// // read_only.write(10); // compile-time error /// ``` - pub fn read_only(self) -> VolatilePtr<'a, T, ReadOnly> { - unsafe { VolatilePtr::new_restricted(ReadOnly, self.pointer) } + pub fn read_only(self) -> VolatileRef<'a, T, ReadOnly> { + unsafe { VolatileRef::new_restricted(ReadOnly, self.pointer) } } /// Restricts access permissions to write-only. @@ -334,15 +334,15 @@ where /// field_2.write(14); /// // field_2.read(); // compile-time error /// ``` - pub fn write_only(self) -> VolatilePtr<'a, T, WriteOnly> { - unsafe { VolatilePtr::new_restricted(WriteOnly, self.pointer) } + pub fn write_only(self) -> VolatileRef<'a, T, WriteOnly> { + unsafe { VolatileRef::new_restricted(WriteOnly, self.pointer) } } } -unsafe impl Send for VolatilePtr<'_, T, A> where T: Sync {} -unsafe impl Sync for VolatilePtr<'_, T, A> where T: Sync {} +unsafe impl Send for VolatileRef<'_, T, A> where T: Sync {} +unsafe impl Sync for VolatileRef<'_, T, A> where T: Sync {} -impl fmt::Debug for VolatilePtr<'_, T, A> +impl fmt::Debug for VolatileRef<'_, T, A> where T: Copy + fmt::Debug + ?Sized, A: Readable, diff --git a/src/ptr_send/tests.rs b/src/volatile_ref/tests.rs similarity index 75% rename from src/ptr_send/tests.rs rename to src/volatile_ref/tests.rs index 433581e..fc1fd79 100644 --- a/src/ptr_send/tests.rs +++ b/src/volatile_ref/tests.rs @@ -1,6 +1,6 @@ use crate::{ access::{ReadOnly, ReadWrite, WriteOnly}, - map_field_mut, VolatilePtr, + map_field_mut, VolatileRef, }; use core::ptr::NonNull; @@ -8,7 +8,7 @@ use core::ptr::NonNull; fn test_read() { let val = 42; assert_eq!( - unsafe { VolatilePtr::new_read_only(NonNull::from(&val)) }.read(), + unsafe { VolatileRef::new_read_only(NonNull::from(&val)) }.read(), 42 ); } @@ -16,7 +16,7 @@ fn test_read() { #[test] fn test_write() { let mut val = 50; - let mut volatile = unsafe { VolatilePtr::new(NonNull::from(&mut val)) }; + let mut volatile = unsafe { VolatileRef::new(NonNull::from(&mut val)) }; volatile.write(50); assert_eq!(val, 50); } @@ -24,7 +24,7 @@ fn test_write() { #[test] fn test_update() { let mut val = 42; - let mut volatile = unsafe { VolatilePtr::new(NonNull::from(&mut val)) }; + let mut volatile = unsafe { VolatileRef::new(NonNull::from(&mut val)) }; volatile.update(|v| v + 1); assert_eq!(val, 43); } @@ -35,20 +35,20 @@ fn test_access() { // ReadWrite assert_eq!( - unsafe { VolatilePtr::new_restricted(ReadWrite, NonNull::from(&mut val)) }.read(), + unsafe { VolatileRef::new_restricted(ReadWrite, NonNull::from(&mut val)) }.read(), 42 ); - unsafe { VolatilePtr::new_restricted(ReadWrite, NonNull::from(&mut val)) }.write(50); + unsafe { VolatileRef::new_restricted(ReadWrite, NonNull::from(&mut val)) }.write(50); assert_eq!(val, 50); - unsafe { VolatilePtr::new_restricted(ReadWrite, NonNull::from(&mut val)) }.update(|i| i + 1); + unsafe { VolatileRef::new_restricted(ReadWrite, NonNull::from(&mut val)) }.update(|i| i + 1); assert_eq!(val, 51); // ReadOnly and WriteOnly assert_eq!( - unsafe { VolatilePtr::new_restricted(ReadOnly, NonNull::from(&mut val)) }.read(), + unsafe { VolatileRef::new_restricted(ReadOnly, NonNull::from(&mut val)) }.read(), 51 ); - unsafe { VolatilePtr::new_restricted(WriteOnly, NonNull::from(&mut val)) }.write(12); + unsafe { VolatileRef::new_restricted(WriteOnly, NonNull::from(&mut val)) }.write(12); assert_eq!(val, 12); } @@ -64,7 +64,7 @@ fn test_struct() { field_1: 60, field_2: true, }; - let mut volatile = unsafe { VolatilePtr::new(NonNull::from(&mut val)) }; + let mut volatile = unsafe { VolatileRef::new(NonNull::from(&mut val)) }; unsafe { volatile .borrow_mut() @@ -97,7 +97,7 @@ fn test_struct_macro() { field_1: 60, field_2: true, }; - let mut volatile = unsafe { VolatilePtr::new(NonNull::from(&mut val)) }; + let mut volatile = unsafe { VolatileRef::new(NonNull::from(&mut val)) }; let volatile_borrowed = volatile.borrow_mut(); let mut field_1 = map_field_mut!(volatile_borrowed.field_1); field_1.update(|v| v + 1); @@ -117,7 +117,7 @@ fn test_struct_macro() { #[test] fn test_slice() { let val: &mut [u32] = &mut [1, 2, 3]; - let mut volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; + let mut volatile = unsafe { VolatileRef::new(NonNull::from(val)) }; volatile.borrow_mut().index_mut(0).update(|v| v + 1); let mut dst = [0; 3]; @@ -130,7 +130,7 @@ fn test_slice() { #[should_panic] fn test_bounds_check_1() { let val: &mut [u32] = &mut [1, 2, 3]; - let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; + let volatile = unsafe { VolatileRef::new(NonNull::from(val)) }; volatile.index_mut(3); } @@ -139,7 +139,7 @@ fn test_bounds_check_1() { #[should_panic] fn test_bounds_check_2() { let val: &mut [u32] = &mut [1, 2, 3]; - let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; + let volatile = unsafe { VolatileRef::new(NonNull::from(val)) }; volatile.index_mut(2..1); } @@ -148,7 +148,7 @@ fn test_bounds_check_2() { #[should_panic] fn test_bounds_check_3() { let val: &mut [u32] = &mut [1, 2, 3]; - let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; + let volatile = unsafe { VolatileRef::new(NonNull::from(val)) }; volatile.index_mut(4..); // `3..` is is still ok (see next test) } @@ -156,7 +156,7 @@ fn test_bounds_check_3() { #[test] fn test_bounds_check_4() { let val: &mut [u32] = &mut [1, 2, 3]; - let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; + let volatile = unsafe { VolatileRef::new(NonNull::from(val)) }; assert_eq!(volatile.index_mut(3..).len(), 0); } @@ -165,7 +165,7 @@ fn test_bounds_check_4() { #[should_panic] fn test_bounds_check_5() { let val: &mut [u32] = &mut [1, 2, 3]; - let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; + let volatile = unsafe { VolatileRef::new(NonNull::from(val)) }; volatile.index_mut(..4); } @@ -173,7 +173,7 @@ fn test_bounds_check_5() { #[test] fn test_chunks() { let val: &mut [u32] = &mut [1, 2, 3, 4, 5, 6]; - let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; + let volatile = unsafe { VolatileRef::new(NonNull::from(val)) }; let mut chunks = volatile.as_chunks_mut().0; chunks.borrow_mut().index_mut(1).write([10, 11, 12]); assert_eq!(chunks.borrow().index(0).read(), [1, 2, 3]); @@ -183,7 +183,7 @@ fn test_chunks() { #[test] fn test_lifetime() { let mut val = 50; - let mut volatile = VolatilePtr::from_mut_ref(&mut val); + let mut volatile = VolatileRef::from_mut_ref(&mut val); volatile.write(50); assert_eq!(val, 50); } diff --git a/src/ptr_send/unstable.rs b/src/volatile_ref/unstable.rs similarity index 100% rename from src/ptr_send/unstable.rs rename to src/volatile_ref/unstable.rs diff --git a/src/ptr_send/very_unstable.rs b/src/volatile_ref/very_unstable.rs similarity index 100% rename from src/ptr_send/very_unstable.rs rename to src/volatile_ref/very_unstable.rs From 401d903d69283367f1941ced58eaf46d28590a7e Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 28 Apr 2023 20:48:21 +0200 Subject: [PATCH 49/68] Rename copyable pointer type to `VolatilePtr` --- src/lib.rs | 4 +- src/{ptr_copy => volatile_ptr}/mod.rs | 49 +++++++++---------- src/{ptr_copy => volatile_ptr}/tests.rs | 0 src/{ptr_copy => volatile_ptr}/unstable.rs | 0 .../very_unstable.rs | 0 src/volatile_ref/mod.rs | 10 ++-- 6 files changed, 30 insertions(+), 33 deletions(-) rename src/{ptr_copy => volatile_ptr}/mod.rs (87%) rename src/{ptr_copy => volatile_ptr}/tests.rs (100%) rename src/{ptr_copy => volatile_ptr}/unstable.rs (100%) rename src/{ptr_copy => volatile_ptr}/very_unstable.rs (100%) diff --git a/src/lib.rs b/src/lib.rs index f4625db..db19360 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,10 +13,10 @@ #![warn(missing_docs)] #![deny(unsafe_op_in_unsafe_fn)] -pub use ptr_copy::VolatilePtrCopy; +pub use volatile_ptr::VolatilePtr; pub use volatile_ref::VolatileRef; pub mod access; mod macros; -mod ptr_copy; +mod volatile_ptr; mod volatile_ref; diff --git a/src/ptr_copy/mod.rs b/src/volatile_ptr/mod.rs similarity index 87% rename from src/ptr_copy/mod.rs rename to src/volatile_ptr/mod.rs index 802e905..afc2ff8 100644 --- a/src/ptr_copy/mod.rs +++ b/src/volatile_ptr/mod.rs @@ -33,7 +33,7 @@ mod very_unstable; /// /// The size of this struct is the same as the size of the contained reference. #[repr(transparent)] -pub struct VolatilePtrCopy<'a, T, A = ReadWrite> +pub struct VolatilePtr<'a, T, A = ReadWrite> where T: ?Sized, { @@ -42,9 +42,9 @@ where access: PhantomData, } -impl<'a, T, A> Copy for VolatilePtrCopy<'a, T, A> where T: ?Sized {} +impl<'a, T, A> Copy for VolatilePtr<'a, T, A> where T: ?Sized {} -impl Clone for VolatilePtrCopy<'_, T, A> +impl Clone for VolatilePtr<'_, T, A> where T: ?Sized, { @@ -58,29 +58,26 @@ where /// These functions construct new `VolatilePtr` values. While the `new` /// function creates a `VolatilePtr` instance with unrestricted access, there /// are also functions for creating read-only or write-only instances. -impl<'a, T> VolatilePtrCopy<'a, T> +impl<'a, T> VolatilePtr<'a, T> where T: ?Sized, { pub unsafe fn new(pointer: NonNull) -> Self { - unsafe { VolatilePtrCopy::new_restricted(ReadWrite, pointer) } + unsafe { VolatilePtr::new_restricted(ReadWrite, pointer) } } pub fn from_mut_ref(reference: &'a mut T) -> Self where T: 'a, { - unsafe { VolatilePtrCopy::new(reference.into()) } + unsafe { VolatilePtr::new(reference.into()) } } - pub const unsafe fn new_read_only(pointer: NonNull) -> VolatilePtrCopy<'a, T, ReadOnly> { + pub const unsafe fn new_read_only(pointer: NonNull) -> VolatilePtr<'a, T, ReadOnly> { unsafe { Self::new_restricted(ReadOnly, pointer) } } - pub const unsafe fn new_restricted( - access: A, - pointer: NonNull, - ) -> VolatilePtrCopy<'a, T, A> + pub const unsafe fn new_restricted(access: A, pointer: NonNull) -> VolatilePtr<'a, T, A> where A: Access, { @@ -88,15 +85,15 @@ where unsafe { Self::new_generic(pointer) } } - pub fn from_ref(reference: &'a T) -> VolatilePtrCopy<'a, T, ReadOnly> + pub fn from_ref(reference: &'a T) -> VolatilePtr<'a, T, ReadOnly> where T: 'a, { - unsafe { VolatilePtrCopy::new_restricted(ReadOnly, reference.into()) } + unsafe { VolatilePtr::new_restricted(ReadOnly, reference.into()) } } - const unsafe fn new_generic(pointer: NonNull) -> VolatilePtrCopy<'a, T, A> { - VolatilePtrCopy { + const unsafe fn new_generic(pointer: NonNull) -> VolatilePtr<'a, T, A> { + VolatilePtr { pointer, reference: PhantomData, access: PhantomData, @@ -104,7 +101,7 @@ where } } -impl<'a, T, A> VolatilePtrCopy<'a, T, A> +impl<'a, T, A> VolatilePtr<'a, T, A> where T: ?Sized, { @@ -261,27 +258,27 @@ where /// value /// })}; /// ``` - pub unsafe fn map(self, f: F) -> VolatilePtrCopy<'a, U, A::RestrictShared> + pub unsafe fn map(self, f: F) -> VolatilePtr<'a, U, A::RestrictShared> where F: FnOnce(NonNull) -> NonNull, A: Access, U: ?Sized, { - unsafe { VolatilePtrCopy::new_restricted(Default::default(), f(self.pointer)) } + unsafe { VolatilePtr::new_restricted(Default::default(), f(self.pointer)) } } - pub unsafe fn map_mut(self, f: F) -> VolatilePtrCopy<'a, U, A> + pub unsafe fn map_mut(self, f: F) -> VolatilePtr<'a, U, A> where F: FnOnce(NonNull) -> NonNull, U: ?Sized, A: Access, { - unsafe { VolatilePtrCopy::new_restricted(A::default(), f(self.pointer)) } + unsafe { VolatilePtr::new_restricted(A::default(), f(self.pointer)) } } } /// Methods for restricting access. -impl<'a, T> VolatilePtrCopy<'a, T, ReadWrite> +impl<'a, T> VolatilePtr<'a, T, ReadWrite> where T: ?Sized, { @@ -300,8 +297,8 @@ where /// assert_eq!(read_only.read(), -4); /// // read_only.write(10); // compile-time error /// ``` - pub fn read_only(self) -> VolatilePtrCopy<'a, T, ReadOnly> { - unsafe { VolatilePtrCopy::new_restricted(ReadOnly, self.pointer) } + pub fn read_only(self) -> VolatilePtr<'a, T, ReadOnly> { + unsafe { VolatilePtr::new_restricted(ReadOnly, self.pointer) } } /// Restricts access permissions to write-only. @@ -323,12 +320,12 @@ where /// field_2.write(14); /// // field_2.read(); // compile-time error /// ``` - pub fn write_only(self) -> VolatilePtrCopy<'a, T, WriteOnly> { - unsafe { VolatilePtrCopy::new_restricted(WriteOnly, self.pointer) } + pub fn write_only(self) -> VolatilePtr<'a, T, WriteOnly> { + unsafe { VolatilePtr::new_restricted(WriteOnly, self.pointer) } } } -impl fmt::Debug for VolatilePtrCopy<'_, T, A> +impl fmt::Debug for VolatilePtr<'_, T, A> where T: Copy + fmt::Debug + ?Sized, A: Readable, diff --git a/src/ptr_copy/tests.rs b/src/volatile_ptr/tests.rs similarity index 100% rename from src/ptr_copy/tests.rs rename to src/volatile_ptr/tests.rs diff --git a/src/ptr_copy/unstable.rs b/src/volatile_ptr/unstable.rs similarity index 100% rename from src/ptr_copy/unstable.rs rename to src/volatile_ptr/unstable.rs diff --git a/src/ptr_copy/very_unstable.rs b/src/volatile_ptr/very_unstable.rs similarity index 100% rename from src/ptr_copy/very_unstable.rs rename to src/volatile_ptr/very_unstable.rs diff --git a/src/volatile_ref/mod.rs b/src/volatile_ref/mod.rs index 753cad2..0c683c4 100644 --- a/src/volatile_ref/mod.rs +++ b/src/volatile_ref/mod.rs @@ -7,7 +7,7 @@ use crate::{ access::{Access, ReadOnly, ReadWrite, Readable, Writable, WriteOnly}, - ptr_copy, + volatile_ptr, }; use core::{fmt, marker::PhantomData, ptr::NonNull}; @@ -276,18 +276,18 @@ where unsafe { VolatileRef::new_restricted(A::default(), f(self.pointer)) } } - pub fn as_ref<'b>(&'b self) -> ptr_copy::VolatilePtrCopy<'b, T, A::RestrictShared> + pub fn as_ref<'b>(&'b self) -> volatile_ptr::VolatilePtr<'b, T, A::RestrictShared> where A: Access, { - unsafe { ptr_copy::VolatilePtrCopy::new_restricted(Default::default(), self.pointer) } + unsafe { volatile_ptr::VolatilePtr::new_restricted(Default::default(), self.pointer) } } - pub fn as_mut<'b>(&'b mut self) -> ptr_copy::VolatilePtrCopy<'b, T, A> + pub fn as_mut<'b>(&'b mut self) -> volatile_ptr::VolatilePtr<'b, T, A> where A: Access, { - unsafe { ptr_copy::VolatilePtrCopy::new_restricted(Default::default(), self.pointer) } + unsafe { volatile_ptr::VolatilePtr::new_restricted(Default::default(), self.pointer) } } } From 1e265b1a3534448fed446f050e8c27b372771d51 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 28 Apr 2023 21:21:32 +0200 Subject: [PATCH 50/68] Remove most methods from `VolatileRef` to avoid duplication The methods are still unsable through `as_ptr`/`as_mut_ptr`. --- src/volatile_ref/mod.rs | 224 +------------ src/volatile_ref/tests.rs | 189 ----------- src/volatile_ref/unstable.rs | 518 ------------------------------ src/volatile_ref/very_unstable.rs | 66 ---- 4 files changed, 17 insertions(+), 980 deletions(-) delete mode 100644 src/volatile_ref/tests.rs delete mode 100644 src/volatile_ref/unstable.rs delete mode 100644 src/volatile_ref/very_unstable.rs diff --git a/src/volatile_ref/mod.rs b/src/volatile_ref/mod.rs index 0c683c4..96ec8db 100644 --- a/src/volatile_ref/mod.rs +++ b/src/volatile_ref/mod.rs @@ -6,18 +6,11 @@ //! looking at the `Atomic` wrapper types found in `libcore` or `libstd`. use crate::{ - access::{Access, ReadOnly, ReadWrite, Readable, Writable, WriteOnly}, + access::{Access, ReadOnly, ReadWrite, Readable, WriteOnly}, volatile_ptr, }; use core::{fmt, marker::PhantomData, ptr::NonNull}; -#[cfg(test)] -mod tests; -#[cfg(feature = "unstable")] -mod unstable; -#[cfg(feature = "very_unstable")] -mod very_unstable; - /// Wraps a pointer to make accesses to the referenced value volatile. /// /// Allows volatile reads and writes on the referenced value. The referenced value needs to @@ -42,8 +35,8 @@ where /// Constructor functions. /// -/// These functions construct new `VolatilePtr` values. While the `new` -/// function creates a `VolatilePtr` instance with unrestricted access, there +/// These functions construct new `Volatile` values. While the `new` +/// function creates a `Volatile` instance with unrestricted access, there /// are also functions for creating read-only or write-only instances. impl<'a, T> VolatileRef<'a, T> where @@ -92,198 +85,14 @@ impl<'a, T, A> VolatileRef<'a, T, A> where T: ?Sized, { - /// Performs a volatile read of the contained value. - /// - /// Returns a copy of the read value. Volatile reads are guaranteed not to be optimized - /// away by the compiler, but by themselves do not have atomic ordering - /// guarantees. To also get atomicity, consider looking at the `Atomic` wrapper types of - /// the standard/`core` library. - /// - /// ## Examples - /// - /// ```rust - /// use volatile::{VolatilePtr, access}; - /// use core::ptr::NonNull; - /// - /// let value = 42; - /// let shared_reference = unsafe { - /// VolatilePtr::new_restricted(access::ReadOnly, NonNull::from(&value)) - /// }; - /// assert_eq!(shared_reference.read(), 42); - /// - /// let mut value = 50; - /// let mut_reference = VolatilePtr::from_mut_ref(&mut value); - /// assert_eq!(mut_reference.read(), 50); - /// ``` - pub fn read(&self) -> T - where - T: Copy, - A: Readable, - { - self.as_ref().read() - } - - /// Performs a volatile write, setting the contained value to the given `value`. - /// - /// Volatile writes are guaranteed to not be optimized away by the compiler, but by - /// themselves do not have atomic ordering guarantees. To also get atomicity, consider - /// looking at the `Atomic` wrapper types of the standard/`core` library. - /// - /// ## Example - /// - /// ```rust - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let mut value = 42; - /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); - /// volatile.write(50); - /// - /// assert_eq!(volatile.read(), 50); - /// ``` - pub fn write(&mut self, value: T) - where - T: Copy, - A: Writable, - { - self.as_mut().write(value) - } - - /// Updates the contained value using the given closure and volatile instructions. - /// - /// Performs a volatile read of the contained value, passes a mutable reference to it to the - /// function `f`, and then performs a volatile write of the (potentially updated) value back to - /// the contained value. - /// - /// ```rust - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let mut value = 42; - /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); - /// volatile.update(|val| val + 1); - /// - /// assert_eq!(volatile.read(), 43); - /// ``` - pub fn update(&mut self, f: F) - where - T: Copy, - A: Readable + Writable, - F: FnOnce(T) -> T, - { - self.as_mut().update(f); - } - - /// Extracts the inner value stored in the wrapper type. - /// - /// This method gives direct access to the wrapped reference and thus allows - /// non-volatile access again. This is seldom what you want since there is usually - /// a reason that a reference is wrapped in `Volatile`. However, in some cases it might - /// be required or useful to use the `read_volatile`/`write_volatile` pointer methods of - /// the standard library directly, which this method makes possible. - /// - /// Since no memory safety violation can occur when accessing the referenced value using - /// non-volatile operations, this method is safe. However, it _can_ lead to bugs at the - /// application level, so this method should be used with care. - /// - /// ## Example - /// - /// ``` - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let mut value = 42; - /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); - /// volatile.write(50); - /// let unwrapped: *mut i32 = volatile.as_ptr().as_ptr(); - /// - /// assert_eq!(unsafe { *unwrapped }, 50); // non volatile access, be careful! - /// ``` - pub fn as_ptr(&self) -> NonNull { - self.pointer - } - - // TODO: Add documentation - pub fn borrow(&self) -> VolatileRef - where - A: Access, - { - unsafe { VolatileRef::new_restricted(Default::default(), self.pointer) } - } - - // TODO: Add documentation - pub fn borrow_mut(&mut self) -> VolatileRef - where - A: Access, - { - unsafe { VolatileRef::new_restricted(A::default(), self.pointer) } - } - - /// Constructs a new `Volatile` reference by mapping the wrapped pointer. - /// - /// This method is useful for accessing only a part of a volatile value, e.g. a subslice or - /// a struct field. For struct field access, there is also the safe [`map_field`] macro that - /// wraps this function. - /// - /// ## Examples - /// - /// Accessing a struct field: - /// - /// ``` - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// struct Example { field_1: u32, field_2: u8, } - /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); - /// - /// // construct a volatile reference to a field - /// let field_2 = unsafe { volatile.map(|ptr| NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).field_2)).unwrap()) }; - /// assert_eq!(field_2.read(), 255); - /// ``` - /// - /// Don't misuse this method to do a non-volatile read of the referenced value: - /// - /// ``` - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let mut value = 5; - /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); - /// - /// // DON'T DO THIS: - /// let mut readout = 0; - /// unsafe { volatile.map(|value| { - /// readout = *value.as_ptr(); // non-volatile read, might lead to bugs - /// value - /// })}; - /// ``` - pub unsafe fn map(self, f: F) -> VolatileRef<'a, U, A::RestrictShared> - where - F: FnOnce(NonNull) -> NonNull, - A: Access, - U: ?Sized, - { - unsafe { VolatileRef::new_restricted(Default::default(), f(self.pointer)) } - } - - pub unsafe fn map_mut(self, f: F) -> VolatileRef<'a, U, A> - where - F: FnOnce(NonNull) -> NonNull, - U: ?Sized, - A: Access, - { - unsafe { VolatileRef::new_restricted(A::default(), f(self.pointer)) } - } - - pub fn as_ref<'b>(&'b self) -> volatile_ptr::VolatilePtr<'b, T, A::RestrictShared> + pub fn as_ptr(&self) -> volatile_ptr::VolatilePtr<'_, T, A::RestrictShared> where A: Access, { unsafe { volatile_ptr::VolatilePtr::new_restricted(Default::default(), self.pointer) } } - pub fn as_mut<'b>(&'b mut self) -> volatile_ptr::VolatilePtr<'b, T, A> + pub fn as_mut_ptr(&mut self) -> volatile_ptr::VolatilePtr<'_, T, A> where A: Access, { @@ -301,15 +110,15 @@ where /// ## Example /// /// ``` - /// use volatile::VolatilePtr; + /// use volatile::VolatileRef; /// use core::ptr::NonNull; /// /// let mut value: i16 = -4; - /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); + /// let mut volatile = VolatileRef::from_mut_ref(&mut value); /// /// let read_only = volatile.read_only(); - /// assert_eq!(read_only.read(), -4); - /// // read_only.write(10); // compile-time error + /// assert_eq!(read_only.as_ptr().read(), -4); + /// // read_only.as_ptr().write(10); // compile-time error /// ``` pub fn read_only(self) -> VolatileRef<'a, T, ReadOnly> { unsafe { VolatileRef::new_restricted(ReadOnly, self.pointer) } @@ -322,17 +131,16 @@ where /// Creating a write-only reference to a struct field: /// /// ``` - /// use volatile::{VolatilePtr, map_field_mut}; + /// use volatile::{VolatileRef, map_field_mut}; /// use core::ptr::NonNull; /// + /// #[derive(Clone, Copy)] /// struct Example { field_1: u32, field_2: u8, } /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = VolatilePtr::from_mut_ref(&mut value); + /// let mut volatile = VolatileRef::from_mut_ref(&mut value); /// - /// // construct a volatile write-only reference to `field_2` - /// let mut field_2 = map_field_mut!(volatile.field_2).write_only(); - /// field_2.write(14); - /// // field_2.read(); // compile-time error + /// let write_only = volatile.write_only(); + /// // write_only.as_ptr().read(); // compile-time error /// ``` pub fn write_only(self) -> VolatileRef<'a, T, WriteOnly> { unsafe { VolatileRef::new_restricted(WriteOnly, self.pointer) } @@ -348,6 +156,8 @@ where A: Readable, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Volatile").field(&self.read()).finish() + f.debug_tuple("Volatile") + .field(&self.as_ptr().read()) + .finish() } } diff --git a/src/volatile_ref/tests.rs b/src/volatile_ref/tests.rs deleted file mode 100644 index fc1fd79..0000000 --- a/src/volatile_ref/tests.rs +++ /dev/null @@ -1,189 +0,0 @@ -use crate::{ - access::{ReadOnly, ReadWrite, WriteOnly}, - map_field_mut, VolatileRef, -}; -use core::ptr::NonNull; - -#[test] -fn test_read() { - let val = 42; - assert_eq!( - unsafe { VolatileRef::new_read_only(NonNull::from(&val)) }.read(), - 42 - ); -} - -#[test] -fn test_write() { - let mut val = 50; - let mut volatile = unsafe { VolatileRef::new(NonNull::from(&mut val)) }; - volatile.write(50); - assert_eq!(val, 50); -} - -#[test] -fn test_update() { - let mut val = 42; - let mut volatile = unsafe { VolatileRef::new(NonNull::from(&mut val)) }; - volatile.update(|v| v + 1); - assert_eq!(val, 43); -} - -#[test] -fn test_access() { - let mut val: i64 = 42; - - // ReadWrite - assert_eq!( - unsafe { VolatileRef::new_restricted(ReadWrite, NonNull::from(&mut val)) }.read(), - 42 - ); - unsafe { VolatileRef::new_restricted(ReadWrite, NonNull::from(&mut val)) }.write(50); - assert_eq!(val, 50); - unsafe { VolatileRef::new_restricted(ReadWrite, NonNull::from(&mut val)) }.update(|i| i + 1); - assert_eq!(val, 51); - - // ReadOnly and WriteOnly - assert_eq!( - unsafe { VolatileRef::new_restricted(ReadOnly, NonNull::from(&mut val)) }.read(), - 51 - ); - unsafe { VolatileRef::new_restricted(WriteOnly, NonNull::from(&mut val)) }.write(12); - assert_eq!(val, 12); -} - -#[test] -fn test_struct() { - #[derive(Debug, PartialEq)] - struct S { - field_1: u32, - field_2: bool, - } - - let mut val = S { - field_1: 60, - field_2: true, - }; - let mut volatile = unsafe { VolatileRef::new(NonNull::from(&mut val)) }; - unsafe { - volatile - .borrow_mut() - .map_mut(|s| NonNull::new(core::ptr::addr_of_mut!((*s.as_ptr()).field_1)).unwrap()) - } - .update(|v| v + 1); - let mut field_2 = unsafe { - volatile.map_mut(|s| NonNull::new(core::ptr::addr_of_mut!((*s.as_ptr()).field_2)).unwrap()) - }; - assert!(field_2.read()); - field_2.write(false); - assert_eq!( - val, - S { - field_1: 61, - field_2: false - } - ); -} - -#[test] -fn test_struct_macro() { - #[derive(Debug, PartialEq)] - struct S { - field_1: u32, - field_2: bool, - } - - let mut val = S { - field_1: 60, - field_2: true, - }; - let mut volatile = unsafe { VolatileRef::new(NonNull::from(&mut val)) }; - let volatile_borrowed = volatile.borrow_mut(); - let mut field_1 = map_field_mut!(volatile_borrowed.field_1); - field_1.update(|v| v + 1); - let mut field_2 = map_field_mut!(volatile.field_2); - assert!(field_2.read()); - field_2.write(false); - assert_eq!( - val, - S { - field_1: 61, - field_2: false - } - ); -} - -#[cfg(feature = "unstable")] -#[test] -fn test_slice() { - let val: &mut [u32] = &mut [1, 2, 3]; - let mut volatile = unsafe { VolatileRef::new(NonNull::from(val)) }; - volatile.borrow_mut().index_mut(0).update(|v| v + 1); - - let mut dst = [0; 3]; - volatile.copy_into_slice(&mut dst); - assert_eq!(dst, [2, 2, 3]); -} - -#[cfg(feature = "unstable")] -#[test] -#[should_panic] -fn test_bounds_check_1() { - let val: &mut [u32] = &mut [1, 2, 3]; - let volatile = unsafe { VolatileRef::new(NonNull::from(val)) }; - volatile.index_mut(3); -} - -#[cfg(feature = "unstable")] -#[test] -#[should_panic] -fn test_bounds_check_2() { - let val: &mut [u32] = &mut [1, 2, 3]; - let volatile = unsafe { VolatileRef::new(NonNull::from(val)) }; - volatile.index_mut(2..1); -} - -#[cfg(feature = "unstable")] -#[test] -#[should_panic] -fn test_bounds_check_3() { - let val: &mut [u32] = &mut [1, 2, 3]; - let volatile = unsafe { VolatileRef::new(NonNull::from(val)) }; - volatile.index_mut(4..); // `3..` is is still ok (see next test) -} - -#[cfg(feature = "unstable")] -#[test] -fn test_bounds_check_4() { - let val: &mut [u32] = &mut [1, 2, 3]; - let volatile = unsafe { VolatileRef::new(NonNull::from(val)) }; - assert_eq!(volatile.index_mut(3..).len(), 0); -} - -#[cfg(feature = "unstable")] -#[test] -#[should_panic] -fn test_bounds_check_5() { - let val: &mut [u32] = &mut [1, 2, 3]; - let volatile = unsafe { VolatileRef::new(NonNull::from(val)) }; - volatile.index_mut(..4); -} - -#[cfg(feature = "unstable")] -#[test] -fn test_chunks() { - let val: &mut [u32] = &mut [1, 2, 3, 4, 5, 6]; - let volatile = unsafe { VolatileRef::new(NonNull::from(val)) }; - let mut chunks = volatile.as_chunks_mut().0; - chunks.borrow_mut().index_mut(1).write([10, 11, 12]); - assert_eq!(chunks.borrow().index(0).read(), [1, 2, 3]); - assert_eq!(chunks.index(1).read(), [10, 11, 12]); -} - -#[test] -fn test_lifetime() { - let mut val = 50; - let mut volatile = VolatileRef::from_mut_ref(&mut val); - volatile.write(50); - assert_eq!(val, 50); -} diff --git a/src/volatile_ref/unstable.rs b/src/volatile_ref/unstable.rs deleted file mode 100644 index 2246c59..0000000 --- a/src/volatile_ref/unstable.rs +++ /dev/null @@ -1,518 +0,0 @@ -use core::{ - intrinsics, - ops::{Range, RangeBounds}, - ptr::{self, NonNull}, - slice::{range, SliceIndex}, -}; - -use crate::{ - access::{Access, Readable, Writable}, - VolatilePtr, -}; - -/// Methods for volatile slices -impl<'a, T, A> VolatilePtr<'a, [T], A> { - pub fn len(&self) -> usize { - self.pointer.len() - } - - pub fn is_empty(&self) -> bool { - self.pointer.len() == 0 - } - - /// Applies the index operation on the wrapped slice. - /// - /// Returns a shared `Volatile` reference to the resulting subslice. - /// - /// This is a convenience method for the `map(|slice| slice.index(index))` operation, so it - /// has the same behavior as the indexing operation on slice (e.g. panic if index is - /// out-of-bounds). - /// - /// ## Examples - /// - /// Accessing a single slice element: - /// - /// ``` - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let array = [1, 2, 3]; - /// let slice = &array[..]; - /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(slice)) }; - /// assert_eq!(volatile.index(1).read(), 2); - /// ``` - /// - /// Accessing a subslice: - /// - /// ``` - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let array = [1, 2, 3]; - /// let slice = &array[..]; - /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(slice)) }; - /// let subslice = volatile.index(1..); - /// assert_eq!(subslice.index(0).read(), 2); - /// ``` - pub fn index( - self, - index: I, - ) -> VolatilePtr<'a, >::Output, A::RestrictShared> - where - I: SliceIndex<[T]> + SliceIndex<[()]> + Clone, - A: Access, - { - bounds_check(self.pointer.len(), index.clone()); - - unsafe { self.map(|slice| slice.get_unchecked_mut(index)) } - } - - pub fn index_mut(self, index: I) -> VolatilePtr<'a, >::Output, A> - where - I: SliceIndex<[T]> + SliceIndex<[()]> + Clone, - A: Access, - { - bounds_check(self.pointer.len(), index.clone()); - - unsafe { self.map_mut(|slice| slice.get_unchecked_mut(index)) } - } - - /// Returns an iterator over the slice. - pub fn iter(self) -> impl Iterator> - where - A: Access, - { - let ptr = self.as_ptr().as_ptr() as *mut T; - let len = self.len(); - (0..len) - .map(move |i| unsafe { VolatilePtr::new_generic(NonNull::new_unchecked(ptr.add(i))) }) - } - - /// Returns an iterator that allows modifying each value. - pub fn iter_mut(self) -> impl Iterator> { - let ptr = self.as_ptr().as_ptr() as *mut T; - let len = self.len(); - (0..len) - .map(move |i| unsafe { VolatilePtr::new_generic(NonNull::new_unchecked(ptr.add(i))) }) - } - - /// Copies all elements from `self` into `dst`, using a volatile memcpy. - /// - /// The length of `dst` must be the same as `self`. - /// - /// The method is only available with the `unstable` feature enabled (requires a nightly - /// Rust compiler). - /// - /// ## Panics - /// - /// This function will panic if the two slices have different lengths. - /// - /// ## Examples - /// - /// Copying two elements from a volatile slice: - /// - /// ``` - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let src = [1, 2]; - /// // the `Volatile` type does not work with arrays, so convert `src` to a slice - /// let slice = &src[..]; - /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(slice)) }; - /// let mut dst = [5, 0, 0]; - /// - /// // Because the slices have to be the same length, - /// // we slice the destination slice from three elements - /// // to two. It will panic if we don't do this. - /// volatile.copy_into_slice(&mut dst[1..]); - /// - /// assert_eq!(src, [1, 2]); - /// assert_eq!(dst, [5, 1, 2]); - /// ``` - pub fn copy_into_slice(&self, dst: &mut [T]) - where - T: Copy, - A: Readable, - { - let len = self.pointer.len(); - assert_eq!( - len, - dst.len(), - "destination and source slices have different lengths" - ); - unsafe { - intrinsics::volatile_copy_nonoverlapping_memory( - dst.as_mut_ptr(), - self.pointer.as_mut_ptr(), - len, - ); - } - } - - /// Copies all elements from `src` into `self`, using a volatile memcpy. - /// - /// The length of `src` must be the same as `self`. - /// - /// This method is similar to the `slice::copy_from_slice` method of the standard library. The - /// difference is that this method performs a volatile copy. - /// - /// The method is only available with the `unstable` feature enabled (requires a nightly - /// Rust compiler). - /// - /// ## Panics - /// - /// This function will panic if the two slices have different lengths. - /// - /// ## Examples - /// - /// Copying two elements from a slice into a volatile slice: - /// - /// ``` - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let src = [1, 2, 3, 4]; - /// let mut dst = [0, 0]; - /// // the `Volatile` type does not work with arrays, so convert `dst` to a slice - /// let slice = &mut dst[..]; - /// let mut volatile = VolatilePtr::from_mut_ref(slice); - /// /// // Because the slices have to be the same length, - /// // we slice the source slice from four elements - /// // to two. It will panic if we don't do this. - /// volatile.copy_from_slice(&src[2..]); - /// - /// assert_eq!(src, [1, 2, 3, 4]); - /// assert_eq!(dst, [3, 4]); - /// ``` - pub fn copy_from_slice(&mut self, src: &[T]) - where - T: Copy, - A: Writable, - { - let len = self.pointer.len(); - assert_eq!( - len, - src.len(), - "destination and source slices have different lengths" - ); - unsafe { - intrinsics::volatile_copy_nonoverlapping_memory( - self.pointer.as_mut_ptr(), - src.as_ptr(), - len, - ); - } - } - - /// Copies elements from one part of the slice to another part of itself, using a - /// volatile `memmove`. - /// - /// `src` is the range within `self` to copy from. `dest` is the starting index of the - /// range within `self` to copy to, which will have the same length as `src`. The two ranges - /// may overlap. The ends of the two ranges must be less than or equal to `self.len()`. - /// - /// This method is similar to the `slice::copy_within` method of the standard library. The - /// difference is that this method performs a volatile copy. - /// - /// This method is only available with the `unstable` feature enabled (requires a nightly - /// Rust compiler). - /// - /// ## Panics - /// - /// This function will panic if either range exceeds the end of the slice, or if the end - /// of `src` is before the start. - /// - /// ## Examples - /// - /// Copying four bytes within a slice: - /// - /// ``` - /// extern crate core; - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let mut byte_array = *b"Hello, World!"; - /// let mut slice: &mut [u8] = &mut byte_array[..]; - /// let mut volatile = VolatilePtr::from_mut_ref(slice); - /// volatile.copy_within(1..5, 8); - /// - /// assert_eq!(&byte_array, b"Hello, Wello!"); - pub fn copy_within(&mut self, src: impl RangeBounds, dest: usize) - where - T: Copy, - A: Readable + Writable, - { - let len = self.pointer.len(); - // implementation taken from https://github.com/rust-lang/rust/blob/683d1bcd405727fcc9209f64845bd3b9104878b8/library/core/src/slice/mod.rs#L2726-L2738 - let Range { - start: src_start, - end: src_end, - } = range(src, ..len); - let count = src_end - src_start; - assert!(dest <= len - count, "dest is out of bounds"); - // SAFETY: the conditions for `volatile_copy_memory` have all been checked above, - // as have those for `ptr::add`. - unsafe { - intrinsics::volatile_copy_memory( - self.pointer.as_mut_ptr().add(dest), - self.pointer.as_mut_ptr().add(src_start), - count, - ); - } - } - - pub fn split_at( - self, - mid: usize, - ) -> ( - VolatilePtr<'a, [T], A::RestrictShared>, - VolatilePtr<'a, [T], A::RestrictShared>, - ) - where - A: Access, - { - assert!(mid <= self.pointer.len()); - // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which - // fulfills the requirements of `from_raw_parts_mut`. - unsafe { self.split_at_unchecked(mid) } - } - - pub fn split_at_mut(self, mid: usize) -> (VolatilePtr<'a, [T], A>, VolatilePtr<'a, [T], A>) - where - A: Access, - { - assert!(mid <= self.pointer.len()); - // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which - // fulfills the requirements of `from_raw_parts_mut`. - unsafe { self.split_at_mut_unchecked(mid) } - } - - unsafe fn split_at_unchecked( - self, - mid: usize, - ) -> ( - VolatilePtr<'a, [T], A::RestrictShared>, - VolatilePtr<'a, [T], A::RestrictShared>, - ) - where - A: Access, - { - // SAFETY: Caller has to check that `0 <= mid <= self.len()` - unsafe { - ( - VolatilePtr::new_generic((self.pointer).get_unchecked_mut(..mid)), - VolatilePtr::new_generic((self.pointer).get_unchecked_mut(mid..)), - ) - } - } - - unsafe fn split_at_mut_unchecked( - self, - mid: usize, - ) -> (VolatilePtr<'a, [T], A>, VolatilePtr<'a, [T], A>) - where - A: Access, - { - let len = self.pointer.len(); - let ptr = self.pointer.as_mut_ptr(); - - // SAFETY: Caller has to check that `0 <= mid <= self.len()`. - // - // `[ptr; mid]` and `[mid; len]` are not overlapping, so returning a mutable reference - // is fine. - unsafe { - ( - VolatilePtr::new_generic( - NonNull::new(ptr::slice_from_raw_parts_mut(ptr, mid)).unwrap(), - ), - VolatilePtr::new_generic( - NonNull::new(ptr::slice_from_raw_parts_mut(ptr.add(mid), len - mid)).unwrap(), - ), - ) - } - } - - pub fn as_chunks( - self, - ) -> ( - VolatilePtr<'a, [[T; N]], ::RestrictShared>, - VolatilePtr<'a, [T], A::RestrictShared>, - ) - where - A: Access, - { - assert_ne!(N, 0); - let len = self.pointer.len() / N; - let (multiple_of_n, remainder) = self.split_at(len * N); - // SAFETY: We already panicked for zero, and ensured by construction - // that the length of the subslice is a multiple of N. - let array_slice = unsafe { multiple_of_n.as_chunks_unchecked() }; - (array_slice, remainder) - } - - pub unsafe fn as_chunks_unchecked( - self, - ) -> VolatilePtr<'a, [[T; N]], A::RestrictShared> - where - A: Access, - { - debug_assert_ne!(N, 0); - debug_assert_eq!(self.pointer.len() % N, 0); - let new_len = - // SAFETY: Our precondition is exactly what's needed to call this - unsafe { core::intrinsics::exact_div(self.pointer.len(), N) }; - // SAFETY: We cast a slice of `new_len * N` elements into - // a slice of `new_len` many `N` elements chunks. - let pointer = NonNull::new(ptr::slice_from_raw_parts_mut( - self.pointer.as_mut_ptr().cast(), - new_len, - )) - .unwrap(); - unsafe { VolatilePtr::new_generic(pointer) } - } - - pub fn as_chunks_mut( - self, - ) -> (VolatilePtr<'a, [[T; N]], A>, VolatilePtr<'a, [T], A>) - where - A: Access, - { - assert_ne!(N, 0); - let len = self.pointer.len() / N; - let (multiple_of_n, remainder) = self.split_at_mut(len * N); - // SAFETY: We already panicked for zero, and ensured by construction - // that the length of the subslice is a multiple of N. - let array_slice = unsafe { multiple_of_n.as_chunks_unchecked_mut() }; - (array_slice, remainder) - } - - pub unsafe fn as_chunks_unchecked_mut(self) -> VolatilePtr<'a, [[T; N]], A> { - debug_assert_ne!(N, 0); - debug_assert_eq!(self.pointer.len() % N, 0); - let new_len = - // SAFETY: Our precondition is exactly what's needed to call this - unsafe { core::intrinsics::exact_div(self.pointer.len(), N) }; - // SAFETY: We cast a slice of `new_len * N` elements into - // a slice of `new_len` many `N` elements chunks. - let pointer = NonNull::new(ptr::slice_from_raw_parts_mut( - self.pointer.as_mut_ptr().cast(), - new_len, - )) - .unwrap(); - unsafe { VolatilePtr::new_generic(pointer) } - } -} - -/// Methods for volatile byte slices -impl VolatilePtr<'_, [u8], A> { - /// Sets all elements of the byte slice to the given `value` using a volatile `memset`. - /// - /// This method is similar to the `slice::fill` method of the standard library, with the - /// difference that this method performs a volatile write operation. Another difference - /// is that this method is only available for byte slices (not general `&mut [T]` slices) - /// because there currently isn't a instrinsic function that allows non-`u8` values. - /// - /// This method is only available with the `unstable` feature enabled (requires a nightly - /// Rust compiler). - /// - /// ## Example - /// - /// ```rust - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let mut vec = vec![0; 10]; - /// let mut buf = VolatilePtr::from_mut_ref(vec.as_mut_slice()); - /// buf.fill(1); - /// assert_eq!(unsafe { buf.as_ptr().as_mut() }, &mut vec![1; 10]); - /// ``` - pub fn fill(&mut self, value: u8) - where - A: Writable, - { - unsafe { - intrinsics::volatile_set_memory(self.pointer.as_mut_ptr(), value, self.pointer.len()); - } - } -} - -/// Methods for converting arrays to slices -/// -/// These methods are only available with the `unstable` feature enabled (requires a nightly -/// Rust compiler). -impl<'a, T, A, const N: usize> VolatilePtr<'a, [T; N], A> { - /// Converts an array reference to a shared slice. - /// - /// This makes it possible to use the methods defined on slices. - /// - /// ## Example - /// - /// Copying two elements from a volatile array reference using `copy_into_slice`: - /// - /// ``` - /// use volatile::VolatilePtr; - /// use core::ptr::NonNull; - /// - /// let src = [1, 2]; - /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(&src)) }; - /// let mut dst = [0, 0]; - /// - /// // convert the `Volatile<&[i32; 2]>` array reference to a `Volatile<&[i32]>` slice - /// let volatile_slice = volatile.as_slice(); - /// // we can now use the slice methods - /// volatile_slice.copy_into_slice(&mut dst); - /// - /// assert_eq!(dst, [1, 2]); - /// ``` - pub fn as_slice(self) -> VolatilePtr<'a, [T], A::RestrictShared> - where - A: Access, - { - unsafe { - self.map(|array| { - NonNull::new(ptr::slice_from_raw_parts_mut(array.as_ptr() as *mut T, N)).unwrap() - }) - } - } - - /// Converts an array reference to a shared slice. - /// - /// This makes it possible to use the methods defined on slices. - /// - /// ## Example - /// - /// Copying two elements into a volatile array reference using `copy_from_slice`: - /// - /// ``` - /// use volatile::{access, VolatilePtr}; - /// use core::ptr::NonNull; - /// - /// let src = [1, 2]; - /// let mut dst = [0, 0]; - /// let mut volatile = unsafe { VolatilePtr::new_restricted(access::WriteOnly, NonNull::from(&dst)) }; - /// - /// // convert the `Volatile<[i32; 2]>` array reference to a `Volatile<[i32]>` slice - /// let mut volatile_slice = volatile.as_slice_mut(); - /// // we can now use the slice methods - /// volatile_slice.copy_from_slice(&src); - /// - /// assert_eq!(dst, [1, 2]); - /// ``` - pub fn as_slice_mut(self) -> VolatilePtr<'a, [T], A> - where - A: Access, - { - unsafe { - self.map_mut(|array| { - NonNull::new(ptr::slice_from_raw_parts_mut(array.as_ptr() as *mut T, N)).unwrap() - }) - } - } -} - -fn bounds_check(len: usize, index: impl SliceIndex<[()]>) { - const MAX_ARRAY: [(); usize::MAX] = [(); usize::MAX]; - - let bound_check_slice = &MAX_ARRAY[..len]; - let _ = &bound_check_slice[index]; -} diff --git a/src/volatile_ref/very_unstable.rs b/src/volatile_ref/very_unstable.rs deleted file mode 100644 index f7bddcb..0000000 --- a/src/volatile_ref/very_unstable.rs +++ /dev/null @@ -1,66 +0,0 @@ -use core::ptr::NonNull; - -use crate::{access::Access, VolatilePtr}; - -impl<'a, T, A> VolatilePtr<'a, T, A> -where - T: ?Sized, -{ - pub const unsafe fn map_const(self, f: F) -> VolatilePtr<'a, U, A::RestrictShared> - where - F: ~const FnOnce(NonNull) -> NonNull, - A: Access, - U: ?Sized, - { - unsafe { VolatilePtr::new_generic(f(self.pointer)) } - } - - pub const unsafe fn map_mut_const(self, f: F) -> VolatilePtr<'a, U, A> - where - F: ~const FnOnce(NonNull) -> NonNull, - U: ?Sized, - { - unsafe { VolatilePtr::new_generic(f(self.pointer)) } - } -} - -/// Methods for volatile slices -#[cfg(feature = "unstable")] -impl<'a, T, A> VolatilePtr<'a, [T], A> { - pub const fn index_const(self, index: usize) -> VolatilePtr<'a, T, A::RestrictShared> - where - A: Access, - { - assert!(index < self.pointer.len(), "index out of bounds"); - - struct Mapper { - index: usize, - } - impl const FnOnce<(NonNull<[T]>,)> for Mapper { - type Output = NonNull; - - extern "rust-call" fn call_once(self, (slice,): (NonNull<[T]>,)) -> Self::Output { - unsafe { NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(self.index)) } - } - } - - unsafe { self.map_const(Mapper { index }) } - } - - pub const fn index_mut_const(self, index: usize) -> VolatilePtr<'a, T, A> { - assert!(index < self.pointer.len(), "index out of bounds"); - - struct Mapper { - index: usize, - } - impl const FnOnce<(NonNull<[T]>,)> for Mapper { - type Output = NonNull; - - extern "rust-call" fn call_once(self, (slice,): (NonNull<[T]>,)) -> Self::Output { - unsafe { NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(self.index)) } - } - } - - unsafe { self.map_mut_const(Mapper { index }) } - } -} From 34ecfdc745307d2858ace4cfa5a92c354329ecb9 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 28 Apr 2023 21:24:23 +0200 Subject: [PATCH 51/68] Various improvements to `VolatilePtr` type - Remove potentially unsafe constructor methods from `&T`/`&mut T`. They're still available on `VolatileRef` if needed. - Refactor: Move most method implementations to new submodule. - Update docs and examples - Rename `as_ptr` method to `as_raw_ptr` to reduce confusion --- src/macros.rs | 9 +- src/volatile_ptr/mod.rs | 281 +----------------------------- src/volatile_ptr/operations.rs | 253 +++++++++++++++++++++++++++ src/volatile_ptr/tests.rs | 45 ++--- src/volatile_ptr/unstable.rs | 104 +++++------ src/volatile_ptr/very_unstable.rs | 18 +- 6 files changed, 334 insertions(+), 376 deletions(-) create mode 100644 src/volatile_ptr/operations.rs diff --git a/src/macros.rs b/src/macros.rs index 9f06237..e10226d 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -10,7 +10,7 @@ /// /// struct Example { field_1: u32, field_2: u8, } /// let mut value = Example { field_1: 15, field_2: 255 }; -/// let mut volatile = VolatilePtr::from_mut_ref(&mut value); +/// let mut volatile = unsafe { VolatilePtr::new((&mut value).into()) }; /// /// // construct a volatile reference to a field /// let field_2 = map_field!(volatile.field_2); @@ -25,7 +25,7 @@ /// #[repr(packed)] /// struct Example { field_1: u8, field_2: usize, } /// let mut value = Example { field_1: 15, field_2: 255 }; -/// let mut volatile = VolatilePtr::from_mut_ref(&mut value); +/// let mut volatile = unsafe { VolatilePtr::new((&mut value).into()) }; /// /// // Constructing a volatile reference to an unaligned field doesn't compile. /// let field_2 = map_field!(volatile.field_2); @@ -38,7 +38,7 @@ macro_rules! map_field { // if statement will never be executed, so it can never cause any UB. if false { #[deny(unaligned_references)] - let _ref_to_field = &(unsafe { &*$volatile.as_ptr().as_ptr() }).$place; + let _ref_to_field = &(unsafe { &*$volatile.as_raw_ptr().as_ptr() }).$place; } unsafe { @@ -56,8 +56,7 @@ macro_rules! map_field_mut { // sure that the field is not potentially unaligned. The body of the // if statement will never be executed, so it can never cause any UB. if false { - #[deny(unaligned_references)] - let _ref_to_field = &(unsafe { &*$volatile.as_ptr().as_ptr() }).$place; + let _ref_to_field = &(unsafe { &*$volatile.as_raw_ptr().as_ptr() }).$place; } unsafe { diff --git a/src/volatile_ptr/mod.rs b/src/volatile_ptr/mod.rs index afc2ff8..9c4dcb4 100644 --- a/src/volatile_ptr/mod.rs +++ b/src/volatile_ptr/mod.rs @@ -5,14 +5,11 @@ //! The wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider //! looking at the `Atomic` wrapper types found in `libcore` or `libstd`. -use core::{ - fmt, - marker::PhantomData, - ptr::{self, NonNull}, -}; +use core::{fmt, marker::PhantomData, ptr::NonNull}; -use crate::access::{Access, ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; +use crate::access::{ReadWrite, Readable}; +mod operations; #[cfg(test)] mod tests; #[cfg(feature = "unstable")] @@ -53,278 +50,6 @@ where } } -/// Constructor functions. -/// -/// These functions construct new `VolatilePtr` values. While the `new` -/// function creates a `VolatilePtr` instance with unrestricted access, there -/// are also functions for creating read-only or write-only instances. -impl<'a, T> VolatilePtr<'a, T> -where - T: ?Sized, -{ - pub unsafe fn new(pointer: NonNull) -> Self { - unsafe { VolatilePtr::new_restricted(ReadWrite, pointer) } - } - - pub fn from_mut_ref(reference: &'a mut T) -> Self - where - T: 'a, - { - unsafe { VolatilePtr::new(reference.into()) } - } - - pub const unsafe fn new_read_only(pointer: NonNull) -> VolatilePtr<'a, T, ReadOnly> { - unsafe { Self::new_restricted(ReadOnly, pointer) } - } - - pub const unsafe fn new_restricted(access: A, pointer: NonNull) -> VolatilePtr<'a, T, A> - where - A: Access, - { - let _ = access; - unsafe { Self::new_generic(pointer) } - } - - pub fn from_ref(reference: &'a T) -> VolatilePtr<'a, T, ReadOnly> - where - T: 'a, - { - unsafe { VolatilePtr::new_restricted(ReadOnly, reference.into()) } - } - - const unsafe fn new_generic(pointer: NonNull) -> VolatilePtr<'a, T, A> { - VolatilePtr { - pointer, - reference: PhantomData, - access: PhantomData, - } - } -} - -impl<'a, T, A> VolatilePtr<'a, T, A> -where - T: ?Sized, -{ - /// Performs a volatile read of the contained value. - /// - /// Returns a copy of the read value. Volatile reads are guaranteed not to be optimized - /// away by the compiler, but by themselves do not have atomic ordering - /// guarantees. To also get atomicity, consider looking at the `Atomic` wrapper types of - /// the standard/`core` library. - /// - /// ## Examples - /// - /// ```rust - /// use volatile::{VolatilePtrCopy, access}; - /// use core::ptr::NonNull; - /// - /// let value = 42; - /// let shared_reference = unsafe { - /// VolatilePtrCopy::new_restricted(access::ReadOnly, NonNull::from(&value)) - /// }; - /// assert_eq!(shared_reference.read(), 42); - /// - /// let mut value = 50; - /// let mut_reference = VolatilePtrCopy::from_mut_ref(&mut value); - /// assert_eq!(mut_reference.read(), 50); - /// ``` - pub fn read(self) -> T - where - T: Copy, - A: Readable, - { - // UNSAFE: Safe, as ... TODO - unsafe { ptr::read_volatile(self.pointer.as_ptr()) } - } - - /// Performs a volatile write, setting the contained value to the given `value`. - /// - /// Volatile writes are guaranteed to not be optimized away by the compiler, but by - /// themselves do not have atomic ordering guarantees. To also get atomicity, consider - /// looking at the `Atomic` wrapper types of the standard/`core` library. - /// - /// ## Example - /// - /// ```rust - /// use volatile::VolatilePtrCopy; - /// use core::ptr::NonNull; - /// - /// let mut value = 42; - /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); - /// volatile.write(50); - /// - /// assert_eq!(volatile.read(), 50); - /// ``` - pub fn write(self, value: T) - where - T: Copy, - A: Writable, - { - // UNSAFE: Safe, as ... TODO - unsafe { ptr::write_volatile(self.pointer.as_ptr(), value) }; - } - - /// Updates the contained value using the given closure and volatile instructions. - /// - /// Performs a volatile read of the contained value, passes a mutable reference to it to the - /// function `f`, and then performs a volatile write of the (potentially updated) value back to - /// the contained value. - /// - /// ```rust - /// use volatile::VolatilePtrCopy; - /// use core::ptr::NonNull; - /// - /// let mut value = 42; - /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); - /// volatile.update(|val| val + 1); - /// - /// assert_eq!(volatile.read(), 43); - /// ``` - pub fn update(self, f: F) - where - T: Copy, - A: Readable + Writable, - F: FnOnce(T) -> T, - { - let new = f(self.read()); - self.write(new); - } - - /// Extracts the inner value stored in the wrapper type. - /// - /// This method gives direct access to the wrapped reference and thus allows - /// non-volatile access again. This is seldom what you want since there is usually - /// a reason that a reference is wrapped in `Volatile`. However, in some cases it might - /// be required or useful to use the `read_volatile`/`write_volatile` pointer methods of - /// the standard library directly, which this method makes possible. - /// - /// Since no memory safety violation can occur when accessing the referenced value using - /// non-volatile operations, this method is safe. However, it _can_ lead to bugs at the - /// application level, so this method should be used with care. - /// - /// ## Example - /// - /// ``` - /// use volatile::VolatilePtrCopy; - /// use core::ptr::NonNull; - /// - /// let mut value = 42; - /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); - /// volatile.write(50); - /// let unwrapped: *mut i32 = volatile.as_ptr().as_ptr(); - /// - /// assert_eq!(unsafe { *unwrapped }, 50); // non volatile access, be careful! - /// ``` - pub fn as_ptr(self) -> NonNull { - self.pointer - } - - /// Constructs a new `Volatile` reference by mapping the wrapped pointer. - /// - /// This method is useful for accessing only a part of a volatile value, e.g. a subslice or - /// a struct field. For struct field access, there is also the safe [`map_field`] macro that - /// wraps this function. - /// - /// ## Examples - /// - /// Accessing a struct field: - /// - /// ``` - /// use volatile::VolatilePtrCopy; - /// use core::ptr::NonNull; - /// - /// struct Example { field_1: u32, field_2: u8, } - /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); - /// - /// // construct a volatile reference to a field - /// let field_2 = unsafe { volatile.map(|ptr| NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).field_2)).unwrap()) }; - /// assert_eq!(field_2.read(), 255); - /// ``` - /// - /// Don't misuse this method to do a non-volatile read of the referenced value: - /// - /// ``` - /// use volatile::VolatilePtrCopy; - /// use core::ptr::NonNull; - /// - /// let mut value = 5; - /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); - /// - /// // DON'T DO THIS: - /// let mut readout = 0; - /// unsafe { volatile.map(|value| { - /// readout = *value.as_ptr(); // non-volatile read, might lead to bugs - /// value - /// })}; - /// ``` - pub unsafe fn map(self, f: F) -> VolatilePtr<'a, U, A::RestrictShared> - where - F: FnOnce(NonNull) -> NonNull, - A: Access, - U: ?Sized, - { - unsafe { VolatilePtr::new_restricted(Default::default(), f(self.pointer)) } - } - - pub unsafe fn map_mut(self, f: F) -> VolatilePtr<'a, U, A> - where - F: FnOnce(NonNull) -> NonNull, - U: ?Sized, - A: Access, - { - unsafe { VolatilePtr::new_restricted(A::default(), f(self.pointer)) } - } -} - -/// Methods for restricting access. -impl<'a, T> VolatilePtr<'a, T, ReadWrite> -where - T: ?Sized, -{ - /// Restricts access permissions to read-only. - /// - /// ## Example - /// - /// ``` - /// use volatile::VolatilePtrCopy; - /// use core::ptr::NonNull; - /// - /// let mut value: i16 = -4; - /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); - /// - /// let read_only = volatile.read_only(); - /// assert_eq!(read_only.read(), -4); - /// // read_only.write(10); // compile-time error - /// ``` - pub fn read_only(self) -> VolatilePtr<'a, T, ReadOnly> { - unsafe { VolatilePtr::new_restricted(ReadOnly, self.pointer) } - } - - /// Restricts access permissions to write-only. - /// - /// ## Example - /// - /// Creating a write-only reference to a struct field: - /// - /// ``` - /// use volatile::{VolatilePtrCopy, map_field_mut}; - /// use core::ptr::NonNull; - /// - /// struct Example { field_1: u32, field_2: u8, } - /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = VolatilePtrCopy::from_mut_ref(&mut value); - /// - /// // construct a volatile write-only reference to `field_2` - /// let mut field_2 = map_field_mut!(volatile.field_2).write_only(); - /// field_2.write(14); - /// // field_2.read(); // compile-time error - /// ``` - pub fn write_only(self) -> VolatilePtr<'a, T, WriteOnly> { - unsafe { VolatilePtr::new_restricted(WriteOnly, self.pointer) } - } -} - impl fmt::Debug for VolatilePtr<'_, T, A> where T: Copy + fmt::Debug + ?Sized, diff --git a/src/volatile_ptr/operations.rs b/src/volatile_ptr/operations.rs new file mode 100644 index 0000000..f548c5b --- /dev/null +++ b/src/volatile_ptr/operations.rs @@ -0,0 +1,253 @@ +use core::{ + marker::PhantomData, + ptr::{self, NonNull}, +}; + +use crate::{ + access::{Access, ReadOnly, ReadWrite, Readable, Writable, WriteOnly}, + VolatilePtr, +}; + +/// Constructor functions. +/// +/// These functions construct new `VolatilePtr` values. While the `new` +/// function creates a `VolatilePtr` instance with unrestricted access, there +/// are also functions for creating read-only or write-only instances. +impl<'a, T> VolatilePtr<'a, T> +where + T: ?Sized, +{ + pub unsafe fn new(pointer: NonNull) -> VolatilePtr<'a, T, ReadWrite> { + unsafe { VolatilePtr::new_restricted(ReadWrite, pointer) } + } + + pub const unsafe fn new_read_only(pointer: NonNull) -> VolatilePtr<'a, T, ReadOnly> { + unsafe { Self::new_restricted(ReadOnly, pointer) } + } + + pub const unsafe fn new_restricted(access: A, pointer: NonNull) -> VolatilePtr<'a, T, A> + where + A: Access, + { + let _ = access; + unsafe { Self::new_generic(pointer) } + } + + const unsafe fn new_generic(pointer: NonNull) -> VolatilePtr<'a, T, A> { + VolatilePtr { + pointer, + reference: PhantomData, + access: PhantomData, + } + } +} + +impl<'a, T, A> VolatilePtr<'a, T, A> +where + T: ?Sized, +{ + /// Performs a volatile read of the contained value. + /// + /// Returns a copy of the read value. Volatile reads are guaranteed not to be optimized + /// away by the compiler, but by themselves do not have atomic ordering + /// guarantees. To also get atomicity, consider looking at the `Atomic` wrapper types of + /// the standard/`core` library. + /// + /// ## Examples + /// + /// ```rust + /// use volatile::{VolatilePtr, access}; + /// use core::ptr::NonNull; + /// + /// let value = 42; + /// let pointer = unsafe { + /// VolatilePtr::new_restricted(access::ReadOnly, NonNull::from(&value)) + /// }; + /// assert_eq!(pointer.read(), 42); + /// ``` + pub fn read(self) -> T + where + T: Copy, + A: Readable, + { + // UNSAFE: Safe, as ... TODO + unsafe { ptr::read_volatile(self.pointer.as_ptr()) } + } + + /// Performs a volatile write, setting the contained value to the given `value`. + /// + /// Volatile writes are guaranteed to not be optimized away by the compiler, but by + /// themselves do not have atomic ordering guarantees. To also get atomicity, consider + /// looking at the `Atomic` wrapper types of the standard/`core` library. + /// + /// ## Example + /// + /// ```rust + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut value = 42; + /// let mut volatile = unsafe { VolatilePtr::new((&mut value).into()) }; + /// volatile.write(50); + /// + /// assert_eq!(volatile.read(), 50); + /// ``` + pub fn write(self, value: T) + where + T: Copy, + A: Writable, + { + // UNSAFE: Safe, as ... TODO + unsafe { ptr::write_volatile(self.pointer.as_ptr(), value) }; + } + + /// Updates the contained value using the given closure and volatile instructions. + /// + /// Performs a volatile read of the contained value, passes it to the + /// function `f`, and then performs a volatile write of the returned value back to + /// the target. + /// + /// ```rust + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut value = 42; + /// let mut volatile = unsafe { VolatilePtr::new((&mut value).into()) }; + /// volatile.update(|val| val + 1); + /// + /// assert_eq!(volatile.read(), 43); + /// ``` + pub fn update(self, f: F) + where + T: Copy, + A: Readable + Writable, + F: FnOnce(T) -> T, + { + let new = f(self.read()); + self.write(new); + } + + /// Extracts the wrapped raw pointer. + /// + /// ## Example + /// + /// ``` + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut value = 42; + /// let mut volatile = unsafe { VolatilePtr::new((&mut value).into()) }; + /// volatile.write(50); + /// let unwrapped: *mut i32 = volatile.as_raw_ptr().as_ptr(); + /// + /// assert_eq!(unsafe { *unwrapped }, 50); // non volatile access, be careful! + /// ``` + pub fn as_raw_ptr(self) -> NonNull { + self.pointer + } + + /// Constructs a new `VolatilePtr` by mapping the wrapped pointer. + /// + /// This method is useful for accessing only a part of a volatile value, e.g. a subslice or + /// a struct field. For struct field access, there is also the safe [`map_field`] macro that + /// wraps this function. + /// + /// ## Examples + /// + /// Accessing a struct field: + /// + /// ``` + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// struct Example { field_1: u32, field_2: u8, } + /// let mut value = Example { field_1: 15, field_2: 255 }; + /// let mut volatile = unsafe { VolatilePtr::new((&mut value).into()) }; + /// + /// // construct a volatile pointer to a field + /// let field_2 = unsafe { volatile.map(|ptr| NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).field_2)).unwrap()) }; + /// assert_eq!(field_2.read(), 255); + /// ``` + /// + /// Don't misuse this method to do a non-volatile read of the referenced value: + /// + /// ``` + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut value = 5; + /// let mut volatile = unsafe { VolatilePtr::new((&mut value).into()) }; + /// + /// // DON'T DO THIS: + /// let mut readout = 0; + /// unsafe { volatile.map(|value| { + /// readout = *value.as_ptr(); // non-volatile read, might lead to bugs + /// value + /// })}; + /// ``` + pub unsafe fn map(self, f: F) -> VolatilePtr<'a, U, A::RestrictShared> + where + F: FnOnce(NonNull) -> NonNull, + A: Access, + U: ?Sized, + { + unsafe { VolatilePtr::new_restricted(Default::default(), f(self.pointer)) } + } + + pub unsafe fn map_mut(self, f: F) -> VolatilePtr<'a, U, A> + where + F: FnOnce(NonNull) -> NonNull, + U: ?Sized, + A: Access, + { + unsafe { VolatilePtr::new_restricted(A::default(), f(self.pointer)) } + } +} + +/// Methods for restricting access. +impl<'a, T> VolatilePtr<'a, T, ReadWrite> +where + T: ?Sized, +{ + /// Restricts access permissions to read-only. + /// + /// ## Example + /// + /// ``` + /// use volatile::VolatilePtr; + /// use core::ptr::NonNull; + /// + /// let mut value: i16 = -4; + /// let mut volatile = unsafe { VolatilePtr::new((&mut value).into()) }; + /// + /// let read_only = volatile.read_only(); + /// assert_eq!(read_only.read(), -4); + /// // read_only.write(10); // compile-time error + /// ``` + pub fn read_only(self) -> VolatilePtr<'a, T, ReadOnly> { + unsafe { VolatilePtr::new_restricted(ReadOnly, self.pointer) } + } + + /// Restricts access permissions to write-only. + /// + /// ## Example + /// + /// Creating a write-only pointer to a struct field: + /// + /// ``` + /// use volatile::{VolatilePtr, map_field_mut}; + /// use core::ptr::NonNull; + /// + /// struct Example { field_1: u32, field_2: u8, } + /// let mut value = Example { field_1: 15, field_2: 255 }; + /// let mut volatile = unsafe { VolatilePtr::new((&mut value).into()) }; + /// + /// // construct a volatile write-only pointer to `field_2` + /// let mut field_2 = map_field_mut!(volatile.field_2).write_only(); + /// field_2.write(14); + /// // field_2.read(); // compile-time error + /// ``` + pub fn write_only(self) -> VolatilePtr<'a, T, WriteOnly> { + unsafe { VolatilePtr::new_restricted(WriteOnly, self.pointer) } + } +} diff --git a/src/volatile_ptr/tests.rs b/src/volatile_ptr/tests.rs index ffe2759..2b7f717 100644 --- a/src/volatile_ptr/tests.rs +++ b/src/volatile_ptr/tests.rs @@ -1,6 +1,6 @@ use crate::{ access::{ReadOnly, ReadWrite, WriteOnly}, - map_field_mut, VolatilePtrCopy, + map_field_mut, VolatilePtr, }; use core::ptr::NonNull; @@ -8,7 +8,7 @@ use core::ptr::NonNull; fn test_read() { let val = 42; assert_eq!( - unsafe { VolatilePtrCopy::new_read_only(NonNull::from(&val)) }.read(), + unsafe { VolatilePtr::new_read_only(NonNull::from(&val)) }.read(), 42 ); } @@ -16,7 +16,7 @@ fn test_read() { #[test] fn test_write() { let mut val = 50; - let volatile = unsafe { VolatilePtrCopy::new(NonNull::from(&mut val)) }; + let volatile = unsafe { VolatilePtr::new(NonNull::from(&mut val)) }; volatile.write(50); assert_eq!(val, 50); } @@ -24,7 +24,7 @@ fn test_write() { #[test] fn test_update() { let mut val = 42; - let volatile = unsafe { VolatilePtrCopy::new(NonNull::from(&mut val)) }; + let volatile = unsafe { VolatilePtr::new(NonNull::from(&mut val)) }; volatile.update(|v| v + 1); assert_eq!(val, 43); } @@ -35,21 +35,20 @@ fn test_access() { // ReadWrite assert_eq!( - unsafe { VolatilePtrCopy::new_restricted(ReadWrite, NonNull::from(&mut val)) }.read(), + unsafe { VolatilePtr::new_restricted(ReadWrite, NonNull::from(&mut val)) }.read(), 42 ); - unsafe { VolatilePtrCopy::new_restricted(ReadWrite, NonNull::from(&mut val)) }.write(50); + unsafe { VolatilePtr::new_restricted(ReadWrite, NonNull::from(&mut val)) }.write(50); assert_eq!(val, 50); - unsafe { VolatilePtrCopy::new_restricted(ReadWrite, NonNull::from(&mut val)) } - .update(|i| i + 1); + unsafe { VolatilePtr::new_restricted(ReadWrite, NonNull::from(&mut val)) }.update(|i| i + 1); assert_eq!(val, 51); // ReadOnly and WriteOnly assert_eq!( - unsafe { VolatilePtrCopy::new_restricted(ReadOnly, NonNull::from(&mut val)) }.read(), + unsafe { VolatilePtr::new_restricted(ReadOnly, NonNull::from(&mut val)) }.read(), 51 ); - unsafe { VolatilePtrCopy::new_restricted(WriteOnly, NonNull::from(&mut val)) }.write(12); + unsafe { VolatilePtr::new_restricted(WriteOnly, NonNull::from(&mut val)) }.write(12); assert_eq!(val, 12); } @@ -65,7 +64,7 @@ fn test_struct() { field_1: 60, field_2: true, }; - let volatile = unsafe { VolatilePtrCopy::new(NonNull::from(&mut val)) }; + let volatile = unsafe { VolatilePtr::new(NonNull::from(&mut val)) }; unsafe { volatile.map_mut(|s| NonNull::new(core::ptr::addr_of_mut!((*s.as_ptr()).field_1)).unwrap()) } @@ -96,7 +95,7 @@ fn test_struct_macro() { field_1: 60, field_2: true, }; - let volatile = unsafe { VolatilePtrCopy::new(NonNull::from(&mut val)) }; + let volatile = unsafe { VolatilePtr::new(NonNull::from(&mut val)) }; let field_1 = map_field_mut!(volatile.field_1); field_1.update(|v| v + 1); let field_2 = map_field_mut!(volatile.field_2); @@ -115,7 +114,7 @@ fn test_struct_macro() { #[test] fn test_slice() { let val: &mut [u32] = &mut [1, 2, 3]; - let volatile = unsafe { VolatilePtrCopy::new(NonNull::from(val)) }; + let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; volatile.index_mut(0).update(|v| v + 1); let mut dst = [0; 3]; @@ -128,7 +127,7 @@ fn test_slice() { #[should_panic] fn test_bounds_check_1() { let val: &mut [u32] = &mut [1, 2, 3]; - let volatile = unsafe { VolatilePtrCopy::new(NonNull::from(val)) }; + let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; volatile.index_mut(3); } @@ -137,7 +136,7 @@ fn test_bounds_check_1() { #[should_panic] fn test_bounds_check_2() { let val: &mut [u32] = &mut [1, 2, 3]; - let volatile = unsafe { VolatilePtrCopy::new(NonNull::from(val)) }; + let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; volatile.index_mut(2..1); } @@ -146,7 +145,7 @@ fn test_bounds_check_2() { #[should_panic] fn test_bounds_check_3() { let val: &mut [u32] = &mut [1, 2, 3]; - let volatile = unsafe { VolatilePtrCopy::new(NonNull::from(val)) }; + let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; volatile.index_mut(4..); // `3..` is is still ok (see next test) } @@ -154,7 +153,7 @@ fn test_bounds_check_3() { #[test] fn test_bounds_check_4() { let val: &mut [u32] = &mut [1, 2, 3]; - let volatile = unsafe { VolatilePtrCopy::new(NonNull::from(val)) }; + let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; assert_eq!(volatile.index_mut(3..).len(), 0); } @@ -163,7 +162,7 @@ fn test_bounds_check_4() { #[should_panic] fn test_bounds_check_5() { let val: &mut [u32] = &mut [1, 2, 3]; - let volatile = unsafe { VolatilePtrCopy::new(NonNull::from(val)) }; + let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; volatile.index_mut(..4); } @@ -171,17 +170,9 @@ fn test_bounds_check_5() { #[test] fn test_chunks() { let val: &mut [u32] = &mut [1, 2, 3, 4, 5, 6]; - let volatile = unsafe { VolatilePtrCopy::new(NonNull::from(val)) }; + let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; let chunks = volatile.as_chunks_mut().0; chunks.index_mut(1).write([10, 11, 12]); assert_eq!(chunks.index(0).read(), [1, 2, 3]); assert_eq!(chunks.index(1).read(), [10, 11, 12]); } - -#[test] -fn test_lifetime() { - let mut val = 50; - let volatile = VolatilePtrCopy::from_mut_ref(&mut val); - volatile.write(50); - assert_eq!(val, 50); -} diff --git a/src/volatile_ptr/unstable.rs b/src/volatile_ptr/unstable.rs index e819b06..9cc4aa8 100644 --- a/src/volatile_ptr/unstable.rs +++ b/src/volatile_ptr/unstable.rs @@ -7,10 +7,10 @@ use core::{ use crate::{ access::{Access, Readable, Writable}, - VolatilePtrCopy, + VolatilePtr, }; -impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { +impl<'a, T, A> VolatilePtr<'a, [T], A> { pub fn len(self) -> usize { self.pointer.len() } @@ -32,31 +32,31 @@ impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { /// Accessing a single slice element: /// /// ``` - /// use volatile::VolatilePtrCopy; + /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// /// let array = [1, 2, 3]; /// let slice = &array[..]; - /// let volatile = unsafe { VolatilePtrCopy::new_read_only(NonNull::from(slice)) }; + /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(slice)) }; /// assert_eq!(volatile.index(1).read(), 2); /// ``` /// /// Accessing a subslice: /// /// ``` - /// use volatile::VolatilePtrCopy; + /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// /// let array = [1, 2, 3]; /// let slice = &array[..]; - /// let volatile = unsafe { VolatilePtrCopy::new_read_only(NonNull::from(slice)) }; + /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(slice)) }; /// let subslice = volatile.index(1..); /// assert_eq!(subslice.index(0).read(), 2); /// ``` pub fn index( self, index: I, - ) -> VolatilePtrCopy<'a, >::Output, A::RestrictShared> + ) -> VolatilePtr<'a, >::Output, A::RestrictShared> where I: SliceIndex<[T]> + SliceIndex<[()]> + Clone, A: Access, @@ -66,7 +66,7 @@ impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { unsafe { self.map(|slice| slice.get_unchecked_mut(index)) } } - pub fn index_mut(self, index: I) -> VolatilePtrCopy<'a, >::Output, A> + pub fn index_mut(self, index: I) -> VolatilePtr<'a, >::Output, A> where I: SliceIndex<[T]> + SliceIndex<[()]> + Clone, A: Access, @@ -77,24 +77,22 @@ impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { } /// Returns an iterator over the slice. - pub fn iter(self) -> impl Iterator> + pub fn iter(self) -> impl Iterator> where A: Access, { let ptr = self.as_ptr().as_ptr() as *mut T; let len = self.len(); - (0..len).map(move |i| unsafe { - VolatilePtrCopy::new_generic(NonNull::new_unchecked(ptr.add(i))) - }) + (0..len) + .map(move |i| unsafe { VolatilePtr::new_generic(NonNull::new_unchecked(ptr.add(i))) }) } /// Returns an iterator that allows modifying each value. - pub fn iter_mut(self) -> impl Iterator> { + pub fn iter_mut(self) -> impl Iterator> { let ptr = self.as_ptr().as_ptr() as *mut T; let len = self.len(); - (0..len).map(move |i| unsafe { - VolatilePtrCopy::new_generic(NonNull::new_unchecked(ptr.add(i))) - }) + (0..len) + .map(move |i| unsafe { VolatilePtr::new_generic(NonNull::new_unchecked(ptr.add(i))) }) } /// Copies all elements from `self` into `dst`, using a volatile memcpy. @@ -113,13 +111,13 @@ impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { /// Copying two elements from a volatile slice: /// /// ``` - /// use volatile::VolatilePtrCopy; + /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// /// let src = [1, 2]; /// // the `Volatile` type does not work with arrays, so convert `src` to a slice /// let slice = &src[..]; - /// let volatile = unsafe { VolatilePtrCopy::new_read_only(NonNull::from(slice)) }; + /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(slice)) }; /// let mut dst = [5, 0, 0]; /// /// // Because the slices have to be the same length, @@ -169,14 +167,14 @@ impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { /// Copying two elements from a slice into a volatile slice: /// /// ``` - /// use volatile::VolatilePtrCopy; + /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// /// let src = [1, 2, 3, 4]; /// let mut dst = [0, 0]; /// // the `Volatile` type does not work with arrays, so convert `dst` to a slice /// let slice = &mut dst[..]; - /// let mut volatile = VolatilePtrCopy::from_mut_ref(slice); + /// let mut volatile = VolatilePtr::from_mut_ref(slice); /// /// // Because the slices have to be the same length, /// // we slice the source slice from four elements /// // to two. It will panic if we don't do this. @@ -229,12 +227,12 @@ impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { /// /// ``` /// extern crate core; - /// use volatile::VolatilePtrCopy; + /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// /// let mut byte_array = *b"Hello, World!"; /// let mut slice: &mut [u8] = &mut byte_array[..]; - /// let mut volatile = VolatilePtrCopy::from_mut_ref(slice); + /// let mut volatile = VolatilePtr::from_mut_ref(slice); /// volatile.copy_within(1..5, 8); /// /// assert_eq!(&byte_array, b"Hello, Wello!"); @@ -266,8 +264,8 @@ impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { self, mid: usize, ) -> ( - VolatilePtrCopy<'a, [T], A::RestrictShared>, - VolatilePtrCopy<'a, [T], A::RestrictShared>, + VolatilePtr<'a, [T], A::RestrictShared>, + VolatilePtr<'a, [T], A::RestrictShared>, ) where A: Access, @@ -278,10 +276,7 @@ impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { unsafe { self.split_at_unchecked(mid) } } - pub fn split_at_mut( - self, - mid: usize, - ) -> (VolatilePtrCopy<'a, [T], A>, VolatilePtrCopy<'a, [T], A>) + pub fn split_at_mut(self, mid: usize) -> (VolatilePtr<'a, [T], A>, VolatilePtr<'a, [T], A>) where A: Access, { @@ -295,8 +290,8 @@ impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { self, mid: usize, ) -> ( - VolatilePtrCopy<'a, [T], A::RestrictShared>, - VolatilePtrCopy<'a, [T], A::RestrictShared>, + VolatilePtr<'a, [T], A::RestrictShared>, + VolatilePtr<'a, [T], A::RestrictShared>, ) where A: Access, @@ -304,8 +299,8 @@ impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { // SAFETY: Caller has to check that `0 <= mid <= self.len()` unsafe { ( - VolatilePtrCopy::new_generic((self.pointer).get_unchecked_mut(..mid)), - VolatilePtrCopy::new_generic((self.pointer).get_unchecked_mut(mid..)), + VolatilePtr::new_generic((self.pointer).get_unchecked_mut(..mid)), + VolatilePtr::new_generic((self.pointer).get_unchecked_mut(mid..)), ) } } @@ -313,7 +308,7 @@ impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { unsafe fn split_at_mut_unchecked( self, mid: usize, - ) -> (VolatilePtrCopy<'a, [T], A>, VolatilePtrCopy<'a, [T], A>) + ) -> (VolatilePtr<'a, [T], A>, VolatilePtr<'a, [T], A>) where A: Access, { @@ -326,10 +321,10 @@ impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { // is fine. unsafe { ( - VolatilePtrCopy::new_generic( + VolatilePtr::new_generic( NonNull::new(ptr::slice_from_raw_parts_mut(ptr, mid)).unwrap(), ), - VolatilePtrCopy::new_generic( + VolatilePtr::new_generic( NonNull::new(ptr::slice_from_raw_parts_mut(ptr.add(mid), len - mid)).unwrap(), ), ) @@ -339,8 +334,8 @@ impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { pub fn as_chunks( self, ) -> ( - VolatilePtrCopy<'a, [[T; N]], ::RestrictShared>, - VolatilePtrCopy<'a, [T], A::RestrictShared>, + VolatilePtr<'a, [[T; N]], ::RestrictShared>, + VolatilePtr<'a, [T], A::RestrictShared>, ) where A: Access, @@ -356,7 +351,7 @@ impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { pub unsafe fn as_chunks_unchecked( self, - ) -> VolatilePtrCopy<'a, [[T; N]], A::RestrictShared> + ) -> VolatilePtr<'a, [[T; N]], A::RestrictShared> where A: Access, { @@ -372,15 +367,12 @@ impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { new_len, )) .unwrap(); - unsafe { VolatilePtrCopy::new_generic(pointer) } + unsafe { VolatilePtr::new_generic(pointer) } } pub fn as_chunks_mut( self, - ) -> ( - VolatilePtrCopy<'a, [[T; N]], A>, - VolatilePtrCopy<'a, [T], A>, - ) + ) -> (VolatilePtr<'a, [[T; N]], A>, VolatilePtr<'a, [T], A>) where A: Access, { @@ -393,9 +385,7 @@ impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { (array_slice, remainder) } - pub unsafe fn as_chunks_unchecked_mut( - self, - ) -> VolatilePtrCopy<'a, [[T; N]], A> { + pub unsafe fn as_chunks_unchecked_mut(self) -> VolatilePtr<'a, [[T; N]], A> { debug_assert_ne!(N, 0); debug_assert_eq!(self.pointer.len() % N, 0); let new_len = @@ -408,12 +398,12 @@ impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { new_len, )) .unwrap(); - unsafe { VolatilePtrCopy::new_generic(pointer) } + unsafe { VolatilePtr::new_generic(pointer) } } } /// Methods for volatile byte slices -impl VolatilePtrCopy<'_, [u8], A> { +impl VolatilePtr<'_, [u8], A> { /// Sets all elements of the byte slice to the given `value` using a volatile `memset`. /// /// This method is similar to the `slice::fill` method of the standard library, with the @@ -427,11 +417,11 @@ impl VolatilePtrCopy<'_, [u8], A> { /// ## Example /// /// ```rust - /// use volatile::VolatilePtrCopy; + /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// /// let mut vec = vec![0; 10]; - /// let mut buf = VolatilePtrCopy::from_mut_ref(vec.as_mut_slice()); + /// let mut buf = VolatilePtr::from_mut_ref(vec.as_mut_slice()); /// buf.fill(1); /// assert_eq!(unsafe { buf.as_ptr().as_mut() }, &mut vec![1; 10]); /// ``` @@ -449,7 +439,7 @@ impl VolatilePtrCopy<'_, [u8], A> { /// /// These methods are only available with the `unstable` feature enabled (requires a nightly /// Rust compiler). -impl<'a, T, A, const N: usize> VolatilePtrCopy<'a, [T; N], A> { +impl<'a, T, A, const N: usize> VolatilePtr<'a, [T; N], A> { /// Converts an array reference to a shared slice. /// /// This makes it possible to use the methods defined on slices. @@ -459,11 +449,11 @@ impl<'a, T, A, const N: usize> VolatilePtrCopy<'a, [T; N], A> { /// Copying two elements from a volatile array reference using `copy_into_slice`: /// /// ``` - /// use volatile::VolatilePtrCopy; + /// use volatile::VolatilePtr; /// use core::ptr::NonNull; /// /// let src = [1, 2]; - /// let volatile = unsafe { VolatilePtrCopy::new_read_only(NonNull::from(&src)) }; + /// let volatile = unsafe { VolatilePtr::new_read_only(NonNull::from(&src)) }; /// let mut dst = [0, 0]; /// /// // convert the `Volatile<&[i32; 2]>` array reference to a `Volatile<&[i32]>` slice @@ -473,7 +463,7 @@ impl<'a, T, A, const N: usize> VolatilePtrCopy<'a, [T; N], A> { /// /// assert_eq!(dst, [1, 2]); /// ``` - pub fn as_slice(self) -> VolatilePtrCopy<'a, [T], A::RestrictShared> + pub fn as_slice(self) -> VolatilePtr<'a, [T], A::RestrictShared> where A: Access, { @@ -493,12 +483,12 @@ impl<'a, T, A, const N: usize> VolatilePtrCopy<'a, [T; N], A> { /// Copying two elements into a volatile array reference using `copy_from_slice`: /// /// ``` - /// use volatile::{access, VolatilePtrCopy}; + /// use volatile::{access, VolatilePtr}; /// use core::ptr::NonNull; /// /// let src = [1, 2]; /// let mut dst = [0, 0]; - /// let mut volatile = unsafe { VolatilePtrCopy::new_restricted(access::WriteOnly, NonNull::from(&dst)) }; + /// let mut volatile = unsafe { VolatilePtr::new_restricted(access::WriteOnly, NonNull::from(&dst)) }; /// /// // convert the `Volatile<[i32; 2]>` array reference to a `Volatile<[i32]>` slice /// let mut volatile_slice = volatile.as_slice_mut(); @@ -507,7 +497,7 @@ impl<'a, T, A, const N: usize> VolatilePtrCopy<'a, [T; N], A> { /// /// assert_eq!(dst, [1, 2]); /// ``` - pub fn as_slice_mut(self) -> VolatilePtrCopy<'a, [T], A> + pub fn as_slice_mut(self) -> VolatilePtr<'a, [T], A> where A: Access, { diff --git a/src/volatile_ptr/very_unstable.rs b/src/volatile_ptr/very_unstable.rs index c3c12a2..f7bddcb 100644 --- a/src/volatile_ptr/very_unstable.rs +++ b/src/volatile_ptr/very_unstable.rs @@ -1,33 +1,33 @@ use core::ptr::NonNull; -use crate::{access::Access, VolatilePtrCopy}; +use crate::{access::Access, VolatilePtr}; -impl<'a, T, A> VolatilePtrCopy<'a, T, A> +impl<'a, T, A> VolatilePtr<'a, T, A> where T: ?Sized, { - pub const unsafe fn map_const(self, f: F) -> VolatilePtrCopy<'a, U, A::RestrictShared> + pub const unsafe fn map_const(self, f: F) -> VolatilePtr<'a, U, A::RestrictShared> where F: ~const FnOnce(NonNull) -> NonNull, A: Access, U: ?Sized, { - unsafe { VolatilePtrCopy::new_generic(f(self.pointer)) } + unsafe { VolatilePtr::new_generic(f(self.pointer)) } } - pub const unsafe fn map_mut_const(self, f: F) -> VolatilePtrCopy<'a, U, A> + pub const unsafe fn map_mut_const(self, f: F) -> VolatilePtr<'a, U, A> where F: ~const FnOnce(NonNull) -> NonNull, U: ?Sized, { - unsafe { VolatilePtrCopy::new_generic(f(self.pointer)) } + unsafe { VolatilePtr::new_generic(f(self.pointer)) } } } /// Methods for volatile slices #[cfg(feature = "unstable")] -impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { - pub const fn index_const(self, index: usize) -> VolatilePtrCopy<'a, T, A::RestrictShared> +impl<'a, T, A> VolatilePtr<'a, [T], A> { + pub const fn index_const(self, index: usize) -> VolatilePtr<'a, T, A::RestrictShared> where A: Access, { @@ -47,7 +47,7 @@ impl<'a, T, A> VolatilePtrCopy<'a, [T], A> { unsafe { self.map_const(Mapper { index }) } } - pub const fn index_mut_const(self, index: usize) -> VolatilePtrCopy<'a, T, A> { + pub const fn index_mut_const(self, index: usize) -> VolatilePtr<'a, T, A> { assert!(index < self.pointer.len(), "index out of bounds"); struct Mapper { From ad026424146eeecd57abaa0694dc20ebb893b71c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 28 Apr 2023 21:34:48 +0200 Subject: [PATCH 52/68] Make `VolatileRef` copyable if it's read-only --- src/access.rs | 24 +++++++++++++++++------- src/volatile_ref/mod.rs | 23 ++++++++++++++++++++++- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/access.rs b/src/access.rs index 4c4dbd3..202b5f4 100644 --- a/src/access.rs +++ b/src/access.rs @@ -18,13 +18,6 @@ pub trait Readable: Copy + Default { } } -impl Access for T -where - T: Readable + Default + Copy, -{ - type RestrictShared = ::RestrictShared; -} - /// Helper trait that is implemented by [`ReadWrite`] and [`WriteOnly`]. pub trait Writable: Access { /// Ensures that this trait cannot be implemented outside of this crate. @@ -33,6 +26,21 @@ pub trait Writable: Access { } } +/// Implemented for access types that permit copying of `VolatileRef`. +pub trait Copyable { + /// Ensures that this trait cannot be implemented outside of this crate. + fn _private() -> _Private { + _Private + } +} + +impl Access for T +where + T: Readable + Default + Copy, +{ + type RestrictShared = ::RestrictShared; +} + /// Zero-sized marker type for allowing both read and write access. #[derive(Debug, Default, Copy, Clone)] pub struct ReadWrite; @@ -47,6 +55,7 @@ pub struct ReadOnly; impl Readable for ReadOnly { type RestrictShared = ReadOnly; } +impl Copyable for ReadOnly {} /// Zero-sized marker type for allowing only write access. #[derive(Debug, Default, Copy, Clone)] @@ -62,6 +71,7 @@ pub struct NoAccess; impl Access for NoAccess { type RestrictShared = NoAccess; } +impl Copyable for NoAccess {} #[non_exhaustive] #[doc(hidden)] diff --git a/src/volatile_ref/mod.rs b/src/volatile_ref/mod.rs index 96ec8db..e12d01b 100644 --- a/src/volatile_ref/mod.rs +++ b/src/volatile_ref/mod.rs @@ -6,7 +6,7 @@ //! looking at the `Atomic` wrapper types found in `libcore` or `libstd`. use crate::{ - access::{Access, ReadOnly, ReadWrite, Readable, WriteOnly}, + access::{Access, Copyable, ReadOnly, ReadWrite, Readable, WriteOnly}, volatile_ptr, }; use core::{fmt, marker::PhantomData, ptr::NonNull}; @@ -147,6 +147,27 @@ where } } +impl<'a, T, A> Clone for VolatileRef<'a, T, A> +where + T: ?Sized, + A: Access + Copyable, +{ + fn clone(&self) -> Self { + Self { + pointer: self.pointer, + reference: self.reference, + access: self.access, + } + } +} + +impl<'a, T, A> Copy for VolatileRef<'a, T, A> +where + T: ?Sized, + A: Access + Copyable, +{ +} + unsafe impl Send for VolatileRef<'_, T, A> where T: Sync {} unsafe impl Sync for VolatileRef<'_, T, A> where T: Sync {} From 519895ea972f6cf9d5aeaf2c9e3b7192cda13f5e Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 28 Apr 2023 22:00:21 +0200 Subject: [PATCH 53/68] Improve docs --- src/access.rs | 5 +++++ src/lib.rs | 17 +++++++++++++++ src/volatile_ptr/mod.rs | 7 ------ src/volatile_ptr/operations.rs | 23 ++++++++++++++++---- src/{volatile_ref/mod.rs => volatile_ref.rs} | 21 +++++++++--------- 5 files changed, 51 insertions(+), 22 deletions(-) rename src/{volatile_ref/mod.rs => volatile_ref.rs} (86%) diff --git a/src/access.rs b/src/access.rs index 202b5f4..42d4ec6 100644 --- a/src/access.rs +++ b/src/access.rs @@ -1,3 +1,6 @@ +//! Marker types for limiting access. + +/// Private trait that is implemented for the types in this module. pub trait Access: Copy + Default { /// Ensures that this trait cannot be implemented outside of this crate. #[doc(hidden)] @@ -5,11 +8,13 @@ pub trait Access: Copy + Default { _Private } + /// Reduced access level to safely share the corresponding value. type RestrictShared: Access; } /// Helper trait that is implemented by [`ReadWrite`] and [`ReadOnly`]. pub trait Readable: Copy + Default { + /// Reduced access level to safely share the corresponding value. type RestrictShared: Readable + Access; /// Ensures that this trait cannot be implemented outside of this crate. diff --git a/src/lib.rs b/src/lib.rs index db19360..5260f2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,20 @@ +//! Provides volatile wrapper types for raw pointers. +//! +//! The volatile wrapper types in this crate wrap a pointer to any [`Copy`]-able +//! type and provide volatile memory access to wrapped value. Volatile memory accesses are +//! never optimized away by the compiler, and are useful in many low-level systems programming +//! and concurrent contexts. +//! +//! This crate provides two different wrapper types: [`VolatilePtr`] and [`VolatileRef`]. The +//! difference between the two types is that the former behaves like a raw pointer, while the +//! latter behaves like a Rust reference type. For example, `VolatilePtr` can be freely copied, +//! but not sent across threads because this could introduce mutable aliasing. The `VolatileRef` +//! type, on the other hand, requires exclusive access for mutation, so that sharing it across +//! thread boundaries is safe. +//! +//! Both wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider +//! looking at the `Atomic` wrapper types found in `libcore` or `libstd`. + #![no_std] #![cfg_attr(feature = "unstable", feature(core_intrinsics))] #![cfg_attr(feature = "unstable", feature(slice_range))] diff --git a/src/volatile_ptr/mod.rs b/src/volatile_ptr/mod.rs index 9c4dcb4..7326293 100644 --- a/src/volatile_ptr/mod.rs +++ b/src/volatile_ptr/mod.rs @@ -1,10 +1,3 @@ -//! Provides the wrapper type `Volatile`, which wraps a reference to any copy-able type and allows -//! for volatile memory access to wrapped value. Volatile memory accesses are never optimized away -//! by the compiler, and are useful in many low-level systems programming and concurrent contexts. -//! -//! The wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider -//! looking at the `Atomic` wrapper types found in `libcore` or `libstd`. - use core::{fmt, marker::PhantomData, ptr::NonNull}; use crate::access::{ReadWrite, Readable}; diff --git a/src/volatile_ptr/operations.rs b/src/volatile_ptr/operations.rs index f548c5b..6ebfd2f 100644 --- a/src/volatile_ptr/operations.rs +++ b/src/volatile_ptr/operations.rs @@ -17,14 +17,31 @@ impl<'a, T> VolatilePtr<'a, T> where T: ?Sized, { + /// Turns the given pointer into a `VolatilePtr`. + /// + /// ## Safety + /// + /// - The given pointer must be valid. + /// - No other thread must have access to the given pointer. This must remain true + /// for the whole lifetime of the `VolatilePtr`. pub unsafe fn new(pointer: NonNull) -> VolatilePtr<'a, T, ReadWrite> { unsafe { VolatilePtr::new_restricted(ReadWrite, pointer) } } + /// Creates a new read-only volatile pointer from the given raw pointer. + /// + /// ## Safety + /// + /// The requirements for [`Self::new`] apply to this function too. pub const unsafe fn new_read_only(pointer: NonNull) -> VolatilePtr<'a, T, ReadOnly> { unsafe { Self::new_restricted(ReadOnly, pointer) } } + /// Creates a new volatile pointer with restricted access from the given raw pointer. + /// + /// ## Safety + /// + /// The requirements for [`Self::new`] apply to this function too. pub const unsafe fn new_restricted(access: A, pointer: NonNull) -> VolatilePtr<'a, T, A> where A: Access, @@ -70,7 +87,6 @@ where T: Copy, A: Readable, { - // UNSAFE: Safe, as ... TODO unsafe { ptr::read_volatile(self.pointer.as_ptr()) } } @@ -97,7 +113,6 @@ where T: Copy, A: Writable, { - // UNSAFE: Safe, as ... TODO unsafe { ptr::write_volatile(self.pointer.as_ptr(), value) }; } @@ -149,8 +164,8 @@ where /// Constructs a new `VolatilePtr` by mapping the wrapped pointer. /// /// This method is useful for accessing only a part of a volatile value, e.g. a subslice or - /// a struct field. For struct field access, there is also the safe [`map_field`] macro that - /// wraps this function. + /// a struct field. For struct field access, there is also the safe + /// [`map_field`][crate::map_field] macro that wraps this function. /// /// ## Examples /// diff --git a/src/volatile_ref/mod.rs b/src/volatile_ref.rs similarity index 86% rename from src/volatile_ref/mod.rs rename to src/volatile_ref.rs index e12d01b..622f55c 100644 --- a/src/volatile_ref/mod.rs +++ b/src/volatile_ref.rs @@ -1,21 +1,20 @@ -//! Provides the wrapper type `Volatile`, which wraps a reference to any copy-able type and allows -//! for volatile memory access to wrapped value. Volatile memory accesses are never optimized away -//! by the compiler, and are useful in many low-level systems programming and concurrent contexts. -//! -//! The wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider -//! looking at the `Atomic` wrapper types found in `libcore` or `libstd`. - use crate::{ access::{Access, Copyable, ReadOnly, ReadWrite, Readable, WriteOnly}, volatile_ptr, }; use core::{fmt, marker::PhantomData, ptr::NonNull}; -/// Wraps a pointer to make accesses to the referenced value volatile. +/// Volatile pointer type that respects Rust's aliasing rules. +/// +/// This pointer type behaves similar to Rust's reference types: +/// +/// - it requires exclusive `&mut self` access for mutability +/// - only read-only types implement [`Clone`] and [`Copy`] +/// - [`Send`] and [`Sync`] are implemented if `T: Sync` /// -/// Allows volatile reads and writes on the referenced value. The referenced value needs to -/// be `Copy` for reading and writing, as volatile reads and writes take and return copies -/// of the value. +/// To perform volatile operations on `VolatileRef` types, use the [`as_ptr`][Self::as_ptr] +/// or [`as_mut_ptr`](Self::as_mut_ptr) methods to create a temporary +/// [`VolatilePtr`][crate::VolatilePtr] instance. /// /// Since not all volatile resources (e.g. memory mapped device registers) are both readable /// and writable, this type supports limiting the allowed access types through an optional second From 739e5b234d02d39e16452fcbcb212f7264381ebf Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 28 Apr 2023 22:20:17 +0200 Subject: [PATCH 54/68] Fix unstable module --- src/volatile_ptr/operations.rs | 2 +- src/volatile_ptr/unstable.rs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/volatile_ptr/operations.rs b/src/volatile_ptr/operations.rs index 6ebfd2f..4347840 100644 --- a/src/volatile_ptr/operations.rs +++ b/src/volatile_ptr/operations.rs @@ -50,7 +50,7 @@ where unsafe { Self::new_generic(pointer) } } - const unsafe fn new_generic(pointer: NonNull) -> VolatilePtr<'a, T, A> { + pub(super) const unsafe fn new_generic(pointer: NonNull) -> VolatilePtr<'a, T, A> { VolatilePtr { pointer, reference: PhantomData, diff --git a/src/volatile_ptr/unstable.rs b/src/volatile_ptr/unstable.rs index 9cc4aa8..ae6d900 100644 --- a/src/volatile_ptr/unstable.rs +++ b/src/volatile_ptr/unstable.rs @@ -81,7 +81,7 @@ impl<'a, T, A> VolatilePtr<'a, [T], A> { where A: Access, { - let ptr = self.as_ptr().as_ptr() as *mut T; + let ptr = self.as_raw_ptr().as_ptr() as *mut T; let len = self.len(); (0..len) .map(move |i| unsafe { VolatilePtr::new_generic(NonNull::new_unchecked(ptr.add(i))) }) @@ -89,7 +89,7 @@ impl<'a, T, A> VolatilePtr<'a, [T], A> { /// Returns an iterator that allows modifying each value. pub fn iter_mut(self) -> impl Iterator> { - let ptr = self.as_ptr().as_ptr() as *mut T; + let ptr = self.as_raw_ptr().as_ptr() as *mut T; let len = self.len(); (0..len) .map(move |i| unsafe { VolatilePtr::new_generic(NonNull::new_unchecked(ptr.add(i))) }) @@ -174,8 +174,8 @@ impl<'a, T, A> VolatilePtr<'a, [T], A> { /// let mut dst = [0, 0]; /// // the `Volatile` type does not work with arrays, so convert `dst` to a slice /// let slice = &mut dst[..]; - /// let mut volatile = VolatilePtr::from_mut_ref(slice); - /// /// // Because the slices have to be the same length, + /// let mut volatile = unsafe { VolatilePtr::new(NonNull::from(slice)) }; + /// // Because the slices have to be the same length, /// // we slice the source slice from four elements /// // to two. It will panic if we don't do this. /// volatile.copy_from_slice(&src[2..]); @@ -232,7 +232,7 @@ impl<'a, T, A> VolatilePtr<'a, [T], A> { /// /// let mut byte_array = *b"Hello, World!"; /// let mut slice: &mut [u8] = &mut byte_array[..]; - /// let mut volatile = VolatilePtr::from_mut_ref(slice); + /// let mut volatile = unsafe { VolatilePtr::new(NonNull::from(slice)) }; /// volatile.copy_within(1..5, 8); /// /// assert_eq!(&byte_array, b"Hello, Wello!"); @@ -421,9 +421,9 @@ impl VolatilePtr<'_, [u8], A> { /// use core::ptr::NonNull; /// /// let mut vec = vec![0; 10]; - /// let mut buf = VolatilePtr::from_mut_ref(vec.as_mut_slice()); + /// let mut buf = unsafe { VolatilePtr::new(NonNull::from(vec.as_mut_slice())) }; /// buf.fill(1); - /// assert_eq!(unsafe { buf.as_ptr().as_mut() }, &mut vec![1; 10]); + /// assert_eq!(unsafe { buf.as_raw_ptr().as_mut() }, &mut vec![1; 10]); /// ``` pub fn fill(self, value: u8) where From e539980f83d876b07e0d28613ec3b9101b45a524 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 24 Jun 2023 12:48:45 +0200 Subject: [PATCH 55/68] Explain why there is no `VolatileCell` type --- src/lib.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 5260f2a..d46ab73 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,22 @@ //! //! Both wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider //! looking at the `Atomic` wrapper types found in `libcore` or `libstd`. +//! +//! ## Why is there no `VolatileCell`? +//! +//! Many people expressed interest in a `VolatileCell` type, i.e. a transparent wrapper type that +//! owns the wrapped value. Such a type would be similar to [`core::cell::Cell`], with the +//! difference that all methods are volatile. +//! +//! Unfortunately, it is not sound to implement such a `VolatileCell` type in Rust. The reason +//! is that Rust and LLVM consider `&` and `&mut` references as _dereferencable_. This means that +//! the compiler is allowed to freely access the referenced value without any restrictions. So +//! no matter how a `VolatileCell` type is implemented, the compiler is allowed to perform +//! non-volatile read operations of the contained value, which can lead to unexpected (or even +//! undefined?) behavior. For more details, see the discussion +//! [in our repository](https://github.com/rust-osdev/volatile/issues/31) +//! and +//! [in the `unsafe-code-guidelines` repository](https://github.com/rust-lang/unsafe-code-guidelines/issues/411). #![no_std] #![cfg_attr(feature = "unstable", feature(core_intrinsics))] From bacaa7789b4dbf2a1699b8647fd3111f7a931188 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 24 Jun 2023 12:51:06 +0200 Subject: [PATCH 56/68] Update crate description in README --- README.md | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 813c85e..32b8ad3 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,27 @@ [![Build Status](https://github.com/rust-osdev/volatile/workflows/Build/badge.svg)](https://github.com/rust-osdev/volatile/actions?query=workflow%3ABuild) [![Docs.rs Badge](https://docs.rs/volatile/badge.svg)](https://docs.rs/volatile/) -Provides the wrapper type `Volatile`, which wraps a reference to any copy-able type and allows for volatile memory access to wrapped value. Volatile memory accesses are never optimized away by the compiler, and are useful in many low-level systems programming and concurrent contexts. +Provides volatile wrapper types for raw pointers. -The wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider looking at the `Atomic` wrapper types found in `libcore` or `libstd`. +The volatile wrapper types in this crate wrap a pointer to any [`Copy`]-able type and provide volatile memory access to wrapped value. +Volatile memory accesses are never optimized away by the compiler, and are useful in many low-level systems programming and concurrent contexts. + +This crate provides two different wrapper types: [`VolatilePtr`] and [`VolatileRef`]. +The difference between the two types is that the former behaves like a raw pointer, while the latter behaves like a Rust reference type. +For example, `VolatilePtr` can be freely copied, but not sent across threads because this could introduce mutable aliasing. +The `VolatileRef` type, on the other hand, requires exclusive access for mutation, so that sharing it across thread boundaries is safe. + +Both wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider looking at the `Atomic` wrapper types found in `libcore` or `libstd`. + +## Why is there no `VolatileCell`? + +Many people expressed interest in a `VolatileCell` type, i.e. a transparent wrapper type that owns the wrapped value. +Such a type would be similar to [`core::cell::Cell`], with the difference that all methods are volatile. +Unfortunately, it is not sound to implement such a `VolatileCell` type in Rust. +The reason is that Rust and LLVM consider `&` and `&mut` references as _dereferencable_. +This means that the compiler is allowed to freely access the referenced value without any restrictions. +So no matter how a `VolatileCell` type is implemented, the compiler is allowed to perform non-volatile read operations of the contained value, which can lead to unexpected (or even undefined?) behavior. +For more details, see the discussion [in our repository](https://github.com/rust-osdev/volatile/issues/31) and [in the `unsafe-code-guidelines` repository](https://github.com/rust-lang/unsafe-code-guidelines/issues/411). ## License From 50cbec6fcf4cbca9aa1e1e1bfdd2a1c1c197c44c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 24 Jun 2023 12:51:50 +0200 Subject: [PATCH 57/68] Update crate metadata --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 85f0312..4169f10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.4.6" authors = ["Philipp Oppermann "] license = "MIT OR Apache-2.0" keywords = ["volatile"] -description = "A simple volatile wrapper type" +description = "Volatile wrapper types for raw pointers" documentation = "https://docs.rs/volatile" repository = "https://github.com/rust-osdev/volatile" edition = "2021" From 759e312575c50dc4090d60e0b9d32122237287bf Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 24 Jun 2023 12:52:18 +0200 Subject: [PATCH 58/68] Delete old `ref.rs` file --- src/ref.rs | 874 ----------------------------------------------------- 1 file changed, 874 deletions(-) delete mode 100644 src/ref.rs diff --git a/src/ref.rs b/src/ref.rs deleted file mode 100644 index 5581eb0..0000000 --- a/src/ref.rs +++ /dev/null @@ -1,874 +0,0 @@ -//! Provides the wrapper type `Volatile`, which wraps a reference to any copy-able type and allows -//! for volatile memory access to wrapped value. Volatile memory accesses are never optimized away -//! by the compiler, and are useful in many low-level systems programming and concurrent contexts. -//! -//! The wrapper types *do not* enforce any atomicity guarantees; to also get atomicity, consider -//! looking at the `Atomic` wrapper types found in `libcore` or `libstd`. - -#![no_std] -#![cfg_attr(feature = "unstable", feature(core_intrinsics))] -#![cfg_attr(feature = "unstable", feature(slice_range))] -#![cfg_attr(feature = "unstable", allow(incomplete_features))] -#![cfg_attr(all(feature = "unstable", test), feature(slice_as_chunks))] -#![warn(missing_docs)] - -use access::{ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; -use core::{ - fmt, - marker::PhantomData, - ops::{Deref, DerefMut, Index, IndexMut}, - ptr, - slice::SliceIndex, -}; -#[cfg(feature = "unstable")] -use core::{ - intrinsics, - ops::{Range, RangeBounds}, - slice::range, -}; - -/// Allows creating read-only and write-only `Volatile` values. -pub mod access; - -/// Wraps a reference to make accesses to the referenced value volatile. -/// -/// Allows volatile reads and writes on the referenced value. The referenced value needs to -/// be `Copy` for reading and writing, as volatile reads and writes take and return copies -/// of the value. -/// -/// Since not all volatile resources (e.g. memory mapped device registers) are both readable -/// and writable, this type supports limiting the allowed access types through an optional second -/// generic parameter `A` that can be one of `ReadWrite`, `ReadOnly`, or `WriteOnly`. It defaults -/// to `ReadWrite`, which allows all operations. -/// -/// The size of this struct is the same as the size of the contained reference. -#[derive(Clone)] -#[repr(transparent)] -pub struct Volatile { - reference: R, - access: PhantomData, -} - -/// Constructor functions for creating new values -/// -/// These functions allow to construct a new `Volatile` instance from a reference type. While -/// the `new` function creates a `Volatile` instance with unrestricted access, there are also -/// functions for creating read-only or write-only instances. -impl Volatile { - /// Constructs a new volatile instance wrapping the given reference. - /// - /// While it is possible to construct `Volatile` instances from arbitrary values (including - /// non-reference values), most of the methods are only available when the wrapped type is - /// a reference. The only reason that we don't forbid non-reference types in the constructor - /// functions is that the Rust compiler does not support trait bounds on generic `const` - /// functions yet. When this becomes possible, we will release a new version of this library - /// with removed support for non-references. For these reasons it is recommended to use - /// the `Volatile` type only with references. - /// - /// ## Example - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let mut value = 0u32; - /// - /// let mut volatile = Volatile::new(&mut value); - /// volatile.write(1); - /// assert_eq!(volatile.read(), 1); - /// ``` - pub const fn new(reference: R) -> Volatile { - Volatile { - reference, - access: PhantomData, - } - } - - /// Constructs a new read-only volatile instance wrapping the given reference. - /// - /// This is equivalent to the `new` function with the difference that the returned - /// `Volatile` instance does not permit write operations. This is for example useful - /// with memory-mapped hardware registers that are defined as read-only by the hardware. - /// - /// ## Example - /// - /// Reading is allowed: - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let value = 0u32; - /// - /// let volatile = Volatile::new_read_only(&value); - /// assert_eq!(volatile.read(), 0); - /// ``` - /// - /// But writing is not: - /// - /// ```compile_fail - /// use volatile::Volatile; - /// - /// let mut value = 0u32; - /// - /// let mut volatile = Volatile::new_read_only(&mut value); - /// volatile.write(1); - /// //ERROR: ^^^^^ the trait `volatile::access::Writable` is not implemented - /// // for `volatile::access::ReadOnly` - /// ``` - pub const fn new_read_only(reference: R) -> Volatile { - Volatile { - reference, - access: PhantomData, - } - } - - /// Constructs a new write-only volatile instance wrapping the given reference. - /// - /// This is equivalent to the `new` function with the difference that the returned - /// `Volatile` instance does not permit read operations. This is for example useful - /// with memory-mapped hardware registers that are defined as write-only by the hardware. - /// - /// ## Example - /// - /// Writing is allowed: - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let mut value = 0u32; - /// - /// let mut volatile = Volatile::new_write_only(&mut value); - /// volatile.write(1); - /// ``` - /// - /// But reading is not: - /// - /// ```compile_fail - /// use volatile::Volatile; - /// - /// let value = 0u32; - /// - /// let volatile = Volatile::new_write_only(&value); - /// volatile.read(); - /// //ERROR: ^^^^ the trait `volatile::access::Readable` is not implemented - /// // for `volatile::access::WriteOnly` - /// ``` - pub const fn new_write_only(reference: R) -> Volatile { - Volatile { - reference, - access: PhantomData, - } - } -} - -/// Methods for references to `Copy` types -impl Volatile -where - R: Deref, - T: Copy, -{ - /// Performs a volatile read of the contained value. - /// - /// Returns a copy of the read value. Volatile reads are guaranteed not to be optimized - /// away by the compiler, but by themselves do not have atomic ordering - /// guarantees. To also get atomicity, consider looking at the `Atomic` wrapper types of - /// the standard/`core` library. - /// - /// ## Examples - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let value = 42; - /// let shared_reference = Volatile::new(&value); - /// assert_eq!(shared_reference.read(), 42); - /// - /// let mut value = 50; - /// let mut_reference = Volatile::new(&mut value); - /// assert_eq!(mut_reference.read(), 50); - /// ``` - pub fn read(&self) -> T - where - A: Readable, - { - // UNSAFE: Safe, as we know that our internal value exists. - unsafe { ptr::read_volatile(&*self.reference) } - } - - /// Performs a volatile write, setting the contained value to the given `value`. - /// - /// Volatile writes are guaranteed to not be optimized away by the compiler, but by - /// themselves do not have atomic ordering guarantees. To also get atomicity, consider - /// looking at the `Atomic` wrapper types of the standard/`core` library. - /// - /// ## Example - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let mut value = 42; - /// let mut volatile = Volatile::new(&mut value); - /// volatile.write(50); - /// - /// assert_eq!(volatile.read(), 50); - /// ``` - pub fn write(&mut self, value: T) - where - A: Writable, - R: DerefMut, - { - // UNSAFE: Safe, as we know that our internal value exists. - unsafe { ptr::write_volatile(&mut *self.reference, value) }; - } - - /// Updates the contained value using the given closure and volatile instructions. - /// - /// Performs a volatile read of the contained value, passes a mutable reference to it to the - /// function `f`, and then performs a volatile write of the (potentially updated) value back to - /// the contained value. - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let mut value = 42; - /// let mut volatile = Volatile::new(&mut value); - /// volatile.update(|val| *val += 1); - /// - /// assert_eq!(volatile.read(), 43); - /// ``` - pub fn update(&mut self, f: F) - where - A: Readable + Writable, - R: DerefMut, - F: FnOnce(&mut T), - { - let mut value = self.read(); - f(&mut value); - self.write(value); - } -} - -/// Method for extracting the wrapped value. -impl Volatile { - /// Extracts the inner value stored in the wrapper type. - /// - /// This method gives direct access to the wrapped reference and thus allows - /// non-volatile access again. This is seldom what you want since there is usually - /// a reason that a reference is wrapped in `Volatile`. However, in some cases it might - /// be required or useful to use the `read_volatile`/`write_volatile` pointer methods of - /// the standard library directly, which this method makes possible. - /// - /// Since no memory safety violation can occur when accessing the referenced value using - /// non-volatile operations, this method is safe. However, it _can_ lead to bugs at the - /// application level, so this method should be used with care. - /// - /// ## Example - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut value = 42; - /// let mut volatile = Volatile::new(&mut value); - /// volatile.write(50); - /// let unwrapped: &mut i32 = volatile.extract_inner(); - /// - /// assert_eq!(*unwrapped, 50); // non volatile access, be careful! - /// ``` - pub fn extract_inner(self) -> R { - self.reference - } -} - -/// Transformation methods for accessing struct fields -impl Volatile -where - R: Deref, - T: ?Sized, -{ - /// Constructs a new `Volatile` reference by mapping the wrapped value. - /// - /// This method is useful for accessing individual fields of volatile structs. - /// - /// Note that this method gives temporary access to the wrapped reference, which allows - /// accessing the value in a non-volatile way. This is normally not what you want, so - /// **this method should only be used for reference-to-reference transformations**. - /// - /// ## Examples - /// - /// Accessing a struct field: - /// - /// ``` - /// use volatile::Volatile; - /// - /// struct Example { field_1: u32, field_2: u8, } - /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = Volatile::new(&mut value); - /// - /// // construct a volatile reference to a field - /// let field_2 = volatile.map(|example| &example.field_2); - /// assert_eq!(field_2.read(), 255); - /// ``` - /// - /// Don't misuse this method to do a non-volatile read of the referenced value: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut value = 5; - /// let mut volatile = Volatile::new(&mut value); - /// - /// // DON'T DO THIS: - /// let mut readout = 0; - /// volatile.map(|value| { - /// readout = *value; // non-volatile read, might lead to bugs - /// value - /// }); - /// ``` - pub fn map<'a, F, U>(&'a self, f: F) -> Volatile<&'a U, A> - where - F: FnOnce(&'a T) -> &'a U, - U: ?Sized, - T: 'a, - { - Volatile { - reference: f(self.reference.deref()), - access: self.access, - } - } - - /// Constructs a new mutable `Volatile` reference by mapping the wrapped value. - /// - /// This method is useful for accessing individual fields of volatile structs. - /// - /// Note that this method gives temporary access to the wrapped reference, which allows - /// accessing the value in a non-volatile way. This is normally not what you want, so - /// **this method should only be used for reference-to-reference transformations**. - /// - /// ## Examples - /// - /// Accessing a struct field: - /// - /// ``` - /// use volatile::Volatile; - /// - /// struct Example { field_1: u32, field_2: u8, } - /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = Volatile::new(&mut value); - /// - /// // construct a volatile reference to a field - /// let mut field_2 = volatile.map_mut(|example| &mut example.field_2); - /// field_2.write(128); - /// assert_eq!(field_2.read(), 128); - /// ``` - /// - /// Don't misuse this method to do a non-volatile read or write of the referenced value: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut value = 5; - /// let mut volatile = Volatile::new(&mut value); - /// - /// // DON'T DO THIS: - /// volatile.map_mut(|value| { - /// *value = 10; // non-volatile write, might lead to bugs - /// value - /// }); - /// ``` - pub fn map_mut<'a, F, U>(&'a mut self, f: F) -> Volatile<&'a mut U, A> - where - F: FnOnce(&mut T) -> &mut U, - R: DerefMut, - U: ?Sized, - T: 'a, - { - Volatile { - reference: f(&mut self.reference), - access: self.access, - } - } -} - -/// Methods for volatile slices -impl Volatile -where - R: Deref, -{ - /// Applies the index operation on the wrapped slice. - /// - /// Returns a shared `Volatile` reference to the resulting subslice. - /// - /// This is a convenience method for the `map(|slice| slice.index(index))` operation, so it - /// has the same behavior as the indexing operation on slice (e.g. panic if index is - /// out-of-bounds). - /// - /// ## Examples - /// - /// Accessing a single slice element: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let array = [1, 2, 3]; - /// let slice = &array[..]; - /// let volatile = Volatile::new(slice); - /// assert_eq!(volatile.index(1).read(), 2); - /// ``` - /// - /// Accessing a subslice: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let array = [1, 2, 3]; - /// let slice = &array[..]; - /// let volatile = Volatile::new(slice); - /// let subslice = volatile.index(1..); - /// assert_eq!(subslice.index(0).read(), 2); - /// ``` - pub fn index<'a, I>(&'a self, index: I) -> Volatile<&'a I::Output, A> - where - I: SliceIndex<[T]>, - T: 'a, - { - self.map(|slice| slice.index(index)) - } - - /// Applies the mutable index operation on the wrapped slice. - /// - /// Returns a mutable `Volatile` reference to the resulting subslice. - /// - /// This is a convenience method for the `map_mut(|slice| slice.index_mut(index))` - /// operation, so it has the same behavior as the indexing operation on slice - /// (e.g. panic if index is out-of-bounds). - /// - /// ## Examples - /// - /// Accessing a single slice element: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut array = [1, 2, 3]; - /// let slice = &mut array[..]; - /// let mut volatile = Volatile::new(slice); - /// volatile.index_mut(1).write(6); - /// assert_eq!(volatile.index(1).read(), 6); - /// ``` - /// - /// Accessing a subslice: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut array = [1, 2, 3]; - /// let slice = &mut array[..]; - /// let mut volatile = Volatile::new(slice); - /// let mut subslice = volatile.index_mut(1..); - /// subslice.index_mut(0).write(6); - /// assert_eq!(subslice.index(0).read(), 6); - /// ``` - pub fn index_mut<'a, I>(&'a mut self, index: I) -> Volatile<&mut I::Output, A> - where - I: SliceIndex<[T]>, - R: DerefMut, - T: 'a, - { - self.map_mut(|slice| slice.index_mut(index)) - } - - /// Copies all elements from `self` into `dst`, using a volatile memcpy. - /// - /// The length of `dst` must be the same as `self`. - /// - /// The method is only available with the `unstable` feature enabled (requires a nightly - /// Rust compiler). - /// - /// ## Panics - /// - /// This function will panic if the two slices have different lengths. - /// - /// ## Examples - /// - /// Copying two elements from a volatile slice: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let src = [1, 2]; - /// // the `Volatile` type does not work with arrays, so convert `src` to a slice - /// let slice = &src[..]; - /// let volatile = Volatile::new(slice); - /// let mut dst = [5, 0, 0]; - /// - /// // Because the slices have to be the same length, - /// // we slice the destination slice from three elements - /// // to two. It will panic if we don't do this. - /// volatile.copy_into_slice(&mut dst[1..]); - /// - /// assert_eq!(src, [1, 2]); - /// assert_eq!(dst, [5, 1, 2]); - /// ``` - #[cfg(feature = "unstable")] - pub fn copy_into_slice(&self, dst: &mut [T]) - where - T: Copy, - { - let src = self.reference.deref(); - assert_eq!( - src.len(), - dst.len(), - "destination and source slices have different lengths" - ); - unsafe { - intrinsics::volatile_copy_nonoverlapping_memory( - dst.as_mut_ptr(), - src.as_ptr(), - src.len(), - ); - } - } - - /// Copies all elements from `src` into `self`, using a volatile memcpy. - /// - /// The length of `src` must be the same as `self`. - /// - /// This method is similar to the `slice::copy_from_slice` method of the standard library. The - /// difference is that this method performs a volatile copy. - /// - /// The method is only available with the `unstable` feature enabled (requires a nightly - /// Rust compiler). - /// - /// ## Panics - /// - /// This function will panic if the two slices have different lengths. - /// - /// ## Examples - /// - /// Copying two elements from a slice into a volatile slice: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let src = [1, 2, 3, 4]; - /// let mut dst = [0, 0]; - /// // the `Volatile` type does not work with arrays, so convert `dst` to a slice - /// let slice = &mut dst[..]; - /// let mut volatile = Volatile::new(slice); - /// - /// // Because the slices have to be the same length, - /// // we slice the source slice from four elements - /// // to two. It will panic if we don't do this. - /// volatile.copy_from_slice(&src[2..]); - /// - /// assert_eq!(src, [1, 2, 3, 4]); - /// assert_eq!(dst, [3, 4]); - /// ``` - #[cfg(feature = "unstable")] - pub fn copy_from_slice(&mut self, src: &[T]) - where - T: Copy, - R: DerefMut, - { - let dest = self.reference.deref_mut(); - assert_eq!( - dest.len(), - src.len(), - "destination and source slices have different lengths" - ); - unsafe { - intrinsics::volatile_copy_nonoverlapping_memory( - dest.as_mut_ptr(), - src.as_ptr(), - dest.len(), - ); - } - } - - /// Copies elements from one part of the slice to another part of itself, using a - /// volatile `memmove`. - /// - /// `src` is the range within `self` to copy from. `dest` is the starting index of the - /// range within `self` to copy to, which will have the same length as `src`. The two ranges - /// may overlap. The ends of the two ranges must be less than or equal to `self.len()`. - /// - /// This method is similar to the `slice::copy_within` method of the standard library. The - /// difference is that this method performs a volatile copy. - /// - /// This method is only available with the `unstable` feature enabled (requires a nightly - /// Rust compiler). - /// - /// ## Panics - /// - /// This function will panic if either range exceeds the end of the slice, or if the end - /// of `src` is before the start. - /// - /// ## Examples - /// - /// Copying four bytes within a slice: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut byte_array = *b"Hello, World!"; - /// let mut slice: &mut [u8] = &mut byte_array[..]; - /// let mut volatile = Volatile::new(slice); - /// - /// volatile.copy_within(1..5, 8); - /// - /// assert_eq!(&byte_array, b"Hello, Wello!"); - #[cfg(feature = "unstable")] - pub fn copy_within(&mut self, src: impl RangeBounds, dest: usize) - where - T: Copy, - R: DerefMut, - { - let slice = self.reference.deref_mut(); - // implementation taken from https://github.com/rust-lang/rust/blob/683d1bcd405727fcc9209f64845bd3b9104878b8/library/core/src/slice/mod.rs#L2726-L2738 - let Range { - start: src_start, - end: src_end, - } = range(src, ..slice.len()); - let count = src_end - src_start; - assert!(dest <= slice.len() - count, "dest is out of bounds"); - // SAFETY: the conditions for `volatile_copy_memory` have all been checked above, - // as have those for `ptr::add`. - unsafe { - intrinsics::volatile_copy_memory( - slice.as_mut_ptr().add(dest), - slice.as_ptr().add(src_start), - count, - ); - } - } -} - -/// Methods for volatile byte slices -impl Volatile -where - R: Deref, -{ - /// Sets all elements of the byte slice to the given `value` using a volatile `memset`. - /// - /// This method is similar to the `slice::fill` method of the standard library, with the - /// difference that this method performs a volatile write operation. Another difference - /// is that this method is only available for byte slices (not general `&mut [T]` slices) - /// because there currently isn't a instrinsic function that allows non-`u8` values. - /// - /// This method is only available with the `unstable` feature enabled (requires a nightly - /// Rust compiler). - /// - /// ## Example - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let mut buf = Volatile::new(vec![0; 10]); - /// buf.fill(1); - /// assert_eq!(buf.extract_inner(), vec![1; 10]); - /// ``` - #[cfg(feature = "unstable")] - pub fn fill(&mut self, value: u8) - where - R: DerefMut, - { - let dest = self.reference.deref_mut(); - unsafe { - intrinsics::volatile_set_memory(dest.as_mut_ptr(), value, dest.len()); - } - } -} - -/// Methods for converting arrays to slices -impl Volatile -where - R: Deref, -{ - /// Converts an array reference to a shared slice. - /// - /// This makes it possible to use the methods defined on slices. - /// - /// ## Example - /// - /// Reading a subslice from a volatile array reference using `index`: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let src = [1, 2, 3, 4]; - /// let volatile = Volatile::new(&src); - /// - /// // convert the `Volatile<&[i32; 4]>` array reference to a `Volatile<&[i32]>` slice - /// let volatile_slice = volatile.as_slice(); - /// // we can now use the slice methods - /// let subslice = volatile_slice.index(2..); - /// - /// assert_eq!(subslice.index(0).read(), 3); - /// assert_eq!(subslice.index(1).read(), 4); - /// ``` - pub fn as_slice(&self) -> Volatile<&[T], A> { - self.map(|array| &array[..]) - } - - /// Converts a mutable array reference to a mutable slice. - /// - /// This makes it possible to use the methods defined on slices. - /// - /// ## Example - /// - /// Writing to an index of a mutable array reference: - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut dst = [0, 0]; - /// let mut volatile = Volatile::new(&mut dst); - /// - /// // convert the `Volatile<&mut [i32; 2]>` array reference to a `Volatile<&mut [i32]>` slice - /// let mut volatile_slice = volatile.as_mut_slice(); - /// // we can now use the slice methods - /// volatile_slice.index_mut(1).write(1); - /// - /// assert_eq!(dst, [0, 1]); - /// ``` - pub fn as_mut_slice(&mut self) -> Volatile<&mut [T], A> - where - R: DerefMut, - { - self.map_mut(|array| &mut array[..]) - } -} - -/// Methods for restricting access. -impl Volatile { - /// Restricts access permissions to read-only. - /// - /// ## Example - /// - /// ``` - /// use volatile::Volatile; - /// - /// let mut value: i16 = -4; - /// let mut volatile = Volatile::new(&mut value); - /// - /// let read_only = volatile.read_only(); - /// assert_eq!(read_only.read(), -4); - /// // read_only.write(10); // compile-time error - /// ``` - pub fn read_only(self) -> Volatile { - Volatile { - reference: self.reference, - access: PhantomData, - } - } - - /// Restricts access permissions to write-only. - /// - /// ## Example - /// - /// Creating a write-only reference to a struct field: - /// - /// ``` - /// use volatile::Volatile; - /// - /// struct Example { field_1: u32, field_2: u8, } - /// let mut value = Example { field_1: 15, field_2: 255 }; - /// let mut volatile = Volatile::new(&mut value); - /// - /// // construct a volatile write-only reference to `field_2` - /// let mut field_2 = volatile.map_mut(|example| &mut example.field_2).write_only(); - /// field_2.write(14); - /// // field_2.read(); // compile-time error - /// ``` - pub fn write_only(self) -> Volatile { - Volatile { - reference: self.reference, - access: PhantomData, - } - } -} - -impl fmt::Debug for Volatile -where - R: Deref, - T: Copy + fmt::Debug, - A: Readable, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Volatile").field(&self.read()).finish() - } -} - -impl fmt::Debug for Volatile -where - R: Deref, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Volatile").field(&"[write-only]").finish() - } -} - -#[cfg(test)] -mod tests { - use super::Volatile; - - #[test] - fn test_read() { - let val = 42; - assert_eq!(Volatile::new(&val).read(), 42); - } - - #[test] - fn test_write() { - let mut val = 50; - let mut volatile = Volatile::new(&mut val); - volatile.write(50); - assert_eq!(val, 50); - } - - #[test] - fn test_update() { - let mut val = 42; - let mut volatile = Volatile::new(&mut val); - volatile.update(|v| *v += 1); - assert_eq!(val, 43); - } - - #[test] - fn test_slice() { - let mut val = [1, 2, 3]; - let mut volatile = Volatile::new(&mut val[..]); - volatile.index_mut(0).update(|v| *v += 1); - assert_eq!(val, [2, 2, 3]); - } - - #[test] - fn test_struct() { - struct S { - field_1: u32, - field_2: bool, - } - - let mut val = S { - field_1: 60, - field_2: true, - }; - let mut volatile = Volatile::new(&mut val); - volatile.map_mut(|s| &mut s.field_1).update(|v| *v += 1); - let mut field_2 = volatile.map_mut(|s| &mut s.field_2); - assert!(field_2.read()); - field_2.write(false); - assert_eq!(volatile.map(|s| &s.field_1).read(), 61); - assert_eq!(volatile.map(|s| &s.field_2).read(), false); - } - - #[cfg(feature = "unstable")] - #[test] - fn test_chunks() { - let mut val = [1, 2, 3, 4, 5, 6]; - let mut volatile = Volatile::new(&mut val[..]); - let mut chunks = volatile.map_mut(|s| s.as_chunks_mut().0); - chunks.index_mut(1).write([10, 11, 12]); - assert_eq!(chunks.index(0).read(), [1, 2, 3]); - assert_eq!(chunks.index(1).read(), [10, 11, 12]); - } -} From 24db29683fc43d61ef5aab1b8844f22a5148e548 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 24 Jun 2023 13:25:44 +0200 Subject: [PATCH 59/68] Remove `*_mut` variants of pointer methods There is no reason to have separate methods now that all methods take `self` by value. --- src/lib.rs | 1 - src/{ => volatile_ptr}/macros.rs | 19 ---- src/volatile_ptr/mod.rs | 2 + src/volatile_ptr/operations.rs | 19 ++-- src/volatile_ptr/tests.rs | 26 ++--- src/volatile_ptr/unstable.rs | 152 ++---------------------------- src/volatile_ptr/very_unstable.rs | 35 +------ src/volatile_ref.rs | 6 +- 8 files changed, 36 insertions(+), 224 deletions(-) rename src/{ => volatile_ptr}/macros.rs (70%) diff --git a/src/lib.rs b/src/lib.rs index d46ab73..7d1f863 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,6 +50,5 @@ pub use volatile_ptr::VolatilePtr; pub use volatile_ref::VolatileRef; pub mod access; -mod macros; mod volatile_ptr; mod volatile_ref; diff --git a/src/macros.rs b/src/volatile_ptr/macros.rs similarity index 70% rename from src/macros.rs rename to src/volatile_ptr/macros.rs index e10226d..6259b38 100644 --- a/src/macros.rs +++ b/src/volatile_ptr/macros.rs @@ -37,7 +37,6 @@ macro_rules! map_field { // sure that the field is not potentially unaligned. The body of the // if statement will never be executed, so it can never cause any UB. if false { - #[deny(unaligned_references)] let _ref_to_field = &(unsafe { &*$volatile.as_raw_ptr().as_ptr() }).$place; } @@ -48,21 +47,3 @@ macro_rules! map_field { } }}; } - -#[macro_export] -macro_rules! map_field_mut { - ($volatile:ident.$place:ident) => {{ - // Simulate creating a reference to the field. This is done to make - // sure that the field is not potentially unaligned. The body of the - // if statement will never be executed, so it can never cause any UB. - if false { - let _ref_to_field = &(unsafe { &*$volatile.as_raw_ptr().as_ptr() }).$place; - } - - unsafe { - $volatile.map_mut(|ptr| { - core::ptr::NonNull::new(core::ptr::addr_of_mut!((*ptr.as_ptr()).$place)).unwrap() - }) - } - }}; -} diff --git a/src/volatile_ptr/mod.rs b/src/volatile_ptr/mod.rs index 7326293..c216c3e 100644 --- a/src/volatile_ptr/mod.rs +++ b/src/volatile_ptr/mod.rs @@ -2,7 +2,9 @@ use core::{fmt, marker::PhantomData, ptr::NonNull}; use crate::access::{ReadWrite, Readable}; +mod macros; mod operations; + #[cfg(test)] mod tests; #[cfg(feature = "unstable")] diff --git a/src/volatile_ptr/operations.rs b/src/volatile_ptr/operations.rs index 4347840..6539982 100644 --- a/src/volatile_ptr/operations.rs +++ b/src/volatile_ptr/operations.rs @@ -200,20 +200,15 @@ where /// value /// })}; /// ``` - pub unsafe fn map(self, f: F) -> VolatilePtr<'a, U, A::RestrictShared> + /// + /// ## Safety + /// + /// The pointer returned by `f` must satisfy the requirements of [`Self::new`]. + pub unsafe fn map(self, f: F) -> VolatilePtr<'a, U, A> where F: FnOnce(NonNull) -> NonNull, A: Access, U: ?Sized, - { - unsafe { VolatilePtr::new_restricted(Default::default(), f(self.pointer)) } - } - - pub unsafe fn map_mut(self, f: F) -> VolatilePtr<'a, U, A> - where - F: FnOnce(NonNull) -> NonNull, - U: ?Sized, - A: Access, { unsafe { VolatilePtr::new_restricted(A::default(), f(self.pointer)) } } @@ -250,7 +245,7 @@ where /// Creating a write-only pointer to a struct field: /// /// ``` - /// use volatile::{VolatilePtr, map_field_mut}; + /// use volatile::{VolatilePtr, map_field}; /// use core::ptr::NonNull; /// /// struct Example { field_1: u32, field_2: u8, } @@ -258,7 +253,7 @@ where /// let mut volatile = unsafe { VolatilePtr::new((&mut value).into()) }; /// /// // construct a volatile write-only pointer to `field_2` - /// let mut field_2 = map_field_mut!(volatile.field_2).write_only(); + /// let mut field_2 = map_field!(volatile.field_2).write_only(); /// field_2.write(14); /// // field_2.read(); // compile-time error /// ``` diff --git a/src/volatile_ptr/tests.rs b/src/volatile_ptr/tests.rs index 2b7f717..acbb9a2 100644 --- a/src/volatile_ptr/tests.rs +++ b/src/volatile_ptr/tests.rs @@ -1,6 +1,6 @@ use crate::{ access::{ReadOnly, ReadWrite, WriteOnly}, - map_field_mut, VolatilePtr, + map_field, VolatilePtr, }; use core::ptr::NonNull; @@ -66,11 +66,11 @@ fn test_struct() { }; let volatile = unsafe { VolatilePtr::new(NonNull::from(&mut val)) }; unsafe { - volatile.map_mut(|s| NonNull::new(core::ptr::addr_of_mut!((*s.as_ptr()).field_1)).unwrap()) + volatile.map(|s| NonNull::new(core::ptr::addr_of_mut!((*s.as_ptr()).field_1)).unwrap()) } .update(|v| v + 1); let field_2 = unsafe { - volatile.map_mut(|s| NonNull::new(core::ptr::addr_of_mut!((*s.as_ptr()).field_2)).unwrap()) + volatile.map(|s| NonNull::new(core::ptr::addr_of_mut!((*s.as_ptr()).field_2)).unwrap()) }; assert!(field_2.read()); field_2.write(false); @@ -96,9 +96,9 @@ fn test_struct_macro() { field_2: true, }; let volatile = unsafe { VolatilePtr::new(NonNull::from(&mut val)) }; - let field_1 = map_field_mut!(volatile.field_1); + let field_1 = map_field!(volatile.field_1); field_1.update(|v| v + 1); - let field_2 = map_field_mut!(volatile.field_2); + let field_2 = map_field!(volatile.field_2); assert!(field_2.read()); field_2.write(false); assert_eq!( @@ -115,7 +115,7 @@ fn test_struct_macro() { fn test_slice() { let val: &mut [u32] = &mut [1, 2, 3]; let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; - volatile.index_mut(0).update(|v| v + 1); + volatile.index(0).update(|v| v + 1); let mut dst = [0; 3]; volatile.copy_into_slice(&mut dst); @@ -128,7 +128,7 @@ fn test_slice() { fn test_bounds_check_1() { let val: &mut [u32] = &mut [1, 2, 3]; let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; - volatile.index_mut(3); + volatile.index(3); } #[cfg(feature = "unstable")] @@ -137,7 +137,7 @@ fn test_bounds_check_1() { fn test_bounds_check_2() { let val: &mut [u32] = &mut [1, 2, 3]; let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; - volatile.index_mut(2..1); + volatile.index(2..1); } #[cfg(feature = "unstable")] @@ -146,7 +146,7 @@ fn test_bounds_check_2() { fn test_bounds_check_3() { let val: &mut [u32] = &mut [1, 2, 3]; let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; - volatile.index_mut(4..); // `3..` is is still ok (see next test) + volatile.index(4..); // `3..` is is still ok (see next test) } #[cfg(feature = "unstable")] @@ -154,7 +154,7 @@ fn test_bounds_check_3() { fn test_bounds_check_4() { let val: &mut [u32] = &mut [1, 2, 3]; let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; - assert_eq!(volatile.index_mut(3..).len(), 0); + assert_eq!(volatile.index(3..).len(), 0); } #[cfg(feature = "unstable")] @@ -163,7 +163,7 @@ fn test_bounds_check_4() { fn test_bounds_check_5() { let val: &mut [u32] = &mut [1, 2, 3]; let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; - volatile.index_mut(..4); + volatile.index(..4); } #[cfg(feature = "unstable")] @@ -171,8 +171,8 @@ fn test_bounds_check_5() { fn test_chunks() { let val: &mut [u32] = &mut [1, 2, 3, 4, 5, 6]; let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; - let chunks = volatile.as_chunks_mut().0; - chunks.index_mut(1).write([10, 11, 12]); + let chunks = volatile.as_chunks().0; + chunks.index(1).write([10, 11, 12]); assert_eq!(chunks.index(0).read(), [1, 2, 3]); assert_eq!(chunks.index(1).read(), [10, 11, 12]); } diff --git a/src/volatile_ptr/unstable.rs b/src/volatile_ptr/unstable.rs index ae6d900..55e9d6c 100644 --- a/src/volatile_ptr/unstable.rs +++ b/src/volatile_ptr/unstable.rs @@ -53,10 +53,7 @@ impl<'a, T, A> VolatilePtr<'a, [T], A> { /// let subslice = volatile.index(1..); /// assert_eq!(subslice.index(0).read(), 2); /// ``` - pub fn index( - self, - index: I, - ) -> VolatilePtr<'a, >::Output, A::RestrictShared> + pub fn index(self, index: I) -> VolatilePtr<'a, >::Output, A> where I: SliceIndex<[T]> + SliceIndex<[()]> + Clone, A: Access, @@ -66,18 +63,8 @@ impl<'a, T, A> VolatilePtr<'a, [T], A> { unsafe { self.map(|slice| slice.get_unchecked_mut(index)) } } - pub fn index_mut(self, index: I) -> VolatilePtr<'a, >::Output, A> - where - I: SliceIndex<[T]> + SliceIndex<[()]> + Clone, - A: Access, - { - bounds_check(self.pointer.len(), index.clone()); - - unsafe { self.map_mut(|slice| slice.get_unchecked_mut(index)) } - } - /// Returns an iterator over the slice. - pub fn iter(self) -> impl Iterator> + pub fn iter(self) -> impl Iterator> where A: Access, { @@ -87,14 +74,6 @@ impl<'a, T, A> VolatilePtr<'a, [T], A> { .map(move |i| unsafe { VolatilePtr::new_generic(NonNull::new_unchecked(ptr.add(i))) }) } - /// Returns an iterator that allows modifying each value. - pub fn iter_mut(self) -> impl Iterator> { - let ptr = self.as_raw_ptr().as_ptr() as *mut T; - let len = self.len(); - (0..len) - .map(move |i| unsafe { VolatilePtr::new_generic(NonNull::new_unchecked(ptr.add(i))) }) - } - /// Copies all elements from `self` into `dst`, using a volatile memcpy. /// /// The length of `dst` must be the same as `self`. @@ -260,13 +239,7 @@ impl<'a, T, A> VolatilePtr<'a, [T], A> { } } - pub fn split_at( - self, - mid: usize, - ) -> ( - VolatilePtr<'a, [T], A::RestrictShared>, - VolatilePtr<'a, [T], A::RestrictShared>, - ) + pub fn split_at(self, mid: usize) -> (VolatilePtr<'a, [T], A>, VolatilePtr<'a, [T], A>) where A: Access, { @@ -276,23 +249,10 @@ impl<'a, T, A> VolatilePtr<'a, [T], A> { unsafe { self.split_at_unchecked(mid) } } - pub fn split_at_mut(self, mid: usize) -> (VolatilePtr<'a, [T], A>, VolatilePtr<'a, [T], A>) - where - A: Access, - { - assert!(mid <= self.pointer.len()); - // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which - // fulfills the requirements of `from_raw_parts_mut`. - unsafe { self.split_at_mut_unchecked(mid) } - } - unsafe fn split_at_unchecked( self, mid: usize, - ) -> ( - VolatilePtr<'a, [T], A::RestrictShared>, - VolatilePtr<'a, [T], A::RestrictShared>, - ) + ) -> (VolatilePtr<'a, [T], A>, VolatilePtr<'a, [T], A>) where A: Access, { @@ -305,38 +265,9 @@ impl<'a, T, A> VolatilePtr<'a, [T], A> { } } - unsafe fn split_at_mut_unchecked( - self, - mid: usize, - ) -> (VolatilePtr<'a, [T], A>, VolatilePtr<'a, [T], A>) - where - A: Access, - { - let len = self.pointer.len(); - let ptr = self.pointer.as_mut_ptr(); - - // SAFETY: Caller has to check that `0 <= mid <= self.len()`. - // - // `[ptr; mid]` and `[mid; len]` are not overlapping, so returning a mutable reference - // is fine. - unsafe { - ( - VolatilePtr::new_generic( - NonNull::new(ptr::slice_from_raw_parts_mut(ptr, mid)).unwrap(), - ), - VolatilePtr::new_generic( - NonNull::new(ptr::slice_from_raw_parts_mut(ptr.add(mid), len - mid)).unwrap(), - ), - ) - } - } - pub fn as_chunks( self, - ) -> ( - VolatilePtr<'a, [[T; N]], ::RestrictShared>, - VolatilePtr<'a, [T], A::RestrictShared>, - ) + ) -> (VolatilePtr<'a, [[T; N]], A>, VolatilePtr<'a, [T], A>) where A: Access, { @@ -349,43 +280,10 @@ impl<'a, T, A> VolatilePtr<'a, [T], A> { (array_slice, remainder) } - pub unsafe fn as_chunks_unchecked( - self, - ) -> VolatilePtr<'a, [[T; N]], A::RestrictShared> - where - A: Access, - { - debug_assert_ne!(N, 0); - debug_assert_eq!(self.pointer.len() % N, 0); - let new_len = - // SAFETY: Our precondition is exactly what's needed to call this - unsafe { core::intrinsics::exact_div(self.pointer.len(), N) }; - // SAFETY: We cast a slice of `new_len * N` elements into - // a slice of `new_len` many `N` elements chunks. - let pointer = NonNull::new(ptr::slice_from_raw_parts_mut( - self.pointer.as_mut_ptr().cast(), - new_len, - )) - .unwrap(); - unsafe { VolatilePtr::new_generic(pointer) } - } - - pub fn as_chunks_mut( - self, - ) -> (VolatilePtr<'a, [[T; N]], A>, VolatilePtr<'a, [T], A>) + pub unsafe fn as_chunks_unchecked(self) -> VolatilePtr<'a, [[T; N]], A> where A: Access, { - assert_ne!(N, 0); - let len = self.pointer.len() / N; - let (multiple_of_n, remainder) = self.split_at_mut(len * N); - // SAFETY: We already panicked for zero, and ensured by construction - // that the length of the subslice is a multiple of N. - let array_slice = unsafe { multiple_of_n.as_chunks_unchecked_mut() }; - (array_slice, remainder) - } - - pub unsafe fn as_chunks_unchecked_mut(self) -> VolatilePtr<'a, [[T; N]], A> { debug_assert_ne!(N, 0); debug_assert_eq!(self.pointer.len() % N, 0); let new_len = @@ -440,7 +338,7 @@ impl VolatilePtr<'_, [u8], A> { /// These methods are only available with the `unstable` feature enabled (requires a nightly /// Rust compiler). impl<'a, T, A, const N: usize> VolatilePtr<'a, [T; N], A> { - /// Converts an array reference to a shared slice. + /// Converts an array pointer to a slice pointer. /// /// This makes it possible to use the methods defined on slices. /// @@ -463,7 +361,7 @@ impl<'a, T, A, const N: usize> VolatilePtr<'a, [T; N], A> { /// /// assert_eq!(dst, [1, 2]); /// ``` - pub fn as_slice(self) -> VolatilePtr<'a, [T], A::RestrictShared> + pub fn as_slice(self) -> VolatilePtr<'a, [T], A> where A: Access, { @@ -473,40 +371,6 @@ impl<'a, T, A, const N: usize> VolatilePtr<'a, [T; N], A> { }) } } - - /// Converts an array reference to a shared slice. - /// - /// This makes it possible to use the methods defined on slices. - /// - /// ## Example - /// - /// Copying two elements into a volatile array reference using `copy_from_slice`: - /// - /// ``` - /// use volatile::{access, VolatilePtr}; - /// use core::ptr::NonNull; - /// - /// let src = [1, 2]; - /// let mut dst = [0, 0]; - /// let mut volatile = unsafe { VolatilePtr::new_restricted(access::WriteOnly, NonNull::from(&dst)) }; - /// - /// // convert the `Volatile<[i32; 2]>` array reference to a `Volatile<[i32]>` slice - /// let mut volatile_slice = volatile.as_slice_mut(); - /// // we can now use the slice methods - /// volatile_slice.copy_from_slice(&src); - /// - /// assert_eq!(dst, [1, 2]); - /// ``` - pub fn as_slice_mut(self) -> VolatilePtr<'a, [T], A> - where - A: Access, - { - unsafe { - self.map_mut(|array| { - NonNull::new(ptr::slice_from_raw_parts_mut(array.as_ptr() as *mut T, N)).unwrap() - }) - } - } } fn bounds_check(len: usize, index: impl SliceIndex<[()]>) { diff --git a/src/volatile_ptr/very_unstable.rs b/src/volatile_ptr/very_unstable.rs index f7bddcb..21a1e3d 100644 --- a/src/volatile_ptr/very_unstable.rs +++ b/src/volatile_ptr/very_unstable.rs @@ -1,21 +1,12 @@ use core::ptr::NonNull; -use crate::{access::Access, VolatilePtr}; +use crate::VolatilePtr; impl<'a, T, A> VolatilePtr<'a, T, A> where T: ?Sized, { - pub const unsafe fn map_const(self, f: F) -> VolatilePtr<'a, U, A::RestrictShared> - where - F: ~const FnOnce(NonNull) -> NonNull, - A: Access, - U: ?Sized, - { - unsafe { VolatilePtr::new_generic(f(self.pointer)) } - } - - pub const unsafe fn map_mut_const(self, f: F) -> VolatilePtr<'a, U, A> + pub const unsafe fn map_const(self, f: F) -> VolatilePtr<'a, U, A> where F: ~const FnOnce(NonNull) -> NonNull, U: ?Sized, @@ -27,10 +18,7 @@ where /// Methods for volatile slices #[cfg(feature = "unstable")] impl<'a, T, A> VolatilePtr<'a, [T], A> { - pub const fn index_const(self, index: usize) -> VolatilePtr<'a, T, A::RestrictShared> - where - A: Access, - { + pub const fn index_const(self, index: usize) -> VolatilePtr<'a, T, A> { assert!(index < self.pointer.len(), "index out of bounds"); struct Mapper { @@ -46,21 +34,4 @@ impl<'a, T, A> VolatilePtr<'a, [T], A> { unsafe { self.map_const(Mapper { index }) } } - - pub const fn index_mut_const(self, index: usize) -> VolatilePtr<'a, T, A> { - assert!(index < self.pointer.len(), "index out of bounds"); - - struct Mapper { - index: usize, - } - impl const FnOnce<(NonNull<[T]>,)> for Mapper { - type Output = NonNull; - - extern "rust-call" fn call_once(self, (slice,): (NonNull<[T]>,)) -> Self::Output { - unsafe { NonNull::new_unchecked(slice.as_non_null_ptr().as_ptr().add(self.index)) } - } - } - - unsafe { self.map_mut_const(Mapper { index }) } - } } diff --git a/src/volatile_ref.rs b/src/volatile_ref.rs index 622f55c..78a7ff2 100644 --- a/src/volatile_ref.rs +++ b/src/volatile_ref.rs @@ -34,8 +34,8 @@ where /// Constructor functions. /// -/// These functions construct new `Volatile` values. While the `new` -/// function creates a `Volatile` instance with unrestricted access, there +/// These functions construct new `VolatileRef` values. While the `new` +/// function creates a `VolatileRef` instance with unrestricted access, there /// are also functions for creating read-only or write-only instances. impl<'a, T> VolatileRef<'a, T> where @@ -130,7 +130,7 @@ where /// Creating a write-only reference to a struct field: /// /// ``` - /// use volatile::{VolatileRef, map_field_mut}; + /// use volatile::{VolatileRef}; /// use core::ptr::NonNull; /// /// #[derive(Clone, Copy)] From 16dcea3981c2fb307a9ff189554430a843ada8b4 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 24 Jun 2023 13:53:39 +0200 Subject: [PATCH 60/68] Print pointer instead of values in `Debug` impl --- src/volatile_ptr/mod.rs | 8 +++++--- src/volatile_ref.rs | 10 +++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/volatile_ptr/mod.rs b/src/volatile_ptr/mod.rs index c216c3e..32e8012 100644 --- a/src/volatile_ptr/mod.rs +++ b/src/volatile_ptr/mod.rs @@ -1,6 +1,6 @@ use core::{fmt, marker::PhantomData, ptr::NonNull}; -use crate::access::{ReadWrite, Readable}; +use crate::access::ReadWrite; mod macros; mod operations; @@ -48,9 +48,11 @@ where impl fmt::Debug for VolatilePtr<'_, T, A> where T: Copy + fmt::Debug + ?Sized, - A: Readable, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Volatile").field(&self.read()).finish() + f.debug_struct("VolatilePtr") + .field("pointer", &self.pointer) + .field("access", &self.access) + .finish() } } diff --git a/src/volatile_ref.rs b/src/volatile_ref.rs index 78a7ff2..3b8caeb 100644 --- a/src/volatile_ref.rs +++ b/src/volatile_ref.rs @@ -1,6 +1,6 @@ use crate::{ - access::{Access, Copyable, ReadOnly, ReadWrite, Readable, WriteOnly}, - volatile_ptr, + access::{Access, Copyable, ReadOnly, ReadWrite, WriteOnly}, + volatile_ptr::VolatilePtr, }; use core::{fmt, marker::PhantomData, ptr::NonNull}; @@ -173,11 +173,11 @@ unsafe impl Sync for VolatileRef<'_, T, A> where T: Sync {} impl fmt::Debug for VolatileRef<'_, T, A> where T: Copy + fmt::Debug + ?Sized, - A: Readable, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Volatile") - .field(&self.as_ptr().read()) + f.debug_struct("VolatileRef") + .field("pointer", &self.pointer) + .field("access", &self.access) .finish() } } From f2208543527d5420615a06a464a35bd8cbbad3ef Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 24 Jun 2023 13:54:08 +0200 Subject: [PATCH 61/68] Document the constructor functions --- src/volatile_ref.rs | 61 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/src/volatile_ref.rs b/src/volatile_ref.rs index 3b8caeb..7ddb081 100644 --- a/src/volatile_ref.rs +++ b/src/volatile_ref.rs @@ -41,21 +41,47 @@ impl<'a, T> VolatileRef<'a, T> where T: ?Sized, { + /// Turns the given pointer into a `VolatileRef`. + /// + /// ## Safety + /// + /// - The pointer must be properly aligned. + /// - It must be “dereferenceable” in the sense defined in the [`core::ptr`] documentation. + /// - The pointer must point to an initialized instance of T. + /// - You must enforce Rust’s aliasing rules, since the returned lifetime 'a is arbitrarily + /// chosen and does not necessarily reflect the actual lifetime of the data. In particular, + /// while this `VolatileRef` exists, the memory the pointer points to must not get accessed + /// (_read or written_) through any other pointer. pub unsafe fn new(pointer: NonNull) -> Self { unsafe { VolatileRef::new_restricted(ReadWrite, pointer) } } - pub fn from_mut_ref(reference: &'a mut T) -> Self - where - T: 'a, - { - unsafe { VolatileRef::new(reference.into()) } - } - + /// Turns the given pointer into a read-only `VolatileRef`. + /// + /// ## Safety + /// + /// - The pointer must be properly aligned. + /// - It must be “dereferenceable” in the sense defined in the [`core::ptr`] documentation. + /// - The pointer must point to an initialized instance of T. + /// - You must enforce Rust’s aliasing rules, since the returned lifetime 'a is arbitrarily + /// chosen and does not necessarily reflect the actual lifetime of the data. In particular, + /// while this `VolatileRef` exists, the memory the pointer points to _must not get mutated_. pub const unsafe fn new_read_only(pointer: NonNull) -> VolatileRef<'a, T, ReadOnly> { unsafe { Self::new_restricted(ReadOnly, pointer) } } + /// Turns the given pointer into a `VolatileRef` instance with the given access. + /// + /// ## Safety + /// + /// - The pointer must be properly aligned. + /// - It must be “dereferenceable” in the sense defined in the [`core::ptr`] documentation. + /// - The pointer must point to an initialized instance of T. + /// - You must enforce Rust’s aliasing rules, since the returned lifetime 'a is arbitrarily + /// chosen and does not necessarily reflect the actual lifetime of the data. In particular, + /// while this `VolatileRef` exists, the memory the pointer points to _must not get mutated_. + /// If the given `access` parameter allows write access, the pointer _must not get read + /// either_ while this `VolatileRef` exists. pub const unsafe fn new_restricted(access: A, pointer: NonNull) -> VolatileRef<'a, T, A> where A: Access, @@ -64,6 +90,13 @@ where unsafe { Self::new_generic(pointer) } } + /// Creates a `VolatileRef` from the given shared reference. + /// + /// **Note:** This function is only intended for testing, not for accessing real volatile + /// data. The reason is that the `&mut T` argument is considered _dereferenceable_ by Rust, + /// so the compiler is allowed to insert non-volatile reads. This might lead to undesired + /// (or even undefined?) behavior when accessing volatile data. So to be safe, only create + /// raw pointers to volatile data and use the [`Self::new`] constructor instead. pub fn from_ref(reference: &'a T) -> VolatileRef<'a, T, ReadOnly> where T: 'a, @@ -71,6 +104,20 @@ where unsafe { VolatileRef::new_restricted(ReadOnly, reference.into()) } } + /// Creates a `VolatileRef` from the given mutable reference. + /// + /// **Note:** This function is only intended for testing, not for accessing real volatile + /// data. The reason is that the `&mut T` argument is considered _dereferenceable_ by Rust, + /// so the compiler is allowed to insert non-volatile reads. This might lead to undesired + /// (or even undefined?) behavior when accessing volatile data. So to be safe, only create + /// raw pointers to volatile data and use the [`Self::new`] constructor instead. + pub fn from_mut_ref(reference: &'a mut T) -> Self + where + T: 'a, + { + unsafe { VolatileRef::new(reference.into()) } + } + const unsafe fn new_generic(pointer: NonNull) -> VolatileRef<'a, T, A> { VolatileRef { pointer, From 00fc0908dda0d974fa14a1de21c46796d0c65870 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 24 Jun 2023 13:55:08 +0200 Subject: [PATCH 62/68] Document the conversion functions and add a `into_ptr` method --- src/volatile_ref.rs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/volatile_ref.rs b/src/volatile_ref.rs index 7ddb081..0dbe697 100644 --- a/src/volatile_ref.rs +++ b/src/volatile_ref.rs @@ -131,18 +131,38 @@ impl<'a, T, A> VolatileRef<'a, T, A> where T: ?Sized, { - pub fn as_ptr(&self) -> volatile_ptr::VolatilePtr<'_, T, A::RestrictShared> + /// Borrows this `VolatileRef` as a read-only [`VolatilePtr`]. + /// + /// Use this method to do (partial) volatile reads of the referenced data. + pub fn as_ptr(&self) -> VolatilePtr<'_, T, A::RestrictShared> where A: Access, { - unsafe { volatile_ptr::VolatilePtr::new_restricted(Default::default(), self.pointer) } + unsafe { VolatilePtr::new_restricted(Default::default(), self.pointer) } } - pub fn as_mut_ptr(&mut self) -> volatile_ptr::VolatilePtr<'_, T, A> + /// Borrows this `VolatileRef` as a mutable [`VolatilePtr`]. + /// + /// Use this method to do (partial) volatile reads or writes of the referenced data. + pub fn as_mut_ptr(&mut self) -> VolatilePtr<'_, T, A> + where + A: Access, + { + unsafe { VolatilePtr::new_restricted(Default::default(), self.pointer) } + } + + /// Converts this `VolatileRef` into a [`VolatilePtr`] with full access without shortening + /// the lifetime. + /// + /// Use this method when you need a [`VolatilePtr`] instance that lives for the full + /// lifetime `'a`. + /// + /// This method consumes the `VolatileRef`. + pub fn into_ptr(self) -> VolatilePtr<'a, T, A> where A: Access, { - unsafe { volatile_ptr::VolatilePtr::new_restricted(Default::default(), self.pointer) } + unsafe { VolatilePtr::new_restricted(Default::default(), self.pointer) } } } From c82b35d6876cb8878e6a9f7dea684781d8ce5c4f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 24 Jun 2023 14:10:34 +0200 Subject: [PATCH 63/68] Remove uneeded feature gates --- src/lib.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7d1f863..4734945 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,14 +35,9 @@ #![cfg_attr(feature = "unstable", feature(core_intrinsics))] #![cfg_attr(feature = "unstable", feature(slice_range))] #![cfg_attr(feature = "unstable", feature(slice_ptr_get))] -#![cfg_attr(feature = "unstable", feature(slice_ptr_len))] -#![cfg_attr(feature = "very_unstable", feature(const_slice_ptr_len))] #![cfg_attr(feature = "very_unstable", feature(const_trait_impl))] -#![cfg_attr(feature = "very_unstable", feature(const_mut_refs))] -#![cfg_attr(feature = "very_unstable", feature(inline_const))] #![cfg_attr(feature = "very_unstable", feature(unboxed_closures))] #![cfg_attr(feature = "very_unstable", feature(fn_traits))] -#![cfg_attr(all(feature = "unstable", test), feature(slice_as_chunks))] #![warn(missing_docs)] #![deny(unsafe_op_in_unsafe_fn)] From 2108aad3800b1fef82727d20cfce0a4012a98604 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 24 Jun 2023 14:11:03 +0200 Subject: [PATCH 64/68] Document remaining methods --- src/volatile_ptr/unstable.rs | 27 +++++++++++++++++++++++++++ src/volatile_ptr/very_unstable.rs | 12 ++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/volatile_ptr/unstable.rs b/src/volatile_ptr/unstable.rs index 55e9d6c..7fb7952 100644 --- a/src/volatile_ptr/unstable.rs +++ b/src/volatile_ptr/unstable.rs @@ -11,10 +11,12 @@ use crate::{ }; impl<'a, T, A> VolatilePtr<'a, [T], A> { + /// Returns the length of the slice. pub fn len(self) -> usize { self.pointer.len() } + /// Returns whether the slice is empty. pub fn is_empty(self) -> bool { self.pointer.len() == 0 } @@ -239,6 +241,16 @@ impl<'a, T, A> VolatilePtr<'a, [T], A> { } } + /// Divides one slice into two at an index. + /// + /// The first will contain all indices from `[0, mid)` (excluding + /// the index `mid` itself) and the second will contain all + /// indices from `[mid, len)` (excluding the index `len` itself). + /// + /// # Panics + /// + /// Panics if `mid > len`. + /// pub fn split_at(self, mid: usize) -> (VolatilePtr<'a, [T], A>, VolatilePtr<'a, [T], A>) where A: Access, @@ -265,6 +277,13 @@ impl<'a, T, A> VolatilePtr<'a, [T], A> { } } + /// Splits the slice into a slice of `N`-element arrays, + /// starting at the beginning of the slice, + /// and a remainder slice with length strictly less than `N`. + /// + /// # Panics + /// + /// Panics if `N` is 0. pub fn as_chunks( self, ) -> (VolatilePtr<'a, [[T; N]], A>, VolatilePtr<'a, [T], A>) @@ -280,6 +299,14 @@ impl<'a, T, A> VolatilePtr<'a, [T], A> { (array_slice, remainder) } + /// Splits the slice into a slice of `N`-element arrays, + /// assuming that there's no remainder. + /// + /// # Safety + /// + /// This may only be called when + /// - The slice splits exactly into `N`-element chunks (aka `self.len() % N == 0`). + /// - `N != 0`. pub unsafe fn as_chunks_unchecked(self) -> VolatilePtr<'a, [[T; N]], A> where A: Access, diff --git a/src/volatile_ptr/very_unstable.rs b/src/volatile_ptr/very_unstable.rs index 21a1e3d..3935928 100644 --- a/src/volatile_ptr/very_unstable.rs +++ b/src/volatile_ptr/very_unstable.rs @@ -6,6 +6,14 @@ impl<'a, T, A> VolatilePtr<'a, T, A> where T: ?Sized, { + /// Compile-time evaluable variant of [`Self::map`]. + /// + /// This function is a copy of [`Self::map`] that uses unstable compiler functions + /// to be callable from `const` contexts. + /// + /// ## Safety + /// + /// The safety requirements of [`Self::map`] apply to this method too. pub const unsafe fn map_const(self, f: F) -> VolatilePtr<'a, U, A> where F: ~const FnOnce(NonNull) -> NonNull, @@ -18,6 +26,10 @@ where /// Methods for volatile slices #[cfg(feature = "unstable")] impl<'a, T, A> VolatilePtr<'a, [T], A> { + /// Compile-time evaluable variant of [`Self::index`]. + /// + /// This function is a copy of [`Self::index`] that uses unstable compiler functions + /// to be callable from `const` contexts. pub const fn index_const(self, index: usize) -> VolatilePtr<'a, T, A> { assert!(index < self.pointer.len(), "index out of bounds"); From 6410c4e880f32eab9cbea240104c573ce9960271 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 24 Jun 2023 14:11:13 +0200 Subject: [PATCH 65/68] Silence two clippy warnings --- src/volatile_ptr/tests.rs | 1 + src/volatile_ptr/unstable.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/volatile_ptr/tests.rs b/src/volatile_ptr/tests.rs index acbb9a2..479bec3 100644 --- a/src/volatile_ptr/tests.rs +++ b/src/volatile_ptr/tests.rs @@ -137,6 +137,7 @@ fn test_bounds_check_1() { fn test_bounds_check_2() { let val: &mut [u32] = &mut [1, 2, 3]; let volatile = unsafe { VolatilePtr::new(NonNull::from(val)) }; + #[allow(clippy::reversed_empty_ranges)] volatile.index(2..1); } diff --git a/src/volatile_ptr/unstable.rs b/src/volatile_ptr/unstable.rs index 7fb7952..9009bc9 100644 --- a/src/volatile_ptr/unstable.rs +++ b/src/volatile_ptr/unstable.rs @@ -284,6 +284,7 @@ impl<'a, T, A> VolatilePtr<'a, [T], A> { /// # Panics /// /// Panics if `N` is 0. + #[allow(clippy::type_complexity)] pub fn as_chunks( self, ) -> (VolatilePtr<'a, [[T; N]], A>, VolatilePtr<'a, [T], A>) From 020ffbe786dc08648fb54bbc78a4268cdd8cad73 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 24 Jun 2023 14:13:24 +0200 Subject: [PATCH 66/68] Update cargo-release configuration to latest version --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4169f10..6fa62fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,6 @@ very_unstable = ["unstable"] rand = "0.8.3" [package.metadata.release] -dev-version = false pre-release-replacements = [ { file = "Changelog.md", search = "# Unreleased", replace = "# Unreleased\n\n# {{version}} – {{date}}", exactly = 1 }, ] From 0d51b2722a34cf9ff197f33fcd53715b2391c140 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 24 Jun 2023 14:29:35 +0200 Subject: [PATCH 67/68] Write changelog entry for 0.5 release --- Changelog.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Changelog.md b/Changelog.md index f6043b1..c1a5154 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,12 @@ # Unreleased +- **Breaking:** [New design based on raw pointers](https://github.com/rust-osdev/volatile/pull/29) + - The previous reference-based design was [unsound](https://github.com/rust-osdev/volatile/pull/13#issuecomment-842455552) because it allowed the compiler to insert spurious reads. + - The new design features two wrapper types for raw pointers: `VolatilePtr` and `VolatileRef` + - `VolatilePtr` provides safe read and write access to volatile values. Like raw pointers, it implements `Copy` and is `!Sync`. + - `VolatileRef` is a pointer type that respects Rust's aliasing rules. It doesn't implement `Copy`, requires a `&mut` reference for modification, and implements `Sync`. It can converted to temporary `VolatilePtr` instances through the `as_ptr`/`as_mut_ptr` methods. +- We now provide methods for volatile slice operations and a `map!` macro for struct field projection. These advanced features are gated behind a cargo feature named _"unstable"_. + # 0.4.6 – 2023-01-17 - Fix UB in slice methods when Deref returns different references ([#27](https://github.com/rust-osdev/volatile/pull/27)) From 225ae4cdf54c79946542aeb88e43a43b04b19852 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 24 Jun 2023 14:30:26 +0200 Subject: [PATCH 68/68] Also test `very_unstable` feature on CI --- .github/workflows/build.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 654bd68..9efeec9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -67,6 +67,26 @@ jobs: command: test args: --features unstable + very_unstable: + name: Test Suite (very_unstable) + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install nightly toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + override: true + + - name: Run cargo test --features very_unstable + uses: actions-rs/cargo@v1 + with: + command: test + args: --features very_unstable + lints: name: Lints