Skip to main content
← PSoXide SDK guides and examples · SDK f2fec1c301f1 · All Cargo features enabled; check feature gates before use.

psx_residency/
buffer.rs

1//! RAM backing for a page pool.
2
3use crate::pool::{PageRun, PAGE_BYTES};
4
5const PAGE_WORDS: usize = PAGE_BYTES / 4;
6
7/// `PAGES` pages of [`PAGE_BYTES`] bytes, word aligned and contiguous.
8///
9/// Pair it with a [`crate::PagePool`] of the same capacity: the pool decides
10/// which pages belong to which allocation, the buffer owns the bytes. The
11/// all-zero image means a static buffer stays in `.bss` rather than the
12/// executable.
13///
14/// A byte view borrows the buffer, so it cannot outlive it, but it describes
15/// its run only until the pool compacts or the run is freed and reused:
16/// re-resolve through [`crate::Residency::try_run`] each use unless the entry
17/// is pinned.
18pub struct PageBuffer<const PAGES: usize> {
19    pages: [[u32; PAGE_WORDS]; PAGES],
20}
21
22impl<const PAGES: usize> Default for PageBuffer<PAGES> {
23    fn default() -> Self {
24        Self::new()
25    }
26}
27
28impl<const PAGES: usize> PageBuffer<PAGES> {
29    /// A zero-filled buffer.
30    pub const fn new() -> Self {
31        Self {
32            pages: [[0; PAGE_WORDS]; PAGES],
33        }
34    }
35
36    /// Number of pages.
37    pub const fn page_count(&self) -> usize {
38        PAGES
39    }
40
41    fn contains(run: PageRun, len: usize) -> bool {
42        (run.end_page() as usize) <= PAGES && len <= run.capacity_bytes()
43    }
44
45    /// The first `len` bytes of `run`, or `None` when the run is outside the
46    /// buffer or shorter than `len`.
47    pub fn bytes(&self, run: PageRun, len: usize) -> Option<&[u8]> {
48        if !Self::contains(run, len) {
49            return None;
50        }
51        // SAFETY: `self.pages.as_ptr()` carries provenance for the whole page
52        // array, and `contains` proved `first_page * PAGE_BYTES + len` lies
53        // inside it. Pages are contiguous `u32` arrays, valid as `u8`.
54        Some(unsafe {
55            core::slice::from_raw_parts(
56                self.pages
57                    .as_ptr()
58                    .cast::<u8>()
59                    .add(run.first_page as usize * PAGE_BYTES),
60                len,
61            )
62        })
63    }
64
65    /// Mutable form of [`PageBuffer::bytes`].
66    pub fn bytes_mut(&mut self, run: PageRun, len: usize) -> Option<&mut [u8]> {
67        if !Self::contains(run, len) {
68            return None;
69        }
70        // SAFETY: as in `bytes`; `&mut self` makes the view exclusive.
71        Some(unsafe {
72            core::slice::from_raw_parts_mut(
73                self.pages
74                    .as_mut_ptr()
75                    .cast::<u8>()
76                    .add(run.first_page as usize * PAGE_BYTES),
77                len,
78            )
79        })
80    }
81
82    /// Copy `data` to `offset` bytes into `run`. `false` when it does not fit.
83    pub fn write(&mut self, run: PageRun, offset: usize, data: &[u8]) -> bool {
84        let Some(end) = offset.checked_add(data.len()) else {
85            return false;
86        };
87        match self.bytes_mut(run, end) {
88            Some(bytes) => {
89                bytes[offset..].copy_from_slice(data);
90                true
91            }
92            None => false,
93        }
94    }
95
96    /// Move `from` to start at `to_page` (a `memmove`: the ranges may
97    /// overlap). The owner calls this for each [`crate::PagePool::compact_step`].
98    /// `false` when either range is outside the buffer.
99    pub fn move_pages(&mut self, from: PageRun, to_page: u32) -> bool {
100        let to = PageRun {
101            first_page: to_page,
102            page_count: from.page_count,
103        };
104        if from.end_page() as usize > PAGES || to.end_page() as usize > PAGES {
105            return false;
106        }
107        self.pages.copy_within(
108            from.first_page as usize..from.end_page() as usize,
109            to_page as usize,
110        );
111        true
112    }
113
114    /// Address of the first byte of `page`, for a transport that lands
115    /// sectors in place. `None` past the end.
116    pub fn page_address(&mut self, page: u32) -> Option<*mut u8> {
117        ((page as usize) < PAGES).then(|| {
118            self.pages
119                .as_mut_ptr()
120                .cast::<u8>()
121                .wrapping_add(page as usize * PAGE_BYTES)
122        })
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn writes_and_reads_across_a_page_boundary_without_staging() {
132        let mut buffer = PageBuffer::<3>::new();
133        let run = PageRun {
134            first_page: 0,
135            page_count: 2,
136        };
137        let payload = [1u8, 2, 3, 4, 5, 6, 7, 8];
138        assert!(buffer.write(run, PAGE_BYTES - 4, &payload));
139        let bytes = buffer.bytes(run, PAGE_BYTES + 4).unwrap();
140        assert_eq!(&bytes[PAGE_BYTES - 4..], &payload);
141    }
142
143    #[test]
144    fn refuses_views_outside_the_buffer_or_run() {
145        let mut buffer = PageBuffer::<2>::new();
146        let run = PageRun {
147            first_page: 1,
148            page_count: 1,
149        };
150        assert!(buffer.bytes(run, PAGE_BYTES + 1).is_none());
151        assert!(!buffer.write(run, PAGE_BYTES - 1, &[0, 0]));
152        let outside = PageRun {
153            first_page: 1,
154            page_count: 2,
155        };
156        assert!(buffer.bytes(outside, 1).is_none());
157        assert!(!buffer.move_pages(outside, 0));
158        assert!(buffer.page_address(2).is_none());
159        assert!(buffer.page_address(1).is_some());
160    }
161
162    #[test]
163    fn move_pages_preserves_bytes_even_when_ranges_overlap() {
164        let mut buffer = PageBuffer::<4>::new();
165        let source = PageRun {
166            first_page: 1,
167            page_count: 3,
168        };
169        buffer.write(source, 0, &[9; 8]);
170        buffer.write(source, 3 * PAGE_BYTES - 2, &[7, 8]);
171        assert!(buffer.move_pages(source, 0));
172        let moved = PageRun {
173            first_page: 0,
174            page_count: 3,
175        };
176        assert_eq!(&buffer.bytes(moved, 8).unwrap()[..8], &[9; 8]);
177        assert_eq!(
178            &buffer.bytes(moved, 3 * PAGE_BYTES).unwrap()[3 * PAGE_BYTES - 2..],
179            &[7, 8]
180        );
181    }
182}