miden_core/stack/
outputs.rs

1use alloc::vec::Vec;
2use core::ops::Deref;
3
4use miden_crypto::{WORD_SIZE, Word, ZERO};
5
6use super::{ByteWriter, Felt, MIN_STACK_DEPTH, OutputError, Serializable, get_num_stack_values};
7use crate::utils::{ByteReader, Deserializable, DeserializationError, range};
8
9// STACK OUTPUTS
10// ================================================================================================
11
12/// Output container for Miden VM programs.
13///
14/// Miden program outputs contain the full state of the stack at the end of execution.
15///
16/// `stack` is expected to be ordered as if the elements were popped off the stack one by one.
17/// Thus, the value at the top of the stack is expected to be in the first position, and the order
18/// of the rest of the output elements will also match the order on the stack.
19#[derive(Debug, Clone, Default, PartialEq, Eq)]
20pub struct StackOutputs {
21    elements: [Felt; MIN_STACK_DEPTH],
22}
23
24impl StackOutputs {
25    // CONSTRUCTORS
26    // --------------------------------------------------------------------------------------------
27
28    /// Constructs a new [StackOutputs] struct from the provided stack elements.
29    ///
30    /// # Errors
31    ///  Returns an error if the number of stack elements is greater than `MIN_STACK_DEPTH` (16).
32    pub fn new(mut stack: Vec<Felt>) -> Result<Self, OutputError> {
33        // validate stack length
34        if stack.len() > MIN_STACK_DEPTH {
35            return Err(OutputError::OutputSizeTooBig(stack.len()));
36        }
37        stack.resize(MIN_STACK_DEPTH, ZERO);
38
39        Ok(Self { elements: stack.try_into().unwrap() })
40    }
41
42    /// Attempts to create [StackOutputs] struct from the provided stack elements represented as
43    /// vector of `u64` values.
44    ///
45    /// # Errors
46    /// Returns an error if:
47    /// - Any of the provided stack elements are invalid field elements.
48    pub fn try_from_ints<I>(iter: I) -> Result<Self, OutputError>
49    where
50        I: IntoIterator<Item = u64>,
51    {
52        // Validate stack elements
53        let stack = iter
54            .into_iter()
55            .map(Felt::try_from)
56            .collect::<Result<Vec<Felt>, _>>()
57            .map_err(OutputError::InvalidStackElement)?;
58
59        Self::new(stack)
60    }
61
62    // PUBLIC ACCESSORS
63    // --------------------------------------------------------------------------------------------
64
65    /// Returns the element located at the specified position on the stack or `None` if out of
66    /// bounds.
67    pub fn get_stack_item(&self, idx: usize) -> Option<Felt> {
68        self.elements.get(idx).cloned()
69    }
70
71    /// Returns the word located starting at the specified Felt position on the stack in big-endian
72    /// (reversed) order, or `None` if out of bounds.
73    ///
74    /// For example, passing in `0` returns the word at the top of the stack, and passing in `4`
75    /// returns the word starting at element index `4`.
76    ///
77    /// In big-endian order, stack element N+3 will be at position 0 of the word, N+2 at
78    /// position 1, N+1 at position 2, and N at position 3. This matches the behavior of
79    /// `mem_loadw_be` where `mem[a+3]` ends up on top of the stack.
80    pub fn get_stack_word_be(&self, idx: usize) -> Option<Word> {
81        let word_elements: [Felt; WORD_SIZE] = {
82            let word_elements: Vec<Felt> = range(idx, 4)
83                .map(|idx| self.get_stack_item(idx))
84                // Elements need to be reversed, since a word `[a, b, c, d]` will be stored on the
85                // stack as `[d, c, b, a]`
86                .rev()
87                .collect::<Option<_>>()?;
88
89            word_elements.try_into().expect("a Word contains 4 elements")
90        };
91
92        Some(word_elements.into())
93    }
94
95    /// Returns the word located starting at the specified Felt position on the stack in
96    /// little-endian (memory) order, or `None` if out of bounds.
97    ///
98    /// For example, passing in `0` returns the word at the top of the stack, and passing in `4`
99    /// returns the word starting at element index `4`.
100    ///
101    /// In little-endian order, stack element N will be at position 0 of the word, N+1 at
102    /// position 1, N+2 at position 2, and N+3 at position 3. This matches the behavior of
103    /// `mem_loadw_le` where `mem[a]` ends up on top of the stack.
104    pub fn get_stack_word_le(&self, idx: usize) -> Option<Word> {
105        self.get_stack_word_be(idx).map(|mut word| {
106            word.reverse();
107            word
108        })
109    }
110
111    /// Returns the word located starting at the specified Felt position on the stack or `None` if
112    /// out of bounds.
113    ///
114    /// This is an alias for [`Self::get_stack_word_be`] for backward compatibility. For new code,
115    /// prefer using the explicit `get_stack_word_be()` or `get_stack_word_le()` to make the
116    /// ordering expectations clear.
117    ///
118    /// See [`Self::get_stack_word_be`] for detailed documentation.
119    #[deprecated(
120        since = "0.19.0",
121        note = "Use `get_stack_word_be()` or `get_stack_word_le()` to make endianness explicit"
122    )]
123    pub fn get_stack_word(&self, idx: usize) -> Option<Word> {
124        self.get_stack_word_be(idx)
125    }
126
127    /// Returns the number of requested stack outputs or returns the full stack if fewer than the
128    /// requested number of stack values exist.
129    pub fn stack_truncated(&self, num_outputs: usize) -> &[Felt] {
130        let len = self.elements.len().min(num_outputs);
131        &self.elements[..len]
132    }
133
134    // PUBLIC MUTATORS
135    // --------------------------------------------------------------------------------------------
136
137    /// Returns mutable access to the stack outputs, to be used for testing or running examples.
138    pub fn stack_mut(&mut self) -> &mut [Felt] {
139        &mut self.elements
140    }
141
142    /// Converts the [`StackOutputs`] into the vector of `u64` values.
143    pub fn as_int_vec(&self) -> Vec<u64> {
144        self.elements.iter().map(|e| (*e).as_int()).collect()
145    }
146}
147
148impl Deref for StackOutputs {
149    type Target = [Felt; MIN_STACK_DEPTH];
150
151    fn deref(&self) -> &Self::Target {
152        &self.elements
153    }
154}
155
156impl From<[Felt; MIN_STACK_DEPTH]> for StackOutputs {
157    fn from(value: [Felt; MIN_STACK_DEPTH]) -> Self {
158        Self { elements: value }
159    }
160}
161
162// SERIALIZATION
163// ================================================================================================
164
165impl Serializable for StackOutputs {
166    fn write_into<W: ByteWriter>(&self, target: &mut W) {
167        let num_stack_values = get_num_stack_values(self);
168        target.write_u8(num_stack_values);
169        target.write_many(&self.elements[..num_stack_values as usize]);
170    }
171}
172
173impl Deserializable for StackOutputs {
174    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
175        let num_elements = source.read_u8()?;
176
177        let elements = source.read_many::<Felt>(num_elements.into())?;
178
179        StackOutputs::new(elements).map_err(|err| {
180            DeserializationError::InvalidValue(format!("failed to create stack outputs: {err}",))
181        })
182    }
183}