essential_constraint_vm/
stack.rs

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