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, RecoveredAllocationMetadata};
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 self::policy::RuntimeMemoryManagerPolicy;
58use crate::{
59 AllocationLedger, CommittedAllocations, PolicyIdentity, RuntimeBootstrapPolicy,
60 STABLE_CELL_VALUE_OFFSET, StableCellLedgerError, StableCellLedgerRecord,
61 requests::SealedDeclarationSnapshot,
62 slot::MEMORY_MANAGER_LEDGER_ID,
63 stable_cell::{EncodedLedgerRecord, decode_stable_cell_ledger_record_from_memory},
64};
65use ic_stable_structures::{
66 Cell, Memory,
67 memory_manager::{MemoryId, MemoryManager},
68};
69
70use std::rc::Rc;
71
72enum RuntimeLifecycle {
73 Unbootstrapped,
74 Bootstrapped {
75 committed_allocations: CommittedAllocations,
76 binding: RuntimeBootstrapBinding,
77 },
78}
79
80struct RuntimeBootstrapBinding {
81 source: SealedDeclarationSnapshot,
82 policy_identity: PolicyIdentity,
83 pool: crate::MemoryAllocationPool,
84}
85
86pub 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 });
183 Ok(Self {
184 memory_manager: MemoryManager::init_with_bucket_size(
185 Rc::clone(&backing),
186 bucket_size_pages,
187 ),
188 growth,
189 lifecycle: RuntimeLifecycle::Unbootstrapped,
190 })
191 }
192
193 #[must_use]
195 pub fn memory_manager_config(&self) -> MemoryManagerConfig {
196 MemoryManagerConfig::from_validated(self.growth.bucket_size_pages)
198 }
199
200 #[must_use]
202 pub const fn is_bootstrapped(&self) -> bool {
203 matches!(self.lifecycle, RuntimeLifecycle::Bootstrapped { .. })
204 }
205
206 pub fn bootstrap<P: RuntimeBootstrapPolicy>(
221 &mut self,
222 declarations: &SealedDeclarationSnapshot,
223 pool: &crate::MemoryAllocationPool,
224 policy: &P,
225 ) -> Result<&CommittedAllocations, RuntimeBootstrapError<P::Error>> {
226 let policy_identity = policy.runtime_bootstrap_identity()?;
227 match &self.lifecycle {
228 RuntimeLifecycle::Unbootstrapped => {
229 self.bootstrap_unbootstrapped(declarations, pool, policy, policy_identity)?;
230 }
231 RuntimeLifecycle::Bootstrapped { binding, .. } => {
232 binding.validate(declarations, pool, &policy_identity)?;
233 }
234 }
235 match &self.lifecycle {
236 RuntimeLifecycle::Bootstrapped {
237 committed_allocations,
238 ..
239 } => Ok(committed_allocations),
240 RuntimeLifecycle::Unbootstrapped => {
241 unreachable!("successful bootstrap publishes committed allocations")
242 }
243 }
244 }
245
246 fn validate_pool_custody(
247 &self,
248 occupied: &[bool; crate::constants::MAX_ALLOCATIONS],
249 pool: &crate::MemoryAllocationPool,
250 ) -> Result<(), super::MemoryResolutionError> {
251 for id in 0..crate::MEMORY_MANAGER_INVALID_ID {
252 if pool.contains(id) && self.memory_size_pages(id) != 0 && !occupied[usize::from(id)] {
253 return Err(super::MemoryResolutionError::UnmanagedAllocation { id });
254 }
255 }
256 Ok(())
257 }
258
259 fn bootstrap_unbootstrapped<P: RuntimeBootstrapPolicy>(
260 &mut self,
261 declarations: &SealedDeclarationSnapshot,
262 pool: &crate::MemoryAllocationPool,
263 policy: &P,
264 policy_identity: PolicyIdentity,
265 ) -> Result<(), RuntimeBootstrapError<P::Error>> {
266 let memory = self.memory(MEMORY_MANAGER_LEDGER_ID);
267 let mut record = decode_stable_cell_ledger_record_from_memory(&memory)?;
268 let genesis = AllocationLedger::empty_genesis();
269 let recovered = record.store_mut().recover_or_initialize(&genesis)?;
270 let occupied = recovered.ledger().occupied_slots();
271 self.validate_pool_custody(&occupied, pool)?;
272 if memory.size() == 0 {
273 write_ledger_cell(memory.clone(), &StableCellLedgerRecord::default())?;
276 }
277 let mut admission = BootstrapAdmission::new(recovered.ledger(), declarations, pool);
278 let preparation = policy.prepare_bootstrap(&mut admission);
279 let historical = admission.complete()?;
280 preparation.map_err(RuntimeBootstrapError::AdmissionPolicy)?;
281 let resolved = declarations.resolve(recovered.ledger(), &occupied, historical, pool)?;
282 let runtime_policy = RuntimeMemoryManagerPolicy {
283 custom_policy: policy,
284 };
285 let commit = crate::runtime::bootstrap::prepare_commit(
286 record.store_mut(),
287 recovered,
288 resolved,
289 &runtime_policy,
290 )?;
291 write_ledger_cell(memory, &record)?;
292 let committed = commit.confirm_persisted();
293 self.lifecycle = RuntimeLifecycle::Bootstrapped {
294 committed_allocations: committed,
295 binding: RuntimeBootstrapBinding {
296 source: declarations.clone(),
297 policy_identity,
298 pool: pool.clone(),
299 },
300 };
301 Ok(())
302 }
303
304 pub const fn committed_allocations(&self) -> Result<&CommittedAllocations, RuntimeOpenError> {
306 match &self.lifecycle {
307 RuntimeLifecycle::Unbootstrapped => Err(RuntimeOpenError::NotBootstrapped),
308 RuntimeLifecycle::Bootstrapped {
309 committed_allocations,
310 ..
311 } => Ok(committed_allocations),
312 }
313 }
314
315 pub fn open_memory(&self, stable_key: &str) -> Result<RuntimeMemory<M>, RuntimeOpenError> {
317 Ok(self.memory(self.memory_id(stable_key)?))
318 }
319
320 pub fn memory_id(&self, stable_key: &str) -> Result<u8, RuntimeOpenError> {
323 crate::key::validate(stable_key)?;
324 if crate::is_ic_memory_stable_key(stable_key) {
325 return Err(RuntimeOpenError::ReservedStableKey {
326 stable_key: stable_key.to_string(),
327 });
328 }
329 let declaration = crate::capability::declaration_for_key(
330 self.committed_allocations()?.declarations(),
331 stable_key,
332 )
333 .ok_or_else(|| RuntimeOpenError::StableKeyNotCommitted(stable_key.to_string()))?;
334 Ok(declaration.slot().id())
335 }
336
337 fn memory(&self, id: u8) -> RuntimeMemory<M> {
338 RuntimeMemory {
339 memory: self.memory_manager.get(MemoryId::new(id)),
340 growth: Rc::clone(&self.growth),
341 }
342 }
343
344 fn memory_size_pages(&self, id: u8) -> u64 {
346 self.memory_manager.get(MemoryId::new(id)).size()
347 }
348
349 fn ledger_record_from_memory(&self) -> Result<StableCellLedgerRecord, StableCellLedgerError> {
350 let memory = self
353 .memory_manager
354 .get(MemoryId::new(MEMORY_MANAGER_LEDGER_ID));
355 decode_stable_cell_ledger_record_from_memory(&memory)
356 }
357}
358
359impl RuntimeBootstrapBinding {
360 fn validate<P>(
361 &self,
362 declarations: &SealedDeclarationSnapshot,
363 pool: &crate::MemoryAllocationPool,
364 policy_identity: &PolicyIdentity,
365 ) -> Result<(), RuntimeBootstrapError<P>> {
366 if &self.source != declarations {
367 return Err(RuntimeBootstrapError::DeclarationSnapshotMismatch);
368 }
369 if &self.pool != pool {
370 return Err(RuntimeBootstrapError::AllocationPoolMismatch);
371 }
372 if &self.policy_identity != policy_identity {
373 return Err(RuntimeBootstrapError::PolicyIdentityMismatch {
374 established: self.policy_identity.clone(),
375 requested: policy_identity.clone(),
376 });
377 }
378 Ok(())
379 }
380}
381
382fn write_ledger_cell<M: Memory, P>(
383 memory: RuntimeMemory<M>,
384 record: &StableCellLedgerRecord,
385) -> Result<(), RuntimeBootstrapError<P>> {
386 let encoded = EncodedLedgerRecord::new(record).map_err(|value_size| {
387 RuntimeBootstrapError::StableCellLedgerWriteTooLarge { value_size }
388 })?;
389 let value_size = encoded.len();
390 let required_bytes = STABLE_CELL_VALUE_OFFSET + value_size as u64;
392 let available_bytes = memory.size().saturating_mul(crate::WASM_PAGE_SIZE_BYTES);
393 if required_bytes > available_bytes {
394 let grow_by = (required_bytes - available_bytes).div_ceil(crate::WASM_PAGE_SIZE_BYTES);
395 memory.grow(grow_by)?;
396 }
397 drop(Cell::new(memory, encoded));
398 Ok(())
399}
400
401const fn check_bucket_size(
402 actual: u16,
403 requested: MemoryManagerConfig,
404) -> Result<(), RuntimeConstructionError> {
405 if actual != requested.bucket_size_pages() {
406 return Err(RuntimeConstructionError::BucketSizeMismatch {
407 persisted: actual,
408 requested: requested.bucket_size_pages(),
409 });
410 }
411 Ok(())
412}