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, AllocationLedger, CommittedAllocations, PolicyIdentity,
60 RuntimeBootstrapPolicy, STABLE_CELL_VALUE_OFFSET, StableCellLedgerError,
61 StableCellLedgerRecord, registry::SealedDeclarationSnapshot, slot::MEMORY_MANAGER_LEDGER_ID,
62 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
71enum RuntimeLifecycle {
72 Unbootstrapped,
73 Bootstrapped {
74 committed_allocations: CommittedAllocations,
75 binding: RuntimeBootstrapBinding,
76 },
77}
78
79struct RuntimeBootstrapBinding {
80 source: SealedDeclarationSnapshot,
81 declarations: SealedDeclarationSnapshot,
82 policy_identity: PolicyIdentity,
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 policy: &P,
223 ) -> Result<&CommittedAllocations, RuntimeBootstrapError<P::Error>> {
224 let policy_identity = policy.runtime_bootstrap_identity()?;
225 match &self.lifecycle {
226 RuntimeLifecycle::Unbootstrapped => {
227 self.bootstrap_unbootstrapped(declarations, policy, policy_identity)?;
228 }
229 RuntimeLifecycle::Bootstrapped { binding, .. } => {
230 binding.validate(declarations, &policy_identity)?;
231 }
232 }
233 match &self.lifecycle {
234 RuntimeLifecycle::Bootstrapped {
235 committed_allocations,
236 ..
237 } => Ok(committed_allocations),
238 RuntimeLifecycle::Unbootstrapped => {
239 unreachable!("successful bootstrap publishes committed allocations")
240 }
241 }
242 }
243
244 fn bootstrap_unbootstrapped<P: RuntimeBootstrapPolicy>(
245 &mut self,
246 declarations: &SealedDeclarationSnapshot,
247 policy: &P,
248 policy_identity: PolicyIdentity,
249 ) -> Result<(), RuntimeBootstrapError<P::Error>> {
250 let memory = self.memory(MEMORY_MANAGER_LEDGER_ID);
251 let mut record = decode_stable_cell_ledger_record_from_memory(&memory)?;
252 if memory.size() == 0 {
253 ensure_ledger_cell_capacity(&memory, &record)?;
256 drop(Cell::new(memory.clone(), StableCellLedgerRecord::default()));
257 }
258 let genesis = AllocationLedger::empty_genesis();
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 ensure_ledger_cell_capacity(&memory, &record)?;
278 drop(Cell::new(memory, record));
279 let committed = commit.confirm_persisted().into_application_allocations();
280 self.lifecycle = RuntimeLifecycle::Bootstrapped {
281 committed_allocations: committed,
282 binding: RuntimeBootstrapBinding {
283 source: declarations.clone(),
284 declarations: resolved,
285 policy_identity,
286 },
287 };
288 Ok(())
289 }
290
291 pub const fn committed_allocations(&self) -> Result<&CommittedAllocations, RuntimeOpenError> {
293 match &self.lifecycle {
294 RuntimeLifecycle::Unbootstrapped => Err(RuntimeOpenError::NotBootstrapped),
295 RuntimeLifecycle::Bootstrapped {
296 committed_allocations,
297 ..
298 } => Ok(committed_allocations),
299 }
300 }
301
302 pub fn open_memory_by_key(
304 &self,
305 stable_key: &str,
306 ) -> Result<RuntimeMemory<M>, RuntimeOpenError> {
307 Ok(self.memory(self.memory_id(stable_key)?))
308 }
309
310 pub fn open_memory(
312 &self,
313 stable_key: &str,
314 expected_id: u8,
315 ) -> Result<RuntimeMemory<M>, RuntimeOpenError> {
316 let committed_id = self.memory_id(stable_key)?;
317 if committed_id != expected_id {
318 return Err(RuntimeOpenError::MemoryIdMismatch {
319 stable_key: stable_key.to_string(),
320 committed_id,
321 requested_id: expected_id,
322 });
323 }
324 Ok(self.memory(committed_id))
325 }
326
327 pub fn memory_id(&self, stable_key: &str) -> Result<u8, RuntimeOpenError> {
330 crate::key::validate(stable_key)?;
331 if crate::is_ic_memory_stable_key(stable_key) {
332 return Err(RuntimeOpenError::ReservedStableKey {
333 stable_key: stable_key.to_string(),
334 });
335 }
336 let slot = crate::capability::slot_for_key(
337 self.committed_allocations()?.declarations(),
338 stable_key,
339 )
340 .ok_or_else(|| RuntimeOpenError::StableKeyNotCommitted(stable_key.to_string()))?;
341 Ok(slot.id())
342 }
343
344 fn memory(&self, id: u8) -> RuntimeMemory<M> {
345 RuntimeMemory {
346 memory: self.memory_manager.get(MemoryId::new(id)),
347 growth: Rc::clone(&self.growth),
348 }
349 }
350
351 fn memory_size_pages(&self, id: u8) -> u64 {
353 self.memory_manager.get(MemoryId::new(id)).size()
354 }
355
356 fn ledger_record_from_memory(&self) -> Result<StableCellLedgerRecord, StableCellLedgerError> {
357 decode_stable_cell_ledger_record_from_memory(&self.memory(MEMORY_MANAGER_LEDGER_ID))
358 }
359}
360
361impl RuntimeBootstrapBinding {
362 fn validate<P>(
363 &self,
364 declarations: &SealedDeclarationSnapshot,
365 policy_identity: &PolicyIdentity,
366 ) -> Result<(), RuntimeBootstrapError<P>> {
367 if &self.source != declarations {
368 return Err(RuntimeBootstrapError::DeclarationSnapshotMismatch);
369 }
370 if &self.policy_identity != policy_identity {
371 return Err(RuntimeBootstrapError::PolicyIdentityMismatch {
372 established: self.policy_identity.clone(),
373 requested: policy_identity.clone(),
374 });
375 }
376 Ok(())
377 }
378}
379
380fn ensure_ledger_cell_capacity<M: Memory, P>(
381 memory: &RuntimeMemory<M>,
382 record: &StableCellLedgerRecord,
383) -> Result<(), RuntimeBootstrapError<P>> {
384 let value_size = record.encoded_size();
385 if value_size > crate::constants::MAX_LEDGER_RECORD_BYTES {
386 return Err(RuntimeBootstrapError::StableCellLedgerWriteTooLarge { value_size });
387 }
388 let required_bytes = STABLE_CELL_VALUE_OFFSET + value_size as u64;
390 let available_bytes = memory.size().saturating_mul(crate::WASM_PAGE_SIZE_BYTES);
391 if required_bytes <= available_bytes {
392 return Ok(());
393 }
394 let grow_by = (required_bytes - available_bytes).div_ceil(crate::WASM_PAGE_SIZE_BYTES);
395 memory.grow(grow_by)?;
396 Ok(())
397}
398
399const fn check_bucket_size(
400 actual: u16,
401 requested: MemoryManagerConfig,
402) -> Result<(), RuntimeConstructionError> {
403 if actual != requested.bucket_size_pages() {
404 return Err(RuntimeConstructionError::BucketSizeMismatch {
405 persisted: actual,
406 requested: requested.bucket_size_pages(),
407 });
408 }
409 Ok(())
410}