unc_vm_types/
units.rs

1use crate::lib::std::convert::TryFrom;
2use crate::lib::std::fmt;
3use crate::lib::std::ops::{Add, Sub};
4use std::convert::TryInto;
5use thiserror::Error;
6
7/// WebAssembly page sizes are fixed to be 64KiB.
8/// Note: large page support may be added in an opt-in manner in the [future].
9///
10/// [future]: https://webassembly.org/docs/future-features/#large-page-support
11pub const WASM_PAGE_SIZE: usize = 0x10000;
12
13/// The number of pages we can have before we run out of byte index space.
14pub const WASM_MAX_PAGES: u32 = 0x10000;
15
16/// The minimum number of pages allowed.
17pub const WASM_MIN_PAGES: u32 = 0x100;
18
19/// Units of WebAssembly pages (as specified to be 65,536 bytes).
20#[derive(
21    Copy,
22    Clone,
23    PartialEq,
24    Eq,
25    PartialOrd,
26    Ord,
27    Hash,
28    rkyv::Serialize,
29    rkyv::Deserialize,
30    rkyv::Archive,
31)]
32#[archive(as = "Self")]
33#[repr(transparent)]
34pub struct Pages(pub u32);
35
36impl Pages {
37    /// Returns the largest value that can be represented by the Pages type.
38    ///
39    /// This is defined by the WebAssembly standard as 65,536 pages.
40    #[inline(always)]
41    pub const fn max_value() -> Self {
42        Self(WASM_MAX_PAGES)
43    }
44
45    /// Checked addition. Computes `self + rhs`,
46    /// returning `None` if overflow occurred.
47    pub fn checked_add(self, rhs: Self) -> Option<Self> {
48        let added = (self.0 as usize) + (rhs.0 as usize);
49        if added <= (WASM_MAX_PAGES as usize) {
50            Some(Self(added as u32))
51        } else {
52            None
53        }
54    }
55
56    /// Calculate number of bytes from pages.
57    pub fn bytes(self) -> Bytes {
58        self.into()
59    }
60}
61
62impl fmt::Debug for Pages {
63    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64        write!(f, "{} pages", self.0)
65    }
66}
67
68impl From<u32> for Pages {
69    fn from(other: u32) -> Self {
70        Self(other)
71    }
72}
73
74/// Units of WebAssembly memory in terms of 8-bit bytes.
75#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
76pub struct Bytes(pub usize);
77
78impl fmt::Debug for Bytes {
79    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80        write!(f, "{} bytes", self.0)
81    }
82}
83
84impl From<Pages> for Bytes {
85    fn from(pages: Pages) -> Self {
86        Self((pages.0 as usize) * WASM_PAGE_SIZE)
87    }
88}
89
90impl From<usize> for Bytes {
91    fn from(other: usize) -> Self {
92        Self(other)
93    }
94}
95
96impl From<u32> for Bytes {
97    fn from(other: u32) -> Self {
98        Self(other.try_into().unwrap())
99    }
100}
101
102impl<T> Sub<T> for Pages
103where
104    T: Into<Self>,
105{
106    type Output = Self;
107    fn sub(self, rhs: T) -> Self {
108        Self(self.0 - rhs.into().0)
109    }
110}
111
112impl<T> Add<T> for Pages
113where
114    T: Into<Self>,
115{
116    type Output = Self;
117    fn add(self, rhs: T) -> Self {
118        Self(self.0 + rhs.into().0)
119    }
120}
121
122/// The only error that can happen when converting `Bytes` to `Pages`
123#[derive(Debug, Clone, Copy, PartialEq, Error)]
124#[error("Number of pages exceeds uint32 range")]
125pub struct PageCountOutOfRange;
126
127impl TryFrom<Bytes> for Pages {
128    type Error = PageCountOutOfRange;
129
130    fn try_from(bytes: Bytes) -> Result<Self, Self::Error> {
131        let pages: u32 = (bytes.0 / WASM_PAGE_SIZE).try_into().or(Err(PageCountOutOfRange))?;
132        Ok(Self(pages))
133    }
134}
135
136impl<T> Sub<T> for Bytes
137where
138    T: Into<Self>,
139{
140    type Output = Self;
141    fn sub(self, rhs: T) -> Self {
142        Self(self.0 - rhs.into().0)
143    }
144}
145
146impl<T> Add<T> for Bytes
147where
148    T: Into<Self>,
149{
150    type Output = Self;
151    fn add(self, rhs: T) -> Self {
152        Self(self.0 + rhs.into().0)
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn convert_bytes_to_pages() {
162        // rounds down
163        let pages = Pages::try_from(Bytes(0)).unwrap();
164        assert_eq!(pages, Pages(0));
165        let pages = Pages::try_from(Bytes(1)).unwrap();
166        assert_eq!(pages, Pages(0));
167        let pages = Pages::try_from(Bytes(WASM_PAGE_SIZE - 1)).unwrap();
168        assert_eq!(pages, Pages(0));
169        let pages = Pages::try_from(Bytes(WASM_PAGE_SIZE)).unwrap();
170        assert_eq!(pages, Pages(1));
171        let pages = Pages::try_from(Bytes(WASM_PAGE_SIZE + 1)).unwrap();
172        assert_eq!(pages, Pages(1));
173        let pages = Pages::try_from(Bytes(28 * WASM_PAGE_SIZE + 42)).unwrap();
174        assert_eq!(pages, Pages(28));
175        let pages = Pages::try_from(Bytes((u32::MAX as usize) * WASM_PAGE_SIZE)).unwrap();
176        assert_eq!(pages, Pages(u32::MAX));
177        let pages = Pages::try_from(Bytes((u32::MAX as usize) * WASM_PAGE_SIZE + 1)).unwrap();
178        assert_eq!(pages, Pages(u32::MAX));
179
180        // Errors when page count cannot be represented as u32
181        let result = Pages::try_from(Bytes((u32::MAX as usize + 1) * WASM_PAGE_SIZE));
182        assert_eq!(result.unwrap_err(), PageCountOutOfRange);
183        let result = Pages::try_from(Bytes(usize::MAX));
184        assert_eq!(result.unwrap_err(), PageCountOutOfRange);
185    }
186}