gear_core/
pages.rs

1// This file is part of Gear.
2
3// Copyright (C) 2023-2025 Gear Technologies Inc.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! Module for memory pages.
20
21use alloc::format;
22use core::cmp::Ordering;
23use num_traits::bounds::{LowerBounded, UpperBounded};
24use numerated::{Bound, Numerated, interval::Interval, iterators::IntervalIterator};
25use scale_info::{
26    TypeInfo,
27    scale::{Decode, Encode},
28};
29
30pub use numerated::{self, num_traits};
31
32/// A WebAssembly page has a constant size of 64KiB.
33const WASM_PAGE_SIZE: u32 = 64 * 1024;
34
35/// A size of memory pages in program data storage.
36/// If program changes some memory page during execution, then page of this size will be uploaded to the storage.
37/// If during execution program accesses some data in memory, then data of this size will be downloaded from the storage.
38/// Currently equal to 16KiB to be bigger than most common host page sizes.
39const GEAR_PAGE_SIZE: u32 = 16 * 1024;
40
41const _: () = assert!(WASM_PAGE_SIZE.is_multiple_of(GEAR_PAGE_SIZE));
42
43/// Struct represents memory pages amount with some constant size `SIZE` in bytes.
44/// - `SIZE` type is u32, so page size < 4GiB (wasm32 memory size limit).
45/// - `SIZE` must be power of two and must not be equal to one or zero bytes.
46#[derive(
47    Clone,
48    Copy,
49    Debug,
50    Decode,
51    Encode,
52    PartialEq,
53    Eq,
54    PartialOrd,
55    Ord,
56    Hash,
57    TypeInfo,
58    Default,
59    derive_more::Into,
60)]
61pub struct PagesAmount<const SIZE: u32>(u32);
62
63impl<const SIZE: u32> PagesAmount<SIZE> {
64    /// Page size. May be any number power of two in interval [2, u32::MAX].
65    ///
66    /// NOTE: In case SIZE == 0 or 1 or any not power of two number, then you would receive compilation error.
67    pub const SIZE: u32 = SIZE;
68
69    /// Number of max pages amount. Equal to max page number + 1.
70    ///
71    /// NOTE: const computation contains checking in order to prevent incorrect SIZE.
72    pub const UPPER: Self = Self(u32::MAX / SIZE + 1 / if SIZE.is_power_of_two() { 1 } else { 0 });
73
74    /// Pages amount addition. Returns None if overflow.
75    #[cfg(test)]
76    pub fn add(&self, other: Self) -> Option<Self> {
77        self.0
78            .checked_add(other.0)
79            .and_then(|r| (r <= Self::UPPER.0).then_some(Self(r)))
80    }
81
82    /// Get page number, which bounds this pages amount.
83    /// If pages amount == 4GB size, then returns None, because such page number does not exist.
84    pub fn to_page_number(&self) -> Option<Page<SIZE>> {
85        self.unbound()
86    }
87
88    /// Returns corresponding amount of pages with another size `S`.
89    pub fn to_pages_amount<const S: u32>(&self) -> PagesAmount<S> {
90        let raw = if Self::SIZE > S {
91            (Self::SIZE / S) * self.0
92        } else {
93            self.0 / (S / Self::SIZE)
94        };
95        PagesAmount(raw)
96    }
97
98    /// Returns amount in bytes.
99    /// Can be also considered as offset of a page with corresponding number.
100    /// In 32-bits address space it can be up to u32::MAX + 1,
101    /// so we returns u64 to prevent overflow.
102    pub fn offset(&self) -> u64 {
103        self.0 as u64 * SIZE as u64
104    }
105}
106
107impl<const SIZE: u32> From<Page<SIZE>> for PagesAmount<SIZE> {
108    fn from(value: Page<SIZE>) -> Self {
109        Self(value.0)
110    }
111}
112
113impl<const SIZE: u32> From<Option<Page<SIZE>>> for PagesAmount<SIZE> {
114    fn from(value: Option<Page<SIZE>>) -> Self {
115        value.map(|page| page.into()).unwrap_or(Self::UPPER)
116    }
117}
118
119impl<const SIZE: u32> Bound<Page<SIZE>> for PagesAmount<SIZE> {
120    fn unbound(self) -> Option<Page<SIZE>> {
121        match self.cmp(&Self::UPPER) {
122            Ordering::Greater => {
123                // This panic is impossible because of `PagesAmount` constructors implementation.
124                let err_msg = format!(
125                    "PagesAmount::unbound: PageBound must be always less or equal than UPPER. \
126                    Page bound - {:?}, UPPER - {:?}",
127                    self,
128                    Self::UPPER
129                );
130
131                log::error!("{err_msg}");
132                unreachable!("{err_msg}")
133            }
134            Ordering::Equal => None,
135            Ordering::Less => Some(Page(self.0)),
136        }
137    }
138}
139
140/// Try from u32 error for [PagesAmount].
141#[derive(Debug, Clone, derive_more::Display)]
142#[display("Tries to make pages amount from {_0}, which must be less or equal to {_1}")]
143pub struct PagesAmountError(u32, u32);
144
145impl<const SIZE: u32> TryFrom<u32> for PagesAmount<SIZE> {
146    type Error = PagesAmountError;
147
148    fn try_from(raw: u32) -> Result<Self, Self::Error> {
149        if raw > Self::UPPER.0 {
150            Err(PagesAmountError(raw, Self::UPPER.0))
151        } else {
152            Ok(Self(raw))
153        }
154    }
155}
156
157impl<const SIZE: u32> PartialEq<Page<SIZE>> for PagesAmount<SIZE> {
158    fn eq(&self, other: &Page<SIZE>) -> bool {
159        self.0 == other.0
160    }
161}
162
163impl<const SIZE: u32> PartialOrd<Page<SIZE>> for PagesAmount<SIZE> {
164    fn partial_cmp(&self, other: &Page<SIZE>) -> Option<Ordering> {
165        self.0.partial_cmp(&other.0)
166    }
167}
168
169impl<const SIZE: u32> PartialEq<PagesAmount<SIZE>> for Page<SIZE> {
170    fn eq(&self, other: &PagesAmount<SIZE>) -> bool {
171        self.0 == other.0
172    }
173}
174
175impl<const SIZE: u32> PartialOrd<PagesAmount<SIZE>> for Page<SIZE> {
176    fn partial_cmp(&self, other: &PagesAmount<SIZE>) -> Option<Ordering> {
177        self.0.partial_cmp(&other.0)
178    }
179}
180
181/// Struct represents memory page number with some constant size `SIZE` in bytes.
182/// - `SIZE` type is u32, so page size < 4GiB (wasm32 memory size limit).
183/// - `SIZE` must be power of two and must not be equal to zero bytes.
184/// - `SIZE == 1` is possible, but then you cannot use [PagesAmount] for these pages.
185#[derive(
186    Clone,
187    Copy,
188    Debug,
189    Decode,
190    Encode,
191    PartialEq,
192    Eq,
193    PartialOrd,
194    Ord,
195    Hash,
196    TypeInfo,
197    Default,
198    derive_more::Into,
199)]
200#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
201pub struct Page<const SIZE: u32>(u32);
202
203impl<const SIZE: u32> Page<SIZE> {
204    /// Page size. May be any number power of two in interval [1, u32::MAX].
205    pub const SIZE: u32 = SIZE;
206
207    /// Max possible page number in 4GB memory.
208    ///
209    /// Note: const computation contains checking in order to prevent incorrect SIZE.
210    #[allow(clippy::erasing_op)]
211    pub const UPPER: Self = Self(u32::MAX / SIZE + 0 / if SIZE.is_power_of_two() { 1 } else { 0 });
212
213    /// Increment page number. Returns `PagesAmount<SIZE>`, because this allows to avoid overflows.
214    pub fn inc(&self) -> PagesAmount<SIZE> {
215        PagesAmount(self.0 + 1)
216    }
217
218    /// Constructs new page from byte offset: returns page which contains this byte.
219    pub fn from_offset(offset: u32) -> Self {
220        Self(offset / Self::SIZE)
221    }
222
223    /// Returns page zero byte offset.
224    pub fn offset(&self) -> u32 {
225        self.0 * Self::SIZE
226    }
227
228    /// Returns page last byte offset.
229    pub fn end_offset(&self) -> u32 {
230        self.0 * Self::SIZE + (Self::SIZE - 1)
231    }
232
233    /// Returns new page, which contains `self` zero byte.
234    pub fn to_page<const S1: u32>(self) -> Page<S1> {
235        Page::from_offset(self.offset())
236    }
237
238    /// Returns an iterator that iterates over the range of pages from `self` to the end page,
239    /// inclusive. Each iteration yields a page of type [`Page<S1>`].
240    ///
241    /// # Example
242    ///
243    /// ```
244    /// use gear_core::pages::{GearPage, WasmPage};
245    ///
246    /// let x: Vec<GearPage> = WasmPage::from(5).to_iter().collect();
247    /// println!("{x:?}");
248    /// ```
249    /// For this example must be printed: `[GearPage(20), GearPage(21), GearPage(22), GearPage(23)]`
250    pub fn to_iter<const S1: u32>(self) -> IntervalIterator<Page<S1>> {
251        let start = Page::<S1>::from_offset(self.offset());
252        let end = Page::<S1>::from_offset(self.end_offset());
253        // Safe, cause end byte offset is always greater or equal to offset, so `start <= end`.
254        unsafe { Interval::new_unchecked(start, end).iter() }
255    }
256}
257
258/// Try from u32 error for [Page].
259#[derive(Debug, Clone, derive_more::Display)]
260#[display("Tries to make page from {_0}, which must be less or equal to {_1}")]
261pub struct PageError(u32, u32);
262
263impl<const SIZE: u32> TryFrom<u32> for Page<SIZE> {
264    type Error = PageError;
265
266    fn try_from(raw: u32) -> Result<Self, Self::Error> {
267        if raw > Self::UPPER.0 {
268            Err(PageError(raw, Self::UPPER.0))
269        } else {
270            Ok(Self(raw))
271        }
272    }
273}
274
275impl<const SIZE: u32> Numerated for Page<SIZE> {
276    type Distance = u32;
277    type Bound = PagesAmount<SIZE>;
278
279    fn add_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self> {
280        self.0
281            .checked_add(num)
282            .and_then(|sum| sum.enclosed_by(&self.0, &other.0).then_some(Self(sum)))
283    }
284
285    fn sub_if_enclosed_by(self, num: Self::Distance, other: Self) -> Option<Self> {
286        self.0
287            .checked_sub(num)
288            .and_then(|sub| sub.enclosed_by(&self.0, &other.0).then_some(Self(sub)))
289    }
290
291    fn distance(self, other: Self) -> Self::Distance {
292        self.0.abs_diff(other.0)
293    }
294}
295
296impl<const SIZE: u32> LowerBounded for Page<SIZE> {
297    fn min_value() -> Self {
298        Self(0)
299    }
300}
301
302impl<const SIZE: u32> UpperBounded for Page<SIZE> {
303    fn max_value() -> Self {
304        Self::UPPER
305    }
306}
307
308/// Page of wasm page size - 64 kiB.
309pub type WasmPage = Page<WASM_PAGE_SIZE>;
310/// Page of gear page size - 16 kiB.
311pub type GearPage = Page<GEAR_PAGE_SIZE>;
312/// Pages amount for wasm page size - 64 kiB.
313pub type WasmPagesAmount = PagesAmount<WASM_PAGE_SIZE>;
314/// Pages amount for gear page size - 16 kiB.
315pub type GearPagesAmount = PagesAmount<GEAR_PAGE_SIZE>;
316
317impl From<u16> for WasmPagesAmount {
318    fn from(value: u16) -> Self {
319        const { assert!(WASM_PAGE_SIZE <= 0x10_000) };
320        Self(value as u32)
321    }
322}
323
324impl From<u16> for WasmPage {
325    fn from(value: u16) -> Self {
326        const { assert!(WASM_PAGE_SIZE <= 0x10_000) };
327        Self(value as u32)
328    }
329}
330
331impl From<u16> for GearPagesAmount {
332    fn from(value: u16) -> Self {
333        const { assert!(GEAR_PAGE_SIZE <= 0x10_000) };
334        Self(value as u32)
335    }
336}
337
338impl From<u16> for GearPage {
339    fn from(value: u16) -> Self {
340        const { assert!(GEAR_PAGE_SIZE <= 0x10_000) };
341        Self(value as u32)
342    }
343}
344
345#[cfg(test)]
346mod tests {
347    use super::*;
348    use alloc::{vec, vec::Vec};
349
350    #[test]
351    fn test_page_inc() {
352        assert_eq!(WasmPage::from(10).inc(), WasmPagesAmount::from(11));
353        assert_eq!(WasmPage::UPPER.inc(), WasmPagesAmount::UPPER);
354    }
355
356    #[test]
357    fn test_page_from_offset() {
358        assert_eq!(WasmPage::from_offset(WASM_PAGE_SIZE - 1), WasmPage::from(0));
359        assert_eq!(WasmPage::from_offset(WASM_PAGE_SIZE), WasmPage::from(1));
360        assert_eq!(WasmPage::from_offset(WASM_PAGE_SIZE + 1), WasmPage::from(1));
361    }
362
363    #[test]
364    fn test_page_offset() {
365        assert_eq!(WasmPage::from(80).offset(), 80 * WASM_PAGE_SIZE);
366    }
367
368    #[test]
369    fn test_page_end_offset() {
370        assert_eq!(
371            WasmPage::from(80).end_offset(),
372            80 * WASM_PAGE_SIZE + (WASM_PAGE_SIZE - 1)
373        );
374    }
375
376    #[test]
377    fn test_page_to_page() {
378        assert_eq!(
379            WasmPage::from(80).to_page::<GEAR_PAGE_SIZE>(),
380            GearPage::from(80 * 4)
381        );
382    }
383
384    #[test]
385    fn test_page_to_iter() {
386        assert_eq!(
387            WasmPage::from(5).to_iter().collect::<Vec<GearPage>>(),
388            vec![
389                GearPage::from(20),
390                GearPage::from(21),
391                GearPage::from(22),
392                GearPage::from(23)
393            ]
394        );
395    }
396
397    #[test]
398    fn test_pages_amount_add() {
399        let a = WasmPagesAmount::from(10);
400        let b = WasmPagesAmount::from(20);
401        assert_eq!(a.add(b), Some(WasmPagesAmount::from(30)));
402        assert_eq!(a.add(WasmPagesAmount::UPPER), None);
403    }
404
405    #[test]
406    fn test_pages_amount_to_page_number() {
407        assert_eq!(
408            WasmPagesAmount::from(10).to_page_number(),
409            Some(WasmPage::from(10))
410        );
411        assert_eq!(WasmPagesAmount::UPPER.to_page_number(), None);
412    }
413
414    #[test]
415    fn test_pages_amount_to_pages_amount() {
416        assert_eq!(
417            WasmPagesAmount::from(10).to_pages_amount::<GEAR_PAGE_SIZE>(),
418            GearPagesAmount::from(40)
419        );
420        assert_eq!(
421            GearPagesAmount::from(40).to_pages_amount::<WASM_PAGE_SIZE>(),
422            WasmPagesAmount::from(10)
423        );
424    }
425
426    #[test]
427    fn test_pages_amount_offset() {
428        assert_eq!(
429            WasmPagesAmount::from(10).offset(),
430            10 * WASM_PAGE_SIZE as u64
431        );
432        assert_eq!(WasmPagesAmount::UPPER.offset(), u32::MAX as u64 + 1);
433    }
434}
435
436#[cfg(test)]
437mod property_tests {
438    use super::*;
439    use numerated::mock::{self, IntervalAction};
440    use proptest::{
441        prelude::{Arbitrary, any},
442        proptest,
443        strategy::{BoxedStrategy, Strategy},
444        test_runner::Config as ProptestConfig,
445    };
446
447    impl<const S: u32> Arbitrary for Page<S> {
448        type Parameters = ();
449        type Strategy = BoxedStrategy<Page<S>>;
450
451        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
452            (0..=Page::<S>::UPPER.0).prop_map(Page).boxed()
453        }
454    }
455
456    proptest! {
457        #![proptest_config(ProptestConfig::with_cases(1024))]
458
459        #[test]
460        fn gear_page_numerated(x in any::<GearPage>(), y in any::<GearPage>()) {
461            mock::test_numerated(x, y);
462        }
463
464        #[test]
465        fn gear_page_interval(action in any::<IntervalAction<GearPage>>()) {
466            mock::test_interval(action);
467        }
468
469        #[test]
470        fn wasm_page_numerated(x in any::<WasmPage>(), y in any::<WasmPage>()) {
471            mock::test_numerated(x, y);
472        }
473
474        #[test]
475        fn wasm_page_interval(action in any::<IntervalAction<WasmPage>>()) {
476            mock::test_interval(action);
477        }
478    }
479
480    proptest! {
481        #![proptest_config(ProptestConfig::with_cases(64))]
482
483        #[test]
484        fn gear_page_tree((initial, actions) in mock::tree_actions::<GearPage>(0..128, 2..8)) {
485            mock::test_tree(initial, actions);
486        }
487
488        #[test]
489        fn wasm_page_tree((initial, actions) in mock::tree_actions::<WasmPage>(0..128, 10..20)) {
490            mock::test_tree(initial, actions);
491        }
492    }
493}