1mod allocations;
2mod backing;
3mod config;
4mod default;
5mod diagnostics;
6mod error;
7mod layout;
8mod policy;
9
10#[cfg(test)]
11mod allocation_tests;
12#[cfg(test)]
13#[allow(
14 unsafe_code,
15 reason = "exercise raw reads with valid uninitialized destinations"
16)]
17mod read_tests;
18#[cfg(test)]
19mod tests;
20
21pub use allocations::{
22 AllocationBinding, AllocationRangeClaim, MemoryAllocation, MemoryAllocations,
23};
24pub use backing::RuntimeMemory;
25pub use config::MemoryManagerConfig;
26pub use default::{
27 bootstrap_default_memory_manager, bootstrap_default_memory_manager_with_config,
28 bootstrap_default_memory_manager_with_policy, committed_allocations,
29 default_memory_manager_commit_recovery_diagnostic, default_memory_manager_diagnostic_export,
30 default_memory_manager_doctor_report, default_memory_manager_doctor_report_with_policy,
31 default_memory_manager_memory_allocations, is_default_memory_manager_bootstrapped,
32 open_default_memory_manager_memory,
33};
34pub use error::{
35 RuntimeBootstrapError, RuntimeConstructionError, RuntimeDiagnosticError, RuntimeOpenError,
36 RuntimePolicyError, RuntimeStateError,
37};
38pub use layout::MemoryManagerLayoutError;
39pub use policy::GenericRangePolicy;
40
41use self::policy::{RuntimeMemoryManagerPolicy, runtime_bootstrap_error_from_bootstrap};
42use crate::{
43 AllocationBootstrap, AllocationHistory, AllocationLedger, AllocationPolicy,
44 CommittedAllocations, PolicyIdentity, RuntimeBootstrapPolicy, STABLE_CELL_VALUE_OFFSET,
45 StableCellLedgerError, StableCellLedgerRecord, StableKey, registry::SealedDeclarationSnapshot,
46 slot::MEMORY_MANAGER_LEDGER_ID, stable_cell::decode_stable_cell_ledger_record_from_memory,
47};
48use ic_stable_structures::{
49 Cell, Memory, Storable,
50 memory_manager::{MemoryId, MemoryManager},
51};
52
53use std::rc::Rc;
54
55type LedgerCell<M> = Cell<StableCellLedgerRecord, RuntimeMemory<M>>;
56
57enum RuntimeLifecycle {
58 Unbootstrapped,
59 Bootstrapped {
60 committed_allocations: CommittedAllocations,
61 binding: RuntimeBootstrapBinding,
62 },
63}
64
65struct RuntimeBootstrapBinding {
66 declarations: SealedDeclarationSnapshot,
67 policy_identity: PolicyIdentity,
68}
69
70pub struct MemoryRuntime<M: Memory> {
85 memory_manager: MemoryManager<Rc<M>>,
86 backing: Rc<M>,
89 bucket_size_pages: u16,
90 ledger_cell: Option<LedgerCell<M>>,
91 lifecycle: RuntimeLifecycle,
92}
93
94impl<M: Memory> MemoryRuntime<M> {
95 pub fn new(memory: M) -> Result<Self, RuntimeConstructionError> {
113 Self::construct(memory, None)
114 }
115
116 pub fn new_with_config(
119 memory: M,
120 config: MemoryManagerConfig,
121 ) -> Result<Self, RuntimeConstructionError> {
122 Self::construct(memory, Some(config))
123 }
124
125 fn construct(
126 memory: M,
127 requested: Option<MemoryManagerConfig>,
128 ) -> Result<Self, RuntimeConstructionError> {
129 if cfg!(target_endian = "big") {
130 return Err(MemoryManagerLayoutError::UnsupportedByteOrder.into());
131 }
132 let bucket_size_pages = if memory.size() == 0 {
133 requested.unwrap_or_default().bucket_size_pages()
134 } else {
135 let actual = layout::read(&memory)?.bucket_pages;
136 if let Some(config) = requested {
137 check_bucket_size(actual, config)?;
138 }
139 actual
140 };
141 let backing = Rc::new(memory);
142 Ok(Self {
143 memory_manager: MemoryManager::init_with_bucket_size(
144 Rc::clone(&backing),
145 bucket_size_pages,
146 ),
147 backing,
148 bucket_size_pages,
149 ledger_cell: None,
150 lifecycle: RuntimeLifecycle::Unbootstrapped,
151 })
152 }
153
154 #[must_use]
156 pub const fn memory_manager_config(&self) -> MemoryManagerConfig {
157 MemoryManagerConfig::from_validated(self.bucket_size_pages)
159 }
160
161 #[must_use]
163 pub const fn is_bootstrapped(&self) -> bool {
164 matches!(self.lifecycle, RuntimeLifecycle::Bootstrapped { .. })
165 }
166
167 pub fn bootstrap<P: RuntimeBootstrapPolicy>(
176 &mut self,
177 declarations: &SealedDeclarationSnapshot,
178 policy: &P,
179 ) -> Result<&CommittedAllocations, RuntimeBootstrapError<P::Error>> {
180 let policy_identity = policy.runtime_bootstrap_identity()?;
181 let already_bootstrapped = match &self.lifecycle {
182 RuntimeLifecycle::Unbootstrapped => false,
183 RuntimeLifecycle::Bootstrapped { binding, .. } => {
184 binding.validate(declarations, &policy_identity)?;
185 true
186 }
187 };
188 if !already_bootstrapped {
189 self.bootstrap_unbootstrapped(declarations, policy, policy_identity)?;
190 }
191 match &self.lifecycle {
192 RuntimeLifecycle::Bootstrapped {
193 committed_allocations,
194 ..
195 } => Ok(committed_allocations),
196 RuntimeLifecycle::Unbootstrapped => Err(RuntimeBootstrapError::State(
197 RuntimeStateError::InconsistentLifecycle,
198 )),
199 }
200 }
201
202 fn bootstrap_unbootstrapped<P: AllocationPolicy>(
203 &mut self,
204 declarations: &SealedDeclarationSnapshot,
205 policy: &P,
206 policy_identity: PolicyIdentity,
207 ) -> Result<(), RuntimeBootstrapError<P::Error>> {
208 self.initialize_ledger_cell()?;
209 let mut record = self
210 .ledger_cell
211 .as_ref()
212 .map(|cell| cell.get().clone())
213 .ok_or(RuntimeStateError::InconsistentLifecycle)?;
214 let runtime_policy = RuntimeMemoryManagerPolicy {
215 declarations,
216 custom_policy: policy,
217 };
218 let genesis = AllocationLedger::new(0, AllocationHistory::default())?;
219 let commit = AllocationBootstrap::new(record.store_mut())
220 .initialize_validate_and_commit(
221 &genesis,
222 declarations.allocation_snapshot().clone(),
223 &runtime_policy,
224 None,
225 )
226 .map_err(runtime_bootstrap_error_from_bootstrap)?;
227 let (ledger, validated) = commit.into_parts();
228
229 self.persist_ledger_record(record)?;
230 let committed =
231 external_runtime_allocations(validated.confirm_persisted(ledger.current_generation()));
232 self.lifecycle = RuntimeLifecycle::Bootstrapped {
233 committed_allocations: committed,
234 binding: RuntimeBootstrapBinding {
235 declarations: declarations.clone(),
236 policy_identity,
237 },
238 };
239 Ok(())
240 }
241
242 pub const fn committed_allocations(&self) -> Result<&CommittedAllocations, RuntimeOpenError> {
244 match &self.lifecycle {
245 RuntimeLifecycle::Unbootstrapped => Err(RuntimeOpenError::NotBootstrapped),
246 RuntimeLifecycle::Bootstrapped {
247 committed_allocations,
248 ..
249 } => Ok(committed_allocations),
250 }
251 }
252
253 pub fn open_memory(
255 &self,
256 stable_key: &str,
257 expected_id: u8,
258 ) -> Result<RuntimeMemory<M>, RuntimeOpenError> {
259 let key = StableKey::parse(stable_key)?;
260 if crate::is_ic_memory_stable_key(key.as_str()) {
261 return Err(RuntimeOpenError::ReservedStableKey {
262 stable_key: stable_key.to_string(),
263 });
264 }
265 let committed = self.committed_allocations()?;
266 let slot = committed
267 .slot_for(&key)
268 .ok_or_else(|| RuntimeOpenError::StableKeyNotCommitted(stable_key.to_string()))?;
269 let committed_id = slot.memory_manager_id()?;
270 if committed_id != expected_id {
271 return Err(RuntimeOpenError::MemoryIdMismatch {
272 stable_key: stable_key.to_string(),
273 committed_id,
274 requested_id: expected_id,
275 });
276 }
277 Ok(self.memory(expected_id))
278 }
279
280 fn initialize_ledger_cell<P>(&mut self) -> Result<(), RuntimeBootstrapError<P>> {
281 if self.ledger_cell.is_some() {
282 return Ok(());
283 }
284 let memory = self.memory(MEMORY_MANAGER_LEDGER_ID);
285 crate::validate_stable_cell_ledger_memory(&memory)?;
286 ensure_ledger_cell_capacity(&memory, &StableCellLedgerRecord::default())?;
287 self.ledger_cell = Some(Cell::init(memory, StableCellLedgerRecord::default()));
288 Ok(())
289 }
290
291 fn persist_ledger_record<P>(
292 &mut self,
293 record: StableCellLedgerRecord,
294 ) -> Result<(), RuntimeBootstrapError<P>> {
295 let memory = self.memory(MEMORY_MANAGER_LEDGER_ID);
296 ensure_ledger_cell_capacity(&memory, &record)?;
297 let cell = self
298 .ledger_cell
299 .as_mut()
300 .ok_or(RuntimeStateError::InconsistentLifecycle)?;
301 let _previous = cell.set(record);
302 Ok(())
303 }
304
305 fn memory(&self, id: u8) -> RuntimeMemory<M> {
306 RuntimeMemory(self.memory_manager.get(MemoryId::new(id)))
307 }
308
309 fn ledger_record_from_memory(&self) -> Result<StableCellLedgerRecord, StableCellLedgerError> {
310 decode_stable_cell_ledger_record_from_memory(&self.memory(MEMORY_MANAGER_LEDGER_ID))
311 }
312}
313
314impl RuntimeBootstrapBinding {
315 fn validate<P>(
316 &self,
317 declarations: &SealedDeclarationSnapshot,
318 policy_identity: &PolicyIdentity,
319 ) -> Result<(), RuntimeBootstrapError<P>> {
320 if !self.declarations.shares_storage_with(declarations) {
321 return Err(RuntimeBootstrapError::DeclarationSnapshotMismatch);
322 }
323 if &self.policy_identity != policy_identity {
324 return Err(RuntimeBootstrapError::PolicyIdentityMismatch {
325 established: self.policy_identity.clone(),
326 requested: policy_identity.clone(),
327 });
328 }
329 Ok(())
330 }
331}
332
333fn ensure_ledger_cell_capacity<M: Memory, P>(
334 memory: &RuntimeMemory<M>,
335 record: &StableCellLedgerRecord,
336) -> Result<(), RuntimeBootstrapError<P>> {
337 let value_size = record.to_bytes().len();
338 let value_size_u32 = u32::try_from(value_size)
339 .map_err(|_| RuntimeBootstrapError::StableCellLedgerWriteTooLarge { value_size })?;
340 let required_bytes = STABLE_CELL_VALUE_OFFSET
341 .checked_add(u64::from(value_size_u32))
342 .ok_or(RuntimeBootstrapError::StableCellLedgerWriteTooLarge { value_size })?;
343 let available_bytes = memory.size().saturating_mul(crate::WASM_PAGE_SIZE_BYTES);
344 if required_bytes <= available_bytes {
345 return Ok(());
346 }
347 let grow_by = required_bytes
348 .saturating_sub(available_bytes)
349 .div_ceil(crate::WASM_PAGE_SIZE_BYTES);
350 if memory.grow(grow_by) < 0 {
351 return Err(RuntimeBootstrapError::StableCellLedgerWriteTooLarge { value_size });
352 }
353 Ok(())
354}
355
356fn external_runtime_allocations(committed: CommittedAllocations) -> CommittedAllocations {
357 committed.without_stable_key_prefix(crate::IC_MEMORY_STABLE_KEY_PREFIX)
358}
359
360const fn check_bucket_size(
361 actual: u16,
362 requested: MemoryManagerConfig,
363) -> Result<(), RuntimeConstructionError> {
364 if actual != requested.bucket_size_pages() {
365 return Err(RuntimeConstructionError::BucketSizeMismatch {
366 persisted: actual,
367 requested: requested.bucket_size_pages(),
368 });
369 }
370 Ok(())
371}