1mod admission;
2#[cfg(test)]
3mod admission_tests;
4mod adoption;
5#[cfg(test)]
6mod adoption_tests;
7mod allocations;
8mod backing;
9mod config;
10mod default;
11mod diagnostics;
12mod error;
13mod layout;
14mod policy;
15
16#[cfg(test)]
17mod allocation_tests;
18#[cfg(test)]
19mod growth_tests;
20#[cfg(test)]
21#[expect(
22 unsafe_code,
23 reason = "exercise raw reads with valid uninitialized destinations"
24)]
25mod read_tests;
26#[cfg(test)]
27mod request_tests;
28#[cfg(test)]
29mod tests;
30
31pub use admission::{BootstrapAdmission, BootstrapAdmissionError, RecoveredAllocationMetadata};
32pub use adoption::RuntimeAdoptionError;
33
34pub use allocations::{
35 AllocationBinding, MemoryAllocation, MemoryAllocationSummary, MemoryAllocations,
36 MemoryBindingSummary,
37};
38pub use backing::RuntimeMemory;
39pub use config::MemoryManagerConfig;
40pub use default::{
41 bootstrap_default_memory_manager, bootstrap_default_memory_manager_with_config,
42 bootstrap_default_memory_manager_with_policy, committed_allocations,
43 default_memory_manager_commit_recovery_diagnostic, default_memory_manager_diagnostic_export,
44 default_memory_manager_doctor_report, default_memory_manager_doctor_report_with_policy,
45 default_memory_manager_memory_allocation_summary, default_memory_manager_memory_allocations,
46 default_memory_manager_memory_id, is_default_memory_manager_bootstrapped,
47 open_default_memory_manager_memory, verify_default_memory_manager_authority,
48};
49pub use error::{
50 MemoryResolutionError, RuntimeBootstrapError, RuntimeConstructionError, RuntimeDiagnosticError,
51 RuntimeGrowError, RuntimeOpenError, RuntimeStateError,
52};
53pub use layout::MemoryManagerLayoutError;
54pub use policy::GenericAllocationPolicy;
55
56use self::policy::{RuntimeMemoryManagerPolicy, runtime_bootstrap_error_from_bootstrap};
57use crate::{
58 AllocationBootstrap, AllocationLedger, CommittedAllocations, PolicyIdentity,
59 RuntimeBootstrapPolicy, STABLE_CELL_VALUE_OFFSET, StableCellLedgerError,
60 StableCellLedgerRecord, registry::SealedDeclarationSnapshot, slot::MEMORY_MANAGER_LEDGER_ID,
61 stable_cell::decode_stable_cell_ledger_record_from_memory,
62};
63use ic_stable_structures::{
64 Cell, Memory,
65 memory_manager::{MemoryId, MemoryManager},
66};
67
68use std::rc::Rc;
69
70enum RuntimeLifecycle {
71 Unbootstrapped,
72 Bootstrapped {
73 committed_allocations: CommittedAllocations,
74 binding: RuntimeBootstrapBinding,
75 },
76}
77
78struct RuntimeBootstrapBinding {
79 source: SealedDeclarationSnapshot,
80 policy_identity: PolicyIdentity,
81 pool: crate::MemoryAllocationPool,
82}
83
84pub struct MemoryRuntime<M: Memory> {
103 memory_manager: MemoryManager<Rc<M>>,
104 growth: Rc<backing::GrowthState<M>>,
107 lifecycle: RuntimeLifecycle,
108}
109
110impl<M: Memory> MemoryRuntime<M> {
111 pub fn new(memory: M) -> Result<Self, RuntimeConstructionError> {
131 Self::construct(memory, None)
132 }
133
134 pub fn new_with_config(
143 memory: M,
144 config: MemoryManagerConfig,
145 ) -> Result<Self, RuntimeConstructionError> {
146 Self::construct(memory, Some(config))
147 }
148
149 fn construct(
150 memory: M,
151 requested: Option<MemoryManagerConfig>,
152 ) -> Result<Self, RuntimeConstructionError> {
153 if cfg!(target_endian = "big") {
154 return Err(MemoryManagerLayoutError::UnsupportedByteOrder.into());
155 }
156 let (bucket_size_pages, allocated_buckets) = if memory.size() == 0 {
157 if memory.grow(1) == -1 {
161 return Err(RuntimeGrowError::BackingRefused {
162 additional_pages: 1,
163 }
164 .into());
165 }
166 (requested.unwrap_or_default().bucket_size_pages(), 0)
167 } else {
168 let measured = layout::read(&memory)?;
169 let actual = measured.bucket_pages;
170 if let Some(config) = requested {
171 check_bucket_size(actual, config)?;
172 }
173 (actual, measured.allocated_buckets)
174 };
175 let backing = Rc::new(memory);
176 let growth = Rc::new(backing::GrowthState {
177 backing: Rc::clone(&backing),
178 bucket_size_pages,
179 allocated_buckets: std::cell::RefCell::new(allocated_buckets),
180 });
181 Ok(Self {
182 memory_manager: MemoryManager::init_with_bucket_size(
183 Rc::clone(&backing),
184 bucket_size_pages,
185 ),
186 growth,
187 lifecycle: RuntimeLifecycle::Unbootstrapped,
188 })
189 }
190
191 #[must_use]
193 pub fn memory_manager_config(&self) -> MemoryManagerConfig {
194 MemoryManagerConfig::from_validated(self.growth.bucket_size_pages)
196 }
197
198 #[must_use]
200 pub const fn is_bootstrapped(&self) -> bool {
201 matches!(self.lifecycle, RuntimeLifecycle::Bootstrapped { .. })
202 }
203
204 pub fn bootstrap<P: RuntimeBootstrapPolicy>(
219 &mut self,
220 declarations: &SealedDeclarationSnapshot,
221 pool: &crate::MemoryAllocationPool,
222 policy: &P,
223 ) -> Result<&CommittedAllocations, RuntimeBootstrapError<P::Error>> {
224 let policy_identity = policy.runtime_bootstrap_identity()?;
225 match &self.lifecycle {
226 RuntimeLifecycle::Unbootstrapped => {
227 self.bootstrap_unbootstrapped(declarations, pool, policy, policy_identity)?;
228 }
229 RuntimeLifecycle::Bootstrapped { binding, .. } => {
230 binding.validate(declarations, pool, &policy_identity)?;
231 }
232 }
233 match &self.lifecycle {
234 RuntimeLifecycle::Bootstrapped {
235 committed_allocations,
236 ..
237 } => Ok(committed_allocations),
238 RuntimeLifecycle::Unbootstrapped => {
239 unreachable!("successful bootstrap publishes committed allocations")
240 }
241 }
242 }
243
244 fn validate_pool_custody(
245 &self,
246 ledger: &AllocationLedger,
247 pool: &crate::MemoryAllocationPool,
248 ) -> Result<(), super::MemoryResolutionError> {
249 for id in 0..crate::MEMORY_MANAGER_INVALID_ID {
250 if pool.contains(id)
251 && self.memory_size_pages(id) != 0
252 && !ledger
253 .records()
254 .iter()
255 .any(|record| record.slot().id() == id)
256 {
257 return Err(super::MemoryResolutionError::UnmanagedAllocation { id });
258 }
259 }
260 Ok(())
261 }
262
263 fn bootstrap_unbootstrapped<P: RuntimeBootstrapPolicy>(
264 &mut self,
265 declarations: &SealedDeclarationSnapshot,
266 pool: &crate::MemoryAllocationPool,
267 policy: &P,
268 policy_identity: PolicyIdentity,
269 ) -> Result<(), RuntimeBootstrapError<P::Error>> {
270 let memory = self.memory(MEMORY_MANAGER_LEDGER_ID);
271 let mut record = decode_stable_cell_ledger_record_from_memory(&memory)?;
272 let genesis = AllocationLedger::empty_genesis();
273 let recovered = record.store_mut().recover_or_initialize(&genesis)?;
274 self.validate_pool_custody(recovered.ledger(), pool)?;
275 if memory.size() == 0 {
276 ensure_ledger_cell_capacity(&memory, &record)?;
279 drop(Cell::new(memory.clone(), StableCellLedgerRecord::default()));
280 }
281 let mut admission = BootstrapAdmission::new(recovered.ledger(), declarations, pool);
282 let preparation = policy.prepare_bootstrap(&mut admission);
283 let historical = admission.complete()?;
284 preparation.map_err(RuntimeBootstrapError::AdmissionPolicy)?;
285 let resolved = declarations.resolve(recovered.ledger(), historical, pool)?;
286 let runtime_policy = RuntimeMemoryManagerPolicy {
287 custom_policy: policy,
288 };
289 let commit = AllocationBootstrap::new(record.store_mut())
290 .validate_against(recovered, resolved, &runtime_policy)
291 .map_err(runtime_bootstrap_error_from_bootstrap)?;
292 ensure_ledger_cell_capacity(&memory, &record)?;
293 drop(Cell::new(memory, record));
294 let committed = commit.confirm_persisted().into_application_allocations();
295 self.lifecycle = RuntimeLifecycle::Bootstrapped {
296 committed_allocations: committed,
297 binding: RuntimeBootstrapBinding {
298 source: declarations.clone(),
299 policy_identity,
300 pool: pool.clone(),
301 },
302 };
303 Ok(())
304 }
305
306 pub const fn committed_allocations(&self) -> Result<&CommittedAllocations, RuntimeOpenError> {
308 match &self.lifecycle {
309 RuntimeLifecycle::Unbootstrapped => Err(RuntimeOpenError::NotBootstrapped),
310 RuntimeLifecycle::Bootstrapped {
311 committed_allocations,
312 ..
313 } => Ok(committed_allocations),
314 }
315 }
316
317 pub fn open_memory(&self, stable_key: &str) -> Result<RuntimeMemory<M>, RuntimeOpenError> {
319 Ok(self.memory(self.memory_id(stable_key)?))
320 }
321
322 pub fn memory_id(&self, stable_key: &str) -> Result<u8, RuntimeOpenError> {
325 crate::key::validate(stable_key)?;
326 if crate::is_ic_memory_stable_key(stable_key) {
327 return Err(RuntimeOpenError::ReservedStableKey {
328 stable_key: stable_key.to_string(),
329 });
330 }
331 let declaration = crate::capability::declaration_for_key(
332 self.committed_allocations()?.declarations(),
333 stable_key,
334 )
335 .ok_or_else(|| RuntimeOpenError::StableKeyNotCommitted(stable_key.to_string()))?;
336 Ok(declaration.slot().id())
337 }
338
339 fn memory(&self, id: u8) -> RuntimeMemory<M> {
340 RuntimeMemory {
341 memory: self.memory_manager.get(MemoryId::new(id)),
342 growth: Rc::clone(&self.growth),
343 }
344 }
345
346 fn memory_size_pages(&self, id: u8) -> u64 {
348 self.memory_manager.get(MemoryId::new(id)).size()
349 }
350
351 fn ledger_record_from_memory(&self) -> Result<StableCellLedgerRecord, StableCellLedgerError> {
352 let memory = self
355 .memory_manager
356 .get(MemoryId::new(MEMORY_MANAGER_LEDGER_ID));
357 decode_stable_cell_ledger_record_from_memory(&memory)
358 }
359}
360
361impl RuntimeBootstrapBinding {
362 fn validate<P>(
363 &self,
364 declarations: &SealedDeclarationSnapshot,
365 pool: &crate::MemoryAllocationPool,
366 policy_identity: &PolicyIdentity,
367 ) -> Result<(), RuntimeBootstrapError<P>> {
368 if &self.source != declarations {
369 return Err(RuntimeBootstrapError::DeclarationSnapshotMismatch);
370 }
371 if &self.pool != pool {
372 return Err(RuntimeBootstrapError::AllocationPoolMismatch);
373 }
374 if &self.policy_identity != policy_identity {
375 return Err(RuntimeBootstrapError::PolicyIdentityMismatch {
376 established: self.policy_identity.clone(),
377 requested: policy_identity.clone(),
378 });
379 }
380 Ok(())
381 }
382}
383
384fn ensure_ledger_cell_capacity<M: Memory, P>(
385 memory: &RuntimeMemory<M>,
386 record: &StableCellLedgerRecord,
387) -> Result<(), RuntimeBootstrapError<P>> {
388 let value_size = record.encoded_size();
389 if value_size > crate::constants::MAX_LEDGER_RECORD_BYTES {
390 return Err(RuntimeBootstrapError::StableCellLedgerWriteTooLarge { value_size });
391 }
392 let required_bytes = STABLE_CELL_VALUE_OFFSET + value_size as u64;
394 let available_bytes = memory.size().saturating_mul(crate::WASM_PAGE_SIZE_BYTES);
395 if required_bytes <= available_bytes {
396 return Ok(());
397 }
398 let grow_by = (required_bytes - available_bytes).div_ceil(crate::WASM_PAGE_SIZE_BYTES);
399 memory.grow(grow_by)?;
400 Ok(())
401}
402
403const fn check_bucket_size(
404 actual: u16,
405 requested: MemoryManagerConfig,
406) -> Result<(), RuntimeConstructionError> {
407 if actual != requested.bucket_size_pages() {
408 return Err(RuntimeConstructionError::BucketSizeMismatch {
409 persisted: actual,
410 requested: requested.bucket_size_pages(),
411 });
412 }
413 Ok(())
414}