essential_vm/
stack.rs

1//! Stack operation and related stack manipulation implementations.
2
3use crate::{
4    asm::Word,
5    error::{LenWordsError, StackError, StackResult},
6};
7use essential_types::convert::bool_from_word;
8
9#[cfg(test)]
10mod frame_tests;
11
12/// The VM's `Stack`, i.e. a `Vec` of `Word`s updated during each step of execution.
13///
14/// A light wrapper around `Vec<Word>` providing helper methods specific to
15/// essential VM execution.
16#[derive(Clone, Debug, PartialEq, Default)]
17pub struct Stack(Vec<Word>);
18
19impl Stack {
20    /// Limit the stack size to 32KB to avoid memory bloat during parallel constraint checking.
21    pub const SIZE_LIMIT: usize = 4096;
22
23    /// Push a word to the stack.
24    ///
25    /// Errors in the case that pushing an element would cause the stack to overflow.
26    pub fn push(&mut self, word: Word) -> StackResult<()> {
27        if self.len() >= Self::SIZE_LIMIT {
28            return Err(StackError::Overflow);
29        }
30        self.0.push(word);
31        Ok(())
32    }
33
34    /// Extend the stack by with the given iterator yielding words.
35    ///
36    /// Errors in the case that pushing an element would cause the stack to overflow.
37    pub fn extend(&mut self, words: impl IntoIterator<Item = Word>) -> StackResult<()> {
38        for word in words {
39            self.push(word)?;
40        }
41        Ok(())
42    }
43
44    /// Reserve a length of zeroed words on the stack.
45    pub(crate) fn reserve_zeroed(&mut self) -> StackResult<()> {
46        let len = self.pop()?;
47        let len = usize::try_from(len).map_err(|_| StackError::IndexOutOfBounds)?;
48        let new_len = self.len().saturating_add(len);
49        if new_len > Self::SIZE_LIMIT {
50            return Err(StackError::IndexOutOfBounds);
51        }
52        self.0.resize(new_len, 0);
53        Ok(())
54    }
55
56    /// Load a word from the given index.
57    pub(crate) fn load(&mut self) -> StackResult<()> {
58        let ix = self.pop()?;
59        let ix = usize::try_from(ix).map_err(|_| StackError::IndexOutOfBounds)?;
60        let word = self
61            .0
62            .get(ix)
63            .copied()
64            .ok_or(StackError::IndexOutOfBounds)?;
65        self.push(word)
66    }
67
68    /// Store a word at the given index.
69    pub(crate) fn store(&mut self) -> StackResult<()> {
70        let [ix, word] = self.pop2()?;
71        let ix = usize::try_from(ix).map_err(|_| StackError::IndexOutOfBounds)?;
72        let Some(w) = self.0.get_mut(ix) else {
73            return Err(StackError::IndexOutOfBounds);
74        };
75        *w = word;
76        Ok(())
77    }
78
79    /// The DupFrom op implementation.
80    pub(crate) fn dup_from(&mut self) -> StackResult<()> {
81        let rev_ix_w = self.pop()?;
82        let rev_ix = usize::try_from(rev_ix_w).map_err(|_| StackError::IndexOutOfBounds)?;
83        let ix = self
84            .len()
85            .checked_sub(rev_ix)
86            .and_then(|i| i.checked_sub(1))
87            .ok_or(StackError::IndexOutOfBounds)?;
88        let w = *self.get(ix).ok_or(StackError::IndexOutOfBounds)?;
89        self.push(w)?;
90        Ok(())
91    }
92
93    /// The SwapIndex op implementation.
94    pub(crate) fn swap_index(&mut self) -> StackResult<()> {
95        let rev_ix_w = self.pop()?;
96        let top_ix = self
97            .len()
98            .checked_sub(1)
99            .ok_or(StackError::IndexOutOfBounds)?;
100        let rev_ix = usize::try_from(rev_ix_w).map_err(|_| StackError::IndexOutOfBounds)?;
101        let ix = top_ix
102            .checked_sub(rev_ix)
103            .ok_or(StackError::IndexOutOfBounds)?;
104        self.0.swap(ix, top_ix);
105        Ok(())
106    }
107
108    /// The Select op implementation.
109    pub(crate) fn select(&mut self) -> StackResult<()> {
110        self.pop().and_then(|cond_w| {
111            self.pop2_push1(|w0, w1| {
112                Ok(
113                    if bool_from_word(cond_w).ok_or(StackError::InvalidCondition(cond_w))? {
114                        w1
115                    } else {
116                        w0
117                    },
118                )
119            })
120        })?;
121        Ok(())
122    }
123
124    /// The SelectRange op implementation.
125    pub(crate) fn select_range(&mut self) -> StackResult<()> {
126        let cond_w = self.pop()?;
127        let cond = bool_from_word(cond_w).ok_or(StackError::InvalidCondition(cond_w))?;
128        let len = self.pop_len()?;
129        if len == 0 {
130            return Ok(());
131        }
132        // check that `len` is at most half the stack length
133        self.len()
134            .checked_sub(len.checked_mul(2).ok_or(StackError::IndexOutOfBounds)?)
135            .ok_or(StackError::IndexOutOfBounds)?;
136        // stack: [arr_a_0, ..arr_a_N, arr_b_0, ..arr_b_N]
137        let arr_b_index = self.len() - len;
138        if cond {
139            // copy arr_b to the space arr_a holds
140            let arr_a_index = arr_b_index - len;
141            self.0
142                .copy_within(arr_b_index..(arr_b_index + len), arr_a_index);
143        }
144        // pop the topmost range that is arr_b
145        self.0.truncate(arr_b_index);
146        Ok(())
147    }
148
149    /// A wrapper around `Vec::pop`, producing an error in the case that the stack is empty.
150    pub fn pop(&mut self) -> StackResult<Word> {
151        self.0.pop().ok_or(StackError::Empty)
152    }
153
154    /// Pop the top 2 values from the stack.
155    ///
156    /// The last values popped appear first in the returned fixed-size array.
157    pub fn pop2(&mut self) -> StackResult<[Word; 2]> {
158        let w1 = self.pop()?;
159        let w0 = self.pop()?;
160        Ok([w0, w1])
161    }
162
163    /// Pop the top 3 values from the stack.
164    ///
165    /// The last values popped appear first in the returned fixed-size array.
166    pub fn pop3(&mut self) -> StackResult<[Word; 3]> {
167        let w2 = self.pop()?;
168        let [w0, w1] = self.pop2()?;
169        Ok([w0, w1, w2])
170    }
171
172    /// Pop the top 4 values from the stack.
173    ///
174    /// The last values popped appear first in the returned fixed-size array.
175    pub fn pop4(&mut self) -> StackResult<[Word; 4]> {
176        let w3 = self.pop()?;
177        let [w0, w1, w2] = self.pop3()?;
178        Ok([w0, w1, w2, w3])
179    }
180
181    /// Pop the top 8 values from the stack.
182    ///
183    /// The last values popped appear first in the returned fixed-size array.
184    pub fn pop8(&mut self) -> StackResult<[Word; 8]> {
185        let [w4, w5, w6, w7] = self.pop4()?;
186        let [w0, w1, w2, w3] = self.pop4()?;
187        Ok([w0, w1, w2, w3, w4, w5, w6, w7])
188    }
189
190    /// Pop 1 word from the stack, apply the given function and push the returned word.
191    pub fn pop1_push1<F, E>(&mut self, f: F) -> Result<(), E>
192    where
193        F: FnOnce(Word) -> Result<Word, E>,
194        E: From<StackError>,
195    {
196        let w = self.pop()?;
197        let x = f(w)?;
198        self.push(x)?;
199        Ok(())
200    }
201
202    /// Pop 2 words from the stack, apply the given function and push the returned word.
203    pub fn pop2_push1<F, E>(&mut self, f: F) -> Result<(), E>
204    where
205        F: FnOnce(Word, Word) -> Result<Word, E>,
206        E: From<StackError>,
207    {
208        let [w0, w1] = self.pop2()?;
209        let x = f(w0, w1)?;
210        self.push(x)?;
211        Ok(())
212    }
213
214    /// Pop 8 words from the stack, apply the given function and push the returned word.
215    pub fn pop8_push1<F, E>(&mut self, f: F) -> Result<(), E>
216    where
217        F: FnOnce([Word; 8]) -> Result<Word, E>,
218        E: From<StackError>,
219    {
220        let ws = self.pop8()?;
221        let x = f(ws)?;
222        self.push(x)?;
223        Ok(())
224    }
225
226    /// Pop 1 word from the stack, apply the given function and push the 2 returned words.
227    pub fn pop1_push2<F, E>(&mut self, f: F) -> Result<(), E>
228    where
229        F: FnOnce(Word) -> Result<[Word; 2], E>,
230        E: From<StackError>,
231    {
232        let w = self.pop()?;
233        let xs = f(w)?;
234        self.extend(xs)?;
235        Ok(())
236    }
237
238    /// Pop 2 words from the stack, apply the given function and push the 2 returned words.
239    pub fn pop2_push2<F, E>(&mut self, f: F) -> Result<(), E>
240    where
241        F: FnOnce(Word, Word) -> Result<[Word; 2], E>,
242        E: From<StackError>,
243    {
244        let [w0, w1] = self.pop2()?;
245        let xs = f(w0, w1)?;
246        self.extend(xs)?;
247        Ok(())
248    }
249
250    /// Pop 2 words from the stack, apply the given function and push the 4 returned words.
251    pub fn pop2_push4<F, E>(&mut self, f: F) -> Result<(), E>
252    where
253        F: FnOnce(Word, Word) -> Result<[Word; 4], E>,
254        E: From<StackError>,
255    {
256        let [w0, w1] = self.pop2()?;
257        let xs = f(w0, w1)?;
258        self.extend(xs)?;
259        Ok(())
260    }
261
262    /// Pop a length value from the top of the stack and return it.
263    pub fn pop_len(&mut self) -> StackResult<usize> {
264        let len_word = self.pop()?;
265        let len = usize::try_from(len_word).map_err(|_| StackError::IndexOutOfBounds)?;
266        Ok(len)
267    }
268
269    /// Pop the length from the top of the stack, then pop and provide that many
270    /// words to the given function.
271    pub fn pop_len_words<F, O, E>(&mut self, f: F) -> Result<O, E>
272    where
273        F: FnOnce(&[Word]) -> Result<O, E>,
274        E: From<StackError>,
275    {
276        let (rest, slice) = slice_split_len_words(self).map_err(StackError::LenWords)?;
277        let out = f(slice)?;
278        self.0.truncate(rest.len());
279        Ok(out)
280    }
281
282    /// Provide the number of words from the top of the stack to the given function.
283    /// Then pop those words off the stack.
284    pub fn pop_words<F, O, E>(&mut self, num_words: usize, f: F) -> Result<O, E>
285    where
286        F: FnOnce(&[Word]) -> Result<O, E>,
287        E: From<StackError>,
288    {
289        let (rest, slice) = slice_split_len(self, num_words).map_err(StackError::LenWords)?;
290        let out = f(slice)?;
291        self.0.truncate(rest.len());
292        Ok(out)
293    }
294
295    /// Pop two slices from the top of the stack, each followed by one word
296    /// describing their length, and pass them to the given function.
297    /// The top slice is provided to the rhs, the bottom slice is provided to the lhs.
298    pub fn pop_len_words2<F, O, E>(&mut self, f: F) -> Result<O, E>
299    where
300        F: FnOnce(&[Word], &[Word]) -> Result<O, E>,
301        E: From<StackError>,
302    {
303        let (rest, rhs) = slice_split_len_words(self).map_err(StackError::LenWords)?;
304        let (rest, lhs) = slice_split_len_words(rest).map_err(StackError::LenWords)?;
305        let out = f(lhs, rhs)?;
306        self.0.truncate(rest.len());
307        Ok(out)
308    }
309
310    /// Reserve additional capacity for the stack.
311    /// Noop if capacity already exists.
312    pub fn reserve(&mut self, additional: usize) {
313        self.0.reserve(additional);
314    }
315}
316
317/// Split a length from the top of the stack slice, then split off a slice of
318/// that length.
319///
320/// Returns `Some((remaining, slice_of_len))`.
321///
322/// Returns `None` if the slice is empty, or the length is greater than the rest
323/// of the slice.
324fn slice_split_len_words(slice: &[Word]) -> Result<(&[Word], &[Word]), LenWordsError> {
325    let (len, rest) = slice.split_last().ok_or(LenWordsError::MissingLength)?;
326    let len = usize::try_from(*len).map_err(|_| LenWordsError::InvalidLength(*len))?;
327    slice_split_len(rest, len)
328}
329
330fn slice_split_len(slice: &[Word], len: usize) -> Result<(&[Word], &[Word]), LenWordsError> {
331    let ix = slice
332        .len()
333        .checked_sub(len)
334        .ok_or(LenWordsError::OutOfBounds(len as Word))?;
335    Ok(slice.split_at(ix))
336}
337
338impl From<Stack> for Vec<Word> {
339    fn from(stack: Stack) -> Self {
340        stack.0
341    }
342}
343
344impl TryFrom<Vec<Word>> for Stack {
345    type Error = StackError;
346    fn try_from(vec: Vec<Word>) -> Result<Self, Self::Error> {
347        if vec.len() > Self::SIZE_LIMIT {
348            Err(StackError::Overflow)
349        } else {
350            Ok(Self(vec))
351        }
352    }
353}
354
355impl core::ops::Deref for Stack {
356    type Target = Vec<Word>;
357    fn deref(&self) -> &Self::Target {
358        &self.0
359    }
360}
361
362#[cfg(test)]
363mod tests {
364    use crate::{
365        asm::Stack,
366        error::{ExecSyncError, OpSyncError, StackError},
367        sync::{exec_ops, test_util::*},
368    };
369
370    #[test]
371    fn dup_from_1() {
372        let ops = &[
373            Stack::Push(42).into(),
374            Stack::Push(2).into(),
375            Stack::Push(1).into(),
376            Stack::Push(0).into(),
377            Stack::Push(3).into(), // Index `3` should be the `42` value.
378            Stack::DupFrom.into(),
379        ];
380        let stack = exec_ops(ops, *test_access()).unwrap();
381        assert_eq!(&stack[..], &[42, 2, 1, 0, 42]);
382    }
383
384    #[test]
385    fn dup_from_2() {
386        let ops = &[
387            Stack::Push(3).into(),
388            Stack::Push(2).into(),
389            Stack::Push(1).into(),
390            Stack::Push(42).into(),
391            Stack::Push(0).into(), // Index `0` should be the `42` value.
392            Stack::DupFrom.into(),
393        ];
394        let stack = exec_ops(ops, *test_access()).unwrap();
395        assert_eq!(&stack[..], &[3, 2, 1, 42, 42]);
396    }
397
398    #[test]
399    fn push1() {
400        let ops = &[Stack::Push(42).into()];
401        let stack = exec_ops(ops, *test_access()).unwrap();
402        assert_eq!(&stack[..], &[42]);
403    }
404
405    #[test]
406    fn push2_pop_push() {
407        let ops = &[
408            Stack::Push(1).into(),
409            Stack::Push(2).into(),
410            Stack::Pop.into(),
411            Stack::Push(3).into(),
412        ];
413        let stack = exec_ops(ops, *test_access()).unwrap();
414        assert_eq!(&stack[..], &[1, 3]);
415    }
416
417    #[test]
418    fn pop_empty() {
419        let ops = &[Stack::Pop.into()];
420        match exec_ops(ops, *test_access()) {
421            Err(ExecSyncError(0, OpSyncError::Stack(StackError::Empty))) => (),
422            _ => panic!("expected empty stack error"),
423        }
424    }
425
426    #[test]
427    fn index_oob() {
428        let ops = &[Stack::Push(0).into(), Stack::DupFrom.into()];
429        match exec_ops(ops, *test_access()) {
430            Err(ExecSyncError(1, OpSyncError::Stack(StackError::IndexOutOfBounds))) => (),
431            _ => panic!("expected index out-of-bounds stack error"),
432        }
433    }
434
435    #[test]
436    fn swap_index() {
437        let ops = &[
438            Stack::Push(3).into(),
439            Stack::Push(4).into(),
440            Stack::Push(5).into(),
441            Stack::Push(42).into(),
442            Stack::Push(2).into(), // Index `2` should be swapped with the `42` value.
443            Stack::SwapIndex.into(),
444        ];
445        let stack = exec_ops(ops, *test_access()).unwrap();
446        assert_eq!(&stack[..], &[3, 42, 5, 4]);
447    }
448
449    #[test]
450    fn swap_index_oob() {
451        let ops = &[
452            Stack::Push(3).into(),
453            Stack::Push(4).into(),
454            Stack::Push(2).into(), // Index `2` is out of range.
455            Stack::SwapIndex.into(),
456        ];
457        match exec_ops(ops, *test_access()) {
458            Err(ExecSyncError(3, OpSyncError::Stack(StackError::IndexOutOfBounds))) => (),
459            _ => panic!("expected index out-of-bounds stack error"),
460        }
461    }
462
463    #[test]
464    fn select() {
465        let ops = &[
466            Stack::Push(3).into(),
467            Stack::Push(4).into(),
468            Stack::Push(1).into(),
469            Stack::Select.into(),
470        ];
471        let stack = exec_ops(ops, *test_access()).unwrap();
472        assert_eq!(&stack[..], &[4]);
473    }
474
475    #[test]
476    fn select_range_cond_1() {
477        let ops = &[
478            Stack::Push(4).into(),
479            Stack::Push(4).into(),
480            Stack::Push(4).into(),
481            Stack::Push(5).into(),
482            Stack::Push(5).into(),
483            Stack::Push(5).into(),
484            Stack::Push(3).into(), // len
485            Stack::Push(1).into(), // cond
486            Stack::SelectRange.into(),
487        ];
488        let stack = exec_ops(ops, *test_access()).unwrap();
489        assert_eq!(&stack[..], &[5, 5, 5]);
490    }
491
492    #[test]
493    fn select_range_cond_0() {
494        let ops = &[
495            Stack::Push(4).into(),
496            Stack::Push(4).into(),
497            Stack::Push(4).into(),
498            Stack::Push(5).into(),
499            Stack::Push(5).into(),
500            Stack::Push(5).into(),
501            Stack::Push(3).into(), // len
502            Stack::Push(0).into(), // cond
503            Stack::SelectRange.into(),
504        ];
505        let stack = exec_ops(ops, *test_access()).unwrap();
506        assert_eq!(&stack[..], &[4, 4, 4]);
507    }
508
509    #[test]
510    fn select_range_cond_invalid() {
511        let ops = &[
512            Stack::Push(4).into(),
513            Stack::Push(5).into(),
514            Stack::Push(1).into(),  // len
515            Stack::Push(42).into(), // cond
516            Stack::SelectRange.into(),
517        ];
518        match exec_ops(ops, *test_access()) {
519            Err(ExecSyncError(4, OpSyncError::Stack(StackError::InvalidCondition(42)))) => (),
520            _ => panic!("expected invalid condition stack error"),
521        }
522    }
523
524    #[test]
525    fn select_range_len_0() {
526        let ops = &[
527            Stack::Push(4).into(),
528            Stack::Push(5).into(),
529            Stack::Push(0).into(), // len
530            Stack::Push(0).into(), // cond
531            Stack::SelectRange.into(),
532        ];
533        let stack = exec_ops(ops, *test_access()).unwrap();
534        assert_eq!(&stack[..], &[4, 5]);
535    }
536
537    #[test]
538    fn select_range_len_negative() {
539        let ops = &[
540            Stack::Push(-42).into(), // len
541            Stack::Push(0).into(),   // cond
542            Stack::SelectRange.into(),
543        ];
544        match exec_ops(ops, *test_access()) {
545            Err(ExecSyncError(2, OpSyncError::Stack(StackError::IndexOutOfBounds))) => (),
546            _ => panic!("expected index out of bounds stack error"),
547        }
548    }
549
550    #[test]
551    fn select_range_len_too_big() {
552        let ops = &[
553            Stack::Push(4).into(),
554            Stack::Push(4).into(),
555            Stack::Push(5).into(),
556            Stack::Push(2).into(), // len
557            Stack::Push(0).into(), // cond
558            Stack::SelectRange.into(),
559        ];
560        match exec_ops(ops, *test_access()) {
561            Err(ExecSyncError(5, OpSyncError::Stack(StackError::IndexOutOfBounds))) => (),
562            _ => panic!("expected index out of bounds stack error"),
563        }
564    }
565}