Skip to main content

use_wasm_memory/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5use std::error::Error;
6
7/// WebAssembly page size in bytes.
8pub const WASM_PAGE_SIZE_BYTES: u64 = 65_536;
9
10/// WebAssembly page size in KiB.
11pub const WASM_PAGE_SIZE_KIB: u64 = 64;
12
13/// Error returned when memory limits are invalid.
14#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
15pub enum WasmMemoryError {
16    /// The byte count is not a whole number of Wasm pages.
17    BytesNotPageAligned,
18    /// The page count does not fit in 'u32'.
19    PageCountOverflow,
20    /// The maximum page count is lower than the minimum page count.
21    MaximumLessThanMinimum,
22}
23
24impl fmt::Display for WasmMemoryError {
25    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
26        match self {
27            Self::BytesNotPageAligned => {
28                formatter.write_str("byte count is not aligned to WebAssembly pages")
29            },
30            Self::PageCountOverflow => formatter.write_str("WebAssembly page count exceeds u32"),
31            Self::MaximumLessThanMinimum => {
32                formatter.write_str("maximum memory pages cannot be lower than minimum pages")
33            },
34        }
35    }
36}
37
38impl Error for WasmMemoryError {}
39
40/// WebAssembly page count.
41#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
42pub struct WasmPageCount(u32);
43
44impl WasmPageCount {
45    /// Creates a page count from a raw page value.
46    #[must_use]
47    pub const fn new(pages: u32) -> Self {
48        Self(pages)
49    }
50
51    /// Creates a page count from a byte count.
52    pub fn from_bytes(bytes: u64) -> Result<Self, WasmMemoryError> {
53        if !bytes.is_multiple_of(WASM_PAGE_SIZE_BYTES) {
54            return Err(WasmMemoryError::BytesNotPageAligned);
55        }
56        let pages = bytes / WASM_PAGE_SIZE_BYTES;
57        let pages = u32::try_from(pages).map_err(|_| WasmMemoryError::PageCountOverflow)?;
58        Ok(Self(pages))
59    }
60
61    /// Returns the raw page count.
62    #[must_use]
63    pub const fn pages(self) -> u32 {
64        self.0
65    }
66
67    /// Returns the represented byte count.
68    #[must_use]
69    pub fn bytes(self) -> u64 {
70        u64::from(self.0) * WASM_PAGE_SIZE_BYTES
71    }
72}
73
74impl fmt::Display for WasmPageCount {
75    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
76        write!(formatter, "{} pages", self.pages())
77    }
78}
79
80/// Minimum linear memory size marker.
81#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
82pub struct MemoryMinimum(WasmPageCount);
83
84impl MemoryMinimum {
85    /// Creates a minimum memory marker.
86    #[must_use]
87    pub const fn new(pages: WasmPageCount) -> Self {
88        Self(pages)
89    }
90
91    /// Returns the wrapped page count.
92    #[must_use]
93    pub const fn page_count(self) -> WasmPageCount {
94        self.0
95    }
96
97    /// Returns the raw page count.
98    #[must_use]
99    pub const fn pages(self) -> u32 {
100        self.0.pages()
101    }
102
103    /// Returns the represented byte count.
104    #[must_use]
105    pub fn bytes(self) -> u64 {
106        self.0.bytes()
107    }
108}
109
110impl From<WasmPageCount> for MemoryMinimum {
111    fn from(value: WasmPageCount) -> Self {
112        Self::new(value)
113    }
114}
115
116impl fmt::Display for MemoryMinimum {
117    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
118        write!(formatter, "minimum {}", self.page_count())
119    }
120}
121
122/// Maximum linear memory size marker.
123#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
124pub struct MemoryMaximum(WasmPageCount);
125
126impl MemoryMaximum {
127    /// Creates a maximum memory marker.
128    #[must_use]
129    pub const fn new(pages: WasmPageCount) -> Self {
130        Self(pages)
131    }
132
133    /// Returns the wrapped page count.
134    #[must_use]
135    pub const fn page_count(self) -> WasmPageCount {
136        self.0
137    }
138
139    /// Returns the raw page count.
140    #[must_use]
141    pub const fn pages(self) -> u32 {
142        self.0.pages()
143    }
144
145    /// Returns the represented byte count.
146    #[must_use]
147    pub fn bytes(self) -> u64 {
148        self.0.bytes()
149    }
150}
151
152impl From<WasmPageCount> for MemoryMaximum {
153    fn from(value: WasmPageCount) -> Self {
154        Self::new(value)
155    }
156}
157
158impl fmt::Display for MemoryMaximum {
159    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
160        write!(formatter, "maximum {}", self.page_count())
161    }
162}
163
164/// Shared linear memory marker.
165#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
166pub enum SharedMemory {
167    /// Memory is not shared.
168    #[default]
169    Unshared,
170    /// Memory is shared.
171    Shared,
172}
173
174impl SharedMemory {
175    /// Creates a shared-memory marker from a boolean.
176    #[must_use]
177    pub const fn from_bool(shared: bool) -> Self {
178        if shared { Self::Shared } else { Self::Unshared }
179    }
180
181    /// Returns the stable marker label.
182    #[must_use]
183    pub const fn as_str(self) -> &'static str {
184        match self {
185            Self::Unshared => "unshared",
186            Self::Shared => "shared",
187        }
188    }
189
190    /// Returns 'true' when memory is shared.
191    #[must_use]
192    pub const fn is_shared(self) -> bool {
193        matches!(self, Self::Shared)
194    }
195}
196
197impl From<bool> for SharedMemory {
198    fn from(value: bool) -> Self {
199        Self::from_bool(value)
200    }
201}
202
203impl From<SharedMemory> for bool {
204    fn from(value: SharedMemory) -> Self {
205        value.is_shared()
206    }
207}
208
209impl fmt::Display for SharedMemory {
210    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
211        formatter.write_str(self.as_str())
212    }
213}
214
215/// Linear memory limit metadata.
216#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
217pub struct MemoryLimits {
218    minimum: WasmPageCount,
219    maximum: Option<WasmPageCount>,
220    shared: SharedMemory,
221}
222
223impl MemoryLimits {
224    /// Creates memory limits after checking that 'maximum >= minimum' when present.
225    pub fn new(
226        minimum: WasmPageCount,
227        maximum: Option<WasmPageCount>,
228    ) -> Result<Self, WasmMemoryError> {
229        if let Some(maximum) = maximum
230            && maximum < minimum
231        {
232            return Err(WasmMemoryError::MaximumLessThanMinimum);
233        }
234
235        Ok(Self {
236            minimum,
237            maximum,
238            shared: SharedMemory::Unshared,
239        })
240    }
241
242    /// Marks the memory as shared or unshared.
243    #[must_use]
244    pub const fn with_shared(mut self, shared: bool) -> Self {
245        self.shared = SharedMemory::from_bool(shared);
246        self
247    }
248
249    /// Marks the memory with an explicit shared-memory marker.
250    #[must_use]
251    pub const fn with_shared_memory(mut self, shared: SharedMemory) -> Self {
252        self.shared = shared;
253        self
254    }
255
256    /// Returns the minimum page count.
257    #[must_use]
258    pub const fn minimum(&self) -> WasmPageCount {
259        self.minimum
260    }
261
262    /// Returns the minimum page count as a raw integer.
263    #[must_use]
264    pub const fn minimum_pages(&self) -> u32 {
265        self.minimum.pages()
266    }
267
268    /// Returns the maximum page count when bounded.
269    #[must_use]
270    pub const fn maximum(&self) -> Option<WasmPageCount> {
271        self.maximum
272    }
273
274    /// Returns the maximum page count as a raw integer when bounded.
275    #[must_use]
276    pub const fn maximum_pages(&self) -> Option<u32> {
277        match self.maximum {
278            Some(maximum) => Some(maximum.pages()),
279            None => None,
280        }
281    }
282
283    /// Returns the shared-memory marker.
284    #[must_use]
285    pub const fn shared_memory(&self) -> SharedMemory {
286        self.shared
287    }
288
289    /// Returns 'true' when the memory is marked as shared.
290    #[must_use]
291    pub const fn is_shared(&self) -> bool {
292        self.shared.is_shared()
293    }
294}
295
296#[cfg(test)]
297mod tests {
298    use super::{
299        MemoryLimits, MemoryMaximum, MemoryMinimum, SharedMemory, WASM_PAGE_SIZE_BYTES,
300        WasmMemoryError, WasmPageCount,
301    };
302
303    #[test]
304    fn converts_pages_and_bytes() {
305        let pages = WasmPageCount::from_bytes(WASM_PAGE_SIZE_BYTES * 2).expect("aligned pages");
306
307        assert_eq!(pages.pages(), 2);
308        assert_eq!(pages.bytes(), WASM_PAGE_SIZE_BYTES * 2);
309        assert_eq!(pages.to_string(), "2 pages");
310        assert_eq!(MemoryMinimum::new(pages).pages(), 2);
311        assert_eq!(MemoryMaximum::new(pages).bytes(), WASM_PAGE_SIZE_BYTES * 2);
312    }
313
314    #[test]
315    fn validates_memory_limits() {
316        let limits = MemoryLimits::new(WasmPageCount::new(1), Some(WasmPageCount::new(4)))
317            .expect("valid limits")
318            .with_shared(true);
319
320        assert_eq!(limits.minimum_pages(), 1);
321        assert_eq!(limits.maximum_pages(), Some(4));
322        assert_eq!(limits.shared_memory(), SharedMemory::Shared);
323        assert!(limits.is_shared());
324        assert_eq!(SharedMemory::from_bool(false).to_string(), "unshared");
325        assert_eq!(
326            MemoryLimits::new(WasmPageCount::new(4), Some(WasmPageCount::new(1))),
327            Err(WasmMemoryError::MaximumLessThanMinimum)
328        );
329    }
330}