1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5use std::error::Error;
6
7pub const WASM_PAGE_SIZE_BYTES: u64 = 65_536;
9
10pub const WASM_PAGE_SIZE_KIB: u64 = 64;
12
13#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
15pub enum WasmMemoryError {
16 BytesNotPageAligned,
18 PageCountOverflow,
20 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#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
42pub struct WasmPageCount(u32);
43
44impl WasmPageCount {
45 #[must_use]
47 pub const fn new(pages: u32) -> Self {
48 Self(pages)
49 }
50
51 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 #[must_use]
63 pub const fn pages(self) -> u32 {
64 self.0
65 }
66
67 #[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#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
82pub struct MemoryMinimum(WasmPageCount);
83
84impl MemoryMinimum {
85 #[must_use]
87 pub const fn new(pages: WasmPageCount) -> Self {
88 Self(pages)
89 }
90
91 #[must_use]
93 pub const fn page_count(self) -> WasmPageCount {
94 self.0
95 }
96
97 #[must_use]
99 pub const fn pages(self) -> u32 {
100 self.0.pages()
101 }
102
103 #[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#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
124pub struct MemoryMaximum(WasmPageCount);
125
126impl MemoryMaximum {
127 #[must_use]
129 pub const fn new(pages: WasmPageCount) -> Self {
130 Self(pages)
131 }
132
133 #[must_use]
135 pub const fn page_count(self) -> WasmPageCount {
136 self.0
137 }
138
139 #[must_use]
141 pub const fn pages(self) -> u32 {
142 self.0.pages()
143 }
144
145 #[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#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
166pub enum SharedMemory {
167 #[default]
169 Unshared,
170 Shared,
172}
173
174impl SharedMemory {
175 #[must_use]
177 pub const fn from_bool(shared: bool) -> Self {
178 if shared { Self::Shared } else { Self::Unshared }
179 }
180
181 #[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 #[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#[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 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 #[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 #[must_use]
251 pub const fn with_shared_memory(mut self, shared: SharedMemory) -> Self {
252 self.shared = shared;
253 self
254 }
255
256 #[must_use]
258 pub const fn minimum(&self) -> WasmPageCount {
259 self.minimum
260 }
261
262 #[must_use]
264 pub const fn minimum_pages(&self) -> u32 {
265 self.minimum.pages()
266 }
267
268 #[must_use]
270 pub const fn maximum(&self) -> Option<WasmPageCount> {
271 self.maximum
272 }
273
274 #[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 #[must_use]
285 pub const fn shared_memory(&self) -> SharedMemory {
286 self.shared
287 }
288
289 #[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}