1mod admission;
2#[cfg(test)]
3mod admission_tests;
4mod adoption;
5#[cfg(test)]
6mod adoption_tests;
7mod allocations;
8mod backing;
9mod config;
10mod diagnostics;
11mod error;
12#[cfg(any(test, feature = "test-support"))]
13#[path = "../testing/diagnostics.rs"]
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 growth_tests;
24#[cfg(test)]
25#[expect(
26 unsafe_code,
27 reason = "exercise raw reads with valid uninitialized destinations"
28)]
29mod read_tests;
30#[cfg(test)]
31mod request_tests;
32#[cfg(test)]
33mod tests;
34
35pub use admission::{BootstrapAdmission, BootstrapAdmissionError};
36pub use adoption::RuntimeAdoptionError;
37
38pub use allocations::{
39 MemoryAllocation, MemoryAllocationSummary, MemoryAllocations, MemoryBindingSummary,
40};
41pub use backing::RuntimeMemory;
42pub use config::MemoryManagerConfig;
43pub use error::{
44 MemoryResolutionError, RuntimeBootstrapError, RuntimeConstructionError, RuntimeDiagnosticError,
45 RuntimeGrowError, RuntimeOpenError,
46};
47pub use layout::MemoryManagerLayoutError;
48pub use policy::GenericAllocationPolicy;
49
50use crate::{
51 LedgerCommitStore, PolicyIdentity, RuntimeBootstrapPolicy, STABLE_CELL_VALUE_OFFSET,
52 StableCellLedgerError,
53 requests::SealedDeclarationSnapshot,
54 slot::MEMORY_MANAGER_LEDGER_ID,
55 stable_cell::{EncodedLedgerRecord, decode_stable_cell_commit_store_from_memory},
56};
57use ic_platform::stable_structures::{
58 Cell, Memory,
59 memory_manager::{MemoryId, MemoryManager},
60};
61
62use std::rc::Rc;
63
64enum RuntimeLifecycle {
65 Unbootstrapped,
66 Bootstrapped {
67 allocations: Vec<crate::AllocationDeclaration>,
68 generation: u64,
69 binding: RuntimeBootstrapBinding,
70 },
71}
72
73struct RuntimeBootstrapBinding {
74 source: SealedDeclarationSnapshot,
75 policy_identity: PolicyIdentity,
76 pool: crate::MemoryAllocationPool,
77}
78
79pub struct MemoryRuntime<M: Memory> {
105 memory_manager: MemoryManager<Rc<M>>,
106 growth: Rc<backing::GrowthState<M>>,
109 lifecycle: RuntimeLifecycle,
110}
111
112impl<M: Memory> MemoryRuntime<M> {
113 pub fn new(memory: M) -> Result<Self, RuntimeConstructionError> {
133 Self::construct(memory, None)
134 }
135
136 pub fn new_with_config(
145 memory: M,
146 config: MemoryManagerConfig,
147 ) -> Result<Self, RuntimeConstructionError> {
148 Self::construct(memory, Some(config))
149 }
150
151 fn construct(
152 memory: M,
153 requested: Option<MemoryManagerConfig>,
154 ) -> Result<Self, RuntimeConstructionError> {
155 if cfg!(target_endian = "big") {
156 return Err(MemoryManagerLayoutError::UnsupportedByteOrder.into());
157 }
158 let (bucket_size_pages, allocated_buckets) = if memory.size() == 0 {
159 if memory.grow(1) == -1 {
163 return Err(RuntimeGrowError::BackingRefused {
164 additional_pages: 1,
165 }
166 .into());
167 }
168 (requested.unwrap_or_default().bucket_size_pages(), 0)
169 } else {
170 let measured = layout::read(&memory)?;
171 let actual = measured.bucket_pages;
172 if let Some(config) = requested {
173 check_bucket_size(actual, config)?;
174 }
175 (actual, measured.allocated_buckets)
176 };
177 let backing = Rc::new(memory);
178 let growth = Rc::new(backing::GrowthState {
179 backing: Rc::clone(&backing),
180 bucket_size_pages,
181 allocated_buckets: std::cell::RefCell::new(allocated_buckets),
182 open_slots: [const { std::cell::Cell::new(0) }; 4],
183 });
184 Ok(Self {
185 memory_manager: MemoryManager::init_with_bucket_size(
186 Rc::clone(&backing),
187 bucket_size_pages,
188 ),
189 growth,
190 lifecycle: RuntimeLifecycle::Unbootstrapped,
191 })
192 }
193
194 #[must_use]
196 pub fn memory_manager_config(&self) -> MemoryManagerConfig {
197 MemoryManagerConfig::from_validated(self.growth.bucket_size_pages)
199 }
200
201 #[must_use]
203 pub const fn is_bootstrapped(&self) -> bool {
204 matches!(self.lifecycle, RuntimeLifecycle::Bootstrapped { .. })
205 }
206
207 pub fn bootstrap<P: RuntimeBootstrapPolicy>(
222 &mut self,
223 declarations: &SealedDeclarationSnapshot,
224 pool: &crate::MemoryAllocationPool,
225 policy: &P,
226 ) -> Result<u64, RuntimeBootstrapError<P::Error>> {
227 match &self.lifecycle {
228 RuntimeLifecycle::Unbootstrapped => self.bootstrap_unbootstrapped(
229 declarations,
230 pool,
231 policy,
232 policy.runtime_bootstrap_identity(),
233 ),
234 RuntimeLifecycle::Bootstrapped { .. } => {
235 self.verify_bootstrap(declarations, pool, policy)
236 }
237 }
238 }
239
240 pub fn verify_bootstrap<P: RuntimeBootstrapPolicy>(
249 &self,
250 declarations: &SealedDeclarationSnapshot,
251 pool: &crate::MemoryAllocationPool,
252 policy: &P,
253 ) -> Result<u64, RuntimeBootstrapError<P::Error>> {
254 let RuntimeLifecycle::Bootstrapped {
255 binding,
256 generation,
257 ..
258 } = &self.lifecycle
259 else {
260 return Err(RuntimeBootstrapError::NotBootstrapped);
261 };
262 binding.validate(declarations, pool, &policy.runtime_bootstrap_identity())?;
263 Ok(*generation)
264 }
265
266 fn validate_pool_custody(
267 &self,
268 occupied: &[bool; crate::constants::MAX_ALLOCATIONS],
269 pool: &crate::MemoryAllocationPool,
270 ) -> Result<(), super::MemoryResolutionError> {
271 for id in 0..crate::MEMORY_MANAGER_INVALID_ID {
272 if pool.contains(id) && self.memory_size_pages(id) != 0 && !occupied[usize::from(id)] {
273 return Err(super::MemoryResolutionError::UnmanagedAllocation { id });
274 }
275 }
276 Ok(())
277 }
278
279 fn bootstrap_unbootstrapped<P: RuntimeBootstrapPolicy>(
280 &mut self,
281 declarations: &SealedDeclarationSnapshot,
282 pool: &crate::MemoryAllocationPool,
283 policy: &P,
284 policy_identity: PolicyIdentity,
285 ) -> Result<u64, RuntimeBootstrapError<P::Error>> {
286 let mut memory = self
287 .memory(MEMORY_MANAGER_LEDGER_ID)
288 .expect("governance memory is not publicly leasable");
289 let mut record = decode_stable_cell_commit_store_from_memory(&memory)?;
290 let recovered = record.recover_or_genesis()?;
291 let occupied = recovered.ledger().occupied_slots();
292 self.validate_pool_custody(&occupied, pool)?;
293 if memory.size() == 0 {
294 memory = write_ledger_cell(memory, &LedgerCommitStore::default())?;
297 }
298 let mut admission = BootstrapAdmission::new(recovered.ledger(), declarations, pool);
299 let preparation = policy.prepare_bootstrap(&mut admission);
300 let historical = admission.complete()?;
301 preparation.map_err(RuntimeBootstrapError::AdmissionPolicy)?;
302 let resolved = declarations.resolve(&recovered, &occupied, historical, pool)?;
303 let (generation, resolved) = {
304 let (staged, resolved) = crate::ledger::stage_generation(recovered, resolved, policy)?;
305 record.commit_generation(&staged)?;
306 (staged.current_generation(), resolved)
307 };
308 drop(write_ledger_cell(memory, &record)?);
309 let mut allocations = resolved;
311 allocations.retain(|declaration| {
312 !crate::is_ic_memory_stable_key(declaration.stable_key().as_str())
313 });
314 self.lifecycle = RuntimeLifecycle::Bootstrapped {
315 allocations,
316 generation,
317 binding: RuntimeBootstrapBinding {
318 source: declarations.clone(),
319 policy_identity,
320 pool: pool.clone(),
321 },
322 };
323 Ok(generation)
324 }
325
326 pub fn committed_allocations(
329 &self,
330 ) -> Result<&[crate::AllocationDeclaration], RuntimeOpenError> {
331 match &self.lifecycle {
332 RuntimeLifecycle::Unbootstrapped => Err(RuntimeOpenError::NotBootstrapped),
333 RuntimeLifecycle::Bootstrapped { allocations, .. } => Ok(allocations),
334 }
335 }
336
337 pub const fn committed_generation(&self) -> Result<u64, RuntimeOpenError> {
339 match &self.lifecycle {
340 RuntimeLifecycle::Unbootstrapped => Err(RuntimeOpenError::NotBootstrapped),
341 RuntimeLifecycle::Bootstrapped { generation, .. } => Ok(*generation),
342 }
343 }
344
345 pub const fn allocation_pool(&self) -> Result<&crate::MemoryAllocationPool, RuntimeOpenError> {
348 match &self.lifecycle {
349 RuntimeLifecycle::Unbootstrapped => Err(RuntimeOpenError::NotBootstrapped),
350 RuntimeLifecycle::Bootstrapped { binding, .. } => Ok(&binding.pool),
351 }
352 }
353
354 pub fn open_memory(&self, stable_key: &str) -> Result<RuntimeMemory<M>, RuntimeOpenError> {
359 self.memory(self.memory_id(stable_key)?)
360 }
361
362 pub fn memory_id(&self, stable_key: &str) -> Result<u8, RuntimeOpenError> {
365 crate::key::validate(stable_key)?;
366 if crate::is_ic_memory_stable_key(stable_key) {
367 return Err(RuntimeOpenError::ReservedStableKey {
368 stable_key: stable_key.to_string(),
369 });
370 }
371 let declaration =
372 crate::declaration::declaration_for_key(self.committed_allocations()?, stable_key)
373 .ok_or_else(|| RuntimeOpenError::StableKeyNotCommitted(stable_key.to_string()))?;
374 Ok(declaration.slot().id())
375 }
376
377 fn memory(&self, id: u8) -> Result<RuntimeMemory<M>, RuntimeOpenError> {
378 let word = &self.growth.open_slots[usize::from(id / 64)];
379 let mask = 1_u64 << (id % 64);
380 if word.get() & mask != 0 {
381 return Err(RuntimeOpenError::AlreadyOpen { id });
382 }
383 let memory = self.memory_manager.get(MemoryId::new(id));
384 word.set(word.get() | mask);
385 Ok(RuntimeMemory {
386 memory,
387 growth: Rc::clone(&self.growth),
388 id,
389 })
390 }
391
392 fn memory_size_pages(&self, id: u8) -> u64 {
394 self.memory_manager.get(MemoryId::new(id)).size()
395 }
396
397 fn ledger_record_from_memory(&self) -> Result<LedgerCommitStore, StableCellLedgerError> {
398 let memory = self
401 .memory_manager
402 .get(MemoryId::new(MEMORY_MANAGER_LEDGER_ID));
403 decode_stable_cell_commit_store_from_memory(&memory)
404 }
405}
406
407impl RuntimeBootstrapBinding {
408 fn validate<P>(
409 &self,
410 declarations: &SealedDeclarationSnapshot,
411 pool: &crate::MemoryAllocationPool,
412 policy_identity: &PolicyIdentity,
413 ) -> Result<(), RuntimeBootstrapError<P>> {
414 if &self.source != declarations {
415 return Err(RuntimeBootstrapError::DeclarationSnapshotMismatch);
416 }
417 if &self.pool != pool {
418 return Err(RuntimeBootstrapError::AllocationPoolMismatch);
419 }
420 if &self.policy_identity != policy_identity {
421 return Err(RuntimeBootstrapError::PolicyIdentityMismatch {
422 established: self.policy_identity,
423 requested: *policy_identity,
424 });
425 }
426 Ok(())
427 }
428}
429
430fn write_ledger_cell<M: Memory, P>(
431 memory: RuntimeMemory<M>,
432 record: &LedgerCommitStore,
433) -> Result<RuntimeMemory<M>, RuntimeBootstrapError<P>> {
434 let encoded = EncodedLedgerRecord::new(record).map_err(|value_size| {
435 RuntimeBootstrapError::StableCellLedgerWriteTooLarge { value_size }
436 })?;
437 let value_size = encoded.len();
438 let required_bytes = STABLE_CELL_VALUE_OFFSET + value_size as u64;
440 let available_bytes = memory.size().saturating_mul(crate::WASM_PAGE_SIZE_BYTES);
441 if required_bytes > available_bytes {
442 let grow_by = (required_bytes - available_bytes).div_ceil(crate::WASM_PAGE_SIZE_BYTES);
443 memory.grow(grow_by)?;
444 }
445 Ok(Cell::new(memory, encoded).into_memory())
446}
447
448const fn check_bucket_size(
449 actual: u16,
450 requested: MemoryManagerConfig,
451) -> Result<(), RuntimeConstructionError> {
452 if actual != requested.bucket_size_pages() {
453 return Err(RuntimeConstructionError::BucketSizeMismatch {
454 persisted: actual,
455 requested: requested.bucket_size_pages(),
456 });
457 }
458 Ok(())
459}