1use 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
32const WASM_PAGE_SIZE: u32 = 64 * 1024;
34
35const GEAR_PAGE_SIZE: u32 = 16 * 1024;
40
41const _: () = assert!(WASM_PAGE_SIZE.is_multiple_of(GEAR_PAGE_SIZE));
42
43#[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 pub const SIZE: u32 = SIZE;
68
69 pub const UPPER: Self = Self(u32::MAX / SIZE + 1 / if SIZE.is_power_of_two() { 1 } else { 0 });
73
74 #[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 pub fn to_page_number(&self) -> Option<Page<SIZE>> {
85 self.unbound()
86 }
87
88 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 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 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#[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#[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 pub const SIZE: u32 = SIZE;
206
207 #[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 pub fn inc(&self) -> PagesAmount<SIZE> {
215 PagesAmount(self.0 + 1)
216 }
217
218 pub fn from_offset(offset: u32) -> Self {
220 Self(offset / Self::SIZE)
221 }
222
223 pub fn offset(&self) -> u32 {
225 self.0 * Self::SIZE
226 }
227
228 pub fn end_offset(&self) -> u32 {
230 self.0 * Self::SIZE + (Self::SIZE - 1)
231 }
232
233 pub fn to_page<const S1: u32>(self) -> Page<S1> {
235 Page::from_offset(self.offset())
236 }
237
238 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 unsafe { Interval::new_unchecked(start, end).iter() }
255 }
256}
257
258#[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
308pub type WasmPage = Page<WASM_PAGE_SIZE>;
310pub type GearPage = Page<GEAR_PAGE_SIZE>;
312pub type WasmPagesAmount = PagesAmount<WASM_PAGE_SIZE>;
314pub 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}