Skip to main content

miden_core/advice/
stack.rs

1use alloc::{collections::VecDeque, vec::Vec};
2
3use super::{AdviceInputs, AdviceMap};
4use crate::{Felt, Word, crypto::merkle::MerkleStore, field::PrimeField64};
5
6// ADVICE STACK BUILDER
7// ================================================================================================
8
9/// A builder for constructing advice stack inputs with intuitive ordering.
10///
11/// The builder maintains a conceptual advice stack where index 0 is the "top" - i.e., the element
12/// that will be consumed first. Method names indicate which MASM instruction pattern they target,
13/// abstracting away the internal transformations needed for correct element ordering.
14///
15/// # Building Direction
16///
17/// Building happens "top-first": the first method call adds elements that will be consumed first
18/// by the MASM code. Each subsequent method call adds elements "below" the previous ones.
19///
20/// # Example
21///
22/// ```ignore
23/// let advice = AdviceStackBuilder::new()
24///     .push_for_adv_push(&[a, b, c])  // Consumed first by adv_push.3
25///     .push_for_adv_loadw(word)       // Consumed second by adv_loadw
26///     .build();
27/// ```
28#[derive(Clone, Debug, Default)]
29pub struct AdviceStackBuilder {
30    /// Conceptual stack where front (index 0) is "top" (consumed first).
31    stack: VecDeque<Felt>,
32}
33
34impl AdviceStackBuilder {
35    /// Creates a new empty builder.
36    pub fn new() -> Self {
37        Self::default()
38    }
39
40    // STATE MUTATORS
41    // --------------------------------------------------------------------------------------------
42
43    /// Pushes a single element onto the advice stack.
44    ///
45    /// Elements are consumed in FIFO order: first pushed = first consumed by advice operations.
46    pub fn push_element(&mut self, value: Felt) -> &mut Self {
47        self.stack.push_back(value);
48        self
49    }
50
51    /// Extends the advice stack with raw elements (already ordered top-to-bottom).
52    ///
53    /// Elements are consumed in FIFO order: first element in iter = first consumed.
54    pub fn push_elements<I>(&mut self, values: I) -> &mut Self
55    where
56        I: IntoIterator<Item = Felt>,
57    {
58        self.stack.extend(values);
59        self
60    }
61
62    /// Adds elements for consumption by `adv_push.n` instructions.
63    ///
64    /// After `adv_push.n`, the operand stack will have `slice[0]` on top.
65    /// The slice length determines n (e.g., 4-element slice → `adv_push.4`).
66    ///
67    /// # How it works
68    ///
69    /// `adv_push.n` pops elements one-by-one from the advice stack and pushes each to the operand
70    /// stack. Since each push goes to the top, the first-popped element ends up at the bottom
71    /// of the n elements, and the last-popped element ends up on top.
72    ///
73    /// Therefore, this method reverses the slice internally so that `slice[0]` is popped last
74    /// and ends up on top of the operand stack.
75    ///
76    /// # Example
77    ///
78    /// ```ignore
79    /// builder.push_for_adv_push(&[a, b, c]);
80    /// // MASM: adv_push.3
81    /// // Result: operand stack = [a, b, c, ...] with a on top
82    /// ```
83    pub fn push_for_adv_push(&mut self, slice: &[Felt]) -> &mut Self {
84        // Reverse the slice: we want slice[0] to be popped last (ending up on top of operand stack)
85        // So we add elements in reverse order to the back of our stack
86        for elem in slice.iter().rev() {
87            self.stack.push_back(*elem);
88        }
89        self
90    }
91
92    /// Adds a word for consumption by `padw adv_loadw`.
93    ///
94    /// After `adv_loadw`, the operand stack will have the structural word loaded directly.
95    /// Use `reversew` afterward to convert to canonical (little-endian) order.
96    ///
97    /// # How it works
98    ///
99    /// The `adv_loadw` instruction:
100    /// 1. Calls `pop_stack_word()` which pops 4 elements from front and creates
101    ///    `Word::new(\[e0,e1,e2,e3\])`
102    /// 2. Places the word on the operand stack with `word\[0\]` on top, `word\[1\]` at position 1,
103    ///    etc.
104    ///
105    /// Elements are pushed without reversal since `adv_loadw` loads the structural word directly.
106    ///
107    /// # Example
108    ///
109    /// ```ignore
110    /// builder.push_for_adv_loadw([w0, w1, w2, w3].into());
111    /// // MASM: padw adv_loadw
112    /// // Result: operand stack = [w0, w1, w2, w3, ...] with w0 on top
113    /// ```
114    pub fn push_for_adv_loadw(&mut self, word: Word) -> &mut Self {
115        // Push elements without reversal. adv_loadw loads the structural word directly,
116        // so a `reversew` is needed afterward to get canonical order on the operand stack.
117        for elem in word.iter() {
118            self.stack.push_back(*elem);
119        }
120        self
121    }
122
123    /// Adds elements for sequential consumption by `adv_pipe` operations.
124    ///
125    /// Elements are consumed in order: `slice[0..8]` first, then `slice[8..16]`, etc.
126    /// No reversal is applied.
127    ///
128    /// # Panics
129    ///
130    /// Panics if the slice length is not a multiple of 8 (double-word aligned).
131    ///
132    /// # Example
133    ///
134    /// ```ignore
135    /// builder.push_for_adv_pipe(&elements); // elements.len() must be multiple of 8
136    /// // MASM: multiple adv_pipe calls
137    /// // Result: elements consumed in order [elements[0], elements[1], ...]
138    /// ```
139    pub fn push_for_adv_pipe(&mut self, slice: &[Felt]) -> &mut Self {
140        assert!(
141            slice.len().is_multiple_of(8),
142            "push_for_adv_pipe requires slice length to be a multiple of 8, got {}",
143            slice.len()
144        );
145
146        for elem in slice.iter() {
147            self.stack.push_back(*elem);
148        }
149        self
150    }
151
152    /// Extends the advice stack with u64 values converted to Felt.
153    ///
154    /// This is a convenience method for test data that is typically specified as u64.
155    /// Elements are consumed in FIFO order: first element = first consumed.
156    ///
157    /// # Example
158    ///
159    /// ```ignore
160    /// builder.push_u64_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
161    /// // Elements consumed in order: 1, 2, 3, 4, 5, 6, 7, 8
162    /// ```
163    pub fn push_u64_slice(&mut self, values: &[u64]) -> &mut Self {
164        self.stack.extend(values.iter().map(|&v| Felt::new(v)));
165        self
166    }
167
168    // INPUT BUILDERS
169    // --------------------------------------------------------------------------------------------
170
171    /// Builds the `AdviceInputs` from the accumulated stack.
172    ///
173    /// The builder's conceptual stack (with index 0 as top) is converted to the format
174    /// expected by `AdviceInputs`, which will be reversed when creating an `AdviceProvider`.
175    pub fn build(self) -> AdviceInputs {
176        AdviceInputs {
177            stack: self.stack.into(),
178            map: AdviceMap::default(),
179            store: MerkleStore::default(),
180        }
181    }
182
183    /// Builds the `AdviceInputs` with additional map and store data.
184    pub fn build_with(self, map: AdviceMap, store: MerkleStore) -> AdviceInputs {
185        AdviceInputs { stack: self.stack.into(), map, store }
186    }
187
188    /// Builds just the advice stack as `Vec<u64>` for use with `build_test!` macro.
189    ///
190    /// This is a convenience method that avoids needing to modify the test infrastructure.
191    pub fn build_vec_u64(self) -> Vec<u64> {
192        self.stack.into_iter().map(|f| f.as_canonical_u64()).collect()
193    }
194
195    /// Consumes the builder and returns the accumulated elements as `Vec<Felt>`.
196    pub fn into_elements(self) -> Vec<Felt> {
197        self.stack.into_iter().collect()
198    }
199}