From 277b25b6e69a9a933c8a07b1331fad0c40e6ecab Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 29 Jul 2020 10:35:10 +0200 Subject: [PATCH 01/31] Different rewrite approaches --- src/access.rs | 13 +++ src/foo.rs | 213 ++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 215 ++++++------------------------------------- src/reference.rs | 82 +++++++++++++++++ src/reference_mut.rs | 163 ++++++++++++++++++++++++++++++++ src/tests.rs | 35 +++++++ 6 files changed, 536 insertions(+), 185 deletions(-) create mode 100644 src/access.rs create mode 100644 src/foo.rs create mode 100644 src/reference.rs create mode 100644 src/reference_mut.rs create mode 100644 src/tests.rs diff --git a/src/access.rs b/src/access.rs new file mode 100644 index 0000000..3aa4d59 --- /dev/null +++ b/src/access.rs @@ -0,0 +1,13 @@ +pub trait Readable {} +pub trait Writable {} + +pub struct Read; + +impl Readable for Read {} + +pub struct Write; +impl Writable for Write {} + +pub struct ReadWrite; +impl Readable for ReadWrite {} +impl Writable for ReadWrite {} diff --git a/src/foo.rs b/src/foo.rs new file mode 100644 index 0000000..88f1b1a --- /dev/null +++ b/src/foo.rs @@ -0,0 +1,213 @@ +use crate::access::{ReadWrite, Readable, Writable}; +use core::{ + marker::PhantomData, + ops::{Index, IndexMut}, + ptr, + slice::SliceIndex, +}; + +/// 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. +/// +/// The size of this struct is the same as the size of the contained type. +#[derive(Debug, Default, Clone)] +#[repr(transparent)] +pub struct Volatile { + value: T, + access: PhantomData, +} + +impl Volatile { + /// Construct a new volatile instance wrapping the given value. + /// + /// ```rust + /// use volatile::Volatile; + /// + /// let value = Volatile::new(0u32); + /// ``` + /// + /// # Panics + /// + /// This method never panics. + pub const fn new(value: T) -> Volatile { + Volatile { + value, + access: PhantomData, + } + } +} + +impl Volatile<&T, A> { + /// 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::Volatile; + /// + /// let value = Volatile::new(42u32); + /// + /// assert_eq!(value.read(), 42u32); + /// ``` + /// + /// # Panics + /// + /// This method never panics. + pub fn read(&self) -> T + where + A: Readable, + { + // UNSAFE: Safe, as we know that our internal value exists. + unsafe { ptr::read_volatile(self.value) } + } +} + +impl Volatile<&mut T, A> { + /// 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::Volatile; + /// + /// let value = Volatile::new(42u32); + /// + /// assert_eq!(value.read(), 42u32); + /// ``` + /// + /// # Panics + /// + /// This method never panics. + 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::Volatile; + /// + /// let mut value = Volatile::new(0u32); + /// + /// value.write(42u32); + /// + /// assert_eq!(value.read(), 42u32); + /// ``` + /// + /// # Panics + /// + /// This method never panics. + pub fn write(&mut self, value: T) + where + A: Writable, + { + // UNSAFE: Safe, as we know that our internal value exists. + unsafe { ptr::write_volatile(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::Volatile; + /// + /// let mut value = Volatile::new(21u32); + /// + /// value.update(|val_ref| *val_ref *= 2); + /// + /// assert_eq!(value.read(), 42u32); + /// ``` + /// + /// # Panics + /// + /// Ths method never panics. + pub fn update(&mut self, f: F) + where + F: FnOnce(&mut T), + A: Readable + Writable, + { + let mut value = self.read(); + f(&mut value); + self.write(value); + } +} + +impl Volatile<&[T], A> { + pub fn index(&self, index: I) -> Volatile<&I::Output, A> + where + I: SliceIndex<[T]>, + { + Volatile { + value: self.value.index(index), + access: self.access, + } + } +} + +impl Volatile<&mut [T], A> { + pub fn index(&self, index: I) -> Volatile<&I::Output, A> + where + I: SliceIndex<[T]>, + { + Volatile { + value: self.value.index(index), + access: self.access, + } + } + + pub fn index_mut(&mut self, index: I) -> Volatile<&mut I::Output, A> + where + I: SliceIndex<[T]>, + { + Volatile { + value: self.value.index_mut(index), + access: self.access, + } + } +} + +#[cfg(test)] +mod tests { + use super::Volatile; + + #[test] + fn test_read() { + let val = 42; + assert_eq!(Volatile::new(&42).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]); + } +} diff --git a/src/lib.rs b/src/lib.rs index 303478e..a9b6e02 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,15 @@ //! of the volatile wrapper types are the same size as their contained values. #![no_std] -use core::ptr; +use access::{ReadWrite, Readable, Writable}; +use core::{marker::PhantomData, ptr}; + +pub mod access; +pub mod foo; +pub mod reference; +pub mod reference_mut; +#[cfg(test)] +mod tests; /// 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 @@ -36,9 +44,12 @@ use core::ptr; /// The size of this struct is the same as the size of the contained type. #[derive(Debug, Default)] #[repr(transparent)] -pub struct Volatile(T); +pub struct Volatile { + value: T, + access: PhantomData, +} -impl Volatile { +impl Volatile { /// Construct a new volatile instance wrapping the given value. /// /// ```rust @@ -50,27 +61,15 @@ impl Volatile { /// # Panics /// /// This method never panics. - #[cfg(feature = "const_fn")] pub const fn new(value: T) -> Volatile { - Volatile(value) - } - - /// Construct a new volatile instance wrapping the given value. - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let value = Volatile::new(0u32); - /// ``` - /// - /// # Panics - /// - /// This method never panics. - #[cfg(not(feature = "const_fn"))] - pub fn new(value: T) -> Volatile { - Volatile(value) + Volatile { + value, + access: PhantomData, + } } +} +impl Volatile { /// 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 @@ -89,9 +88,11 @@ impl Volatile { /// This method never panics. pub fn read(&self) -> T { // UNSAFE: Safe, as we know that our internal value exists. - unsafe { ptr::read_volatile(&self.0) } + unsafe { ptr::read_volatile(&self.value) } } +} +impl Volatile { /// 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` @@ -112,9 +113,11 @@ impl Volatile { /// This method never panics. pub fn write(&mut self, value: T) { // UNSAFE: Safe, as we know that our internal value exists. - unsafe { ptr::write_volatile(&mut self.0, value) }; + unsafe { ptr::write_volatile(&mut self.value, value) }; } +} +impl Volatile { /// 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. @@ -142,169 +145,11 @@ impl Volatile { } } -impl Clone for Volatile { +impl Clone for Volatile { fn clone(&self) -> Self { - Volatile(self.read()) - } -} - -/// A volatile wrapper which only allows read operations. -/// -/// The size of this struct is the same as the contained type. -#[derive(Debug, Clone, Default)] -pub struct ReadOnly(Volatile); - -impl ReadOnly { - /// Construct a new read-only volatile wrapper wrapping the given value. - /// - /// ```rust - /// use volatile::ReadOnly; - /// - /// let value = ReadOnly::new(42u32); - /// ``` - /// - /// # Panics - /// - /// This function never panics. - #[cfg(feature = "const_fn")] - pub const fn new(value: T) -> ReadOnly { - ReadOnly(Volatile::new(value)) - } - - /// Construct a new read-only volatile wrapper wrapping the given value. - /// - /// ```rust - /// use volatile::ReadOnly; - /// - /// let value = ReadOnly::new(42u32); - /// ``` - /// - /// # Panics - /// - /// This function never panics. - #[cfg(not(feature = "const_fn"))] - pub fn new(value: T) -> ReadOnly { - ReadOnly(Volatile::new(value)) - } - - /// Perform a volatile read of the contained value, returning a copy of the read value. - /// Functionally equivalent to `Volatile::read`. - /// - /// ```rust - /// use volatile::ReadOnly; - /// - /// let value = ReadOnly::new(42u32); - /// assert_eq!(value.read(), 42u32); - /// ``` - /// - /// # Panics - /// - /// This function never panics. - pub fn read(&self) -> T { - self.0.read() - } -} - -/// A volatile wrapper which only allows write operations. -/// -/// The size of this struct is the same as the contained type. -#[derive(Default)] -pub struct WriteOnly(Volatile); - -impl WriteOnly { - /// Constructs a new write only volatile wrapper around the given value. - /// - /// ```rust - /// use volatile::WriteOnly; - /// - /// let value = WriteOnly::new(0u32); - /// ``` - /// - /// # Panics - /// - /// This function never panics. - #[cfg(feature = "const_fn")] - pub const fn new(value: T) -> WriteOnly { - WriteOnly(Volatile::new(value)) - } - - /// Constructs a new write only volatile wrapper around the given value. - /// - /// ```rust - /// use volatile::WriteOnly; - /// - /// let value = WriteOnly::new(0u32); - /// ``` - /// - /// # Panics - /// - /// This function never panics. - #[cfg(not(feature = "const_fn"))] - pub fn new(value: T) -> WriteOnly { - WriteOnly(Volatile::new(value)) - } - - /// Performs a volatile write of value `value` into the contained value. Functionally identical - /// to `Volatile::write`. - /// - /// ```rust - /// use volatile::WriteOnly; - /// - /// let mut value = WriteOnly::new(0u32); - /// - /// value.write(42u32); - /// ``` - /// - /// # Panics - /// - /// This method never panics. - pub fn write(&mut self, value: T) { - self.0.write(value) - } -} - -/// A volatile wrapper which allows both read and write operations; -/// functionally equivalent to the `Volatile` type, as it is a type -/// alias for it. -/// -/// The size of this struct is the same as the contained type. -pub type ReadWrite = Volatile; - -#[cfg(test)] -mod tests { - use super::Volatile; - - #[test] - fn test_read() { - assert_eq!(Volatile(42).read(), 42); - } - - #[test] - fn test_write() { - let mut volatile = Volatile(42); - volatile.write(50); - assert_eq!(volatile.0, 50); - } - - #[test] - fn test_update() { - let mut volatile = Volatile(42); - volatile.update(|v| *v += 1); - assert_eq!(volatile.0, 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 Volatile; - - // UNSAFE: Safe, as we know the value exists on the stack. - unsafe { - (*volatile_ptr).write(42u32); + Volatile { + value: self.read(), + access: self.access, } - - assert_eq!(target_value, 42u32); } } diff --git a/src/reference.rs b/src/reference.rs new file mode 100644 index 0000000..61a0dda --- /dev/null +++ b/src/reference.rs @@ -0,0 +1,82 @@ +use core::{ops::Index, ptr, slice::SliceIndex}; + +/// 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. +/// +/// The size of this struct is the same as the size of the contained type. +#[derive(Debug, Clone)] +#[repr(transparent)] +pub struct VolatileRef<'a, T: ?Sized> { + pub(crate) value: &'a T, +} + +impl VolatileRef<'_, T> { + /// Construct a new volatile instance wrapping the given value. + /// + /// ```rust + /// use volatile::Volatile; + /// + /// let value = Volatile::new(0u32); + /// ``` + /// + /// # Panics/// + /// This method never panics. + #[cfg(feature = "const_fn")] + pub const fn new(value: &T) -> VolatileRef { + Volatile { + value, + access: PhantomData, + } + } + + /// Construct a new volatile instance wrapping the given value. + /// + /// ```rust + /// use volatile::Volatile; + /// + /// let value = Volatile::new(0u32); + /// ``` + /// + /// # Panics + /// + /// This method never panics. + #[cfg(not(feature = "const_fn"))] + pub fn new(value: &T) -> VolatileRef { + VolatileRef { value } + } +} + +impl VolatileRef<'_, T> { + /// 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::Volatile; + /// + /// let value = Volatile::new(42u32); + /// + /// assert_eq!(value.read(), 42u32); + /// ``` + /// + /// # Panics + /// + /// This method never panics. + pub fn read(&self) -> T { + // UNSAFE: Safe, as we know that our internal value exists. + unsafe { ptr::read_volatile(self.value) } + } +} + +impl VolatileRef<'_, [T]> { + pub fn index(&self, index: I) -> VolatileRef + where + I: SliceIndex<[T], Output = [T]>, + { + VolatileRef { + value: self.value.index(index), + } + } +} diff --git a/src/reference_mut.rs b/src/reference_mut.rs new file mode 100644 index 0000000..163226d --- /dev/null +++ b/src/reference_mut.rs @@ -0,0 +1,163 @@ +use crate::{ + access::{ReadWrite, Readable, Writable}, + reference::VolatileRef, +}; +use core::{ + marker::PhantomData, + ops::{Index, IndexMut}, + ptr, + slice::SliceIndex, +}; + +/// 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. +/// +/// The size of this struct is the same as the size of the contained type. +#[derive(Debug)] +#[repr(transparent)] +pub struct VolatileRefMut<'a, T: ?Sized, A = ReadWrite> { + value: &'a mut T, + access: PhantomData, +} + +impl VolatileRefMut<'_, T> { + /// Construct a new volatile instance wrapping the given value. + /// + /// ```rust + /// use volatile::Volatile; + /// + /// let value = Volatile::new(0u32); + /// ``` + /// + /// # Panics + /// + /// This method never panics. + #[cfg(feature = "const_fn")] + pub const fn new(value: &mut T) -> VolatileRefMut { + VolatileRefMut { + value, + access: PhantomData, + } + } + + /// Construct a new volatile instance wrapping the given value. + /// + /// ```rust + /// use volatile::Volatile; + /// + /// let value = Volatile::new(0u32); + /// ``` + /// + /// # Panics + /// + /// This method never panics. + #[cfg(not(feature = "const_fn"))] + pub fn new(value: &mut T) -> VolatileRefMut { + VolatileRefMut { + value, + access: PhantomData, + } + } +} + +impl VolatileRefMut<'_, T, A> { + /// 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::Volatile; + /// + /// let value = Volatile::new(42u32); + /// + /// assert_eq!(value.read(), 42u32); + /// ``` + /// + /// # Panics + /// + /// This method never panics. + 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::Volatile; + /// + /// let mut value = Volatile::new(0u32); + /// + /// value.write(42u32); + /// + /// assert_eq!(value.read(), 42u32); + /// ``` + /// + /// # Panics + /// + /// This method never panics. + pub fn write(&mut self, value: T) + where + A: Writable, + { + // UNSAFE: Safe, as we know that our internal value exists. + unsafe { ptr::write_volatile(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::Volatile; + /// + /// let mut value = Volatile::new(21u32); + /// + /// value.update(|val_ref| *val_ref *= 2); + /// + /// assert_eq!(value.read(), 42u32); + /// ``` + /// + /// # Panics + /// + /// Ths method never panics. + pub fn update(&mut self, f: F) + where + F: FnOnce(&mut T), + A: Readable + Writable, + { + let mut value = self.read(); + f(&mut value); + self.write(value); + } +} + +impl VolatileRefMut<'_, [T], A> { + pub fn index(&self, index: I) -> VolatileRef + where + I: SliceIndex<[T], Output = [T]>, + A: Readable, + { + VolatileRef { + value: self.value.index(index), + } + } + + pub fn index_mut(&mut self, index: I) -> VolatileRefMut + where + I: SliceIndex<[T], Output = [T]>, + { + VolatileRefMut { + value: self.value.index_mut(index), + access: self.access, + } + } +} diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..27c53d8 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,35 @@ +use super::Volatile; + +#[test] +fn test_read() { + assert_eq!(Volatile::new(42).read(), 42); +} + +#[test] +fn test_write() { + let mut volatile = Volatile::new(42); + volatile.write(50); + assert_eq!(volatile.value, 50); +} + +#[test] +fn test_update() { + let mut volatile = Volatile::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 Volatile; + + // UNSAFE: Safe, as we know the value exists on the stack. + unsafe { + (*volatile_ptr).write(42u32); + } + + assert_eq!(target_value, 42u32); +} From 93fb1e151b67c5abdfb8d1f9ca9c909004f223fa Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 29 Jul 2020 10:37:33 +0200 Subject: [PATCH 02/31] Decide for foo.rs approach --- src/foo.rs | 213 ------------------------------------------- src/lib.rs | 129 ++++++++++++++++++++++---- src/reference.rs | 82 ----------------- src/reference_mut.rs | 163 --------------------------------- src/tests.rs | 35 ------- 5 files changed, 109 insertions(+), 513 deletions(-) delete mode 100644 src/foo.rs delete mode 100644 src/reference.rs delete mode 100644 src/reference_mut.rs delete mode 100644 src/tests.rs diff --git a/src/foo.rs b/src/foo.rs deleted file mode 100644 index 88f1b1a..0000000 --- a/src/foo.rs +++ /dev/null @@ -1,213 +0,0 @@ -use crate::access::{ReadWrite, Readable, Writable}; -use core::{ - marker::PhantomData, - ops::{Index, IndexMut}, - ptr, - slice::SliceIndex, -}; - -/// 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. -/// -/// The size of this struct is the same as the size of the contained type. -#[derive(Debug, Default, Clone)] -#[repr(transparent)] -pub struct Volatile { - value: T, - access: PhantomData, -} - -impl Volatile { - /// Construct a new volatile instance wrapping the given value. - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let value = Volatile::new(0u32); - /// ``` - /// - /// # Panics - /// - /// This method never panics. - pub const fn new(value: T) -> Volatile { - Volatile { - value, - access: PhantomData, - } - } -} - -impl Volatile<&T, A> { - /// 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::Volatile; - /// - /// let value = Volatile::new(42u32); - /// - /// assert_eq!(value.read(), 42u32); - /// ``` - /// - /// # Panics - /// - /// This method never panics. - pub fn read(&self) -> T - where - A: Readable, - { - // UNSAFE: Safe, as we know that our internal value exists. - unsafe { ptr::read_volatile(self.value) } - } -} - -impl Volatile<&mut T, A> { - /// 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::Volatile; - /// - /// let value = Volatile::new(42u32); - /// - /// assert_eq!(value.read(), 42u32); - /// ``` - /// - /// # Panics - /// - /// This method never panics. - 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::Volatile; - /// - /// let mut value = Volatile::new(0u32); - /// - /// value.write(42u32); - /// - /// assert_eq!(value.read(), 42u32); - /// ``` - /// - /// # Panics - /// - /// This method never panics. - pub fn write(&mut self, value: T) - where - A: Writable, - { - // UNSAFE: Safe, as we know that our internal value exists. - unsafe { ptr::write_volatile(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::Volatile; - /// - /// let mut value = Volatile::new(21u32); - /// - /// value.update(|val_ref| *val_ref *= 2); - /// - /// assert_eq!(value.read(), 42u32); - /// ``` - /// - /// # Panics - /// - /// Ths method never panics. - pub fn update(&mut self, f: F) - where - F: FnOnce(&mut T), - A: Readable + Writable, - { - let mut value = self.read(); - f(&mut value); - self.write(value); - } -} - -impl Volatile<&[T], A> { - pub fn index(&self, index: I) -> Volatile<&I::Output, A> - where - I: SliceIndex<[T]>, - { - Volatile { - value: self.value.index(index), - access: self.access, - } - } -} - -impl Volatile<&mut [T], A> { - pub fn index(&self, index: I) -> Volatile<&I::Output, A> - where - I: SliceIndex<[T]>, - { - Volatile { - value: self.value.index(index), - access: self.access, - } - } - - pub fn index_mut(&mut self, index: I) -> Volatile<&mut I::Output, A> - where - I: SliceIndex<[T]>, - { - Volatile { - value: self.value.index_mut(index), - access: self.access, - } - } -} - -#[cfg(test)] -mod tests { - use super::Volatile; - - #[test] - fn test_read() { - let val = 42; - assert_eq!(Volatile::new(&42).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]); - } -} diff --git a/src/lib.rs b/src/lib.rs index a9b6e02..1e6dc40 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,22 +27,22 @@ //! of the volatile wrapper types are the same size as their contained values. #![no_std] -use access::{ReadWrite, Readable, Writable}; -use core::{marker::PhantomData, ptr}; +pub use crate::access::{ReadWrite, Readable, Writable}; +use core::{ + marker::PhantomData, + ops::{Index, IndexMut}, + ptr, + slice::SliceIndex, +}; -pub mod access; -pub mod foo; -pub mod reference; -pub mod reference_mut; -#[cfg(test)] -mod tests; +mod access; /// 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. /// /// The size of this struct is the same as the size of the contained type. -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] #[repr(transparent)] pub struct Volatile { value: T, @@ -69,7 +69,7 @@ impl Volatile { } } -impl Volatile { +impl Volatile<&T, A> { /// 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 @@ -86,13 +86,40 @@ impl Volatile { /// # Panics /// /// This method never panics. - pub fn read(&self) -> T { + pub fn read(&self) -> T + where + A: Readable, + { // UNSAFE: Safe, as we know that our internal value exists. - unsafe { ptr::read_volatile(&self.value) } + unsafe { ptr::read_volatile(self.value) } } } -impl Volatile { +impl Volatile<&mut T, A> { + /// 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::Volatile; + /// + /// let value = Volatile::new(42u32); + /// + /// assert_eq!(value.read(), 42u32); + /// ``` + /// + /// # Panics + /// + /// This method never panics. + 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` @@ -111,13 +138,14 @@ impl Volatile { /// # Panics /// /// This method never panics. - pub fn write(&mut self, value: T) { + 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) }; + unsafe { ptr::write_volatile(self.value, value) }; } -} -impl Volatile { /// 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. @@ -138,6 +166,7 @@ impl Volatile { pub fn update(&mut self, f: F) where F: FnOnce(&mut T), + A: Readable + Writable, { let mut value = self.read(); f(&mut value); @@ -145,11 +174,71 @@ impl Volatile { } } -impl Clone for Volatile { - fn clone(&self) -> Self { +impl Volatile<&[T], A> { + pub fn index(&self, index: I) -> Volatile<&I::Output, A> + where + I: SliceIndex<[T]>, + { + Volatile { + value: self.value.index(index), + access: self.access, + } + } +} + +impl Volatile<&mut [T], A> { + pub fn index(&self, index: I) -> Volatile<&I::Output, A> + where + I: SliceIndex<[T]>, + { Volatile { - value: self.read(), + value: self.value.index(index), access: self.access, } } + + pub fn index_mut(&mut self, index: I) -> Volatile<&mut I::Output, A> + where + I: SliceIndex<[T]>, + { + Volatile { + value: self.value.index_mut(index), + access: self.access, + } + } +} + +#[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]); + } } diff --git a/src/reference.rs b/src/reference.rs deleted file mode 100644 index 61a0dda..0000000 --- a/src/reference.rs +++ /dev/null @@ -1,82 +0,0 @@ -use core::{ops::Index, ptr, slice::SliceIndex}; - -/// 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. -/// -/// The size of this struct is the same as the size of the contained type. -#[derive(Debug, Clone)] -#[repr(transparent)] -pub struct VolatileRef<'a, T: ?Sized> { - pub(crate) value: &'a T, -} - -impl VolatileRef<'_, T> { - /// Construct a new volatile instance wrapping the given value. - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let value = Volatile::new(0u32); - /// ``` - /// - /// # Panics/// - /// This method never panics. - #[cfg(feature = "const_fn")] - pub const fn new(value: &T) -> VolatileRef { - Volatile { - value, - access: PhantomData, - } - } - - /// Construct a new volatile instance wrapping the given value. - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let value = Volatile::new(0u32); - /// ``` - /// - /// # Panics - /// - /// This method never panics. - #[cfg(not(feature = "const_fn"))] - pub fn new(value: &T) -> VolatileRef { - VolatileRef { value } - } -} - -impl VolatileRef<'_, T> { - /// 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::Volatile; - /// - /// let value = Volatile::new(42u32); - /// - /// assert_eq!(value.read(), 42u32); - /// ``` - /// - /// # Panics - /// - /// This method never panics. - pub fn read(&self) -> T { - // UNSAFE: Safe, as we know that our internal value exists. - unsafe { ptr::read_volatile(self.value) } - } -} - -impl VolatileRef<'_, [T]> { - pub fn index(&self, index: I) -> VolatileRef - where - I: SliceIndex<[T], Output = [T]>, - { - VolatileRef { - value: self.value.index(index), - } - } -} diff --git a/src/reference_mut.rs b/src/reference_mut.rs deleted file mode 100644 index 163226d..0000000 --- a/src/reference_mut.rs +++ /dev/null @@ -1,163 +0,0 @@ -use crate::{ - access::{ReadWrite, Readable, Writable}, - reference::VolatileRef, -}; -use core::{ - marker::PhantomData, - ops::{Index, IndexMut}, - ptr, - slice::SliceIndex, -}; - -/// 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. -/// -/// The size of this struct is the same as the size of the contained type. -#[derive(Debug)] -#[repr(transparent)] -pub struct VolatileRefMut<'a, T: ?Sized, A = ReadWrite> { - value: &'a mut T, - access: PhantomData, -} - -impl VolatileRefMut<'_, T> { - /// Construct a new volatile instance wrapping the given value. - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let value = Volatile::new(0u32); - /// ``` - /// - /// # Panics - /// - /// This method never panics. - #[cfg(feature = "const_fn")] - pub const fn new(value: &mut T) -> VolatileRefMut { - VolatileRefMut { - value, - access: PhantomData, - } - } - - /// Construct a new volatile instance wrapping the given value. - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let value = Volatile::new(0u32); - /// ``` - /// - /// # Panics - /// - /// This method never panics. - #[cfg(not(feature = "const_fn"))] - pub fn new(value: &mut T) -> VolatileRefMut { - VolatileRefMut { - value, - access: PhantomData, - } - } -} - -impl VolatileRefMut<'_, T, A> { - /// 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::Volatile; - /// - /// let value = Volatile::new(42u32); - /// - /// assert_eq!(value.read(), 42u32); - /// ``` - /// - /// # Panics - /// - /// This method never panics. - 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::Volatile; - /// - /// let mut value = Volatile::new(0u32); - /// - /// value.write(42u32); - /// - /// assert_eq!(value.read(), 42u32); - /// ``` - /// - /// # Panics - /// - /// This method never panics. - pub fn write(&mut self, value: T) - where - A: Writable, - { - // UNSAFE: Safe, as we know that our internal value exists. - unsafe { ptr::write_volatile(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::Volatile; - /// - /// let mut value = Volatile::new(21u32); - /// - /// value.update(|val_ref| *val_ref *= 2); - /// - /// assert_eq!(value.read(), 42u32); - /// ``` - /// - /// # Panics - /// - /// Ths method never panics. - pub fn update(&mut self, f: F) - where - F: FnOnce(&mut T), - A: Readable + Writable, - { - let mut value = self.read(); - f(&mut value); - self.write(value); - } -} - -impl VolatileRefMut<'_, [T], A> { - pub fn index(&self, index: I) -> VolatileRef - where - I: SliceIndex<[T], Output = [T]>, - A: Readable, - { - VolatileRef { - value: self.value.index(index), - } - } - - pub fn index_mut(&mut self, index: I) -> VolatileRefMut - where - I: SliceIndex<[T], Output = [T]>, - { - VolatileRefMut { - value: self.value.index_mut(index), - access: self.access, - } - } -} diff --git a/src/tests.rs b/src/tests.rs deleted file mode 100644 index 27c53d8..0000000 --- a/src/tests.rs +++ /dev/null @@ -1,35 +0,0 @@ -use super::Volatile; - -#[test] -fn test_read() { - assert_eq!(Volatile::new(42).read(), 42); -} - -#[test] -fn test_write() { - let mut volatile = Volatile::new(42); - volatile.write(50); - assert_eq!(volatile.value, 50); -} - -#[test] -fn test_update() { - let mut volatile = Volatile::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 Volatile; - - // UNSAFE: Safe, as we know the value exists on the stack. - unsafe { - (*volatile_ptr).write(42u32); - } - - assert_eq!(target_value, 42u32); -} From 8be44300dec35d5cb3090d1e6eac64f0287131a0 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 29 Jul 2020 10:53:22 +0200 Subject: [PATCH 03/31] Update documentation --- src/lib.rs | 115 +++++++++++++++++++++++------------------------------ 1 file changed, 50 insertions(+), 65 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1e6dc40..cea41d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,30 +1,14 @@ #![cfg_attr(feature = "const_fn", feature(const_fn))] -//! Provides wrapper types `Volatile`, `ReadOnly`, `WriteOnly`, `ReadWrite`, which wrap 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 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 type found in `libcore` or `libstd`. //! //! These wrappers do not depend on the standard library and never panic. -//! -//! # 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::Volatile; -//! -//! let mut_ptr = 0xFEE00000 as *mut u32; -//! -//! let volatile_ptr = mut_ptr as *mut Volatile; -//! ``` -//! -//! 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. + #![no_std] pub use crate::access::{ReadWrite, Readable, Writable}; @@ -37,11 +21,14 @@ use core::{ mod access; -/// 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. +/// A wrapper type around a reference to a volatile variable. +/// +/// Allows volatile reads and writes on the referenced value. The referenced value needs to +/// be `Copy`, as volatile reads and writes take and return copies of the value. /// /// The size of this struct is the same as the size of the contained type. +/// +/// TODO: read/write permissions #[derive(Debug, Default, Clone)] #[repr(transparent)] pub struct Volatile { @@ -50,17 +37,18 @@ pub struct Volatile { } impl Volatile { - /// Construct a new volatile instance wrapping the given value. + /// Construct a new volatile instance wrapping the given value reference. + /// + /// ## Example /// /// ```rust /// use volatile::Volatile; /// - /// let value = Volatile::new(0u32); - /// ``` - /// - /// # Panics + /// let value = 0u32; /// - /// This method never panics. + /// let volatile = Volatile::new(&value); + /// assert_eq!(volatile.read(), 0); + /// ``` pub const fn new(value: T) -> Volatile { Volatile { value, @@ -70,22 +58,22 @@ impl Volatile { } impl Volatile<&T, A> { - /// Performs a volatile read of the contained value, returning a copy - /// of the read value. Volatile reads are guaranteed not to be optimized + /// 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 type. /// + /// ## Example + /// /// ```rust /// use volatile::Volatile; /// - /// let value = Volatile::new(42u32); + /// let value = 42; + /// let volatile = Volatile::new(&value); /// - /// assert_eq!(value.read(), 42u32); + /// assert_eq!(volatile.read(), 42); /// ``` - /// - /// # Panics - /// - /// This method never panics. pub fn read(&self) -> T where A: Readable, @@ -96,22 +84,22 @@ impl Volatile<&T, A> { } impl Volatile<&mut T, A> { - /// Performs a volatile read of the contained value, returning a copy - /// of the read value. Volatile reads are guaranteed not to be optimized + /// 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 type. /// + /// ## Example + /// /// ```rust /// use volatile::Volatile; /// - /// let value = Volatile::new(42u32); + /// let mut value = 42; + /// let volatile = Volatile::new(&mut value); /// - /// assert_eq!(value.read(), 42u32); + /// assert_eq!(volatile.read(), 42); /// ``` - /// - /// # Panics - /// - /// This method never panics. pub fn read(&self) -> T where A: Readable, @@ -120,24 +108,23 @@ impl Volatile<&mut T, A> { 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. + /// 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 type. + /// + /// ## Example /// /// ```rust /// use volatile::Volatile; /// - /// let mut value = Volatile::new(0u32); + /// let mut value = 42; + /// let mut volatile = Volatile::new(&mut value); + /// volatile.write(50); /// - /// value.write(42u32); - /// - /// assert_eq!(value.read(), 42u32); + /// assert_eq!(volatile.read(), 50); /// ``` - /// - /// # Panics - /// - /// This method never panics. pub fn write(&mut self, value: T) where A: Writable, @@ -146,6 +133,8 @@ impl Volatile<&mut T, A> { unsafe { ptr::write_volatile(self.value, 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. @@ -153,16 +142,12 @@ impl Volatile<&mut T, A> { /// ```rust /// use volatile::Volatile; /// - /// let mut value = Volatile::new(21u32); + /// let mut value = 42; + /// let mut volatile = Volatile::new(&mut value); + /// volatile.update(|val| *val += 1); /// - /// value.update(|val_ref| *val_ref *= 2); - /// - /// assert_eq!(value.read(), 42u32); + /// assert_eq!(volatile.read(), 43); /// ``` - /// - /// # Panics - /// - /// Ths method never panics. pub fn update(&mut self, f: F) where F: FnOnce(&mut T), From 401a86bd60b24691be2d9cf6c0474052e0d52217 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 29 Jul 2020 14:56:01 +0200 Subject: [PATCH 04/31] Remove const_fn cargo feature --- Cargo.toml | 3 --- src/lib.rs | 2 -- 2 files changed, 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 29b0c28..0d4bd85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,6 @@ repository = "https://github.com/phil-opp/volatile" [dependencies] -[features] -const_fn = [] - [package.metadata.release] no-dev-version = true pre-release-replacements = [ diff --git a/src/lib.rs b/src/lib.rs index cea41d1..b4a86d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ -#![cfg_attr(feature = "const_fn", feature(const_fn))] - //! 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. From 59dd91d9c382adf09a47aee27e7c63cd9482d887 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 29 Jul 2020 14:56:33 +0200 Subject: [PATCH 05/31] Reorganize access module and make it public --- src/access.rs | 14 +++++++------- src/lib.rs | 5 +++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/access.rs b/src/access.rs index 3aa4d59..9399492 100644 --- a/src/access.rs +++ b/src/access.rs @@ -1,13 +1,13 @@ pub trait Readable {} pub trait Writable {} -pub struct Read; - -impl Readable for Read {} - -pub struct Write; -impl Writable for Write {} - pub struct ReadWrite; impl Readable for ReadWrite {} impl Writable for ReadWrite {} + +pub struct ReadOnly; + +impl Readable for ReadOnly {} + +pub struct WriteOnly; +impl Writable for WriteOnly {} diff --git a/src/lib.rs b/src/lib.rs index b4a86d7..0be18f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ #![no_std] -pub use crate::access::{ReadWrite, Readable, Writable}; +use access::{ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; use core::{ marker::PhantomData, ops::{Index, IndexMut}, @@ -17,7 +17,8 @@ use core::{ slice::SliceIndex, }; -mod access; +/// Allows creating read-only and write-only `Volatile` values. +pub mod access; /// A wrapper type around a reference to a volatile variable. /// From 53fd257c6324561477f2236ba344500e558b1fa0 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 29 Jul 2020 14:58:07 +0200 Subject: [PATCH 06/31] Add constructor methods for read/write-only types Also: minor improvements --- src/lib.rs | 51 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0be18f3..85d9311 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,13 +30,13 @@ pub mod access; /// TODO: read/write permissions #[derive(Debug, Default, Clone)] #[repr(transparent)] -pub struct Volatile { - value: T, +pub struct Volatile { + reference: R, access: PhantomData, } -impl Volatile { - /// Construct a new volatile instance wrapping the given value reference. +impl Volatile { + /// Construct a new volatile instance wrapping the given reference. /// /// ## Example /// @@ -48,15 +48,32 @@ impl Volatile { /// let volatile = Volatile::new(&value); /// assert_eq!(volatile.read(), 0); /// ``` - pub const fn new(value: T) -> Volatile { + pub const fn new(reference: R) -> Volatile { Volatile { - value, + reference, + access: PhantomData, + } + } + + pub const fn new_read_only(reference: R) -> Volatile { + Volatile { + reference, + access: PhantomData, + } + } + + pub const fn new_write_only(reference: R) -> Volatile { + Volatile { + reference, access: PhantomData, } } } -impl Volatile<&T, A> { +impl Volatile<&T, A> +where + 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 @@ -78,7 +95,7 @@ impl Volatile<&T, A> { A: Readable, { // UNSAFE: Safe, as we know that our internal value exists. - unsafe { ptr::read_volatile(self.value) } + unsafe { ptr::read_volatile(self.reference) } } } @@ -94,17 +111,17 @@ impl Volatile<&mut T, A> { /// ```rust /// use volatile::Volatile; /// - /// let mut value = 42; + /// let mut value = 10; /// let volatile = Volatile::new(&mut value); /// - /// assert_eq!(volatile.read(), 42); + /// assert_eq!(volatile.read(), 10); /// ``` pub fn read(&self) -> T where A: Readable, { // UNSAFE: Safe, as we know that our internal value exists. - unsafe { ptr::read_volatile(self.value) } + unsafe { ptr::read_volatile(self.reference) } } /// Performs a volatile write, setting the contained value to the given `value`. @@ -129,7 +146,7 @@ impl Volatile<&mut T, A> { A: Writable, { // UNSAFE: Safe, as we know that our internal value exists. - unsafe { ptr::write_volatile(self.value, value) }; + unsafe { ptr::write_volatile(self.reference, value) }; } /// Updates the contained value using the given closure and volatile instructions. @@ -158,25 +175,25 @@ impl Volatile<&mut T, A> { } } -impl Volatile<&[T], A> { +impl Volatile<&[T], A> { pub fn index(&self, index: I) -> Volatile<&I::Output, A> where I: SliceIndex<[T]>, { Volatile { - value: self.value.index(index), + reference: self.reference.index(index), access: self.access, } } } -impl Volatile<&mut [T], A> { +impl Volatile<&mut [T], A> { pub fn index(&self, index: I) -> Volatile<&I::Output, A> where I: SliceIndex<[T]>, { Volatile { - value: self.value.index(index), + reference: self.reference.index(index), access: self.access, } } @@ -186,7 +203,7 @@ impl Volatile<&mut [T], A> { I: SliceIndex<[T]>, { Volatile { - value: self.value.index_mut(index), + reference: self.reference.index_mut(index), access: self.access, } } From b469f427b5e48a491017e477fdcb6feb659c4caf Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 29 Jul 2020 16:49:28 +0200 Subject: [PATCH 07/31] Make methods generic using `Deref` trait --- src/lib.rs | 51 +++++++++++++++++---------------------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 85d9311..9b1d859 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,8 @@ use access::{ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; use core::{ marker::PhantomData, - ops::{Index, IndexMut}, + ops::Deref, + ops::{DerefMut, Index, IndexMut}, ptr, slice::SliceIndex, }; @@ -35,6 +36,7 @@ pub struct Volatile { access: PhantomData, } +/// Construction functions impl Volatile { /// Construct a new volatile instance wrapping the given reference. /// @@ -70,8 +72,10 @@ impl Volatile { } } -impl Volatile<&T, A> +/// Methods for references to `Copy` types +impl Volatile where + R: Deref, T: Copy, { /// Performs a volatile read of the contained value. @@ -80,48 +84,25 @@ where /// away by the compiler, but by themselves do not have atomic ordering /// guarantees. To also get atomicity, consider looking at the `Atomic` wrapper type. /// - /// ## Example + /// ## Examples /// /// ```rust /// use volatile::Volatile; /// /// let value = 42; - /// let volatile = Volatile::new(&value); + /// let shared_reference = Volatile::new(&value); + /// assert_eq!(shared_reference.read(), 42); /// - /// assert_eq!(volatile.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) } - } -} - -impl Volatile<&mut T, A> { - /// 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 type. - /// - /// ## Example - /// - /// ```rust - /// use volatile::Volatile; - /// - /// let mut value = 10; - /// let volatile = Volatile::new(&mut value); - /// - /// assert_eq!(volatile.read(), 10); - /// ``` - pub fn read(&self) -> T - where - A: Readable, - { - // UNSAFE: Safe, as we know that our internal value exists. - unsafe { ptr::read_volatile(self.reference) } + unsafe { ptr::read_volatile(&*self.reference) } } /// Performs a volatile write, setting the contained value to the given `value`. @@ -144,9 +125,10 @@ impl Volatile<&mut T, A> { 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(self.reference, value) }; + unsafe { ptr::write_volatile(&mut *self.reference, value) }; } /// Updates the contained value using the given closure and volatile instructions. @@ -166,8 +148,9 @@ impl Volatile<&mut T, A> { /// ``` pub fn update(&mut self, f: F) where - F: FnOnce(&mut T), A: Readable + Writable, + R: DerefMut, + F: FnOnce(&mut T), { let mut value = self.read(); f(&mut value); From d0bcea2c17e18f3e9a6b672bdb18cbd64d4b07b9 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 29 Jul 2020 16:50:31 +0200 Subject: [PATCH 08/31] Add methods for working with volatile slices These methods are only available on nightly since the required functions of `core::intrinsics` are still unstable. --- Cargo.toml | 3 ++ src/lib.rs | 88 +++++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 80 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0d4bd85..969dd34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,9 @@ repository = "https://github.com/phil-opp/volatile" [dependencies] +[features] +nightly = [] + [package.metadata.release] no-dev-version = true pre-release-replacements = [ diff --git a/src/lib.rs b/src/lib.rs index 9b1d859..7b659b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,8 +8,11 @@ //! These wrappers do not depend on the standard library and never panic. #![no_std] +#![cfg_attr(feature = "nightly", feature(core_intrinsics))] use access::{ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; +#[cfg(feature = "nightly")] +use core::intrinsics; use core::{ marker::PhantomData, ops::Deref, @@ -158,36 +161,99 @@ where } } -impl Volatile<&[T], A> { - pub fn index(&self, index: I) -> Volatile<&I::Output, A> +/// Methods for volatile slices +impl Volatile +where + R: Deref, +{ + pub fn index<'a, I>(&'a self, index: I) -> Volatile<&'a I::Output, A> where I: SliceIndex<[T]>, + T: 'a, { Volatile { reference: self.reference.index(index), access: self.access, } } -} -impl Volatile<&mut [T], A> { - pub fn index(&self, index: I) -> Volatile<&I::Output, A> + pub fn index_mut<'a, I>(&'a mut self, index: I) -> Volatile<&mut I::Output, A> where I: SliceIndex<[T]>, + R: DerefMut, + T: 'a, { Volatile { - reference: self.reference.index(index), + reference: self.reference.index_mut(index), access: self.access, } } - pub fn index_mut(&mut self, index: I) -> Volatile<&mut I::Output, A> + #[cfg(feature = "nightly")] + pub fn copy_into_slice(&self, dst: &mut [T]) where - I: SliceIndex<[T]>, + T: Copy, { - Volatile { - reference: self.reference.index_mut(index), - access: self.access, + assert_eq!( + self.reference.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(), + ); + } + } + + /// Copies all elements from `src` into `self`, using a volatile memcpy. + /// + /// The length of `src` must be the same as `self`. + /// + /// ## Panics + /// + /// This function will panic if the two slices have different lengths. + /// + /// ## Examples + /// + /// Copying two elements from a slice into another: + /// + /// ``` + /// 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 = "nightly")] + pub fn copy_from_slice(&mut self, src: &[T]) + where + T: Copy, + R: DerefMut, + { + assert_eq!( + self.reference.len(), + src.len(), + "destination and source slices have different lengths" + ); + unsafe { + intrinsics::volatile_copy_nonoverlapping_memory( + self.reference.as_mut_ptr(), + src.as_ptr(), + self.reference.len(), + ); } } } From 1b4931dabc5f749cf72b0c4024a8ebf2b1675189 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 29 Jul 2020 17:48:34 +0200 Subject: [PATCH 09/31] Document `copy_into_slice` method --- src/lib.rs | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 7b659b6..e8e524d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -189,6 +189,38 @@ where } } + /// 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 `nightly` 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 = "nightly")] pub fn copy_into_slice(&self, dst: &mut [T]) where @@ -212,13 +244,16 @@ where /// /// The length of `src` must be the same as `self`. /// + /// The method is only available with the `nightly` 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 another: + /// Copying two elements from a slice into a volatile slice: /// /// ``` /// use volatile::Volatile; From 4766a435a95a5911e994e86311dc4f4486dff883 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 30 Jul 2020 09:57:57 +0200 Subject: [PATCH 10/31] Add `as_slice`/`as_mut_slice` methods for volatile array references --- src/lib.rs | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index e8e524d..abae5d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,8 @@ #![no_std] #![cfg_attr(feature = "nightly", feature(core_intrinsics))] +#![cfg_attr(feature = "nightly", feature(const_generics))] +#![cfg_attr(feature = "nightly", allow(incomplete_features))] use access::{ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; #[cfg(feature = "nightly")] @@ -293,6 +295,77 @@ where } } +/// Methods for converting arrays to slices +/// +/// These methods are only available with the `nightly` feature enabled (requires a nightly +/// Rust compiler). +#[cfg(feature = "nightly")] +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 + /// + /// Copying two elements from a volatile array reference using `copy_into_slice`: + /// + /// ``` + /// use volatile::Volatile; + /// + /// let src = [1, 2]; + /// let volatile = Volatile::new(&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) -> Volatile<&[T], A> { + Volatile { + reference: &*self.reference, + access: self.access, + } + } + + /// 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, + { + Volatile { + reference: &mut *self.reference, + access: self.access, + } + } +} + #[cfg(test)] mod tests { use super::Volatile; From cfbd66ea886a5b1bff8453431b92056d135bb49c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 31 Jul 2020 10:39:03 +0200 Subject: [PATCH 11/31] Rename the 'nightly' feature to 'unstable' --- Cargo.toml | 3 ++- src/lib.rs | 16 +++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 969dd34..2f95c59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,8 @@ repository = "https://github.com/phil-opp/volatile" [dependencies] [features] -nightly = [] +# Enable unstable features; requires Rust nightly; might break on compiler updates +unstable = [] [package.metadata.release] no-dev-version = true diff --git a/src/lib.rs b/src/lib.rs index abae5d0..e1da887 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,12 +8,14 @@ //! These wrappers do not depend on the standard library and never panic. #![no_std] -#![cfg_attr(feature = "nightly", feature(core_intrinsics))] -#![cfg_attr(feature = "nightly", feature(const_generics))] -#![cfg_attr(feature = "nightly", allow(incomplete_features))] +#![cfg_attr(feature = "unstable", feature(core_intrinsics))] +#![cfg_attr(feature = "unstable", feature(const_generics))] +#![cfg_attr(feature = "unstable", allow(incomplete_features))] + +//#![warn(missing_docs)] use access::{ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; -#[cfg(feature = "nightly")] +#[cfg(feature = "unstable")] use core::intrinsics; use core::{ marker::PhantomData, @@ -223,7 +225,7 @@ where /// assert_eq!(src, [1, 2]); /// assert_eq!(dst, [5, 1, 2]); /// ``` - #[cfg(feature = "nightly")] + #[cfg(feature = "unstable")] pub fn copy_into_slice(&self, dst: &mut [T]) where T: Copy, @@ -274,7 +276,7 @@ where /// assert_eq!(src, [1, 2, 3, 4]); /// assert_eq!(dst, [3, 4]); /// ``` - #[cfg(feature = "nightly")] + #[cfg(feature = "unstable")] pub fn copy_from_slice(&mut self, src: &[T]) where T: Copy, @@ -299,7 +301,7 @@ where /// /// These methods are only available with the `nightly` feature enabled (requires a nightly /// Rust compiler). -#[cfg(feature = "nightly")] +#[cfg(feature = "unstable")] impl Volatile where R: Deref, From bf8031c119972541919fc9ad3b2ba06c631c14f7 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 31 Jul 2020 10:54:33 +0200 Subject: [PATCH 12/31] Document constructor functions --- src/lib.rs | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 77 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e1da887..63c7a28 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,19 +43,29 @@ pub struct Volatile { access: PhantomData, } -/// Construction functions +/// Constructor functions +/// +/// 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 { - /// Construct a new volatile instance wrapping the given reference. + /// 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. There are also special methods for some valus, for example slicing methods + /// if the wrapped value is a slice. /// /// ## Example /// /// ```rust /// use volatile::Volatile; /// - /// let value = 0u32; + /// let mut value = 0u32; /// - /// let volatile = Volatile::new(&value); - /// assert_eq!(volatile.read(), 0); + /// let mut volatile = Volatile::new(&mut value); + /// volatile.write(1); + /// assert_eq!(volatile.read(), 1); /// ``` pub const fn new(reference: R) -> Volatile { Volatile { @@ -64,6 +74,37 @@ impl Volatile { } } + /// 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, @@ -71,6 +112,37 @@ impl Volatile { } } + /// 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, From 93e7150b06b7a2a88feed861616d03e23d07f86e Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 31 Jul 2020 11:07:56 +0200 Subject: [PATCH 13/31] Provide a custom fmt::Debug implementation to prevent reading of write-only values --- src/lib.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 63c7a28..1b52a35 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ use core::{ ops::Deref, ops::{DerefMut, Index, IndexMut}, ptr, - slice::SliceIndex, + slice::SliceIndex, fmt, }; /// Allows creating read-only and write-only `Volatile` values. @@ -36,7 +36,7 @@ pub mod access; /// The size of this struct is the same as the size of the contained type. /// /// TODO: read/write permissions -#[derive(Debug, Default, Clone)] +#[derive(Default, Clone)] #[repr(transparent)] pub struct Volatile { reference: R, @@ -440,6 +440,18 @@ where } } +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; From 8682a86cd8367501aecc8fdd9d4e2e01faf9e8e1 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 31 Jul 2020 11:08:09 +0200 Subject: [PATCH 14/31] Improve documentation of `Volatile::new` --- src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1b52a35..e422ee2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,8 +53,11 @@ impl Volatile { /// /// 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. There are also special methods for some valus, for example slicing methods - /// if the wrapped value is a slice. + /// 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 not recommended to use + /// the `Volatile` type only with references. /// /// ## Example /// From 46a7603cffcfa32993f01e911b44d1c4ae391068 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 31 Jul 2020 11:09:13 +0200 Subject: [PATCH 15/31] Warn on missing documentation --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index e422ee2..1103b46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,7 @@ #![cfg_attr(feature = "unstable", feature(const_generics))] #![cfg_attr(feature = "unstable", allow(incomplete_features))] -//#![warn(missing_docs)] +#![warn(missing_docs)] use access::{ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; #[cfg(feature = "unstable")] From 7dffe2cbf16120d6db65438311f665345a2149c2 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 19 Aug 2020 12:16:02 +0200 Subject: [PATCH 16/31] Improve documentation --- src/access.rs | 6 ++++++ src/lib.rs | 21 +++++++++++++-------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/access.rs b/src/access.rs index 9399492..254bfec 100644 --- a/src/access.rs +++ b/src/access.rs @@ -1,13 +1,19 @@ +/// Helper trait that is implemented by [`ReadWrite`] and [`ReadOnly`]. pub trait Readable {} + +/// Helper trait that is implemented by [`ReadWrite`] and [`WriteOnly`]. pub trait Writable {} +/// Zero-sized marker type for allowing both read and write access. pub struct ReadWrite; impl Readable for ReadWrite {} impl Writable for ReadWrite {} +/// Zero-sized marker type for allowing only read access. pub struct ReadOnly; impl Readable for ReadOnly {} +/// Zero-sized marker type for allowing only write access. pub struct WriteOnly; impl Writable for WriteOnly {} diff --git a/src/lib.rs b/src/lib.rs index 1103b46..2988bb7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ //! 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 type found in `libcore` or `libstd`. +//! looking at the `Atomic` wrapper types found in `libcore` or `libstd`. //! //! These wrappers do not depend on the standard library and never panic. @@ -28,14 +28,18 @@ use core::{ /// Allows creating read-only and write-only `Volatile` values. pub mod access; -/// A wrapper type around a reference to a volatile variable. +/// Wraps a reference to make accesses to referenced value volatile. /// /// Allows volatile reads and writes on the referenced value. The referenced value needs to -/// be `Copy`, as volatile reads and writes take and return copies of the value. +/// be `Copy` for reading and writing, as volatile reads and writes take and return copies +/// of the value. /// -/// The size of this struct is the same as the size of the contained type. +/// 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. /// -/// TODO: read/write permissions +/// The size of this struct is the same as the size of the contained reference. #[derive(Default, Clone)] #[repr(transparent)] pub struct Volatile { @@ -43,7 +47,7 @@ pub struct Volatile { access: PhantomData, } -/// Constructor functions +/// 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 @@ -164,7 +168,8 @@ where /// /// 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 type. + /// guarantees. To also get atomicity, consider looking at the `Atomic` wrapper types of + /// the standard/`core` library. /// /// ## Examples /// @@ -191,7 +196,7 @@ where /// /// 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. + /// looking at the `Atomic` wrapper types of the standard/`core` library. /// /// ## Example /// From dfbaa689337fcf2e63ee5c59a61bd9730147495c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 19 Aug 2020 12:16:50 +0200 Subject: [PATCH 17/31] Run cargo fmt --- src/lib.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2988bb7..0aa86f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,18 +11,18 @@ #![cfg_attr(feature = "unstable", feature(core_intrinsics))] #![cfg_attr(feature = "unstable", feature(const_generics))] #![cfg_attr(feature = "unstable", allow(incomplete_features))] - #![warn(missing_docs)] use access::{ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; #[cfg(feature = "unstable")] use core::intrinsics; use core::{ + fmt, marker::PhantomData, ops::Deref, ops::{DerefMut, Index, IndexMut}, ptr, - slice::SliceIndex, fmt, + slice::SliceIndex, }; /// Allows creating read-only and write-only `Volatile` values. @@ -448,13 +448,21 @@ where } } -impl fmt::Debug for Volatile where R: Deref, T:Copy + fmt::Debug, A: Readable { +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 { +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() } From 76e76575e0f0b260a6a1e31f39d094972aa8ef90 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 19 Aug 2020 12:17:06 +0200 Subject: [PATCH 18/31] Don't derive Default --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 0aa86f2..5677df5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,7 @@ 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(Default, Clone)] +#[derive(Clone)] #[repr(transparent)] pub struct Volatile { reference: R, From 4ba88d3f68ba05ee9aaee57abccfe9f74bca80a3 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 19 Aug 2020 12:17:47 +0200 Subject: [PATCH 19/31] Add `map`/`map_mut`/`extract_inner` methods --- src/lib.rs | 161 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 5677df5..ccbe71f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -158,6 +158,147 @@ impl Volatile { } } +/// 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 references to `Copy` types impl Volatile where @@ -501,4 +642,24 @@ mod tests { 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); + } } From fb7e6a5e35c640e5d1ae69c253617753d626040d Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 19 Aug 2020 12:18:09 +0200 Subject: [PATCH 20/31] Use map/map_mut for index/index_mut implementation --- src/lib.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ccbe71f..f7b1f5f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -396,10 +396,7 @@ where I: SliceIndex<[T]>, T: 'a, { - Volatile { - reference: self.reference.index(index), - access: self.access, - } + self.map(|slice| slice.index(index)) } pub fn index_mut<'a, I>(&'a mut self, index: I) -> Volatile<&mut I::Output, A> @@ -408,10 +405,7 @@ where R: DerefMut, T: 'a, { - Volatile { - reference: self.reference.index_mut(index), - access: self.access, - } + self.map_mut(|slice| slice.index_mut(index)) } /// Copies all elements from `self` into `dst`, using a volatile memcpy. From a78fd04d4df3c214469db384d342d125d4d03207 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 19 Aug 2020 12:19:48 +0200 Subject: [PATCH 21/31] Use map/map_mut for array to slice conversion methods --- src/lib.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f7b1f5f..3793b86 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -544,10 +544,7 @@ where /// assert_eq!(dst, [1, 2]); /// ``` pub fn as_slice(&self) -> Volatile<&[T], A> { - Volatile { - reference: &*self.reference, - access: self.access, - } + self.map(|array| &*array) } /// Converts a mutable array reference to a mutable slice. @@ -576,10 +573,7 @@ where where R: DerefMut, { - Volatile { - reference: &mut *self.reference, - access: self.access, - } + self.map_mut(|array| &mut *array) } } From eabda2ef7a3c366059dd470bf0e23442ad6a1127 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 19 Aug 2020 12:20:03 +0200 Subject: [PATCH 22/31] Add documentation for slice index methods --- src/lib.rs | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 3793b86..bca2a15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -391,6 +391,38 @@ 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]>, @@ -399,6 +431,40 @@ where 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]>, From 2e272d6f616c7ba5386c266b8cfac966231c3bc4 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 19 Aug 2020 12:26:04 +0200 Subject: [PATCH 23/31] Fix typo in docs --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index bca2a15..85f7954 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,7 @@ use core::{ /// Allows creating read-only and write-only `Volatile` values. pub mod access; -/// Wraps a reference to make accesses to referenced value volatile. +/// 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 From d12063861e38d1f555bf1169eba9b853013d5945 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 19 Aug 2020 12:27:47 +0200 Subject: [PATCH 24/31] Change order of impl blocks to show `read`/`write` methods first in docs --- src/lib.rs | 174 ++++++++++++++++++++++++++--------------------------- 1 file changed, 87 insertions(+), 87 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 85f7954..fc6fb6f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -158,6 +158,93 @@ impl Volatile { } } +/// 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. @@ -299,93 +386,6 @@ where } } -/// 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); - } -} - /// Methods for volatile slices impl Volatile where From 966bea98e84b2ccd94aedc48fdfbc06bcc56d5e9 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 19 Aug 2020 12:30:21 +0200 Subject: [PATCH 25/31] Enable `unstable` feature when building docs on docs.rs --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 2f95c59..49c199c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,6 @@ pre-release-replacements = [ { file="Changelog.md", search="# Unreleased", replace="# Unreleased\n\n# {{version}} – {{date}}", exactly=1 }, ] pre-release-commit-message = "Release version {{version}}" + +[package.metadata.docs.rs] +features = ["unstable"] From 408a5f1693b5f677b466ae8d818e8a19819ac93b Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 19 Aug 2020 12:34:47 +0200 Subject: [PATCH 26/31] Create a CI workflow --- .github/workflows/build.yml | 95 +++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..654bd68 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,95 @@ +# Based on https://github.com/actions-rs/meta/blob/master/recipes/quickstart.md +# +# While our "example" application has the platform-specific code, +# for simplicity we are compiling and testing everything on the Ubuntu environment only. +# For multi-OS testing see the `cross.yml` workflow. + +on: [push, pull_request] + +name: Build + +jobs: + check: + name: Check + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - name: Run cargo check + uses: actions-rs/cargo@v1 + with: + command: check + + test: + name: Test Suite + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - name: Run cargo test + uses: actions-rs/cargo@v1 + with: + command: test + + unstable: + name: Test Suite (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 unstable + uses: actions-rs/cargo@v1 + with: + command: test + args: --features unstable + + + lints: + name: Lints + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: rustfmt, clippy + + - name: Run cargo fmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + - name: Run cargo clippy + uses: actions-rs/cargo@v1 + with: + command: clippy From 9bc9f9517043aa8db5d869e9c1acb2e9c1beb3e3 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 19 Aug 2020 12:37:25 +0200 Subject: [PATCH 27/31] Fix as_slice/as_mut_slice --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fc6fb6f..c700596 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -610,7 +610,7 @@ where /// assert_eq!(dst, [1, 2]); /// ``` pub fn as_slice(&self) -> Volatile<&[T], A> { - self.map(|array| &*array) + self.map(|array| &array[..]) } /// Converts a mutable array reference to a mutable slice. @@ -639,7 +639,7 @@ where where R: DerefMut, { - self.map_mut(|array| &mut *array) + self.map_mut(|array| &mut array[..]) } } From 267ef61889152e1e25839cf1e2a09ce856e0e40b Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 9 Sep 2020 17:52:55 +0200 Subject: [PATCH 28/31] Add `fill` and `copy_within` methods for slices The `fill` method is only support for `u8` slices because there is no intrinsic for volatile memset with a generic type `T`. --- src/lib.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c700596..f770091 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,12 +10,11 @@ #![no_std] #![cfg_attr(feature = "unstable", feature(core_intrinsics))] #![cfg_attr(feature = "unstable", feature(const_generics))] +#![cfg_attr(feature = "unstable", feature(slice_check_range))] #![cfg_attr(feature = "unstable", allow(incomplete_features))] #![warn(missing_docs)] use access::{ReadOnly, ReadWrite, Readable, Writable, WriteOnly}; -#[cfg(feature = "unstable")] -use core::intrinsics; use core::{ fmt, marker::PhantomData, @@ -24,6 +23,11 @@ use core::{ ptr, slice::SliceIndex, }; +#[cfg(feature = "unstable")] +use core::{ + intrinsics, + ops::{Range, RangeBounds}, +}; /// Allows creating read-only and write-only `Volatile` values. pub mod access; @@ -576,6 +580,53 @@ where ); } } + + #[cfg(feature = "unstable")] + pub fn copy_within(&mut self, src: impl RangeBounds, dest: usize) + where + T: Copy, + R: DerefMut, + { + // 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, + } = self.reference.check_range(src); + let count = src_end - src_start; + assert!( + dest <= self.reference.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), + count, + ); + } + } +} + +/// Methods for volatile byte slices +impl Volatile +where + R: Deref, +{ + #[cfg(feature = "unstable")] + pub fn fill(&mut self, value: u8) + where + R: DerefMut, + { + unsafe { + intrinsics::volatile_set_memory( + self.reference.as_mut_ptr(), + value, + self.reference.len(), + ); + } + } } /// Methods for converting arrays to slices From 401c5baa59ffc2c4d8dadecf18da58a606fad00c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 21 Sep 2020 11:31:20 +0200 Subject: [PATCH 29/31] Derive `Debug` and `Clone` for access types --- src/access.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/access.rs b/src/access.rs index 254bfec..438d233 100644 --- a/src/access.rs +++ b/src/access.rs @@ -5,15 +5,18 @@ pub trait Readable {} pub trait Writable {} /// Zero-sized marker type for allowing both read and write access. +#[derive(Debug, Copy, Clone)] pub struct ReadWrite; impl Readable for ReadWrite {} impl Writable for ReadWrite {} /// Zero-sized marker type for allowing only read access. +#[derive(Debug, Copy, Clone)] pub struct ReadOnly; impl Readable for ReadOnly {} /// Zero-sized marker type for allowing only write access. +#[derive(Debug, Copy, Clone)] pub struct WriteOnly; impl Writable for WriteOnly {} From a4ccd3d9efe67ee96067dba2686645ca6cf3775d Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 21 Sep 2020 11:31:36 +0200 Subject: [PATCH 30/31] Document new slice methods --- src/lib.rs | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f770091..f066ff5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -482,7 +482,7 @@ where /// /// The length of `dst` must be the same as `self`. /// - /// The method is only available with the `nightly` feature enabled (requires a nightly + /// The method is only available with the `unstable` feature enabled (requires a nightly /// Rust compiler). /// /// ## Panics @@ -533,7 +533,10 @@ where /// /// The length of `src` must be the same as `self`. /// - /// The method is only available with the `nightly` feature enabled (requires a nightly + /// 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 @@ -581,6 +584,38 @@ where } } + /// 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 @@ -614,6 +649,25 @@ 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 @@ -631,7 +685,7 @@ where /// Methods for converting arrays to slices /// -/// These methods are only available with the `nightly` feature enabled (requires a nightly +/// These methods are only available with the `unstable` feature enabled (requires a nightly /// Rust compiler). #[cfg(feature = "unstable")] impl Volatile From 2eaa1d6b09b0b38659a37bf5bf4ff97b092426b8 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 21 Sep 2020 11:42:49 +0200 Subject: [PATCH 31/31] Update check_range call for latest nightly --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index f066ff5..7c7ea5a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,7 @@ use core::{ use core::{ intrinsics, ops::{Range, RangeBounds}, + slice, }; /// Allows creating read-only and write-only `Volatile` values. @@ -626,7 +627,7 @@ where let Range { start: src_start, end: src_end, - } = self.reference.check_range(src); + } = slice::check_range(self.reference.len(), src); let count = src_end - src_start; assert!( dest <= self.reference.len() - count,