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