ic_stable_memory/mem/
s_slice.rs

1//! A primitive smart-pointer that points to an allocated memory block.
2//!
3//! This data structure's main purpose is to tell the dimensions of an allocated memory block and allow taking
4//! a pointer somewhere inside it. One can create such a memory block by calling [allocate](crate::allocate)
5//! function. This is a managed resource, so please be careful and always [deallocate](crate::deallocate)
6//! memory blocks, when you don't longer need them.
7
8use crate::encoding::{AsFixedSizeBytes, Buffer};
9use crate::mem::allocator::EMPTY_PTR;
10use crate::mem::free_block::FreeBlock;
11use crate::mem::{StablePtr, StablePtrBuf};
12use crate::utils::mem_context::stable;
13
14pub(crate) const ALLOCATED: u64 = 2u64.pow(u64::BITS - 1); // first biggest bit set to 1, other set to 0
15pub(crate) const FREE: u64 = ALLOCATED - 1; // first biggest bit set to 0, other set to 1
16
17/// An allocated block of stable memory.
18///
19/// Represented by a pointer to the first byte of the memory block and a [u64] size of this block in
20/// bytes. It implements [Copy], but using it after deallocation is undefined behavior.
21///
22/// In stable memory each memory block has the following layout:
23/// - bytes `0..8` - `size` + `allocated bit flag` (the flag uses the first bit of little endian encoded size)
24/// - bytes `8..(size + 8)` - the data
25/// - bytes `(size + 8)..(size + 16)` - another `size` + `allocated bit flag`
26/// So, a memory block is simply `size` bytes of data wrapped with some metadata from both sides.
27/// [FreeBlock](mem::free_block::FreeBlock) is stored exactly in a same way.
28#[derive(Debug, Copy, Clone)]
29pub struct SSlice {
30    ptr: StablePtr,
31    size: u64,
32}
33
34impl SSlice {
35    pub(crate) fn new(ptr: StablePtr, size: u64, write_size: bool) -> Self {
36        if write_size {
37            Self::write_size(ptr, size);
38        }
39
40        Self { ptr, size }
41    }
42
43    /// Recreate an [SSlice] from a pointer to the front of the memory block.
44    ///
45    /// See also [SSlice::from_rear_ptr].
46    ///
47    /// This call will check whether a pointer is valid (points to an *allocated* memory block) and
48    /// if it's not, it will return [None].
49    ///
50    /// # Safety
51    /// By calling this function, you're basically create a copy of an [SSlice], be careful
52    pub unsafe fn from_ptr(ptr: StablePtr) -> Option<Self> {
53        if ptr == 0 || ptr == EMPTY_PTR {
54            return None;
55        }
56
57        let size = Self::read_size(ptr)?;
58
59        Some(Self::new(ptr, size, false))
60    }
61
62    /// Recreate an [SSlice] from a pointer to the back of the memory block.
63    ///
64    /// See also [SSlice::from_ptr].
65    ///
66    /// # Safety
67    /// By calling this function, you're basically create a copy of an [SSlice], be careful
68    pub unsafe fn from_rear_ptr(ptr: StablePtr) -> Option<Self> {
69        if ptr == 0 || ptr == EMPTY_PTR {
70            return None;
71        }
72
73        let size = Self::read_size(ptr)?;
74
75        Some(Self::new(
76            ptr - (StablePtr::SIZE as u64) - size,
77            size,
78            false,
79        ))
80    }
81
82    /// Returns a pointer to the memory block.
83    ///
84    /// *Don't use this function to point to the data inside this memory block!* Use [SSlice::offset]
85    /// instead.
86    #[inline]
87    pub fn as_ptr(&self) -> StablePtr {
88        self.ptr
89    }
90
91    /// Returns the size of the data in this memory block in bytes.
92    #[inline]
93    pub fn get_size_bytes(&self) -> u64 {
94        self.size
95    }
96
97    /// Returns the size of the whole memory block in bytes (including metadata).
98    #[inline]
99    pub fn get_total_size_bytes(&self) -> u64 {
100        self.get_size_bytes() + StablePtr::SIZE as u64 * 2
101    }
102
103    /// Static analog of [SSlice::offset].
104    ///
105    /// Does not perform boundary check.
106    #[inline]
107    pub fn _offset(self_ptr: u64, offset: u64) -> StablePtr {
108        debug_assert_ne!(self_ptr, EMPTY_PTR);
109
110        self_ptr + (StablePtr::SIZE as u64) + offset
111    }
112
113    /// Returns a pointer to the data inside [SSlice].
114    ///
115    /// One should use this function to write data in a memory block by using [mem::write_fixed] or
116    /// [mem::write_bytes].
117    ///
118    /// # Panics
119    /// Panics if boundary check fails (if the offset is outside the memory block).
120    ///
121    /// # Example
122    /// ```rust
123    /// # use ic_stable_memory::{allocate, mem, stable_memory_init};
124    /// # unsafe { ic_stable_memory::mem::clear(); }
125    /// # stable_memory_init();
126    /// let slice = unsafe { allocate(100).expect("Out of memory") };
127    /// let ptr = slice.offset(20);
128    ///
129    /// // will write `10` as little endian bytes into the memory block
130    /// // starting from 20th byte
131    /// unsafe { mem::write_fixed(ptr, &mut 10u64); }
132    /// ```
133    #[inline]
134    pub fn offset(&self, offset: u64) -> StablePtr {
135        let ptr = Self::_offset(self.as_ptr(), offset);
136        assert!(ptr <= self.as_ptr() + StablePtr::SIZE as u64 + self.get_size_bytes());
137
138        ptr
139    }
140
141    #[inline]
142    pub(crate) fn to_free_block(self) -> FreeBlock {
143        FreeBlock::new(self.ptr, self.size)
144    }
145
146    fn read_size(ptr: StablePtr) -> Option<u64> {
147        let mut meta = StablePtrBuf::new(StablePtr::SIZE);
148        stable::read(ptr, &mut meta);
149
150        let encoded_size = u64::from_le_bytes(meta);
151        let mut size = encoded_size;
152
153        let allocated = if encoded_size & ALLOCATED == ALLOCATED {
154            size &= FREE;
155            true
156        } else {
157            false
158        };
159
160        if allocated {
161            Some(size)
162        } else {
163            None
164        }
165    }
166
167    fn write_size(ptr: StablePtr, size: u64) {
168        let encoded_size = size | ALLOCATED;
169
170        let meta = encoded_size.to_le_bytes();
171
172        stable::write(ptr, &meta);
173        stable::write(ptr + (StablePtr::SIZE as u64) + size, &meta);
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use crate::encoding::AsFixedSizeBytes;
180    use crate::mem::allocator::MIN_PTR;
181    use crate::mem::s_slice::SSlice;
182    use crate::mem::StablePtr;
183    use crate::utils::mem_context::stable;
184
185    #[test]
186    fn read_write_work_fine() {
187        stable::clear();
188        stable::grow(10).expect("Unable to grow");
189
190        let m1 = SSlice::new(MIN_PTR, 100, true);
191        let m1 = unsafe {
192            SSlice::from_rear_ptr(
193                MIN_PTR + m1.get_total_size_bytes() as u64 - StablePtr::SIZE as u64,
194            )
195            .unwrap()
196        };
197
198        let a = vec![1u8, 2, 3, 4, 5, 6, 7, 8];
199        let b = vec![1u8, 3, 3, 7];
200        let c = vec![9u8, 8, 7, 6, 5, 4, 3, 2, 1];
201
202        unsafe { crate::mem::write_bytes(m1.offset(0), &a) };
203        unsafe { crate::mem::write_bytes(m1.offset(8), &b) };
204        unsafe { crate::mem::write_bytes(m1.offset(90), &c) };
205
206        let mut a1 = [0u8; 8];
207        let mut b1 = [0u8; 4];
208        let mut c1 = [0u8; 9];
209
210        unsafe { crate::mem::read_bytes(m1.offset(0), &mut a1) };
211        unsafe { crate::mem::read_bytes(m1.offset(8), &mut b1) };
212        unsafe { crate::mem::read_bytes(m1.offset(90), &mut c1) };
213
214        assert_eq!(&a, &a1);
215        assert_eq!(&b, &b1);
216        assert_eq!(&c, &c1);
217    }
218}