ic_memory/runtime/
layout.rs1use super::RuntimeConstructionError;
5use crate::WASM_PAGE_SIZE_BYTES;
6use ic_stable_structures::Memory;
7
8pub(super) const IDS: usize = 255;
9pub(super) const BUCKETS: usize = 32_768;
10pub(super) const HEADER_BYTES: usize = 40 + IDS * 8;
11pub(super) const METADATA_BYTES: usize = HEADER_BYTES + BUCKETS;
12
13#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
21#[non_exhaustive]
22pub enum MemoryManagerLayoutError {
23 #[error("unsupported big-endian manager layout")]
25 UnsupportedByteOrder,
26 #[error("persisted bucket size is zero")]
28 ZeroBucketSize,
29 #[error("nonzero reserved manager header bytes")]
31 ReservedHeader,
32 #[error("allocated bucket count {count} exceeds 32768")]
34 BucketCount { count: u16 },
35 #[error("invalid bucket table entry at index {index}")]
37 BucketTable { index: u16 },
38 #[error("virtual extent and bucket count disagree for memory ID {id}")]
40 VirtualExtent { id: u8 },
41 #[error("physical extent {physical_pages} pages is below assigned end {required_pages}")]
43 TruncatedBacking {
44 physical_pages: u64,
45 required_pages: u64,
46 },
47 #[error("backing memory extent overflows bytes")]
49 ExtentOverflow,
50 #[error("persisted manager metadata differs from runtime authority")]
52 RuntimeMismatch,
53}
54
55pub(super) struct Layout {
56 pub physical_pages: u64,
57 pub bucket_pages: u16,
58 pub allocated_buckets: u16,
59 pub pages: [u64; IDS],
60 pub buckets: [u16; IDS],
61}
62
63pub(super) fn read<M: Memory>(memory: &M) -> Result<Layout, RuntimeConstructionError> {
64 if cfg!(target_endian = "big") {
65 return Err(MemoryManagerLayoutError::UnsupportedByteOrder.into());
66 }
67 let physical_pages = memory.size();
68 physical_pages
69 .checked_mul(WASM_PAGE_SIZE_BYTES)
70 .ok_or(MemoryManagerLayoutError::ExtentOverflow)?;
71 if physical_pages == 0 {
73 return Err(MemoryManagerLayoutError::TruncatedBacking {
74 physical_pages,
75 required_pages: 1,
76 }
77 .into());
78 }
79 let mut header = [0; HEADER_BYTES];
80 memory.read(0, &mut header);
81 let observed_magic = [header[0], header[1], header[2]];
82 if observed_magic != *b"MGR" {
83 return Err(RuntimeConstructionError::ForeignMemory { observed_magic });
84 }
85 if header[3] != 1 {
86 return Err(RuntimeConstructionError::UnsupportedMemoryManagerVersion {
87 observed: header[3],
88 supported: 1,
89 });
90 }
91 if header[8..40].iter().any(|byte| *byte != 0) {
92 return Err(MemoryManagerLayoutError::ReservedHeader.into());
93 }
94 let allocated_buckets = u16::from_le_bytes([header[4], header[5]]);
95 let bucket_pages = u16::from_le_bytes([header[6], header[7]]);
96 if bucket_pages == 0 {
97 return Err(MemoryManagerLayoutError::ZeroBucketSize.into());
98 }
99 if usize::from(allocated_buckets) > BUCKETS {
100 return Err(MemoryManagerLayoutError::BucketCount {
101 count: allocated_buckets,
102 }
103 .into());
104 }
105 let required_pages = 1 + u64::from(allocated_buckets) * u64::from(bucket_pages);
106 if physical_pages < required_pages {
107 return Err(MemoryManagerLayoutError::TruncatedBacking {
108 physical_pages,
109 required_pages,
110 }
111 .into());
112 }
113 let mut table = vec![0; BUCKETS];
115 memory.read(HEADER_BYTES as u64, &mut table);
116 let mut buckets = [0_u16; IDS];
117 for (index, id) in (0_u16..32_768).zip(table) {
118 if (index < allocated_buckets) != (id != 255) {
119 return Err(MemoryManagerLayoutError::BucketTable { index }.into());
120 }
121 if id != 255 {
122 buckets[usize::from(id)] += 1;
123 }
124 }
125 let mut pages = [0; IDS];
126 for (id, value) in (0_u8..255).zip(&mut pages) {
127 let offset = 40 + usize::from(id) * 8;
128 *value = u64::from_le_bytes(header[offset..offset + 8].try_into().expect("eight bytes"));
129 if value.div_ceil(u64::from(bucket_pages)) != u64::from(buckets[usize::from(id)]) {
130 return Err(MemoryManagerLayoutError::VirtualExtent { id }.into());
131 }
132 }
133 Ok(Layout {
134 physical_pages,
135 bucket_pages,
136 allocated_buckets,
137 pages,
138 buckets,
139 })
140}