rtvm_interpreter/interpreter/
shared_memory.rs

1use rtvm_primitives::{B256, U256};
2
3use core::{
4    cmp::min,
5    fmt,
6    ops::{BitAnd, Not, Range},
7};
8use std::vec::Vec;
9
10/// A sequential memory shared between calls, which uses
11/// a `Vec` for internal representation.
12/// A [SharedMemory] instance should always be obtained using
13/// the `new` static method to ensure memory safety.
14#[derive(Clone, PartialEq, Eq, Hash)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub struct SharedMemory {
17    /// The underlying buffer.
18    buffer: Vec<u8>,
19    /// Memory checkpoints for each depth.
20    /// Invariant: these are always in bounds of `data`.
21    checkpoints: Vec<usize>,
22    /// Invariant: equals `self.checkpoints.last()`
23    last_checkpoint: usize,
24    /// Memory limit. See [`CfgEnv`](rtvm_primitives::CfgEnv).
25    #[cfg(feature = "memory_limit")]
26    memory_limit: u64,
27}
28
29/// Empty shared memory.
30///
31/// Used as placeholder inside Interpreter when it is not running.
32pub const EMPTY_SHARED_MEMORY: SharedMemory = SharedMemory {
33    buffer: Vec::new(),
34    checkpoints: Vec::new(),
35    last_checkpoint: 0,
36    #[cfg(feature = "memory_limit")]
37    memory_limit: u64::MAX,
38};
39
40impl fmt::Debug for SharedMemory {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        f.debug_struct("SharedMemory")
43            .field("current_len", &self.len())
44            .field(
45                "context_memory",
46                &crate::primitives::hex::encode(self.context_memory()),
47            )
48            .finish_non_exhaustive()
49    }
50}
51
52impl Default for SharedMemory {
53    #[inline]
54    fn default() -> Self {
55        Self::new()
56    }
57}
58
59impl SharedMemory {
60    /// Creates a new memory instance that can be shared between calls.
61    ///
62    /// The default initial capacity is 4KiB.
63    #[inline]
64    pub fn new() -> Self {
65        Self::with_capacity(4 * 1024) // from evmone
66    }
67
68    /// Creates a new memory instance that can be shared between calls with the given `capacity`.
69    #[inline]
70    pub fn with_capacity(capacity: usize) -> Self {
71        Self {
72            buffer: Vec::with_capacity(capacity),
73            checkpoints: Vec::with_capacity(32),
74            last_checkpoint: 0,
75            #[cfg(feature = "memory_limit")]
76            memory_limit: u64::MAX,
77        }
78    }
79
80    /// Creates a new memory instance that can be shared between calls,
81    /// with `memory_limit` as upper bound for allocation size.
82    ///
83    /// The default initial capacity is 4KiB.
84    #[cfg(feature = "memory_limit")]
85    #[inline]
86    pub fn new_with_memory_limit(memory_limit: u64) -> Self {
87        Self {
88            memory_limit,
89            ..Self::new()
90        }
91    }
92
93    /// Returns `true` if the `new_size` for the current context memory will
94    /// make the shared buffer length exceed the `memory_limit`.
95    #[cfg(feature = "memory_limit")]
96    #[inline]
97    pub fn limit_reached(&self, new_size: usize) -> bool {
98        (self.last_checkpoint + new_size) as u64 > self.memory_limit
99    }
100
101    /// Prepares the shared memory for a new context.
102    #[inline]
103    pub fn new_context(&mut self) {
104        let new_checkpoint = self.buffer.len();
105        self.checkpoints.push(new_checkpoint);
106        self.last_checkpoint = new_checkpoint;
107    }
108
109    /// Prepares the shared memory for returning to the previous context.
110    #[inline]
111    pub fn free_context(&mut self) {
112        if let Some(old_checkpoint) = self.checkpoints.pop() {
113            self.last_checkpoint = self.checkpoints.last().cloned().unwrap_or_default();
114            // SAFETY: buffer length is less than or equal `old_checkpoint`
115            unsafe { self.buffer.set_len(old_checkpoint) };
116        }
117    }
118
119    /// Returns the length of the current memory range.
120    #[inline]
121    pub fn len(&self) -> usize {
122        self.buffer.len() - self.last_checkpoint
123    }
124
125    /// Returns `true` if the current memory range is empty.
126    #[inline]
127    pub fn is_empty(&self) -> bool {
128        self.len() == 0
129    }
130
131    /// Resizes the memory in-place so that `len` is equal to `new_len`.
132    #[inline]
133    pub fn resize(&mut self, new_size: usize) {
134        self.buffer.resize(self.last_checkpoint + new_size, 0);
135    }
136
137    /// Returns a byte slice of the memory region at the given offset.
138    ///
139    /// # Panics
140    ///
141    /// Panics on out of bounds.
142    #[inline]
143    #[cfg_attr(debug_assertions, track_caller)]
144    pub fn slice(&self, offset: usize, size: usize) -> &[u8] {
145        self.slice_range(offset..offset + size)
146    }
147
148    #[inline]
149    #[cfg_attr(debug_assertions, track_caller)]
150    pub fn slice_range(&self, range: Range<usize>) -> &[u8] {
151        let last_checkpoint = self.last_checkpoint;
152
153        self.buffer
154            .get(last_checkpoint + range.start..last_checkpoint + range.end)
155            .unwrap_or_else(|| {
156                debug_unreachable!(
157                    "slice OOB: {}..{}; len: {}",
158                    range.start,
159                    range.end,
160                    self.len()
161                )
162            })
163    }
164
165    /// Returns a byte slice of the memory region at the given offset.
166    ///
167    /// # Panics
168    ///
169    /// Panics on out of bounds.
170    #[inline]
171    #[cfg_attr(debug_assertions, track_caller)]
172    pub fn slice_mut(&mut self, offset: usize, size: usize) -> &mut [u8] {
173        let len = self.len();
174        let end = offset + size;
175        let last_checkpoint = self.last_checkpoint;
176
177        self.buffer
178            .get_mut(last_checkpoint + offset..last_checkpoint + offset + size)
179            .unwrap_or_else(|| debug_unreachable!("slice OOB: {offset}..{end}; len: {}", len))
180    }
181
182    /// Returns the byte at the given offset.
183    ///
184    /// # Panics
185    ///
186    /// Panics on out of bounds.
187    #[inline]
188    pub fn get_byte(&self, offset: usize) -> u8 {
189        self.slice(offset, 1)[0]
190    }
191
192    /// Returns a 32-byte slice of the memory region at the given offset.
193    ///
194    /// # Panics
195    ///
196    /// Panics on out of bounds.
197    #[inline]
198    pub fn get_word(&self, offset: usize) -> B256 {
199        self.slice(offset, 32).try_into().unwrap()
200    }
201
202    /// Returns a U256 of the memory region at the given offset.
203    ///
204    /// # Panics
205    ///
206    /// Panics on out of bounds.
207    #[inline]
208    pub fn get_u256(&self, offset: usize) -> U256 {
209        self.get_word(offset).into()
210    }
211
212    /// Sets the `byte` at the given `index`.
213    ///
214    /// # Panics
215    ///
216    /// Panics on out of bounds.
217    #[inline]
218    #[cfg_attr(debug_assertions, track_caller)]
219    pub fn set_byte(&mut self, offset: usize, byte: u8) {
220        self.set(offset, &[byte]);
221    }
222
223    /// Sets the given 32-byte `value` to the memory region at the given `offset`.
224    ///
225    /// # Panics
226    ///
227    /// Panics on out of bounds.
228    #[inline]
229    #[cfg_attr(debug_assertions, track_caller)]
230    pub fn set_word(&mut self, offset: usize, value: &B256) {
231        self.set(offset, &value[..]);
232    }
233
234    /// Sets the given U256 `value` to the memory region at the given `offset`.
235    ///
236    /// # Panics
237    ///
238    /// Panics on out of bounds.
239    #[inline]
240    #[cfg_attr(debug_assertions, track_caller)]
241    pub fn set_u256(&mut self, offset: usize, value: U256) {
242        self.set(offset, &value.to_be_bytes::<32>());
243    }
244
245    /// Set memory region at given `offset`.
246    ///
247    /// # Panics
248    ///
249    /// Panics on out of bounds.
250    #[inline]
251    #[cfg_attr(debug_assertions, track_caller)]
252    pub fn set(&mut self, offset: usize, value: &[u8]) {
253        if !value.is_empty() {
254            self.slice_mut(offset, value.len()).copy_from_slice(value);
255        }
256    }
257
258    /// Set memory from data. Our memory offset+len is expected to be correct but we
259    /// are doing bound checks on data/data_offeset/len and zeroing parts that is not copied.
260    ///
261    /// # Panics
262    ///
263    /// Panics on out of bounds.
264    #[inline]
265    #[cfg_attr(debug_assertions, track_caller)]
266    pub fn set_data(&mut self, memory_offset: usize, data_offset: usize, len: usize, data: &[u8]) {
267        if data_offset >= data.len() {
268            // nullify all memory slots
269            self.slice_mut(memory_offset, len).fill(0);
270            return;
271        }
272        let data_end = min(data_offset + len, data.len());
273        let data_len = data_end - data_offset;
274        debug_assert!(data_offset < data.len() && data_end <= data.len());
275        let data = unsafe { data.get_unchecked(data_offset..data_end) };
276        self.slice_mut(memory_offset, data_len)
277            .copy_from_slice(data);
278
279        // nullify rest of memory slots
280        // SAFETY: Memory is assumed to be valid, and it is commented where this assumption is made.
281        self.slice_mut(memory_offset + data_len, len - data_len)
282            .fill(0);
283    }
284
285    /// Copies elements from one part of the memory to another part of itself.
286    ///
287    /// # Panics
288    ///
289    /// Panics on out of bounds.
290    #[inline]
291    #[cfg_attr(debug_assertions, track_caller)]
292    pub fn copy(&mut self, dst: usize, src: usize, len: usize) {
293        self.context_memory_mut().copy_within(src..src + len, dst);
294    }
295
296    /// Returns a reference to the memory of the current context, the active memory.
297    #[inline]
298    pub fn context_memory(&self) -> &[u8] {
299        // SAFETY: access bounded by buffer length
300        unsafe {
301            self.buffer
302                .get_unchecked(self.last_checkpoint..self.buffer.len())
303        }
304    }
305
306    /// Returns a mutable reference to the memory of the current context.
307    #[inline]
308    pub fn context_memory_mut(&mut self) -> &mut [u8] {
309        let buf_len = self.buffer.len();
310        // SAFETY: access bounded by buffer length
311        unsafe { self.buffer.get_unchecked_mut(self.last_checkpoint..buf_len) }
312    }
313}
314
315/// Rounds up `x` to the closest multiple of 32. If `x % 32 == 0` then `x` is returned. Note, if `x`
316/// is greater than `usize::MAX - 31` this will return `usize::MAX` which isn't a multiple of 32.
317#[inline]
318pub fn next_multiple_of_32(x: usize) -> usize {
319    let r = x.bitand(31).not().wrapping_add(1).bitand(31);
320    x.saturating_add(r)
321}
322
323#[cfg(test)]
324mod tests {
325    use super::*;
326
327    #[test]
328    fn test_next_multiple_of_32() {
329        // next_multiple_of_32 returns x when it is a multiple of 32
330        for i in 0..32 {
331            let x = i * 32;
332            assert_eq!(x, next_multiple_of_32(x));
333        }
334
335        // next_multiple_of_32 rounds up to the nearest multiple of 32 when `x % 32 != 0`
336        for x in 0..1024 {
337            if x % 32 == 0 {
338                continue;
339            }
340            let next_multiple = x + 32 - (x % 32);
341            assert_eq!(next_multiple, next_multiple_of_32(x));
342        }
343
344        // We expect large values to saturate and not overflow.
345        assert_eq!(usize::MAX, next_multiple_of_32(usize::MAX));
346    }
347
348    #[test]
349    fn new_free_context() {
350        let mut shared_memory = SharedMemory::new();
351        shared_memory.new_context();
352
353        assert_eq!(shared_memory.buffer.len(), 0);
354        assert_eq!(shared_memory.checkpoints.len(), 1);
355        assert_eq!(shared_memory.last_checkpoint, 0);
356
357        unsafe { shared_memory.buffer.set_len(32) };
358        assert_eq!(shared_memory.len(), 32);
359        shared_memory.new_context();
360
361        assert_eq!(shared_memory.buffer.len(), 32);
362        assert_eq!(shared_memory.checkpoints.len(), 2);
363        assert_eq!(shared_memory.last_checkpoint, 32);
364        assert_eq!(shared_memory.len(), 0);
365
366        unsafe { shared_memory.buffer.set_len(96) };
367        assert_eq!(shared_memory.len(), 64);
368        shared_memory.new_context();
369
370        assert_eq!(shared_memory.buffer.len(), 96);
371        assert_eq!(shared_memory.checkpoints.len(), 3);
372        assert_eq!(shared_memory.last_checkpoint, 96);
373        assert_eq!(shared_memory.len(), 0);
374
375        // free contexts
376        shared_memory.free_context();
377        assert_eq!(shared_memory.buffer.len(), 96);
378        assert_eq!(shared_memory.checkpoints.len(), 2);
379        assert_eq!(shared_memory.last_checkpoint, 32);
380        assert_eq!(shared_memory.len(), 64);
381
382        shared_memory.free_context();
383        assert_eq!(shared_memory.buffer.len(), 32);
384        assert_eq!(shared_memory.checkpoints.len(), 1);
385        assert_eq!(shared_memory.last_checkpoint, 0);
386        assert_eq!(shared_memory.len(), 32);
387
388        shared_memory.free_context();
389        assert_eq!(shared_memory.buffer.len(), 0);
390        assert_eq!(shared_memory.checkpoints.len(), 0);
391        assert_eq!(shared_memory.last_checkpoint, 0);
392        assert_eq!(shared_memory.len(), 0);
393    }
394
395    #[test]
396    fn resize() {
397        let mut shared_memory = SharedMemory::new();
398        shared_memory.new_context();
399
400        shared_memory.resize(32);
401        assert_eq!(shared_memory.buffer.len(), 32);
402        assert_eq!(shared_memory.len(), 32);
403        assert_eq!(shared_memory.buffer.get(0..32), Some(&[0_u8; 32] as &[u8]));
404
405        shared_memory.new_context();
406        shared_memory.resize(96);
407        assert_eq!(shared_memory.buffer.len(), 128);
408        assert_eq!(shared_memory.len(), 96);
409        assert_eq!(
410            shared_memory.buffer.get(32..128),
411            Some(&[0_u8; 96] as &[u8])
412        );
413
414        shared_memory.free_context();
415        shared_memory.resize(64);
416        assert_eq!(shared_memory.buffer.len(), 64);
417        assert_eq!(shared_memory.len(), 64);
418        assert_eq!(shared_memory.buffer.get(0..64), Some(&[0_u8; 64] as &[u8]));
419    }
420}