Skip to main content

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}