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}