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}