1mod admission;
2#[cfg(test)]
3mod admission_tests;
4mod adoption;
5#[cfg(test)]
6mod adoption_tests;
7mod allocations;
8mod backing;
9mod config;
10mod custody;
11mod diagnostics;
12mod error;
13#[cfg(any(test, feature = "test-support"))]
14pub mod fixture_diagnostics;
15#[cfg(test)]
16mod fixtures;
17mod layout;
18mod policy;
19
20#[cfg(all(test, not(target_arch = "wasm32")))]
21mod allocation_tests;
22#[cfg(test)]
23mod custody_tests;
24#[cfg(test)]
25mod growth_tests;
26#[cfg(test)]
27#[expect(
28 unsafe_code,
29 reason = "exercise raw reads with valid uninitialized destinations"
30)]
31mod read_tests;
32#[cfg(test)]
33mod request_tests;
34#[cfg(test)]
35mod tests;
36
37pub use admission::{BootstrapAdmission, BootstrapAdmissionError};
38pub use adoption::RuntimeAdoptionError;
39
40pub use allocations::{
41 MemoryAllocation, MemoryAllocationSummary, MemoryAllocations, MemoryBindingSummary,
42};
43pub use backing::RuntimeMemory;
44pub use config::MemoryManagerConfig;
45pub use custody::{BackingIdentity, MemoryBacking};
46pub use error::{
47 MemoryResolutionError, RuntimeBootstrapError, RuntimeConstructionError, RuntimeDiagnosticError,
48 RuntimeGrowError, RuntimeOpenError,
49};
50pub use layout::MemoryManagerLayoutError;
51pub use policy::GenericAllocationPolicy;
52
53use crate::{
54 LedgerCommitStore, PolicyIdentity, RuntimeBootstrapPolicy, STABLE_CELL_VALUE_OFFSET,
55 StableCellLedgerError,
56 requests::SealedDeclarationSnapshot,
57 slot::MEMORY_MANAGER_LEDGER_ID,
58 stable_cell::{EncodedLedgerRecord, decode_stable_cell_commit_store_from_memory},
59};
60use ic_platform::stable_structures::{
61 Cell, Memory,
62 memory_manager::{MemoryId, MemoryManager},
63};
64
65use std::rc::Rc;
66
67enum RuntimeLifecycle {
68 Unbootstrapped,
69 Bootstrapped(CommittedRuntime),
70}
71
72struct CommittedRuntime {
73 allocations: Vec<crate::AllocationDeclaration>,
74 generation: u64,
75 binding: RuntimeBootstrapBinding,
76}
77
78struct RuntimeBootstrapBinding {
79 source: SealedDeclarationSnapshot,
80 policy_identity: PolicyIdentity,
81 pool: crate::MemoryAllocationPool,
82}
83
84pub struct MemoryRuntime<M: Memory> {
111 memory_manager: MemoryManager<Rc<M>>,
112 growth: Rc<backing::GrowthState<M>>,
115 lifecycle: RuntimeLifecycle,
116}
117
118impl<M: Memory> MemoryRuntime<M> {
119 pub fn new(memory: M) -> Result<Self, RuntimeConstructionError>
141 where
142 M: MemoryBacking,
143 {
144 Self::construct(memory, None)
145 }
146
147 pub fn new_with_config(
156 memory: M,
157 config: MemoryManagerConfig,
158 ) -> Result<Self, RuntimeConstructionError>
159 where
160 M: MemoryBacking,
161 {
162 Self::construct(memory, Some(config))
163 }
164
165 fn construct(
166 memory: M,
167 requested: Option<MemoryManagerConfig>,
168 ) -> Result<Self, RuntimeConstructionError>
169 where
170 M: MemoryBacking,
171 {
172 let backing = Rc::new(memory);
175 let claim = custody::BackingClaim::acquire(backing.as_ref())?;
176 if cfg!(target_endian = "big") {
177 return Err(MemoryManagerLayoutError::UnsupportedByteOrder.into());
178 }
179 let bucket_size_pages = if backing.size() == 0 {
180 if backing.grow(1) == -1 {
184 return Err(RuntimeGrowError::BackingRefused {
185 additional_pages: 1,
186 }
187 .into());
188 }
189 requested.unwrap_or_default().bucket_size_pages()
190 } else {
191 let measured = layout::read(backing.as_ref())?;
192 let actual = measured.bucket_pages;
193 if let Some(config) = requested {
194 check_bucket_size(actual, config)?;
195 }
196 actual
197 };
198 let memory_manager =
199 MemoryManager::init_with_bucket_size(Rc::clone(&backing), bucket_size_pages);
200 let authority =
201 std::cell::RefCell::new(layout::ManagerAuthority::capture(backing.as_ref()));
202 let growth = Rc::new(backing::GrowthState {
203 _claim: claim,
204 backing: Rc::clone(&backing),
205 bucket_size_pages,
206 authority,
207 open_slots: [const { std::cell::Cell::new(0) }; 4],
208 });
209 Ok(Self {
210 memory_manager,
211 growth,
212 lifecycle: RuntimeLifecycle::Unbootstrapped,
213 })
214 }
215
216 #[must_use]
218 pub fn memory_manager_config(&self) -> MemoryManagerConfig {
219 MemoryManagerConfig::from_validated(self.growth.bucket_size_pages)
221 }
222
223 #[must_use]
225 pub const fn is_bootstrapped(&self) -> bool {
226 matches!(self.lifecycle, RuntimeLifecycle::Bootstrapped(_))
227 }
228
229 pub fn bootstrap<P: RuntimeBootstrapPolicy>(
244 &mut self,
245 declarations: &SealedDeclarationSnapshot,
246 pool: &crate::MemoryAllocationPool,
247 policy: &P,
248 ) -> Result<u64, RuntimeBootstrapError<P::Error>> {
249 match &self.lifecycle {
250 RuntimeLifecycle::Unbootstrapped => self.bootstrap_unbootstrapped(
251 declarations,
252 pool,
253 policy,
254 policy.runtime_bootstrap_identity(),
255 ),
256 RuntimeLifecycle::Bootstrapped(_) => self.verify_bootstrap(declarations, pool, policy),
257 }
258 }
259
260 pub fn verify_bootstrap<P: RuntimeBootstrapPolicy>(
269 &self,
270 declarations: &SealedDeclarationSnapshot,
271 pool: &crate::MemoryAllocationPool,
272 policy: &P,
273 ) -> Result<u64, RuntimeBootstrapError<P::Error>> {
274 let committed = self
275 .committed()
276 .map_err(|_| RuntimeBootstrapError::NotBootstrapped)?;
277 committed
278 .binding
279 .validate(declarations, pool, &policy.runtime_bootstrap_identity())?;
280 Ok(committed.generation)
281 }
282
283 const fn committed_state(&self) -> Option<&CommittedRuntime> {
284 match &self.lifecycle {
285 RuntimeLifecycle::Unbootstrapped => None,
286 RuntimeLifecycle::Bootstrapped(committed) => Some(committed),
287 }
288 }
289
290 fn committed(&self) -> Result<&CommittedRuntime, RuntimeOpenError> {
291 self.committed_state()
292 .ok_or(RuntimeOpenError::NotBootstrapped)
293 }
294
295 fn validate_pool_custody(
296 &self,
297 occupied: &[bool; crate::constants::MAX_ALLOCATIONS],
298 pool: &crate::MemoryAllocationPool,
299 ) -> Result<(), super::MemoryResolutionError> {
300 for id in 0..crate::MEMORY_MANAGER_INVALID_ID {
301 if pool.contains(id) && self.memory_size_pages(id) != 0 && !occupied[usize::from(id)] {
302 return Err(super::MemoryResolutionError::UnmanagedAllocation { id });
303 }
304 }
305 Ok(())
306 }
307
308 fn bootstrap_unbootstrapped<P: RuntimeBootstrapPolicy>(
309 &mut self,
310 declarations: &SealedDeclarationSnapshot,
311 pool: &crate::MemoryAllocationPool,
312 policy: &P,
313 policy_identity: PolicyIdentity,
314 ) -> Result<u64, RuntimeBootstrapError<P::Error>> {
315 self.growth.verify()?;
316 let mut memory = self
317 .memory(MEMORY_MANAGER_LEDGER_ID)
318 .expect("governance memory is not publicly leasable");
319 let mut record = decode_stable_cell_commit_store_from_memory(&memory)?;
320 let recovered = record.recover_or_genesis()?;
321 let occupied = recovered.ledger().occupied_slots();
322 self.validate_pool_custody(&occupied, pool)?;
323 if memory.size() == 0 {
324 memory = write_ledger_cell(memory, &LedgerCommitStore::default())?;
327 }
328 let mut admission = BootstrapAdmission::new(recovered.ledger(), declarations, pool);
329 let preparation = policy.prepare_bootstrap(&mut admission);
330 let historical = admission.complete()?;
331 preparation.map_err(RuntimeBootstrapError::AdmissionPolicy)?;
332 let resolved = declarations.resolve(&recovered, &occupied, historical, pool)?;
333 let (generation, resolved) = {
334 let (staged, resolved) = crate::ledger::stage_generation(recovered, resolved, policy)?;
335 record.commit_generation(&staged)?;
336 (staged.current_generation(), resolved)
337 };
338 drop(write_ledger_cell(memory, &record)?);
339 let mut allocations = resolved;
341 allocations.retain(|declaration| {
342 !crate::is_ic_memory_stable_key(declaration.stable_key().as_str())
343 });
344 self.lifecycle = RuntimeLifecycle::Bootstrapped(CommittedRuntime {
345 allocations,
346 generation,
347 binding: RuntimeBootstrapBinding {
348 source: declarations.clone(),
349 policy_identity,
350 pool: pool.clone(),
351 },
352 });
353 Ok(generation)
354 }
355
356 pub fn committed_allocations(
359 &self,
360 ) -> Result<&[crate::AllocationDeclaration], RuntimeOpenError> {
361 Ok(&self.committed()?.allocations)
362 }
363
364 pub const fn committed_generation(&self) -> Result<u64, RuntimeOpenError> {
366 match self.committed_state() {
367 Some(committed) => Ok(committed.generation),
368 None => Err(RuntimeOpenError::NotBootstrapped),
369 }
370 }
371
372 pub const fn allocation_pool(&self) -> Result<&crate::MemoryAllocationPool, RuntimeOpenError> {
375 match self.committed_state() {
376 Some(committed) => Ok(&committed.binding.pool),
377 None => Err(RuntimeOpenError::NotBootstrapped),
378 }
379 }
380
381 pub fn open_memory(&self, stable_key: &str) -> Result<RuntimeMemory<M>, RuntimeOpenError> {
388 self.memory(self.memory_id(stable_key)?)
389 }
390
391 pub fn memory_id(&self, stable_key: &str) -> Result<u8, RuntimeOpenError> {
394 crate::key::validate(stable_key)?;
395 if crate::is_ic_memory_stable_key(stable_key) {
396 return Err(RuntimeOpenError::ReservedStableKey {
397 stable_key: stable_key.to_string(),
398 });
399 }
400 let declaration =
401 crate::declaration::declaration_for_key(self.committed_allocations()?, stable_key)
402 .ok_or_else(|| RuntimeOpenError::StableKeyNotCommitted(stable_key.to_string()))?;
403 Ok(declaration.slot().id())
404 }
405
406 fn memory(&self, id: u8) -> Result<RuntimeMemory<M>, RuntimeOpenError> {
407 let (word, mask) = self.growth.lease_bit(id);
408 if word.get() & mask != 0 {
409 return Err(RuntimeOpenError::AlreadyOpen { id });
410 }
411 self.growth.verify()?;
412 let memory = self.memory_manager.get(MemoryId::new(id));
413 word.set(word.get() | mask);
414 Ok(RuntimeMemory {
415 memory,
416 growth: Rc::clone(&self.growth),
417 id,
418 })
419 }
420
421 fn memory_size_pages(&self, id: u8) -> u64 {
423 self.memory_manager.get(MemoryId::new(id)).size()
424 }
425
426 fn ledger_record_from_memory(&self) -> Result<LedgerCommitStore, StableCellLedgerError> {
427 let memory = self
430 .memory_manager
431 .get(MemoryId::new(MEMORY_MANAGER_LEDGER_ID));
432 decode_stable_cell_commit_store_from_memory(&memory)
433 }
434}
435
436impl RuntimeBootstrapBinding {
437 fn validate<P>(
438 &self,
439 declarations: &SealedDeclarationSnapshot,
440 pool: &crate::MemoryAllocationPool,
441 policy_identity: &PolicyIdentity,
442 ) -> Result<(), RuntimeBootstrapError<P>> {
443 if &self.source != declarations {
444 return Err(RuntimeBootstrapError::DeclarationSnapshotMismatch);
445 }
446 if &self.pool != pool {
447 return Err(RuntimeBootstrapError::AllocationPoolMismatch);
448 }
449 if &self.policy_identity != policy_identity {
450 return Err(RuntimeBootstrapError::PolicyIdentityMismatch {
451 established: self.policy_identity,
452 requested: *policy_identity,
453 });
454 }
455 Ok(())
456 }
457}
458
459fn write_ledger_cell<M: Memory, P>(
460 memory: RuntimeMemory<M>,
461 record: &LedgerCommitStore,
462) -> Result<RuntimeMemory<M>, RuntimeBootstrapError<P>> {
463 let encoded =
464 EncodedLedgerRecord::new(record).map_err(RuntimeBootstrapError::StableCellLedgerEncode)?;
465 let value_size = encoded.len();
466 let required_bytes = STABLE_CELL_VALUE_OFFSET + value_size as u64;
468 let available_bytes = memory.size().saturating_mul(crate::WASM_PAGE_SIZE_BYTES);
469 if required_bytes > available_bytes {
470 let grow_by = (required_bytes - available_bytes).div_ceil(crate::WASM_PAGE_SIZE_BYTES);
471 memory.grow(grow_by)?;
472 }
473 Ok(Cell::new(memory, encoded).into_memory())
474}
475
476const fn check_bucket_size(
477 actual: u16,
478 requested: MemoryManagerConfig,
479) -> Result<(), RuntimeConstructionError> {
480 if actual != requested.bucket_size_pages() {
481 return Err(RuntimeConstructionError::BucketSizeMismatch {
482 persisted: actual,
483 requested: requested.bucket_size_pages(),
484 });
485 }
486 Ok(())
487}