near_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#[repr(transparent)]
33pub struct Pages(pub u32);
34
35impl Pages {
36    /// Returns the largest value that can be represented by the Pages type.
37    ///
38    /// This is defined by the WebAssembly standard as 65,536 pages.
39    #[inline(always)]
40    pub const fn max_value() -> Self {
41        Self(WASM_MAX_PAGES)
42    }
43
44    /// Checked addition. Computes `self + rhs`,
45    /// returning `None` if overflow occurred.
46    pub fn checked_add(self, rhs: Self) -> Option<Self> {
47        let added = (self.0 as usize) + (rhs.0 as usize);
48        if added <= (WASM_MAX_PAGES as usize) {
49            Some(Self(added as u32))
50        } else {
51            None
52        }
53    }
54
55    /// Calculate number of bytes from pages.
56    pub fn bytes(self) -> Bytes {
57        self.into()
58    }
59}
60
61impl fmt::Debug for Pages {
62    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63        write!(f, "{} pages", self.0)
64    }
65}
66
67impl From<u32> for Pages {
68    fn from(other: u32) -> Self {
69        Self(other)
70    }
71}
72
73/// Units of WebAssembly memory in terms of 8-bit bytes.
74#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
75pub struct Bytes(pub usize);
76
77impl fmt::Debug for Bytes {
78    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
79        write!(f, "{} bytes", self.0)
80    }
81}
82
83impl From<Pages> for Bytes {
84    fn from(pages: Pages) -> Self {
85        Self((pages.0 as usize) * WASM_PAGE_SIZE)
86    }
87}
88
89impl From<usize> for Bytes {
90    fn from(other: usize) -> Self {
91        Self(other)
92    }
93}
94
95impl From<u32> for Bytes {
96    fn from(other: u32) -> Self {
97        Self(other.try_into().unwrap())
98    }
99}
100
101impl<T> Sub<T> for Pages
102where
103    T: Into<Self>,
104{
105    type Output = Self;
106    fn sub(self, rhs: T) -> Self {
107        Self(self.0 - rhs.into().0)
108    }
109}
110
111impl<T> Add<T> for Pages
112where
113    T: Into<Self>,
114{
115    type Output = Self;
116    fn add(self, rhs: T) -> Self {
117        Self(self.0 + rhs.into().0)
118    }
119}
120
121/// The only error that can happen when converting `Bytes` to `Pages`
122#[derive(Debug, Clone, Copy, PartialEq, Error)]
123#[error("Number of pages exceeds uint32 range")]
124pub struct PageCountOutOfRange;
125
126impl TryFrom<Bytes> for Pages {
127    type Error = PageCountOutOfRange;
128
129    fn try_from(bytes: Bytes) -> Result<Self, Self::Error> {
130        let pages: u32 = (bytes.0 / WASM_PAGE_SIZE).try_into().or(Err(PageCountOutOfRange))?;
131        Ok(Self(pages))
132    }
133}
134
135impl<T> Sub<T> for Bytes
136where
137    T: Into<Self>,
138{
139    type Output = Self;
140    fn sub(self, rhs: T) -> Self {
141        Self(self.0 - rhs.into().0)
142    }
143}
144
145impl<T> Add<T> for Bytes
146where
147    T: Into<Self>,
148{
149    type Output = Self;
150    fn add(self, rhs: T) -> Self {
151        Self(self.0 + rhs.into().0)
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    #[test]
160    fn convert_bytes_to_pages() {
161        // rounds down
162        let pages = Pages::try_from(Bytes(0)).unwrap();
163        assert_eq!(pages, Pages(0));
164        let pages = Pages::try_from(Bytes(1)).unwrap();
165        assert_eq!(pages, Pages(0));
166        let pages = Pages::try_from(Bytes(WASM_PAGE_SIZE - 1)).unwrap();
167        assert_eq!(pages, Pages(0));
168        let pages = Pages::try_from(Bytes(WASM_PAGE_SIZE)).unwrap();
169        assert_eq!(pages, Pages(1));
170        let pages = Pages::try_from(Bytes(WASM_PAGE_SIZE + 1)).unwrap();
171        assert_eq!(pages, Pages(1));
172        let pages = Pages::try_from(Bytes(28 * WASM_PAGE_SIZE + 42)).unwrap();
173        assert_eq!(pages, Pages(28));
174        let pages = Pages::try_from(Bytes((u32::MAX as usize) * WASM_PAGE_SIZE)).unwrap();
175        assert_eq!(pages, Pages(u32::MAX));
176        let pages = Pages::try_from(Bytes((u32::MAX as usize) * WASM_PAGE_SIZE + 1)).unwrap();
177        assert_eq!(pages, Pages(u32::MAX));
178
179        // Errors when page count cannot be represented as u32
180        let result = Pages::try_from(Bytes((u32::MAX as usize + 1) * WASM_PAGE_SIZE));
181        assert_eq!(result.unwrap_err(), PageCountOutOfRange);
182        let result = Pages::try_from(Bytes(usize::MAX));
183        assert_eq!(result.unwrap_err(), PageCountOutOfRange);
184    }
185}