Skip to main content

miden_core/mast/node/
loop_node.rs

1use alloc::{boxed::Box, vec::Vec};
2use core::fmt;
3
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Serialize};
6
7use super::{MastForestContributor, MastNodeExt};
8use crate::{
9    Felt, Word,
10    chiplets::hasher,
11    mast::{
12        DecoratorId, DecoratorStore, MastForest, MastForestError, MastNode, MastNodeFingerprint,
13        MastNodeId,
14    },
15    operations::OPCODE_LOOP,
16    prettier::PrettyPrint,
17    utils::{Idx, LookupByIdx},
18};
19
20// LOOP NODE
21// ================================================================================================
22
23/// A Loop node defines condition-controlled iterative execution. When the VM encounters a Loop
24/// node, it will keep executing the body of the loop as long as the top of the stack is `1``.
25///
26/// The loop is exited when at the end of executing the loop body the top of the stack is `0``.
27/// If the top of the stack is neither `0` nor `1` when the condition is checked, the execution
28/// fails.
29#[derive(Debug, Clone, PartialEq, Eq)]
30#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
31#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
32pub struct LoopNode {
33    body: MastNodeId,
34    digest: Word,
35    decorator_store: DecoratorStore,
36}
37
38/// Constants
39impl LoopNode {
40    /// The domain of the loop node (used for control block hashing).
41    pub const DOMAIN: Felt = Felt::new(OPCODE_LOOP as u64);
42}
43
44impl LoopNode {
45    /// Returns the ID of the node presenting the body of the loop.
46    pub fn body(&self) -> MastNodeId {
47        self.body
48    }
49}
50
51// PRETTY PRINTING
52// ================================================================================================
53
54impl LoopNode {
55    pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a {
56        LoopNodePrettyPrint { loop_node: self, mast_forest }
57    }
58
59    pub(super) fn to_pretty_print<'a>(
60        &'a self,
61        mast_forest: &'a MastForest,
62    ) -> impl PrettyPrint + 'a {
63        LoopNodePrettyPrint { loop_node: self, mast_forest }
64    }
65}
66
67struct LoopNodePrettyPrint<'a> {
68    loop_node: &'a LoopNode,
69    mast_forest: &'a MastForest,
70}
71
72impl crate::prettier::PrettyPrint for LoopNodePrettyPrint<'_> {
73    fn render(&self) -> crate::prettier::Document {
74        use crate::prettier::*;
75
76        let pre_decorators = {
77            let mut pre_decorators = self
78                .loop_node
79                .before_enter(self.mast_forest)
80                .iter()
81                .map(|&decorator_id| self.mast_forest[decorator_id].render())
82                .reduce(|acc, doc| acc + const_text(" ") + doc)
83                .unwrap_or_default();
84            if !pre_decorators.is_empty() {
85                pre_decorators += nl();
86            }
87
88            pre_decorators
89        };
90
91        let post_decorators = {
92            let mut post_decorators = self
93                .loop_node
94                .after_exit(self.mast_forest)
95                .iter()
96                .map(|&decorator_id| self.mast_forest[decorator_id].render())
97                .reduce(|acc, doc| acc + const_text(" ") + doc)
98                .unwrap_or_default();
99            if !post_decorators.is_empty() {
100                post_decorators = nl() + post_decorators;
101            }
102
103            post_decorators
104        };
105
106        let loop_body = self.mast_forest[self.loop_node.body].to_pretty_print(self.mast_forest);
107
108        pre_decorators
109            + indent(4, const_text("while.true") + nl() + loop_body.render())
110            + nl()
111            + const_text("end")
112            + post_decorators
113    }
114}
115
116impl fmt::Display for LoopNodePrettyPrint<'_> {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        use crate::prettier::PrettyPrint;
119        self.pretty_print(f)
120    }
121}
122
123// MAST NODE TRAIT IMPLEMENTATION
124// ================================================================================================
125
126impl MastNodeExt for LoopNode {
127    /// Returns a commitment to this Loop node.
128    ///
129    /// The commitment is computed as a hash of the loop body and an empty word ([ZERO; 4]) in
130    /// the domain defined by [Self::DOMAIN] - i..e,:
131    /// ```
132    /// # use miden_core::mast::LoopNode;
133    /// # use miden_crypto::{Word, hash::poseidon2::Poseidon2 as Hasher};
134    /// # let body_digest = Word::default();
135    /// Hasher::merge_in_domain(&[body_digest, Word::default()], LoopNode::DOMAIN);
136    /// ```
137    fn digest(&self) -> Word {
138        self.digest
139    }
140
141    /// Returns the decorators to be executed before this node is executed.
142    fn before_enter<'a>(&'a self, forest: &'a MastForest) -> &'a [DecoratorId] {
143        #[cfg(debug_assertions)]
144        self.verify_node_in_forest(forest);
145        self.decorator_store.before_enter(forest)
146    }
147
148    /// Returns the decorators to be executed after this node is executed.
149    fn after_exit<'a>(&'a self, forest: &'a MastForest) -> &'a [DecoratorId] {
150        #[cfg(debug_assertions)]
151        self.verify_node_in_forest(forest);
152        self.decorator_store.after_exit(forest)
153    }
154
155    fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn fmt::Display + 'a> {
156        Box::new(LoopNode::to_display(self, mast_forest))
157    }
158
159    fn to_pretty_print<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn PrettyPrint + 'a> {
160        Box::new(LoopNode::to_pretty_print(self, mast_forest))
161    }
162
163    fn has_children(&self) -> bool {
164        true
165    }
166
167    fn append_children_to(&self, target: &mut Vec<MastNodeId>) {
168        target.push(self.body());
169    }
170
171    fn for_each_child<F>(&self, mut f: F)
172    where
173        F: FnMut(MastNodeId),
174    {
175        f(self.body());
176    }
177
178    fn domain(&self) -> Felt {
179        Self::DOMAIN
180    }
181
182    type Builder = LoopNodeBuilder;
183
184    fn to_builder(self, forest: &MastForest) -> Self::Builder {
185        // Extract decorators from decorator_store if in Owned state
186        match self.decorator_store {
187            DecoratorStore::Owned { before_enter, after_exit, .. } => {
188                let mut builder = LoopNodeBuilder::new(self.body);
189                builder = builder.with_before_enter(before_enter).with_after_exit(after_exit);
190                builder
191            },
192            DecoratorStore::Linked { id } => {
193                // Extract decorators from forest storage when in Linked state
194                let before_enter = forest.before_enter_decorators(id).to_vec();
195                let after_exit = forest.after_exit_decorators(id).to_vec();
196                let mut builder = LoopNodeBuilder::new(self.body);
197                builder = builder.with_before_enter(before_enter).with_after_exit(after_exit);
198                builder
199            },
200        }
201    }
202
203    #[cfg(debug_assertions)]
204    fn verify_node_in_forest(&self, forest: &MastForest) {
205        if let Some(id) = self.decorator_store.linked_id() {
206            // Verify that this node is the one stored at the given ID in the forest
207            let self_ptr = self as *const Self;
208            let forest_node = &forest.nodes[id];
209            let forest_node_ptr = match forest_node {
210                MastNode::Loop(loop_node) => loop_node as *const LoopNode as *const (),
211                _ => panic!("Node type mismatch at {:?}", id),
212            };
213            let self_as_void = self_ptr as *const ();
214            debug_assert_eq!(
215                self_as_void, forest_node_ptr,
216                "Node pointer mismatch: expected node at {:?} to be self",
217                id
218            );
219        }
220    }
221}
222
223// ARBITRARY IMPLEMENTATION
224// ================================================================================================
225
226#[cfg(all(feature = "arbitrary", test))]
227impl proptest::prelude::Arbitrary for LoopNode {
228    type Parameters = ();
229
230    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
231        use proptest::prelude::*;
232
233        use crate::Felt;
234
235        // Generate one MastNodeId value and digest for the body
236        (any::<MastNodeId>(), any::<[u64; 4]>())
237            .prop_map(|(body, digest_array)| {
238                // Generate a random digest
239                let digest = Word::from(digest_array.map(Felt::new));
240                // Construct directly to avoid MastForest validation for arbitrary data
241                LoopNode {
242                    body,
243                    digest,
244                    decorator_store: DecoratorStore::default(),
245                }
246            })
247            .no_shrink()  // Pure random values, no meaningful shrinking pattern
248            .boxed()
249    }
250
251    type Strategy = proptest::prelude::BoxedStrategy<Self>;
252}
253
254// ------------------------------------------------------------------------------------------------
255/// Builder for creating [`LoopNode`] instances with decorators.
256#[derive(Debug)]
257pub struct LoopNodeBuilder {
258    body: MastNodeId,
259    before_enter: Vec<DecoratorId>,
260    after_exit: Vec<DecoratorId>,
261    digest: Option<Word>,
262}
263
264impl LoopNodeBuilder {
265    /// Creates a new builder for a LoopNode with the specified body.
266    pub fn new(body: MastNodeId) -> Self {
267        Self {
268            body,
269            before_enter: Vec::new(),
270            after_exit: Vec::new(),
271            digest: None,
272        }
273    }
274
275    /// Builds the LoopNode with the specified decorators.
276    pub fn build(self, mast_forest: &MastForest) -> Result<LoopNode, MastForestError> {
277        if self.body.to_usize() >= mast_forest.nodes.len() {
278            return Err(MastForestError::NodeIdOverflow(self.body, mast_forest.nodes.len()));
279        }
280
281        // Use the forced digest if provided, otherwise compute the digest
282        let digest = if let Some(forced_digest) = self.digest {
283            forced_digest
284        } else {
285            let body_hash = mast_forest[self.body].digest();
286
287            hasher::merge_in_domain(&[body_hash, Word::default()], LoopNode::DOMAIN)
288        };
289
290        Ok(LoopNode {
291            body: self.body,
292            digest,
293            decorator_store: DecoratorStore::new_owned_with_decorators(
294                self.before_enter,
295                self.after_exit,
296            ),
297        })
298    }
299}
300
301impl MastForestContributor for LoopNodeBuilder {
302    fn add_to_forest(self, forest: &mut MastForest) -> Result<MastNodeId, MastForestError> {
303        let node = self.build(forest)?;
304
305        let LoopNode {
306            body,
307            digest,
308            decorator_store: DecoratorStore::Owned { before_enter, after_exit, .. },
309        } = node
310        else {
311            unreachable!("LoopNodeBuilder::build() should always return owned decorators");
312        };
313
314        // Determine the node ID that will be assigned
315        let future_node_id = MastNodeId::new_unchecked(forest.nodes.len() as u32);
316
317        // Store node-level decorators in the centralized NodeToDecoratorIds for efficient access
318        forest
319            .debug_info
320            .register_node_decorators(future_node_id, &before_enter, &after_exit);
321
322        // Create the node in the forest with Linked variant from the start
323        // Move the data directly without intermediate cloning
324        let node_id = forest
325            .nodes
326            .push(
327                LoopNode {
328                    body,
329                    digest,
330                    decorator_store: DecoratorStore::Linked { id: future_node_id },
331                }
332                .into(),
333            )
334            .map_err(|_| MastForestError::TooManyNodes)?;
335
336        Ok(node_id)
337    }
338
339    fn fingerprint_for_node(
340        &self,
341        forest: &MastForest,
342        hash_by_node_id: &impl LookupByIdx<MastNodeId, MastNodeFingerprint>,
343    ) -> Result<MastNodeFingerprint, MastForestError> {
344        // Use the fingerprint_from_parts helper function
345        crate::mast::node_fingerprint::fingerprint_from_parts(
346            forest,
347            hash_by_node_id,
348            &self.before_enter,
349            &self.after_exit,
350            &[self.body],
351            // Use the forced digest if available, otherwise compute the digest
352            if let Some(forced_digest) = self.digest {
353                forced_digest
354            } else {
355                let body_hash = forest[self.body].digest();
356
357                crate::chiplets::hasher::merge_in_domain(
358                    &[body_hash, miden_crypto::Word::default()],
359                    LoopNode::DOMAIN,
360                )
361            },
362        )
363    }
364
365    fn remap_children(self, remapping: &impl LookupByIdx<MastNodeId, MastNodeId>) -> Self {
366        LoopNodeBuilder {
367            body: *remapping.get(self.body).unwrap_or(&self.body),
368            before_enter: self.before_enter,
369            after_exit: self.after_exit,
370            digest: self.digest,
371        }
372    }
373
374    fn with_before_enter(mut self, decorators: impl Into<Vec<DecoratorId>>) -> Self {
375        self.before_enter = decorators.into();
376        self
377    }
378
379    fn with_after_exit(mut self, decorators: impl Into<Vec<DecoratorId>>) -> Self {
380        self.after_exit = decorators.into();
381        self
382    }
383
384    fn append_before_enter(&mut self, decorators: impl IntoIterator<Item = DecoratorId>) {
385        self.before_enter.extend(decorators);
386    }
387
388    fn append_after_exit(&mut self, decorators: impl IntoIterator<Item = DecoratorId>) {
389        self.after_exit.extend(decorators);
390    }
391
392    fn with_digest(mut self, digest: crate::Word) -> Self {
393        self.digest = Some(digest);
394        self
395    }
396}
397
398impl LoopNodeBuilder {
399    /// Add this node to a forest using relaxed validation.
400    ///
401    /// This method is used during deserialization where nodes may reference child nodes
402    /// that haven't been added to the forest yet. The child node IDs have already been
403    /// validated against the expected final node count during the `try_into_mast_node_builder`
404    /// step, so we can safely skip validation here.
405    ///
406    /// Note: This is not part of the `MastForestContributor` trait because it's only
407    /// intended for internal use during deserialization.
408    pub(in crate::mast) fn add_to_forest_relaxed(
409        self,
410        forest: &mut MastForest,
411    ) -> Result<MastNodeId, MastForestError> {
412        // Use the forced digest if provided, otherwise use a default digest
413        // The actual digest computation will be handled when the forest is complete
414        let Some(digest) = self.digest else {
415            return Err(MastForestError::DigestRequiredForDeserialization);
416        };
417
418        let future_node_id = MastNodeId::new_unchecked(forest.nodes.len() as u32);
419
420        // Create the node in the forest with Linked variant from the start
421        // Move the data directly without intermediate cloning
422        let node_id = forest
423            .nodes
424            .push(
425                LoopNode {
426                    body: self.body,
427                    digest,
428                    decorator_store: DecoratorStore::Linked { id: future_node_id },
429                }
430                .into(),
431            )
432            .map_err(|_| MastForestError::TooManyNodes)?;
433
434        Ok(node_id)
435    }
436}
437
438#[cfg(any(test, feature = "arbitrary"))]
439impl proptest::prelude::Arbitrary for LoopNodeBuilder {
440    type Parameters = LoopNodeBuilderParams;
441    type Strategy = proptest::strategy::BoxedStrategy<Self>;
442
443    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
444        use proptest::prelude::*;
445
446        (
447            any::<MastNodeId>(),
448            proptest::collection::vec(
449                super::arbitrary::decorator_id_strategy(params.max_decorator_id_u32),
450                0..=params.max_decorators,
451            ),
452            proptest::collection::vec(
453                super::arbitrary::decorator_id_strategy(params.max_decorator_id_u32),
454                0..=params.max_decorators,
455            ),
456        )
457            .prop_map(|(body, before_enter, after_exit)| {
458                Self::new(body).with_before_enter(before_enter).with_after_exit(after_exit)
459            })
460            .boxed()
461    }
462}
463
464/// Parameters for generating LoopNodeBuilder instances
465#[cfg(any(test, feature = "arbitrary"))]
466#[derive(Clone, Debug)]
467pub struct LoopNodeBuilderParams {
468    pub max_decorators: usize,
469    pub max_decorator_id_u32: u32,
470}
471
472#[cfg(any(test, feature = "arbitrary"))]
473impl Default for LoopNodeBuilderParams {
474    fn default() -> Self {
475        Self {
476            max_decorators: 4,
477            max_decorator_id_u32: 10,
478        }
479    }
480}