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