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