1mod allocations;
2mod backing;
3mod config;
4mod default;
5mod diagnostics;
6mod error;
7mod layout;
8mod policy;
9
10#[cfg(test)]
11mod allocation_tests;
12#[cfg(test)]
13#[allow(
14 unsafe_code,
15 reason = "exercise raw reads with valid uninitialized destinations"
16)]
17mod read_tests;
18#[cfg(test)]
19mod tests;
20
21pub use allocations::{
22 AllocationBinding, AllocationRangeClaim, MemoryAllocation, MemoryAllocations,
23};
24pub use backing::RuntimeMemory;
25pub use config::MemoryManagerConfig;
26pub use default::{
27 bootstrap_default_memory_manager, bootstrap_default_memory_manager_with_config,
28 bootstrap_default_memory_manager_with_policy, committed_allocations,
29 default_memory_manager_commit_recovery_diagnostic, default_memory_manager_diagnostic_export,
30 default_memory_manager_doctor_report, default_memory_manager_doctor_report_with_policy,
31 default_memory_manager_memory_allocations, is_default_memory_manager_bootstrapped,
32 open_default_memory_manager_memory,
33};
34pub use error::{
35 RuntimeBootstrapError, RuntimeConstructionError, RuntimeDiagnosticError, RuntimeOpenError,
36 RuntimePolicyError, RuntimeStateError,
37};
38pub use layout::MemoryManagerLayoutError;
39
40use self::policy::{RuntimeMemoryManagerPolicy, runtime_bootstrap_error_from_bootstrap};
41use crate::{
42 AllocationBootstrap, AllocationHistory, AllocationLedger, AllocationPolicy,
43 CommittedAllocations, PolicyIdentity, RuntimeBootstrapPolicy, STABLE_CELL_VALUE_OFFSET,
44 StableCellLedgerError, StableCellLedgerRecord, StableKey, registry::SealedDeclarationSnapshot,
45 slot::MEMORY_MANAGER_LEDGER_ID, stable_cell::decode_stable_cell_ledger_record_from_memory,
46};
47use ic_stable_structures::{
48 Cell, Memory, Storable,
49 memory_manager::{MemoryId, MemoryManager},
50};
51
52use std::rc::Rc;
53
54type LedgerCell<M> = Cell<StableCellLedgerRecord, RuntimeMemory<M>>;
55
56enum RuntimeLifecycle {
57 Unbootstrapped,
58 Bootstrapped {
59 committed_allocations: CommittedAllocations,
60 binding: RuntimeBootstrapBinding,
61 },
62}
63
64struct RuntimeBootstrapBinding {
65 declarations: SealedDeclarationSnapshot,
66 policy_identity: PolicyIdentity,
67}
68
69pub struct MemoryRuntime<M: Memory> {
84 memory_manager: MemoryManager<Rc<M>>,
85 backing: Rc<M>,
88 bucket_size_pages: u16,
89 ledger_cell: Option<LedgerCell<M>>,
90 lifecycle: RuntimeLifecycle,
91}
92
93impl<M: Memory> MemoryRuntime<M> {
94 pub fn new(memory: M) -> Result<Self, RuntimeConstructionError> {
112 Self::construct(memory, None)
113 }
114
115 pub fn new_with_config(
118 memory: M,
119 config: MemoryManagerConfig,
120 ) -> Result<Self, RuntimeConstructionError> {
121 Self::construct(memory, Some(config))
122 }
123
124 fn construct(
125 memory: M,
126 requested: Option<MemoryManagerConfig>,
127 ) -> Result<Self, RuntimeConstructionError> {
128 if cfg!(target_endian = "big") {
129 return Err(MemoryManagerLayoutError::UnsupportedByteOrder.into());
130 }
131 let bucket_size_pages = if memory.size() == 0 {
132 requested.unwrap_or_default().bucket_size_pages()
133 } else {
134 let actual = layout::read(&memory)?.bucket_pages;
135 if let Some(config) = requested {
136 check_bucket_size(actual, config)?;
137 }
138 actual
139 };
140 let backing = Rc::new(memory);
141 Ok(Self {
142 memory_manager: MemoryManager::init_with_bucket_size(
143 Rc::clone(&backing),
144 bucket_size_pages,
145 ),
146 backing,
147 bucket_size_pages,
148 ledger_cell: None,
149 lifecycle: RuntimeLifecycle::Unbootstrapped,
150 })
151 }
152
153 #[must_use]
155 pub const fn memory_manager_config(&self) -> MemoryManagerConfig {
156 MemoryManagerConfig::from_validated(self.bucket_size_pages)
158 }
159
160 #[must_use]
162 pub const fn is_bootstrapped(&self) -> bool {
163 matches!(self.lifecycle, RuntimeLifecycle::Bootstrapped { .. })
164 }
165
166 pub fn bootstrap<P: RuntimeBootstrapPolicy>(
175 &mut self,
176 declarations: &SealedDeclarationSnapshot,
177 policy: &P,
178 ) -> Result<&CommittedAllocations, RuntimeBootstrapError<P::Error>> {
179 let policy_identity = policy.runtime_bootstrap_identity()?;
180 let already_bootstrapped = match &self.lifecycle {
181 RuntimeLifecycle::Unbootstrapped => false,
182 RuntimeLifecycle::Bootstrapped { binding, .. } => {
183 binding.validate(declarations, &policy_identity)?;
184 true
185 }
186 };
187 if !already_bootstrapped {
188 self.bootstrap_unbootstrapped(declarations, policy, policy_identity)?;
189 }
190 match &self.lifecycle {
191 RuntimeLifecycle::Bootstrapped {
192 committed_allocations,
193 ..
194 } => Ok(committed_allocations),
195 RuntimeLifecycle::Unbootstrapped => Err(RuntimeBootstrapError::State(
196 RuntimeStateError::InconsistentLifecycle,
197 )),
198 }
199 }
200
201 fn bootstrap_unbootstrapped<P: AllocationPolicy>(
202 &mut self,
203 declarations: &SealedDeclarationSnapshot,
204 policy: &P,
205 policy_identity: PolicyIdentity,
206 ) -> Result<(), RuntimeBootstrapError<P::Error>> {
207 self.initialize_ledger_cell()?;
208 let mut record = self
209 .ledger_cell
210 .as_ref()
211 .map(|cell| cell.get().clone())
212 .ok_or(RuntimeStateError::InconsistentLifecycle)?;
213 let runtime_policy = RuntimeMemoryManagerPolicy {
214 declarations,
215 custom_policy: policy,
216 };
217 let genesis = AllocationLedger::new(0, AllocationHistory::default())?;
218 let commit = AllocationBootstrap::new(record.store_mut())
219 .initialize_validate_and_commit(
220 &genesis,
221 declarations.allocation_snapshot().clone(),
222 &runtime_policy,
223 None,
224 )
225 .map_err(runtime_bootstrap_error_from_bootstrap)?;
226 let (ledger, validated) = commit.into_parts();
227
228 self.persist_ledger_record(record)?;
229 let committed =
230 external_runtime_allocations(validated.confirm_persisted(ledger.current_generation()));
231 self.lifecycle = RuntimeLifecycle::Bootstrapped {
232 committed_allocations: committed,
233 binding: RuntimeBootstrapBinding {
234 declarations: declarations.clone(),
235 policy_identity,
236 },
237 };
238 Ok(())
239 }
240
241 pub const fn committed_allocations(&self) -> Result<&CommittedAllocations, RuntimeOpenError> {
243 match &self.lifecycle {
244 RuntimeLifecycle::Unbootstrapped => Err(RuntimeOpenError::NotBootstrapped),
245 RuntimeLifecycle::Bootstrapped {
246 committed_allocations,
247 ..
248 } => Ok(committed_allocations),
249 }
250 }
251
252 pub fn open_memory(
254 &self,
255 stable_key: &str,
256 expected_id: u8,
257 ) -> Result<RuntimeMemory<M>, RuntimeOpenError> {
258 let key = StableKey::parse(stable_key)?;
259 if crate::is_ic_memory_stable_key(key.as_str()) {
260 return Err(RuntimeOpenError::ReservedStableKey {
261 stable_key: stable_key.to_string(),
262 });
263 }
264 let committed = self.committed_allocations()?;
265 let slot = committed
266 .slot_for(&key)
267 .ok_or_else(|| RuntimeOpenError::StableKeyNotCommitted(stable_key.to_string()))?;
268 let committed_id = slot.memory_manager_id()?;
269 if committed_id != expected_id {
270 return Err(RuntimeOpenError::MemoryIdMismatch {
271 stable_key: stable_key.to_string(),
272 committed_id,
273 requested_id: expected_id,
274 });
275 }
276 Ok(self.memory(expected_id))
277 }
278
279 fn initialize_ledger_cell<P>(&mut self) -> Result<(), RuntimeBootstrapError<P>> {
280 if self.ledger_cell.is_some() {
281 return Ok(());
282 }
283 let memory = self.memory(MEMORY_MANAGER_LEDGER_ID);
284 crate::validate_stable_cell_ledger_memory(&memory)?;
285 ensure_ledger_cell_capacity(&memory, &StableCellLedgerRecord::default())?;
286 self.ledger_cell = Some(Cell::init(memory, StableCellLedgerRecord::default()));
287 Ok(())
288 }
289
290 fn persist_ledger_record<P>(
291 &mut self,
292 record: StableCellLedgerRecord,
293 ) -> Result<(), RuntimeBootstrapError<P>> {
294 let memory = self.memory(MEMORY_MANAGER_LEDGER_ID);
295 ensure_ledger_cell_capacity(&memory, &record)?;
296 let cell = self
297 .ledger_cell
298 .as_mut()
299 .ok_or(RuntimeStateError::InconsistentLifecycle)?;
300 let _previous = cell.set(record);
301 Ok(())
302 }
303
304 fn memory(&self, id: u8) -> RuntimeMemory<M> {
305 RuntimeMemory(self.memory_manager.get(MemoryId::new(id)))
306 }
307
308 fn ledger_record_from_memory(&self) -> Result<StableCellLedgerRecord, StableCellLedgerError> {
309 decode_stable_cell_ledger_record_from_memory(&self.memory(MEMORY_MANAGER_LEDGER_ID))
310 }
311}
312
313impl RuntimeBootstrapBinding {
314 fn validate<P>(
315 &self,
316 declarations: &SealedDeclarationSnapshot,
317 policy_identity: &PolicyIdentity,
318 ) -> Result<(), RuntimeBootstrapError<P>> {
319 if !self.declarations.shares_storage_with(declarations) {
320 return Err(RuntimeBootstrapError::DeclarationSnapshotMismatch);
321 }
322 if &self.policy_identity != policy_identity {
323 return Err(RuntimeBootstrapError::PolicyIdentityMismatch {
324 established: self.policy_identity.clone(),
325 requested: policy_identity.clone(),
326 });
327 }
328 Ok(())
329 }
330}
331
332fn ensure_ledger_cell_capacity<M: Memory, P>(
333 memory: &RuntimeMemory<M>,
334 record: &StableCellLedgerRecord,
335) -> Result<(), RuntimeBootstrapError<P>> {
336 let value_size = record.to_bytes().len();
337 let value_size_u32 = u32::try_from(value_size)
338 .map_err(|_| RuntimeBootstrapError::StableCellLedgerWriteTooLarge { value_size })?;
339 let required_bytes = STABLE_CELL_VALUE_OFFSET
340 .checked_add(u64::from(value_size_u32))
341 .ok_or(RuntimeBootstrapError::StableCellLedgerWriteTooLarge { value_size })?;
342 let available_bytes = memory.size().saturating_mul(crate::WASM_PAGE_SIZE_BYTES);
343 if required_bytes <= available_bytes {
344 return Ok(());
345 }
346 let grow_by = required_bytes
347 .saturating_sub(available_bytes)
348 .div_ceil(crate::WASM_PAGE_SIZE_BYTES);
349 if memory.grow(grow_by) < 0 {
350 return Err(RuntimeBootstrapError::StableCellLedgerWriteTooLarge { value_size });
351 }
352 Ok(())
353}
354
355fn external_runtime_allocations(committed: CommittedAllocations) -> CommittedAllocations {
356 committed.without_stable_key_prefix(crate::IC_MEMORY_STABLE_KEY_PREFIX)
357}
358
359const fn check_bucket_size(
360 actual: u16,
361 requested: MemoryManagerConfig,
362) -> Result<(), RuntimeConstructionError> {
363 if actual != requested.bucket_size_pages() {
364 return Err(RuntimeConstructionError::BucketSizeMismatch {
365 persisted: actual,
366 requested: requested.bucket_size_pages(),
367 });
368 }
369 Ok(())
370}