wasmi_core/
units.rs

1/// An amount of linear memory pages.
2#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
3#[repr(transparent)]
4pub struct Pages(u32);
5
6impl Pages {
7    /// The maximum amount of pages on the `wasm32` target.
8    ///
9    /// # Note
10    ///
11    /// This is the maximum since WebAssembly is a 32-bit platform
12    /// and a page is 2^16 bytes in size. Therefore there can be at
13    /// most 2^16 pages of a single linear memory so that all bytes
14    /// are still accessible.
15    pub const fn max() -> Self {
16        Self(65536) // 2^16
17    }
18}
19
20impl From<u16> for Pages {
21    /// Creates an `amount` of [`Pages`].
22    ///
23    /// # Note
24    ///
25    /// This is infallible since `u16` cannot represent invalid amounts
26    /// of [`Pages`]. However, `u16` can also not represent [`Pages::max()`].
27    ///
28    /// [`Pages::max()`]: struct.Pages.html#method.max
29    fn from(amount: u16) -> Self {
30        Self(u32::from(amount))
31    }
32}
33
34impl Pages {
35    /// Creates a new amount of [`Pages`] if the amount is within bounds.
36    ///
37    /// Returns `None` if the given `amount` of [`Pages`] exceeds [`Pages::max()`].
38    ///
39    /// [`Pages::max()`]: struct.Pages.html#method.max
40    pub fn new(amount: u32) -> Option<Self> {
41        if amount > u32::from(Self::max()) {
42            return None;
43        }
44        Some(Self(amount))
45    }
46
47    /// Adds the given amount of pages to `self`.
48    ///
49    /// Returns `Some` if the result is within bounds and `None` otherwise.
50    pub fn checked_add<T>(self, rhs: T) -> Option<Self>
51    where
52        T: Into<u32>,
53    {
54        let lhs: u32 = self.into();
55        let rhs: u32 = rhs.into();
56        lhs.checked_add(rhs).and_then(Self::new)
57    }
58
59    /// Substracts the given amount of pages from `self`.
60    ///
61    /// Returns `None` if the subtraction underflows or the result is out of bounds.
62    pub fn checked_sub<T>(self, rhs: T) -> Option<Self>
63    where
64        T: Into<u32>,
65    {
66        let lhs: u32 = self.into();
67        let rhs: u32 = rhs.into();
68        lhs.checked_sub(rhs).and_then(Self::new)
69    }
70
71    /// Returns the amount of bytes required for the amount of [`Pages`].
72    ///
73    /// Returns `None` if the amount of pages represented by `self` cannot
74    /// be represented as bytes on the executing platform.
75    pub fn to_bytes(self) -> Option<usize> {
76        Bytes::new(self).map(Into::into)
77    }
78}
79
80impl From<Pages> for u32 {
81    fn from(pages: Pages) -> Self {
82        pages.0
83    }
84}
85
86/// An amount of bytes of a linear memory.
87#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
88#[repr(transparent)]
89pub struct Bytes(usize);
90
91impl Bytes {
92    /// A 16-bit platform cannot represent the size of a single Wasm page.
93    const fn max16() -> u64 {
94        i16::MAX as u64 + 1
95    }
96
97    /// A 32-bit platform can represent at most i32::MAX + 1 Wasm pages.
98    const fn max32() -> u64 {
99        i32::MAX as u64 + 1
100    }
101
102    /// A 64-bit platform can represent all possible u32::MAX + 1 Wasm pages.
103    const fn max64() -> u64 {
104        u32::MAX as u64 + 1
105    }
106
107    /// The bytes per WebAssembly linear memory page.
108    ///
109    /// # Note
110    ///
111    /// As mandated by the WebAssembly specification every linear memory page
112    /// has exactly 2^16 (65536) bytes.
113    const fn per_page() -> Self {
114        Self(65536) // 2^16
115    }
116
117    /// Creates [`Bytes`] from the given amount of [`Pages`] if possible.
118    ///
119    /// Returns `None` if the amount of bytes is out of bounds. This may
120    /// happen for example when trying to allocate bytes for more than
121    /// `i16::MAX + 1` pages on a 32-bit platform since that amount would
122    /// not be representable by a pointer sized `usize`.
123    fn new(pages: Pages) -> Option<Bytes> {
124        if cfg!(target_pointer_width = "16") {
125            Self::new16(pages)
126        } else if cfg!(target_pointer_width = "32") {
127            Self::new32(pages)
128        } else if cfg!(target_pointer_width = "64") {
129            Self::new64(pages)
130        } else {
131            None
132        }
133    }
134
135    /// Creates [`Bytes`] from the given amount of [`Pages`] as if
136    /// on a 16-bit platform if possible.
137    ///
138    /// Returns `None` otherwise.
139    ///
140    /// # Note
141    ///
142    /// This API exists in isolation for cross-platform testing purposes.
143    fn new16(pages: Pages) -> Option<Bytes> {
144        Self::new_impl(pages, Bytes::max16())
145    }
146
147    /// Creates [`Bytes`] from the given amount of [`Pages`] as if
148    /// on a 32-bit platform if possible.
149    ///
150    /// Returns `None` otherwise.
151    ///
152    /// # Note
153    ///
154    /// This API exists in isolation for cross-platform testing purposes.
155    fn new32(pages: Pages) -> Option<Bytes> {
156        Self::new_impl(pages, Bytes::max32())
157    }
158
159    /// Creates [`Bytes`] from the given amount of [`Pages`] as if
160    /// on a 64-bit platform if possible.
161    ///
162    /// Returns `None` otherwise.
163    ///
164    /// # Note
165    ///
166    /// This API exists in isolation for cross-platform testing purposes.
167    fn new64(pages: Pages) -> Option<Bytes> {
168        Self::new_impl(pages, Bytes::max64())
169    }
170
171    /// Actual underlying implementation of [`Bytes::new`].
172    fn new_impl(pages: Pages, max: u64) -> Option<Bytes> {
173        let pages = u64::from(u32::from(pages));
174        let bytes_per_page = usize::from(Self::per_page()) as u64;
175        let bytes = pages
176            .checked_mul(bytes_per_page)
177            .filter(|&amount| amount <= max)?;
178        Some(Self(bytes as usize))
179    }
180}
181
182impl From<Bytes> for usize {
183    #[inline]
184    fn from(bytes: Bytes) -> Self {
185        bytes.0
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    fn pages(amount: u32) -> Pages {
194        Pages::new(amount).unwrap()
195    }
196
197    fn bytes(amount: usize) -> Bytes {
198        Bytes(amount)
199    }
200
201    #[test]
202    fn pages_max() {
203        assert_eq!(Pages::max(), pages(u32::from(u16::MAX) + 1));
204    }
205
206    #[test]
207    fn pages_new() {
208        assert_eq!(Pages::new(0), Some(Pages(0)));
209        assert_eq!(Pages::new(1), Some(Pages(1)));
210        assert_eq!(Pages::new(1000), Some(Pages(1000)));
211        assert_eq!(
212            Pages::new(u32::from(u16::MAX)),
213            Some(Pages(u32::from(u16::MAX)))
214        );
215        assert_eq!(Pages::new(u32::from(u16::MAX) + 1), Some(Pages::max()));
216        assert_eq!(Pages::new(u32::from(u16::MAX) + 2), None);
217        assert_eq!(Pages::new(u32::MAX), None);
218    }
219
220    #[test]
221    fn pages_checked_add() {
222        let max_pages = u32::from(Pages::max());
223
224        assert_eq!(pages(0).checked_add(0u32), Some(pages(0)));
225        assert_eq!(pages(0).checked_add(1u32), Some(pages(1)));
226        assert_eq!(pages(1).checked_add(0u32), Some(pages(1)));
227
228        assert_eq!(pages(0).checked_add(max_pages), Some(Pages::max()));
229        assert_eq!(pages(0).checked_add(Pages::max()), Some(Pages::max()));
230        assert_eq!(pages(1).checked_add(max_pages), None);
231        assert_eq!(pages(1).checked_add(Pages::max()), None);
232
233        assert_eq!(Pages::max().checked_add(0u32), Some(Pages::max()));
234        assert_eq!(Pages::max().checked_add(1u32), None);
235        assert_eq!(pages(0).checked_add(u32::MAX), None);
236
237        for i in 0..100 {
238            for j in 0..100 {
239                assert_eq!(pages(i).checked_add(pages(j)), Some(pages(i + j)));
240            }
241        }
242    }
243
244    #[test]
245    fn pages_checked_sub() {
246        let max_pages = u32::from(Pages::max());
247
248        assert_eq!(pages(0).checked_sub(0u32), Some(pages(0)));
249        assert_eq!(pages(0).checked_sub(1u32), None);
250        assert_eq!(pages(1).checked_sub(0u32), Some(pages(1)));
251        assert_eq!(pages(1).checked_sub(1u32), Some(pages(0)));
252
253        assert_eq!(Pages::max().checked_sub(Pages::max()), Some(pages(0)));
254        assert_eq!(Pages::max().checked_sub(u32::MAX), None);
255        assert_eq!(Pages::max().checked_sub(1u32), Some(pages(max_pages - 1)));
256
257        for i in 0..100 {
258            for j in 0..100 {
259                assert_eq!(pages(i).checked_sub(pages(j)), i.checked_sub(j).map(pages));
260            }
261        }
262    }
263
264    #[test]
265    fn pages_to_bytes() {
266        assert_eq!(pages(0).to_bytes(), Some(0));
267        if cfg!(target_pointer_width = "16") {
268            assert_eq!(pages(1).to_bytes(), None);
269        }
270        if cfg!(target_pointer_width = "32") || cfg!(target_pointer_width = "64") {
271            let bytes_per_page = usize::from(Bytes::per_page());
272            for n in 1..10 {
273                assert_eq!(pages(n as u32).to_bytes(), Some(n * bytes_per_page));
274            }
275        }
276    }
277
278    #[test]
279    fn bytes_new16() {
280        assert_eq!(Bytes::new16(pages(0)), Some(bytes(0)));
281        assert_eq!(Bytes::new16(pages(1)), None);
282        assert!(Bytes::new16(Pages::max()).is_none());
283    }
284
285    #[test]
286    fn bytes_new32() {
287        assert_eq!(Bytes::new32(pages(0)), Some(bytes(0)));
288        assert_eq!(Bytes::new32(pages(1)), Some(Bytes::per_page()));
289        let bytes_per_page = usize::from(Bytes::per_page());
290        for n in 2..10 {
291            assert_eq!(
292                Bytes::new32(pages(n as u32)),
293                Some(bytes(n * bytes_per_page))
294            );
295        }
296        assert!(Bytes::new32(pages(i16::MAX as u32 + 1)).is_some());
297        assert!(Bytes::new32(pages(i16::MAX as u32 + 2)).is_none());
298        assert!(Bytes::new32(Pages::max()).is_none());
299    }
300
301    #[test]
302    fn bytes_new64() {
303        assert_eq!(Bytes::new64(pages(0)), Some(bytes(0)));
304        assert_eq!(Bytes::new64(pages(1)), Some(Bytes::per_page()));
305        let bytes_per_page = usize::from(Bytes::per_page());
306        for n in 2..10 {
307            assert_eq!(
308                Bytes::new64(pages(n as u32)),
309                Some(bytes(n * bytes_per_page))
310            );
311        }
312        assert!(Bytes::new64(Pages(u32::from(u16::MAX) + 1)).is_some());
313        assert!(Bytes::new64(Pages(u32::from(u16::MAX) + 2)).is_none());
314        assert!(Bytes::new64(Pages::max()).is_some());
315    }
316}