Skip to main content

miden_core/program/stack/
outputs.rs

1use alloc::vec::Vec;
2use core::ops::Deref;
3
4use super::{MIN_STACK_DEPTH, get_num_stack_values};
5use crate::{
6    Felt, WORD_SIZE, Word, ZERO,
7    serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
8};
9
10// STACK OUTPUTS
11// ================================================================================================
12
13/// Defines the final state of the VM's operand stack at the end of program execution.
14///
15/// The first element is at position 0 (top of stack).
16#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
17#[cfg_attr(
18    all(feature = "arbitrary", test),
19    miden_test_serialization_macros::serialization_test
20)]
21pub struct StackOutputs {
22    elements: [Felt; MIN_STACK_DEPTH],
23}
24
25impl StackOutputs {
26    // CONSTRUCTORS
27    // --------------------------------------------------------------------------------------------
28
29    /// Constructs a new [StackOutputs] struct from the provided stack elements.
30    ///
31    /// # Errors
32    ///  Returns an error if the number of stack elements is greater than `MIN_STACK_DEPTH` (16).
33    pub fn new(values: &[Felt]) -> Result<Self, OutputError> {
34        if values.len() > MIN_STACK_DEPTH {
35            return Err(OutputError::OutputStackTooBig(MIN_STACK_DEPTH, values.len()));
36        }
37
38        let mut elements = [ZERO; MIN_STACK_DEPTH];
39        elements[..values.len()].copy_from_slice(values);
40
41        Ok(Self { elements })
42    }
43
44    // PUBLIC ACCESSORS
45    // --------------------------------------------------------------------------------------------
46
47    /// Returns the element located at the specified position on the stack or `None` if out of
48    /// bounds.
49    pub fn get_element(&self, idx: usize) -> Option<Felt> {
50        self.elements.get(idx).cloned()
51    }
52
53    /// Returns the word located starting at the specified Felt position on the stack in
54    /// little-endian order, or `None` if out of bounds.
55    ///
56    /// For example, passing in `0` returns the word at the top of the stack, and passing in `4`
57    /// returns the word starting at element index `4`.
58    ///
59    /// Stack element N will be at position 0 of the word, N+1 at position 1, N+2 at position 2,
60    /// and N+3 at position 3. `Word[0]` corresponds to the top of the stack.
61    pub fn get_word(&self, idx: usize) -> Option<Word> {
62        if idx > MIN_STACK_DEPTH - WORD_SIZE {
63            return None;
64        }
65
66        Some(Word::from([
67            self.elements[idx],
68            self.elements[idx + 1],
69            self.elements[idx + 2],
70            self.elements[idx + 3],
71        ]))
72    }
73
74    /// Returns the number of requested elements up to the maximum available in the stack outputs.
75    pub fn get_num_elements(&self, num_outputs: usize) -> &[Felt] {
76        let len = self.elements.len().min(num_outputs);
77        &self.elements[..len]
78    }
79
80    // TESTING UTILITIES
81    // --------------------------------------------------------------------------------------------
82
83    /// Attempts to create [StackOutputs] struct from the provided stack elements represented as
84    /// vector of `u64` values.
85    ///
86    /// # Errors
87    /// Returns an error if:
88    /// - Any of the provided stack elements are invalid field elements.
89    #[cfg(any(test, feature = "testing"))]
90    pub fn try_from_ints<I>(iter: I) -> Result<Self, OutputError>
91    where
92        I: IntoIterator<Item = u64>,
93    {
94        use miden_crypto::field::QuotientMap;
95
96        // Validate stack elements
97        let values = iter
98            .into_iter()
99            .map(|v| Felt::from_canonical_checked(v).ok_or(OutputError::InvalidStackElement(v)))
100            .collect::<Result<Vec<Felt>, _>>()?;
101
102        Self::new(&values)
103    }
104
105    /// Converts the [`StackOutputs`] into the vector of `u64` values.
106    #[cfg(any(test, feature = "testing"))]
107    pub fn as_int_vec(&self) -> Vec<u64> {
108        self.elements.iter().map(|e| (*e).as_canonical_u64()).collect()
109    }
110}
111
112impl Deref for StackOutputs {
113    type Target = [Felt; MIN_STACK_DEPTH];
114
115    fn deref(&self) -> &Self::Target {
116        &self.elements
117    }
118}
119
120impl From<[Felt; MIN_STACK_DEPTH]> for StackOutputs {
121    fn from(value: [Felt; MIN_STACK_DEPTH]) -> Self {
122        Self { elements: value }
123    }
124}
125
126#[cfg(any(test, feature = "testing"))]
127impl AsMut<[Felt]> for StackOutputs {
128    /// Returns mutable access to the stack outputs, to be used for testing.
129    fn as_mut(&mut self) -> &mut [Felt] {
130        &mut self.elements
131    }
132}
133
134// SERIALIZATION
135// ================================================================================================
136
137impl Serializable for StackOutputs {
138    fn write_into<W: ByteWriter>(&self, target: &mut W) {
139        let num_stack_values = get_num_stack_values(self);
140        target.write_u8(num_stack_values);
141        target.write_many(&self.elements[..num_stack_values as usize]);
142    }
143}
144
145impl Deserializable for StackOutputs {
146    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
147        let num_elements = source.read_u8()?;
148
149        let elements: Vec<Felt> =
150            source.read_many_iter::<Felt>(num_elements.into())?.collect::<Result<_, _>>()?;
151
152        StackOutputs::new(&elements).map_err(|err| {
153            DeserializationError::InvalidValue(format!("failed to create stack outputs: {err}",))
154        })
155    }
156}
157
158// OUTPUT ERROR
159// ================================================================================================
160
161#[derive(Clone, Debug, thiserror::Error)]
162pub enum OutputError {
163    #[error("value {0} exceeds field modulus")]
164    InvalidStackElement(u64),
165    #[error("number of output values on the stack cannot exceed {0}, but was {1}")]
166    OutputStackTooBig(usize, usize),
167}
168
169// ARBITRARY (proptest)
170// ================================================================================================
171
172#[cfg(any(test, feature = "arbitrary"))]
173mod arbitrary {
174    use proptest::prelude::*;
175
176    use super::{MIN_STACK_DEPTH, StackOutputs};
177    use crate::Felt;
178
179    impl Arbitrary for StackOutputs {
180        type Parameters = ();
181        type Strategy = BoxedStrategy<Self>;
182
183        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
184            // Any field elements are valid stack outputs. Serialization trims trailing zeros and
185            // deserialization re-pads with `ZERO`, and `Felt` equality ignores representation,
186            // so exact round-trip equality holds.
187            any::<[Felt; MIN_STACK_DEPTH]>().prop_map(StackOutputs::from).boxed()
188        }
189    }
190}