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