ic_memory/runtime/construction.rs
1//! Backing custody and manager construction before bootstrap authority exists.
2use super::{
3 MemoryBacking, MemoryManagerConfig, MemoryManagerLayoutError, MemoryRuntime,
4 RuntimeConstructionError, RuntimeGrowError, RuntimeLifecycle, backing, custody, layout,
5};
6use ic_platform::stable_structures::{Memory, memory_manager::MemoryManager};
7use std::rc::Rc;
8
9impl<M: Memory> MemoryRuntime<M> {
10 /// Construct an unbootstrapped runtime without overwriting foreign memory.
11 ///
12 /// Empty backing memory is initialized as an
13 /// [`MemoryManager`]. Nonempty memory must pass bounded
14 /// validation of the current manager header, bucket table, and extents; otherwise
15 /// construction returns a typed error before the manager can
16 /// write its header or allocation table. A pre-grown blank memory is
17 /// nonempty and is therefore rejected rather than assumed disposable.
18 ///
19 /// # Errors
20 ///
21 /// Returns [`RuntimeConstructionError::BackingAlreadyClaimed`] before any
22 /// backing IO if another runtime or handle retains custody.
23 /// Returns [`RuntimeConstructionError::ForeignMemory`] for nonempty memory
24 /// without `MemoryManager` magic, or
25 /// [`RuntimeConstructionError::UnsupportedMemoryManagerVersion`] when the
26 /// magic is recognized but the layout version is not current. Invalid
27 /// metadata returns [`RuntimeConstructionError::Layout`]. Refused fresh
28 /// metadata growth returns [`RuntimeConstructionError::Growth`] without
29 /// writes, allowing the same backing memory to be retried. Reopening honors
30 /// the actual persisted bucket size; only fresh memory uses 128 pages.
31 pub fn new(memory: M) -> Result<Self, RuntimeConstructionError>
32 where
33 M: MemoryBacking,
34 {
35 Self::construct(memory, None)
36 }
37
38 /// Construct with an explicit immutable bucket policy. Existing memory must
39 /// match exactly; mismatches fail before manager initialization or writes.
40 ///
41 /// # Errors
42 ///
43 /// Returns the construction errors described by [`Self::new`], or
44 /// [`RuntimeConstructionError::BucketSizeMismatch`] when existing geometry
45 /// differs from `config`.
46 pub fn new_with_config(
47 memory: M,
48 config: MemoryManagerConfig,
49 ) -> Result<Self, RuntimeConstructionError>
50 where
51 M: MemoryBacking,
52 {
53 Self::construct(memory, Some(config))
54 }
55
56 fn construct(
57 memory: M,
58 requested: Option<MemoryManagerConfig>,
59 ) -> Result<Self, RuntimeConstructionError>
60 where
61 M: MemoryBacking,
62 {
63 // Locals drop in reverse order. Establish backing ownership before its
64 // claim so refusal/unwind also retires the identity before freeing it.
65 let backing = Rc::new(memory);
66 let claim = custody::BackingClaim::acquire(backing.as_ref())?;
67 if cfg!(target_endian = "big") {
68 return Err(MemoryManagerLayoutError::UnsupportedByteOrder.into());
69 }
70 let bucket_size_pages = if backing.size() == 0 {
71 // Reserve the metadata page before the dependency writes its header.
72 // This fresh allocation is owned here; caller-supplied nonempty
73 // memory still passes the layout checks below before any writes.
74 if backing.grow(1) == -1 {
75 return Err(RuntimeGrowError::BackingRefused {
76 additional_pages: 1,
77 }
78 .into());
79 }
80 requested.unwrap_or_default().bucket_size_pages()
81 } else {
82 let measured = layout::read(backing.as_ref())?;
83 let actual = measured.bucket_pages;
84 if let Some(config) = requested {
85 check_bucket_size(actual, config)?;
86 }
87 actual
88 };
89 let memory_manager =
90 MemoryManager::init_with_bucket_size(Rc::clone(&backing), bucket_size_pages);
91 let authority =
92 std::cell::RefCell::new(layout::ManagerAuthority::capture(backing.as_ref()));
93 let growth = Rc::new(backing::GrowthState {
94 _claim: claim,
95 backing: Rc::clone(&backing),
96 bucket_size_pages,
97 authority,
98 open_slots: [const { std::cell::Cell::new(0) }; 4],
99 });
100 Ok(Self {
101 memory_manager,
102 growth,
103 lifecycle: RuntimeLifecycle::Unbootstrapped,
104 })
105 }
106
107 /// Return the immutable bucket configuration bound to this runtime's manager.
108 #[must_use]
109 pub fn memory_manager_config(&self) -> MemoryManagerConfig {
110 // Construction has already validated the nonzero persisted setting.
111 MemoryManagerConfig::from_validated(self.growth.bucket_size_pages)
112 }
113}
114
115const fn check_bucket_size(
116 actual: u16,
117 requested: MemoryManagerConfig,
118) -> Result<(), RuntimeConstructionError> {
119 if actual != requested.bucket_size_pages() {
120 return Err(RuntimeConstructionError::BucketSizeMismatch {
121 persisted: actual,
122 requested: requested.bucket_size_pages(),
123 });
124 }
125 Ok(())
126}