Skip to main content

ic_storage/adapter/
region.rs

1//! Operation-local collection access within a retained allocation region.
2use super::bridge::{self, Codec, TreeMemory};
3use super::error::{CollectionError as Error, Result as CollectionResult, ensure};
4use super::{Memory, Storable};
5use crate::DefaultMemoryImpl;
6use crate::{RuntimeGrowError, RuntimeMemory, WASM_PAGE_SIZE_BYTES as PAGE_BYTES};
7use std::{marker::PhantomData, ops::Range, ops::RangeBounds, rc::Rc};
8
9const MAX_PAGES: u64 = 4_194_304;
10
11///
12/// `RegionGrowError`
13///
14/// Failure to reserve a governed region's virtual extent, before any writes.
15///
16
17#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
18pub enum RegionGrowError {
19    /// The requested relative extent exceeds the selected region.
20    #[error("requested region extent {minimum} exceeds {maximum} pages")]
21    LimitExceeded {
22        /// Requested relative page count.
23        minimum: u64,
24        /// Maximum relative page count.
25        maximum: u64,
26    },
27    /// Storage refused to grow the containing allocation.
28    #[error("{0}")]
29    Runtime(#[source] RuntimeGrowError),
30}
31
32/// A B-tree view inside one existing, governed allocation lease.
33///
34/// The caller retains its `Rc<RuntimeMemory>` for disjoint application frames.
35/// Each collection operation loads the current owned tree root and allocator;
36/// multiple views may interleave operations without retaining stale roots.
37/// Key scans return owned snapshots, so no cached tree or iterator escapes.
38/// Applications own namespace bounds, codec admission and recovery policy.
39///
40/// Construction performs no IO. Inspect retained framing with [`Self::read`]
41/// before calling collection operations. Framing admission returns errors;
42/// engine and codec failures trap. Only [`Self::initialize`] creates a tree, and only in an empty
43/// region. No method reinitializes nonempty memory or exports raw backing.
44/// As with ordinary collections, callers must not reenter collection access
45/// from a codec or mutate the tree's bytes through their retained lease.
46pub struct BTreeMapRegion<K: Storable + Ord + Clone, V: Storable, M: Memory = DefaultMemoryImpl> {
47    memory: RegionMemory<M>,
48    record: PhantomData<(K, V)>,
49}
50
51impl<K: Storable + Ord + Clone, V: Storable, M: Memory> BTreeMapRegion<K, V, M> {
52    /// Select an absolute, half-open range of 65,536-byte allocation pages.
53    ///
54    /// Retains the same allocation lease; no allocation or collection is opened.
55    /// Other owners of the `Rc` may access disjoint frames in that allocation.
56    /// Drop every view and the caller's last `Rc` before reopening the lease.
57    ///
58    /// # Panics
59    /// Panics unless `pages.start < pages.end <= 4_194_304`.
60    #[must_use]
61    pub fn new(memory: Rc<RuntimeMemory<M>>, pages: Range<u64>) -> Self {
62        assert!(pages.start < pages.end && pages.end <= MAX_PAGES);
63        Self {
64            memory: RegionMemory { memory, pages },
65            record: PhantomData,
66        }
67    }
68
69    /// Return the currently allocated relative extent in Wasm pages.
70    #[must_use]
71    pub fn allocated_pages(&self) -> u64 {
72        self.memory.size()
73    }
74
75    /// Read relative bytes for the application's retained-framing admission.
76    ///
77    /// # Panics
78    /// Panics before IO if the complete read exceeds the allocated region.
79    /// An empty read is valid at the region's end, but not beyond it.
80    pub fn read(&self, offset: u64, dst: &mut [u8]) {
81        self.memory.read(offset, dst);
82    }
83
84    /// Create an owned tree only when the selected region has zero pages.
85    ///
86    /// Call before reserving extra pages. Retained trees need no initialization:
87    /// admit their framing and use the operations directly.
88    ///
89    /// # Errors
90    /// Rejects a nonempty region or capacity refusal without initializing a replacement.
91    pub fn initialize(&mut self) -> CollectionResult<()> {
92        ensure(self.allocated_pages() == 0, Error::NotEmpty)?;
93        bridge::reserve(&self.memory)?;
94        #[cfg(not(target_arch = "wasm32"))]
95        let _guard = self.memory.memory.poison_on_unwind();
96        bridge::Map::<K, V, _>::new(TreeMemory::Region(self.memory.clone()));
97        Ok(())
98    }
99
100    /// Reserve an absolute relative extent without promising entry capacity.
101    ///
102    /// # Errors
103    /// Returns a region-limit or Storage growth error without changing bytes
104    /// or allocation accounting. A satisfied request performs no growth or IO.
105    /// Callers own byte/page budgets and must qualify them for their codecs.
106    /// Mutations retain the host failure contract; this is not a transaction
107    /// around an insert that later runs out of space or panics in a codec.
108    pub fn reserve_pages(&mut self, minimum: u64) -> Result<(), RegionGrowError> {
109        self.memory.reserve_pages(minimum)
110    }
111
112    /// Read a record using the current retained root.
113    ///
114    /// # Errors
115    /// Refuses foreign framing; engine and stored-codec failures trap.
116    pub fn get(&self, key: &K) -> CollectionResult<Option<V>> {
117        Ok(self.current_map()?.get(Codec::borrowed(key)).map(|v| v.0))
118    }
119
120    /// Insert using the current retained root and copied ICSS engine.
121    ///
122    /// # Errors
123    /// Refuses foreign framing. Engine/codec failures trap, possibly after native writes.
124    pub fn insert(&mut self, key: K, value: V) -> CollectionResult<Option<V>> {
125        #[cfg(not(target_arch = "wasm32"))]
126        let _guard = self.memory.memory.poison_on_unwind();
127        Ok(self
128            .current_map()?
129            .insert(Codec(key), Codec(value))
130            .map(|v| v.0))
131    }
132
133    /// Remove using the current retained root and copied ICSS engine.
134    ///
135    /// # Errors
136    /// Refuses foreign framing. Engine/codec failures trap, possibly after native writes.
137    pub fn remove(&mut self, key: &K) -> CollectionResult<Option<V>> {
138        #[cfg(not(target_arch = "wasm32"))]
139        let _guard = self.memory.memory.poison_on_unwind();
140        Ok(self
141            .current_map()?
142            .remove(Codec::borrowed(key))
143            .map(|v| v.0))
144    }
145
146    /// Return the current tree's record count across all namespaces.
147    ///
148    /// # Errors
149    /// Refuses foreign framing; malformed engine state traps.
150    pub fn len(&self) -> CollectionResult<u64> {
151        Ok(self.current_map()?.len())
152    }
153
154    /// Return whether the current tree has no records.
155    ///
156    /// # Errors
157    /// Refuses foreign framing; malformed engine state traps.
158    pub fn is_empty(&self) -> CollectionResult<bool> {
159        Ok(self.current_map()?.len() == 0)
160    }
161
162    /// Snapshot ordered keys within the caller's namespace/range bounds.
163    ///
164    /// The scan completes before returning; the iterator retains owned keys,
165    /// with no collection cache, memory handle or borrow across later mutations.
166    ///
167    /// # Errors
168    /// Refuses foreign framing; invalid ranges, engine state or key codecs can trap.
169    pub fn keys_range(
170        &self,
171        bounds: impl RangeBounds<K>,
172    ) -> CollectionResult<std::vec::IntoIter<K>> {
173        self.keys_range_limited(bounds, usize::MAX)
174    }
175
176    /// Snapshot at most `limit` ordered keys, stopping iteration at that bound.
177    ///
178    /// Unlike `keys_range(bounds).take(limit)`, this bounds collection work before
179    /// allocating the snapshot. Zero returns without loading the tree. Resume
180    /// with an excluded last key; intervening mutations are visible to each call.
181    /// The limit bounds key count, not encoded bytes or instruction cost.
182    ///
183    /// # Errors
184    /// Refuses foreign framing; invalid ranges, engine state or key codecs can trap.
185    pub fn keys_range_limited(
186        &self,
187        bounds: impl RangeBounds<K>,
188        limit: usize,
189    ) -> CollectionResult<std::vec::IntoIter<K>> {
190        if limit == 0 {
191            return Ok(Vec::new().into_iter());
192        }
193        Ok(self
194            .current_map()?
195            .keys_range(super::map::codec_bounds(&bounds))
196            .take(limit)
197            .map(|k| k.0)
198            .collect::<Vec<_>>()
199            .into_iter())
200    }
201
202    fn current_map(&self) -> CollectionResult<bridge::Map<K, V, M>> {
203        bridge::Map::load(TreeMemory::Region(self.memory.clone())).ok_or(Error::Format)
204    }
205}
206
207// Private bounded IO over an admitted lease. Reads, writes and growth stay
208// inside the selected region, including initially absent regions.
209pub(super) struct RegionMemory<M: Memory> {
210    memory: Rc<RuntimeMemory<M>>,
211    pages: Range<u64>,
212}
213
214impl<M: Memory> Clone for RegionMemory<M> {
215    fn clone(&self) -> Self {
216        Self {
217            memory: Rc::clone(&self.memory),
218            pages: self.pages.clone(),
219        }
220    }
221}
222impl<M: Memory> RegionMemory<M> {
223    pub(super) fn allocation(&self) -> &RuntimeMemory<M> {
224        &self.memory
225    }
226
227    fn reserve_pages(&self, minimum: u64) -> Result<(), RegionGrowError> {
228        let maximum = self.pages.end - self.pages.start;
229        if minimum > maximum {
230            return Err(RegionGrowError::LimitExceeded { minimum, maximum });
231        }
232        if minimum > self.size() {
233            let additional = self.pages.start + minimum - self.memory.size();
234            self.memory
235                .as_ref()
236                .grow(additional)
237                .map_err(RegionGrowError::Runtime)?;
238        }
239        Ok(())
240    }
241
242    #[inline]
243    fn check_io(&self, offset: u64, count: usize) {
244        // The region owns its static namespace limit; RuntimeMemory admits the
245        // complete live allocation range. Avoid looking up that extent twice.
246        // Empty IO does not delegate, so it must check the current extent here.
247        let extent = if count == 0 {
248            self.size()
249        } else {
250            self.pages.end - self.pages.start
251        } * PAGE_BYTES;
252        assert!(offset <= extent && count as u64 <= extent - offset);
253    }
254}
255
256impl<M: Memory> Memory for RegionMemory<M> {
257    fn size(&self) -> u64 {
258        self.memory.size().clamp(self.pages.start, self.pages.end) - self.pages.start
259    }
260
261    fn grow(&self, delta: u64) -> i64 {
262        let previous = self.size();
263        let Some(minimum) = previous.checked_add(delta) else {
264            return -1;
265        };
266        if self.reserve_pages(minimum).is_ok() {
267            // The selected range is bounded by MAX_PAGES.
268            i64::try_from(previous).unwrap()
269        } else {
270            -1
271        }
272    }
273
274    fn read(&self, offset: u64, dst: &mut [u8]) {
275        self.check_io(offset, dst.len());
276        if !dst.is_empty() {
277            self.memory
278                .read(self.pages.start * PAGE_BYTES + offset, dst);
279        }
280    }
281
282    fn write(&self, offset: u64, src: &[u8]) {
283        self.check_io(offset, src.len());
284        if !src.is_empty() {
285            self.memory
286                .write(self.pages.start * PAGE_BYTES + offset, src);
287        }
288    }
289
290    #[expect(
291        unsafe_code,
292        reason = "Check the region namespace before forwarding the admitted destination"
293    )]
294    unsafe fn read_unsafe(&self, offset: u64, dst: *mut u8, count: usize) {
295        self.check_io(offset, count);
296        if count != 0 {
297            // SAFETY: the destination is unchanged; the lease checks the entire
298            // translated live range before initializing any of its chunks.
299            unsafe {
300                self.memory
301                    .read_unsafe(self.pages.start * PAGE_BYTES + offset, dst, count);
302            }
303        }
304    }
305}