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, AllocationRangeClaim, MemoryAllocation, MemoryAllocationSummary,
36 MemoryAllocations, 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, open_default_memory_manager_memory_by_key,
48 verify_default_memory_manager_authority,
49};
50pub use error::{
51 MemoryResolutionError, RuntimeBootstrapError, RuntimeConstructionError, RuntimeDiagnosticError,
52 RuntimeGrowError, RuntimeOpenError, RuntimePolicyError, RuntimeStateError,
53};
54pub use layout::MemoryManagerLayoutError;
55pub use policy::GenericRangePolicy;
56
57use self::policy::{RuntimeMemoryManagerPolicy, runtime_bootstrap_error_from_bootstrap};
58use crate::{
59 AllocationBootstrap, AllocationHistory, AllocationLedger, CommittedAllocations, PolicyIdentity,
60 RuntimeBootstrapPolicy, STABLE_CELL_VALUE_OFFSET, StableCellLedgerError,
61 StableCellLedgerRecord, StableKey, registry::SealedDeclarationSnapshot,
62 slot::MEMORY_MANAGER_LEDGER_ID, stable_cell::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
71type LedgerCell<M> = Cell<StableCellLedgerRecord, RuntimeMemory<M>>;
72
73enum RuntimeLifecycle {
74 Unbootstrapped,
75 Bootstrapped {
76 committed_allocations: CommittedAllocations,
77 binding: RuntimeBootstrapBinding,
78 },
79}
80
81struct RuntimeBootstrapBinding {
82 source: SealedDeclarationSnapshot,
83 declarations: SealedDeclarationSnapshot,
84 policy_identity: PolicyIdentity,
85}
86
87pub struct MemoryRuntime<M: Memory> {
102 memory_manager: MemoryManager<Rc<M>>,
103 backing: Rc<M>,
107 bucket_size_pages: u16,
108 growth: Rc<backing::GrowthState<M>>,
109 ledger_cell: Option<LedgerCell<M>>,
110 lifecycle: RuntimeLifecycle,
111}
112
113impl<M: Memory> MemoryRuntime<M> {
114 pub fn new(memory: M) -> Result<Self, RuntimeConstructionError> {
134 Self::construct(memory, None)
135 }
136
137 pub fn new_with_config(
146 memory: M,
147 config: MemoryManagerConfig,
148 ) -> Result<Self, RuntimeConstructionError> {
149 Self::construct(memory, Some(config))
150 }
151
152 fn construct(
153 memory: M,
154 requested: Option<MemoryManagerConfig>,
155 ) -> Result<Self, RuntimeConstructionError> {
156 if cfg!(target_endian = "big") {
157 return Err(MemoryManagerLayoutError::UnsupportedByteOrder.into());
158 }
159 let (bucket_size_pages, allocated_buckets) = if memory.size() == 0 {
160 if memory.grow(1) == -1 {
164 return Err(RuntimeGrowError::BackingRefused {
165 additional_pages: 1,
166 }
167 .into());
168 }
169 (requested.unwrap_or_default().bucket_size_pages(), 0)
170 } else {
171 let measured = layout::read(&memory)?;
172 let actual = measured.bucket_pages;
173 if let Some(config) = requested {
174 check_bucket_size(actual, config)?;
175 }
176 (actual, measured.allocated_buckets)
177 };
178 let backing = Rc::new(memory);
179 let growth = Rc::new(backing::GrowthState {
180 backing: Rc::clone(&backing),
181 bucket_size_pages,
182 allocated_buckets: std::cell::RefCell::new(allocated_buckets),
183 });
184 Ok(Self {
185 memory_manager: MemoryManager::init_with_bucket_size(
186 Rc::clone(&backing),
187 bucket_size_pages,
188 ),
189 backing,
190 bucket_size_pages,
191 growth,
192 ledger_cell: None,
193 lifecycle: RuntimeLifecycle::Unbootstrapped,
194 })
195 }
196
197 #[must_use]
199 pub const fn memory_manager_config(&self) -> MemoryManagerConfig {
200 MemoryManagerConfig::from_validated(self.bucket_size_pages)
202 }
203
204 #[must_use]
206 pub const fn is_bootstrapped(&self) -> bool {
207 matches!(self.lifecycle, RuntimeLifecycle::Bootstrapped { .. })
208 }
209
210 pub fn bootstrap<P: RuntimeBootstrapPolicy>(
220 &mut self,
221 declarations: &SealedDeclarationSnapshot,
222 policy: &P,
223 ) -> Result<&CommittedAllocations, RuntimeBootstrapError<P::Error>> {
224 let policy_identity = policy.runtime_bootstrap_identity()?;
225 let already_bootstrapped = match &self.lifecycle {
226 RuntimeLifecycle::Unbootstrapped => false,
227 RuntimeLifecycle::Bootstrapped { binding, .. } => {
228 binding.validate(declarations, &policy_identity)?;
229 true
230 }
231 };
232 if !already_bootstrapped {
233 self.bootstrap_unbootstrapped(declarations, policy, policy_identity)?;
234 }
235 match &self.lifecycle {
236 RuntimeLifecycle::Bootstrapped {
237 committed_allocations,
238 ..
239 } => Ok(committed_allocations),
240 RuntimeLifecycle::Unbootstrapped => Err(RuntimeBootstrapError::State(
241 RuntimeStateError::InconsistentLifecycle,
242 )),
243 }
244 }
245
246 fn bootstrap_unbootstrapped<P: RuntimeBootstrapPolicy>(
247 &mut self,
248 declarations: &SealedDeclarationSnapshot,
249 policy: &P,
250 policy_identity: PolicyIdentity,
251 ) -> Result<(), RuntimeBootstrapError<P::Error>> {
252 self.initialize_ledger_cell()?;
253 let mut record = self
254 .ledger_cell
255 .as_ref()
256 .map(|cell| cell.get().clone())
257 .ok_or(RuntimeStateError::InconsistentLifecycle)?;
258 let genesis = AllocationLedger::new(0, AllocationHistory::default())?;
259 let recovered = record.store_mut().recover_or_initialize(&genesis)?;
260 let mut admission = BootstrapAdmission::new(recovered.ledger(), declarations);
261 let preparation = policy.prepare_bootstrap(&mut admission);
262 let historical = admission.complete()?;
263 preparation.map_err(RuntimeBootstrapError::AdmissionPolicy)?;
264 let resolved = declarations.resolve(recovered.ledger(), historical)?;
265 let runtime_policy = RuntimeMemoryManagerPolicy {
266 declarations: &resolved,
267 custom_policy: policy,
268 };
269 let commit = AllocationBootstrap::new(record.store_mut())
270 .validate_against(
271 recovered,
272 resolved.allocation_snapshot().clone(),
273 &runtime_policy,
274 None,
275 )
276 .map_err(runtime_bootstrap_error_from_bootstrap)?;
277 let (ledger, validated) = commit.into_parts();
278
279 self.persist_ledger_record(record)?;
280 let committed =
281 external_runtime_allocations(validated.confirm_persisted(ledger.current_generation()));
282 self.lifecycle = RuntimeLifecycle::Bootstrapped {
283 committed_allocations: committed,
284 binding: RuntimeBootstrapBinding {
285 source: declarations.clone(),
286 declarations: resolved,
287 policy_identity,
288 },
289 };
290 Ok(())
291 }
292
293 pub const fn committed_allocations(&self) -> Result<&CommittedAllocations, RuntimeOpenError> {
295 match &self.lifecycle {
296 RuntimeLifecycle::Unbootstrapped => Err(RuntimeOpenError::NotBootstrapped),
297 RuntimeLifecycle::Bootstrapped {
298 committed_allocations,
299 ..
300 } => Ok(committed_allocations),
301 }
302 }
303
304 pub fn open_memory_by_key(
306 &self,
307 stable_key: &str,
308 ) -> Result<RuntimeMemory<M>, RuntimeOpenError> {
309 Ok(self.memory(self.memory_id(stable_key)?))
310 }
311
312 pub fn open_memory(
314 &self,
315 stable_key: &str,
316 expected_id: u8,
317 ) -> Result<RuntimeMemory<M>, RuntimeOpenError> {
318 let committed_id = self.memory_id(stable_key)?;
319 if committed_id != expected_id {
320 return Err(RuntimeOpenError::MemoryIdMismatch {
321 stable_key: stable_key.to_string(),
322 committed_id,
323 requested_id: expected_id,
324 });
325 }
326 Ok(self.memory(committed_id))
327 }
328
329 pub fn memory_id(&self, stable_key: &str) -> Result<u8, RuntimeOpenError> {
332 let key = StableKey::parse(stable_key)?;
333 if crate::is_ic_memory_stable_key(key.as_str()) {
334 return Err(RuntimeOpenError::ReservedStableKey {
335 stable_key: stable_key.to_string(),
336 });
337 }
338 let slot = self
339 .committed_allocations()?
340 .slot_for(&key)
341 .ok_or_else(|| RuntimeOpenError::StableKeyNotCommitted(stable_key.to_string()))?;
342 Ok(slot.memory_manager_id()?)
343 }
344
345 fn initialize_ledger_cell<P>(&mut self) -> Result<(), RuntimeBootstrapError<P>> {
346 if self.ledger_cell.is_some() {
347 return Ok(());
348 }
349 let memory = self.memory(MEMORY_MANAGER_LEDGER_ID);
350 crate::validate_stable_cell_ledger_memory(&memory)?;
351 ensure_ledger_cell_capacity(&memory, &StableCellLedgerRecord::default())?;
352 self.ledger_cell = Some(Cell::init(memory, StableCellLedgerRecord::default()));
353 Ok(())
354 }
355
356 fn persist_ledger_record<P>(
357 &mut self,
358 record: StableCellLedgerRecord,
359 ) -> Result<(), RuntimeBootstrapError<P>> {
360 let memory = self.memory(MEMORY_MANAGER_LEDGER_ID);
361 ensure_ledger_cell_capacity(&memory, &record)?;
362 let cell = self
363 .ledger_cell
364 .as_mut()
365 .ok_or(RuntimeStateError::InconsistentLifecycle)?;
366 let _previous = cell.set(record);
367 Ok(())
368 }
369
370 fn memory(&self, id: u8) -> RuntimeMemory<M> {
371 RuntimeMemory {
372 memory: self.memory_manager.get(MemoryId::new(id)),
373 growth: Rc::clone(&self.growth),
374 }
375 }
376
377 fn ledger_record_from_memory(&self) -> Result<StableCellLedgerRecord, StableCellLedgerError> {
378 decode_stable_cell_ledger_record_from_memory(&self.memory(MEMORY_MANAGER_LEDGER_ID))
379 }
380}
381
382impl RuntimeBootstrapBinding {
383 fn validate<P>(
384 &self,
385 declarations: &SealedDeclarationSnapshot,
386 policy_identity: &PolicyIdentity,
387 ) -> Result<(), RuntimeBootstrapError<P>> {
388 if &self.source != declarations {
389 return Err(RuntimeBootstrapError::DeclarationSnapshotMismatch);
390 }
391 if &self.policy_identity != policy_identity {
392 return Err(RuntimeBootstrapError::PolicyIdentityMismatch {
393 established: self.policy_identity.clone(),
394 requested: policy_identity.clone(),
395 });
396 }
397 Ok(())
398 }
399}
400
401fn ensure_ledger_cell_capacity<M: Memory, P>(
402 memory: &RuntimeMemory<M>,
403 record: &StableCellLedgerRecord,
404) -> Result<(), RuntimeBootstrapError<P>> {
405 let value_size = record.encoded_size();
406 if value_size > crate::constants::MAX_LEDGER_RECORD_BYTES {
407 return Err(RuntimeBootstrapError::StableCellLedgerWriteTooLarge { value_size });
408 }
409 let value_size_u32 = u32::try_from(value_size)
410 .map_err(|_| RuntimeBootstrapError::StableCellLedgerWriteTooLarge { value_size })?;
411 let required_bytes = STABLE_CELL_VALUE_OFFSET
412 .checked_add(u64::from(value_size_u32))
413 .ok_or(RuntimeBootstrapError::StableCellLedgerWriteTooLarge { value_size })?;
414 let available_bytes = memory.size().saturating_mul(crate::WASM_PAGE_SIZE_BYTES);
415 if required_bytes <= available_bytes {
416 return Ok(());
417 }
418 let grow_by = required_bytes
419 .saturating_sub(available_bytes)
420 .div_ceil(crate::WASM_PAGE_SIZE_BYTES);
421 memory.grow(grow_by)?;
422 Ok(())
423}
424
425fn external_runtime_allocations(committed: CommittedAllocations) -> CommittedAllocations {
426 committed.without_stable_key_prefix(crate::IC_MEMORY_STABLE_KEY_PREFIX)
427}
428
429const fn check_bucket_size(
430 actual: u16,
431 requested: MemoryManagerConfig,
432) -> Result<(), RuntimeConstructionError> {
433 if actual != requested.bucket_size_pages() {
434 return Err(RuntimeConstructionError::BucketSizeMismatch {
435 persisted: actual,
436 requested: requested.bucket_size_pages(),
437 });
438 }
439 Ok(())
440}