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}