base/
volatile_memory.rs

1// Copyright 2017 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Types for volatile access to memory.
6//!
7//! Two of the core rules for safe rust is no data races and no aliased mutable references.
8//! `VolatileSlice`, along with types that produce it which implement
9//! `VolatileMemory`, allow us to sidestep that rule by wrapping pointers that absolutely have to be
10//! accessed volatile. Some systems really do need to operate on shared memory and can't have the
11//! compiler reordering or eliding access because it has no visibility into what other systems are
12//! doing with that hunk of memory.
13//!
14//! For the purposes of maintaining safety, volatile memory has some rules of its own:
15//! 1. No references or slices to volatile memory (`&` or `&mut`).
16//! 2. Access should always been done with a volatile read or write.
17//!
18//! The first rule is because having references of any kind to memory considered volatile would
19//! violate pointer aliasing. The second is because unvolatile accesses are inherently undefined if
20//! done concurrently without synchronization. With volatile access we know that the compiler has
21//! not reordered or elided the access.
22
23use std::cmp::min;
24use std::mem::size_of;
25use std::ptr::copy;
26use std::ptr::read_volatile;
27use std::ptr::write_bytes;
28use std::ptr::write_volatile;
29use std::result;
30use std::slice;
31
32use remain::sorted;
33use thiserror::Error;
34use zerocopy::FromBytes;
35use zerocopy::IntoBytes;
36
37use crate::IoBufMut;
38
39#[sorted]
40#[derive(Error, Eq, PartialEq, Debug)]
41pub enum VolatileMemoryError {
42    /// `addr` is out of bounds of the volatile memory slice.
43    #[error("address 0x{addr:x} is out of bounds")]
44    OutOfBounds { addr: usize },
45    /// Taking a slice at `base` with `offset` would overflow `usize`.
46    #[error("address 0x{base:x} offset by 0x{offset:x} would overflow")]
47    Overflow { base: usize, offset: usize },
48}
49
50pub type VolatileMemoryResult<T> = result::Result<T, VolatileMemoryError>;
51
52use crate::VolatileMemoryError as Error;
53type Result<T> = VolatileMemoryResult<T>;
54
55/// Trait for types that support raw volatile access to their data.
56pub trait VolatileMemory {
57    /// Gets a slice of memory at `offset` that is `count` bytes in length and supports volatile
58    /// access.
59    fn get_slice(&self, offset: usize, count: usize) -> Result<VolatileSlice>;
60}
61
62/// A slice of raw memory that supports volatile access. Like `std::io::IoSliceMut`, this type is
63/// guaranteed to be ABI-compatible with `libc::iovec` but unlike `IoSliceMut`, it doesn't
64/// automatically deref to `&mut [u8]`.
65#[derive(Copy, Clone, Debug)]
66#[repr(transparent)]
67pub struct VolatileSlice<'a>(IoBufMut<'a>);
68
69impl<'a> VolatileSlice<'a> {
70    /// Creates a slice of raw memory that must support volatile access.
71    pub fn new(buf: &mut [u8]) -> VolatileSlice {
72        VolatileSlice(IoBufMut::new(buf))
73    }
74
75    /// Creates a `VolatileSlice` from a pointer and a length.
76    ///
77    /// # Safety
78    ///
79    /// In order to use this method safely, `addr` must be valid for reads and writes of `len` bytes
80    /// and should live for the entire duration of lifetime `'a`.
81    pub unsafe fn from_raw_parts(addr: *mut u8, len: usize) -> VolatileSlice<'a> {
82        VolatileSlice(IoBufMut::from_raw_parts(addr, len))
83    }
84
85    /// Gets a const pointer to this slice's memory.
86    pub fn as_ptr(&self) -> *const u8 {
87        self.0.as_ptr()
88    }
89
90    /// Gets a mutable pointer to this slice's memory.
91    pub fn as_mut_ptr(&self) -> *mut u8 {
92        self.0.as_mut_ptr()
93    }
94
95    /// Gets the size of this slice.
96    pub fn size(&self) -> usize {
97        self.0.len()
98    }
99
100    /// Advance the starting position of this slice.
101    ///
102    /// Panics if `count > self.size()`.
103    pub fn advance(&mut self, count: usize) {
104        self.0.advance(count)
105    }
106
107    /// Shorten the length of the slice.
108    ///
109    /// Has no effect if `len > self.size()`.
110    pub fn truncate(&mut self, len: usize) {
111        self.0.truncate(len)
112    }
113
114    /// Returns this `VolatileSlice` as an `IoBufMut`.
115    pub fn as_iobuf(&self) -> &IoBufMut {
116        &self.0
117    }
118
119    /// Converts a slice of `VolatileSlice`s into a slice of `IoBufMut`s
120    #[allow(clippy::wrong_self_convention)]
121    pub fn as_iobufs<'mem, 'slice>(
122        iovs: &'slice [VolatileSlice<'mem>],
123    ) -> &'slice [IoBufMut<'mem>] {
124        // SAFETY:
125        // Safe because `VolatileSlice` is ABI-compatible with `IoBufMut`.
126        unsafe { slice::from_raw_parts(iovs.as_ptr() as *const IoBufMut, iovs.len()) }
127    }
128
129    /// Converts a mutable slice of `VolatileSlice`s into a mutable slice of `IoBufMut`s
130    #[inline]
131    pub fn as_iobufs_mut<'mem, 'slice>(
132        iovs: &'slice mut [VolatileSlice<'mem>],
133    ) -> &'slice mut [IoBufMut<'mem>] {
134        // SAFETY:
135        // Safe because `VolatileSlice` is ABI-compatible with `IoBufMut`.
136        unsafe { slice::from_raw_parts_mut(iovs.as_mut_ptr() as *mut IoBufMut, iovs.len()) }
137    }
138
139    /// Creates a copy of this slice with the address increased by `count` bytes, and the size
140    /// reduced by `count` bytes.
141    pub fn offset(self, count: usize) -> Result<VolatileSlice<'a>> {
142        let new_addr = (self.as_mut_ptr() as usize).checked_add(count).ok_or(
143            VolatileMemoryError::Overflow {
144                base: self.as_mut_ptr() as usize,
145                offset: count,
146            },
147        )?;
148        let new_size = self
149            .size()
150            .checked_sub(count)
151            .ok_or(VolatileMemoryError::OutOfBounds { addr: new_addr })?;
152
153        // SAFETY:
154        // Safe because the memory has the same lifetime and points to a subset of the memory of the
155        // original slice.
156        unsafe { Ok(VolatileSlice::from_raw_parts(new_addr as *mut u8, new_size)) }
157    }
158
159    /// Similar to `get_slice` but the returned slice outlives this slice.
160    ///
161    /// The returned slice's lifetime is still limited by the underlying data's lifetime.
162    pub fn sub_slice(self, offset: usize, count: usize) -> Result<VolatileSlice<'a>> {
163        let mem_end = offset
164            .checked_add(count)
165            .ok_or(VolatileMemoryError::Overflow {
166                base: offset,
167                offset: count,
168            })?;
169        if mem_end > self.size() {
170            return Err(Error::OutOfBounds { addr: mem_end });
171        }
172        let new_addr = (self.as_mut_ptr() as usize).checked_add(offset).ok_or(
173            VolatileMemoryError::Overflow {
174                base: self.as_mut_ptr() as usize,
175                offset,
176            },
177        )?;
178
179        // SAFETY:
180        // Safe because we have verified that the new memory is a subset of the original slice.
181        Ok(unsafe { VolatileSlice::from_raw_parts(new_addr as *mut u8, count) })
182    }
183
184    /// Sets each byte of this slice with the given byte, similar to `memset`.
185    ///
186    /// The bytes of this slice are accessed in an arbitray order.
187    ///
188    /// # Examples
189    ///
190    /// ```
191    /// # use base::VolatileSlice;
192    /// # fn test_write_45() -> Result<(), ()> {
193    /// let mut mem = [0u8; 32];
194    /// let vslice = VolatileSlice::new(&mut mem[..]);
195    /// vslice.write_bytes(45);
196    /// for &v in &mem[..] {
197    ///     assert_eq!(v, 45);
198    /// }
199    /// # Ok(())
200    /// # }
201    pub fn write_bytes(&self, value: u8) {
202        // SAFETY:
203        // Safe because the memory is valid and needs only byte alignment.
204        unsafe {
205            write_bytes(self.as_mut_ptr(), value, self.size());
206        }
207    }
208
209    /// Copies `self.size()` or `buf.len()` times the size of `T` bytes, whichever is smaller, to
210    /// `buf`.
211    ///
212    /// The copy happens from smallest to largest address in `T` sized chunks using volatile reads.
213    ///
214    /// # Examples
215    ///
216    /// ```
217    /// # use std::fs::File;
218    /// # use std::path::Path;
219    /// # use base::VolatileSlice;
220    /// # fn test_write_null() -> Result<(), ()> {
221    /// let mut mem = [0u8; 32];
222    /// let vslice = VolatileSlice::new(&mut mem[..]);
223    /// let mut buf = [5u8; 16];
224    /// vslice.copy_to(&mut buf[..]);
225    /// for v in &buf[..] {
226    ///     assert_eq!(buf[0], 0);
227    /// }
228    /// # Ok(())
229    /// # }
230    /// ```
231    pub fn copy_to<T>(&self, buf: &mut [T])
232    where
233        T: FromBytes + IntoBytes + Copy,
234    {
235        let mut addr = self.as_mut_ptr() as *const u8;
236        for v in buf.iter_mut().take(self.size() / size_of::<T>()) {
237            // SAFETY: Safe because buf is valid, aligned to type `T` and is initialized.
238            unsafe {
239                *v = read_volatile(addr as *const T);
240                addr = addr.add(size_of::<T>());
241            }
242        }
243    }
244
245    /// Copies `self.size()` or `slice.size()` bytes, whichever is smaller, to `slice`.
246    ///
247    /// The copies happen in an undefined order.
248    /// # Examples
249    ///
250    /// ```
251    /// # use base::VolatileMemory;
252    /// # use base::VolatileSlice;
253    /// # fn test_write_null() -> Result<(), ()> {
254    /// let mut mem = [0u8; 32];
255    /// let vslice = VolatileSlice::new(&mut mem[..]);
256    /// vslice.copy_to_volatile_slice(vslice.get_slice(16, 16).map_err(|_| ())?);
257    /// # Ok(())
258    /// # }
259    /// ```
260    pub fn copy_to_volatile_slice(&self, slice: VolatileSlice) {
261        // SAFETY: Safe because slice is valid and is byte aligned.
262        unsafe {
263            copy(
264                self.as_mut_ptr() as *const u8,
265                slice.as_mut_ptr(),
266                min(self.size(), slice.size()),
267            );
268        }
269    }
270
271    /// Copies `self.size()` or `buf.len()` times the size of `T` bytes, whichever is smaller, to
272    /// this slice's memory.
273    ///
274    /// The copy happens from smallest to largest address in `T` sized chunks using volatile writes.
275    ///
276    /// # Examples
277    ///
278    /// ```
279    /// # use std::fs::File;
280    /// # use std::path::Path;
281    /// # use base::VolatileMemory;
282    /// # use base::VolatileSlice;
283    /// # fn test_write_null() -> Result<(), ()> {
284    /// let mut mem = [0u8; 32];
285    /// let vslice = VolatileSlice::new(&mut mem[..]);
286    /// let buf = [5u8; 64];
287    /// vslice.copy_from(&buf[..]);
288    /// let mut copy_buf = [0u32; 4];
289    /// vslice.copy_to(&mut copy_buf);
290    /// for i in 0..4 {
291    ///     assert_eq!(copy_buf[i], 0x05050505);
292    /// }
293    /// # Ok(())
294    /// # }
295    /// ```
296    pub fn copy_from<T>(&self, buf: &[T])
297    where
298        T: IntoBytes + Copy,
299    {
300        let mut addr = self.as_mut_ptr();
301        for v in buf.iter().take(self.size() / size_of::<T>()) {
302            // SAFETY: Safe because buf is valid, aligned to type `T` and is mutable.
303            unsafe {
304                write_volatile(addr as *mut T, *v);
305                addr = addr.add(size_of::<T>());
306            }
307        }
308    }
309
310    /// Returns whether all bytes in this slice are zero or not.
311    ///
312    /// This is optimized for [VolatileSlice] aligned with 16 bytes.
313    ///
314    /// TODO(b/274840085): Use SIMD for better performance.
315    pub fn is_all_zero(&self) -> bool {
316        const MASK_4BIT: usize = 0x0f;
317        let head_addr = self.as_ptr() as usize;
318        // Round up by 16
319        let aligned_head_addr = (head_addr + MASK_4BIT) & !MASK_4BIT;
320        let tail_addr = head_addr + self.size();
321        // Round down by 16
322        let aligned_tail_addr = tail_addr & !MASK_4BIT;
323
324        if aligned_head_addr > aligned_tail_addr {
325            // The slice does not cross a 16-byte aligned boundary; check only [head_addr,
326            // tail_addr).
327            // SAFETY: The range [head_addr, tail_addr) is within VolatileSlice.
328            return unsafe { is_all_zero_naive(head_addr, tail_addr) };
329        }
330
331        // Check 16 bytes at once. The addresses should be 16 bytes aligned for better performance.
332        if (aligned_head_addr..aligned_tail_addr).step_by(16).any(
333            |aligned_addr|
334                // SAFETY: Each aligned_addr is within VolatileSlice
335                unsafe { *(aligned_addr as *const u128) } != 0,
336        ) {
337            return false;
338        }
339
340        if head_addr == aligned_head_addr && tail_addr == aligned_tail_addr {
341            // If head_addr and tail_addr are aligned, we can skip the unaligned part which contains
342            // at least 2 conditional branches.
343            true
344        } else {
345            // Check unaligned part.
346            // SAFETY: The range [head_addr, aligned_head_addr) and [aligned_tail_addr, tail_addr)
347            // are within VolatileSlice.
348            unsafe {
349                is_all_zero_naive(head_addr, aligned_head_addr)
350                    && is_all_zero_naive(aligned_tail_addr, tail_addr)
351            }
352        }
353    }
354}
355
356/// Check whether every byte is zero.
357///
358/// This checks byte by byte.
359///
360/// # Safety
361///
362/// * `head_addr` <= `tail_addr`
363/// * Bytes between `head_addr` and `tail_addr` is valid to access.
364unsafe fn is_all_zero_naive(head_addr: usize, tail_addr: usize) -> bool {
365    (head_addr..tail_addr).all(|addr| *(addr as *const u8) == 0)
366}
367
368impl VolatileMemory for VolatileSlice<'_> {
369    fn get_slice(&self, offset: usize, count: usize) -> Result<VolatileSlice> {
370        self.sub_slice(offset, count)
371    }
372}
373
374impl PartialEq<VolatileSlice<'_>> for VolatileSlice<'_> {
375    fn eq(&self, other: &VolatileSlice) -> bool {
376        let size = self.size();
377        if size != other.size() {
378            return false;
379        }
380
381        // SAFETY: We pass pointers into valid VolatileSlice regions, and size is checked above.
382        let cmp = unsafe { libc::memcmp(self.as_ptr() as _, other.as_ptr() as _, size) };
383
384        cmp == 0
385    }
386}
387
388/// The `PartialEq` implementation for `VolatileSlice` is reflexive, symmetric, and transitive.
389impl Eq for VolatileSlice<'_> {}
390
391impl std::io::Write for VolatileSlice<'_> {
392    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
393        let len = buf.len().min(self.size());
394        self.copy_from(&buf[..len]);
395        self.advance(len);
396        Ok(len)
397    }
398
399    fn flush(&mut self) -> std::io::Result<()> {
400        Ok(())
401    }
402}
403
404#[cfg(test)]
405mod tests {
406    use std::io::Write;
407    use std::sync::Arc;
408    use std::sync::Barrier;
409    use std::thread::spawn;
410
411    use super::*;
412
413    #[derive(Clone)]
414    struct VecMem {
415        mem: Arc<Vec<u8>>,
416    }
417
418    impl VecMem {
419        fn new(size: usize) -> VecMem {
420            VecMem {
421                mem: Arc::new(vec![0u8; size]),
422            }
423        }
424    }
425
426    impl VolatileMemory for VecMem {
427        fn get_slice(&self, offset: usize, count: usize) -> Result<VolatileSlice> {
428            let mem_end = offset
429                .checked_add(count)
430                .ok_or(VolatileMemoryError::Overflow {
431                    base: offset,
432                    offset: count,
433                })?;
434            if mem_end > self.mem.len() {
435                return Err(Error::OutOfBounds { addr: mem_end });
436            }
437
438            let new_addr = (self.mem.as_ptr() as usize).checked_add(offset).ok_or(
439                VolatileMemoryError::Overflow {
440                    base: self.mem.as_ptr() as usize,
441                    offset,
442                },
443            )?;
444
445            Ok(
446                // SAFETY: trivially safe
447                unsafe { VolatileSlice::from_raw_parts(new_addr as *mut u8, count) },
448            )
449        }
450    }
451
452    #[test]
453    fn observe_mutate() {
454        let a = VecMem::new(1);
455        let a_clone = a.clone();
456        a.get_slice(0, 1).unwrap().write_bytes(99);
457
458        let start_barrier = Arc::new(Barrier::new(2));
459        let thread_start_barrier = start_barrier.clone();
460        let end_barrier = Arc::new(Barrier::new(2));
461        let thread_end_barrier = end_barrier.clone();
462        spawn(move || {
463            thread_start_barrier.wait();
464            a_clone.get_slice(0, 1).unwrap().write_bytes(0);
465            thread_end_barrier.wait();
466        });
467
468        let mut byte = [0u8; 1];
469        a.get_slice(0, 1).unwrap().copy_to(&mut byte);
470        assert_eq!(byte[0], 99);
471
472        start_barrier.wait();
473        end_barrier.wait();
474
475        a.get_slice(0, 1).unwrap().copy_to(&mut byte);
476        assert_eq!(byte[0], 0);
477    }
478
479    #[test]
480    fn slice_size() {
481        let a = VecMem::new(100);
482        let s = a.get_slice(0, 27).unwrap();
483        assert_eq!(s.size(), 27);
484
485        let s = a.get_slice(34, 27).unwrap();
486        assert_eq!(s.size(), 27);
487
488        let s = s.get_slice(20, 5).unwrap();
489        assert_eq!(s.size(), 5);
490    }
491
492    #[test]
493    fn slice_overflow_error() {
494        let a = VecMem::new(1);
495        let res = a.get_slice(usize::MAX, 1).unwrap_err();
496        assert_eq!(
497            res,
498            Error::Overflow {
499                base: usize::MAX,
500                offset: 1,
501            }
502        );
503    }
504
505    #[test]
506    fn slice_oob_error() {
507        let a = VecMem::new(100);
508        a.get_slice(50, 50).unwrap();
509        let res = a.get_slice(55, 50).unwrap_err();
510        assert_eq!(res, Error::OutOfBounds { addr: 105 });
511    }
512
513    #[test]
514    fn is_all_zero_16bytes_aligned() {
515        let a = VecMem::new(1024);
516        let slice = a.get_slice(0, 1024).unwrap();
517
518        assert!(slice.is_all_zero());
519        a.get_slice(129, 1).unwrap().write_bytes(1);
520        assert!(!slice.is_all_zero());
521    }
522
523    #[test]
524    fn is_all_zero_head_not_aligned() {
525        let a = VecMem::new(1024);
526        let slice = a.get_slice(1, 1023).unwrap();
527
528        assert!(slice.is_all_zero());
529        a.get_slice(0, 1).unwrap().write_bytes(1);
530        assert!(slice.is_all_zero());
531        a.get_slice(1, 1).unwrap().write_bytes(1);
532        assert!(!slice.is_all_zero());
533        a.get_slice(1, 1).unwrap().write_bytes(0);
534        a.get_slice(129, 1).unwrap().write_bytes(1);
535        assert!(!slice.is_all_zero());
536    }
537
538    #[test]
539    fn is_all_zero_tail_not_aligned() {
540        let a = VecMem::new(1024);
541        let slice = a.get_slice(0, 1023).unwrap();
542
543        assert!(slice.is_all_zero());
544        a.get_slice(1023, 1).unwrap().write_bytes(1);
545        assert!(slice.is_all_zero());
546        a.get_slice(1022, 1).unwrap().write_bytes(1);
547        assert!(!slice.is_all_zero());
548        a.get_slice(1022, 1).unwrap().write_bytes(0);
549        a.get_slice(0, 1).unwrap().write_bytes(1);
550        assert!(!slice.is_all_zero());
551    }
552
553    #[test]
554    fn is_all_zero_no_aligned_16bytes() {
555        let a = VecMem::new(1024);
556        let slice = a.get_slice(1, 16).unwrap();
557
558        assert!(slice.is_all_zero());
559        a.get_slice(0, 1).unwrap().write_bytes(1);
560        assert!(slice.is_all_zero());
561        for i in 1..17 {
562            a.get_slice(i, 1).unwrap().write_bytes(1);
563            assert!(!slice.is_all_zero());
564            a.get_slice(i, 1).unwrap().write_bytes(0);
565        }
566        a.get_slice(17, 1).unwrap().write_bytes(1);
567        assert!(slice.is_all_zero());
568    }
569
570    #[test]
571    fn write_partial() {
572        let mem = VecMem::new(1024);
573        let mut slice = mem.get_slice(1, 16).unwrap();
574        slice.write_bytes(0xCC);
575
576        // Writing 4 bytes should succeed and advance the slice by 4 bytes.
577        let write_len = slice.write(&[1, 2, 3, 4]).unwrap();
578        assert_eq!(write_len, 4);
579        assert_eq!(slice.size(), 16 - 4);
580
581        // The written data should appear in the memory at offset 1.
582        assert_eq!(mem.mem[1..=4], [1, 2, 3, 4]);
583
584        // The next byte of the slice should be unmodified.
585        assert_eq!(mem.mem[5], 0xCC);
586    }
587
588    #[test]
589    fn write_multiple() {
590        let mem = VecMem::new(1024);
591        let mut slice = mem.get_slice(1, 16).unwrap();
592        slice.write_bytes(0xCC);
593
594        // Writing 4 bytes should succeed and advance the slice by 4 bytes.
595        let write_len = slice.write(&[1, 2, 3, 4]).unwrap();
596        assert_eq!(write_len, 4);
597        assert_eq!(slice.size(), 16 - 4);
598
599        // The next byte of the slice should be unmodified.
600        assert_eq!(mem.mem[5], 0xCC);
601
602        // Writing another 4 bytes should succeed and advance the slice again.
603        let write2_len = slice.write(&[5, 6, 7, 8]).unwrap();
604        assert_eq!(write2_len, 4);
605        assert_eq!(slice.size(), 16 - 4 - 4);
606
607        // The written data should appear in the memory at offset 1.
608        assert_eq!(mem.mem[1..=8], [1, 2, 3, 4, 5, 6, 7, 8]);
609
610        // The next byte of the slice should be unmodified.
611        assert_eq!(mem.mem[9], 0xCC);
612    }
613
614    #[test]
615    fn write_exact_slice_size() {
616        let mem = VecMem::new(1024);
617        let mut slice = mem.get_slice(1, 4).unwrap();
618        slice.write_bytes(0xCC);
619
620        // Writing 4 bytes should succeed and consume the entire slice.
621        let write_len = slice.write(&[1, 2, 3, 4]).unwrap();
622        assert_eq!(write_len, 4);
623        assert_eq!(slice.size(), 0);
624
625        // The written data should appear in the memory at offset 1.
626        assert_eq!(mem.mem[1..=4], [1, 2, 3, 4]);
627
628        // The byte after the slice should be unmodified.
629        assert_eq!(mem.mem[5], 0);
630    }
631
632    #[test]
633    fn write_more_than_slice_size() {
634        let mem = VecMem::new(1024);
635        let mut slice = mem.get_slice(1, 4).unwrap();
636        slice.write_bytes(0xCC);
637
638        // Attempting to write 5 bytes should succeed but only write 4 bytes.
639        let write_len = slice.write(&[1, 2, 3, 4, 5]).unwrap();
640        assert_eq!(write_len, 4);
641        assert_eq!(slice.size(), 0);
642
643        // The written data should appear in the memory at offset 1.
644        assert_eq!(mem.mem[1..=4], [1, 2, 3, 4]);
645
646        // The byte after the slice should be unmodified.
647        assert_eq!(mem.mem[5], 0);
648    }
649
650    #[test]
651    fn write_empty_slice() {
652        let mem = VecMem::new(1024);
653        let mut slice = mem.get_slice(1, 0).unwrap();
654
655        // Writing to an empty slice should always return 0.
656        assert_eq!(slice.write(&[1, 2, 3, 4]).unwrap(), 0);
657        assert_eq!(slice.write(&[5, 6, 7, 8]).unwrap(), 0);
658        assert_eq!(slice.write(&[]).unwrap(), 0);
659    }
660
661    #[test]
662    fn is_all_zero_sub_16bytes_unaligned() {
663        let a = VecMem::new(64);
664        // Set all bytes to non-zero, then zero only a 4-byte unaligned subslice [3..7].
665        a.get_slice(0, 64).unwrap().write_bytes(0xFF);
666        let slice = a.get_slice(3, 4).unwrap();
667        slice.write_bytes(0);
668        assert!(slice.is_all_zero());
669
670        // An empty unaligned slice inside non-zero memory must also be all-zero.
671        let empty_slice = a.get_slice(3, 0).unwrap();
672        assert!(empty_slice.is_all_zero());
673    }
674}
675
676#[cfg(kani)]
677mod kani_proofs {
678    use super::*;
679
680    #[repr(align(16))]
681    struct AlignedBuf([u8; 36]);
682
683    /// Proves that `VolatileSlice::is_all_zero` never accesses memory outside `[offset, offset +
684    /// len)` and is functionally equivalent to checking whether every byte in the slice is
685    /// zero, across all possible head/tail alignments and lengths (including < 16 bytes, 0
686    /// bytes, unaligned, and multi-block).
687    #[kani::proof]
688    #[kani::unwind(38)]
689    fn proof_volatile_slice_is_all_zero() {
690        let mut aligned = AlignedBuf(kani::any());
691        let offset: usize = kani::any();
692        let len: usize = kani::any();
693        kani::assume(offset <= 36);
694        kani::assume(len <= 36 - offset);
695
696        let expected = aligned.0[offset..offset + len].iter().all(|&b| b == 0);
697        let slice = VolatileSlice::new(&mut aligned.0[offset..offset + len]);
698        assert_eq!(slice.is_all_zero(), expected);
699    }
700
701    /// Also proves memory safety when `VolatileSlice` wraps a small stack buffer whose surrounding
702    /// bytes outside the allocation are not valid to dereference.
703    #[kani::proof]
704    #[kani::unwind(16)]
705    fn proof_volatile_slice_small_allocation_memory_safety() {
706        let mut small_buf: [u8; 4] = kani::any();
707        let expected = small_buf.iter().all(|&b| b == 0);
708        let slice = VolatileSlice::new(&mut small_buf);
709        assert_eq!(slice.is_all_zero(), expected);
710    }
711
712    /// Proves safety and algebraic properties of `VolatileSlice::sub_slice` and
713    /// `VolatileSlice::offset`.
714    #[kani::proof]
715    fn proof_volatile_slice_sub_slice_and_offset() {
716        let mut buf = [0u8; 64];
717        let total_len: usize = kani::any();
718        kani::assume(total_len <= 64);
719
720        let base_slice = VolatileSlice::new(&mut buf[..total_len]);
721        let offset: usize = kani::any();
722        let count: usize = kani::any();
723
724        match base_slice.sub_slice(offset, count) {
725            Ok(sub) => {
726                assert_eq!(sub.size(), count);
727                assert!(offset
728                    .checked_add(count)
729                    .is_some_and(|end| end <= total_len));
730                assert_eq!(
731                    sub.as_ptr() as usize,
732                    (base_slice.as_ptr() as usize) + offset
733                );
734            }
735            Err(VolatileMemoryError::Overflow { .. }) => {
736                assert!(offset.checked_add(count).is_none());
737            }
738            Err(VolatileMemoryError::OutOfBounds { .. }) => {
739                assert!(offset.checked_add(count).is_some_and(|end| end > total_len));
740            }
741        }
742
743        match base_slice.offset(offset) {
744            Ok(off_slice) => {
745                assert!(offset <= total_len);
746                assert_eq!(off_slice.size(), total_len - offset);
747                assert_eq!(
748                    off_slice.as_ptr() as usize,
749                    (base_slice.as_ptr() as usize) + offset
750                );
751            }
752            Err(_) => {
753                assert!(
754                    offset > total_len
755                        || (base_slice.as_ptr() as usize).checked_add(offset).is_none()
756                );
757            }
758        }
759    }
760}