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_unchecked(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 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 {id:?} to be self"
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_unchecked));
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.register_node_decorators(future_node_id, &before_enter, &after_exit);
319
320        // Create the node in the forest with Linked variant from the start
321        // Move the data directly without intermediate cloning
322        let node_id = forest
323            .nodes
324            .push(
325                LoopNode {
326                    body,
327                    digest,
328                    decorator_store: DecoratorStore::Linked { id: future_node_id },
329                }
330                .into(),
331            )
332            .map_err(|_| MastForestError::TooManyNodes)?;
333
334        Ok(node_id)
335    }
336
337    fn fingerprint_for_node(
338        &self,
339        forest: &MastForest,
340        hash_by_node_id: &impl LookupByIdx<MastNodeId, MastNodeFingerprint>,
341    ) -> Result<MastNodeFingerprint, MastForestError> {
342        // Use the fingerprint_from_parts helper function
343        crate::mast::node_fingerprint::fingerprint_from_parts(
344            forest,
345            hash_by_node_id,
346            &self.before_enter,
347            &self.after_exit,
348            &[self.body],
349            // Use the forced digest if available, otherwise compute the digest
350            if let Some(forced_digest) = self.digest {
351                forced_digest
352            } else {
353                let body_hash = forest[self.body].digest();
354
355                hasher::merge_in_domain(&[body_hash, Word::default()], LoopNode::DOMAIN)
356            },
357        )
358    }
359
360    fn remap_children(self, remapping: &impl LookupByIdx<MastNodeId, MastNodeId>) -> Self {
361        LoopNodeBuilder {
362            body: *remapping.get(self.body).unwrap_or(&self.body),
363            before_enter: self.before_enter,
364            after_exit: self.after_exit,
365            digest: self.digest,
366        }
367    }
368
369    fn with_before_enter(mut self, decorators: impl Into<Vec<DecoratorId>>) -> Self {
370        self.before_enter = decorators.into();
371        self
372    }
373
374    fn with_after_exit(mut self, decorators: impl Into<Vec<DecoratorId>>) -> Self {
375        self.after_exit = decorators.into();
376        self
377    }
378
379    fn append_before_enter(&mut self, decorators: impl IntoIterator<Item = DecoratorId>) {
380        self.before_enter.extend(decorators);
381    }
382
383    fn append_after_exit(&mut self, decorators: impl IntoIterator<Item = DecoratorId>) {
384        self.after_exit.extend(decorators);
385    }
386
387    fn with_digest(mut self, digest: Word) -> Self {
388        self.digest = Some(digest);
389        self
390    }
391}
392
393impl LoopNodeBuilder {
394    /// Add this node to a forest using relaxed validation.
395    ///
396    /// This method is used during deserialization where nodes may reference child nodes
397    /// that haven't been added to the forest yet. The child node IDs have already been
398    /// validated against the expected final node count during the `try_into_mast_node_builder`
399    /// step, so we can safely skip validation here.
400    ///
401    /// Note: This is not part of the `MastForestContributor` trait because it's only
402    /// intended for internal use during deserialization.
403    pub(in crate::mast) fn add_to_forest_relaxed(
404        self,
405        forest: &mut MastForest,
406    ) -> Result<MastNodeId, MastForestError> {
407        // Use the forced digest if provided, otherwise use a default digest
408        // The actual digest computation will be handled when the forest is complete
409        let Some(digest) = self.digest else {
410            return Err(MastForestError::DigestRequiredForDeserialization);
411        };
412
413        let future_node_id = MastNodeId::new_unchecked(forest.nodes.len() as u32);
414
415        // Create the node in the forest with Linked variant from the start
416        // Move the data directly without intermediate cloning
417        let node_id = forest
418            .nodes
419            .push(
420                LoopNode {
421                    body: self.body,
422                    digest,
423                    decorator_store: DecoratorStore::Linked { id: future_node_id },
424                }
425                .into(),
426            )
427            .map_err(|_| MastForestError::TooManyNodes)?;
428
429        Ok(node_id)
430    }
431}
432
433#[cfg(any(test, feature = "arbitrary"))]
434impl proptest::prelude::Arbitrary for LoopNodeBuilder {
435    type Parameters = LoopNodeBuilderParams;
436    type Strategy = proptest::strategy::BoxedStrategy<Self>;
437
438    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
439        use proptest::prelude::*;
440
441        (
442            any::<MastNodeId>(),
443            proptest::collection::vec(
444                super::arbitrary::decorator_id_strategy(params.max_decorator_id_u32),
445                0..=params.max_decorators,
446            ),
447            proptest::collection::vec(
448                super::arbitrary::decorator_id_strategy(params.max_decorator_id_u32),
449                0..=params.max_decorators,
450            ),
451        )
452            .prop_map(|(body, before_enter, after_exit)| {
453                Self::new(body).with_before_enter(before_enter).with_after_exit(after_exit)
454            })
455            .boxed()
456    }
457}
458
459/// Parameters for generating LoopNodeBuilder instances
460#[cfg(any(test, feature = "arbitrary"))]
461#[derive(Clone, Debug)]
462pub struct LoopNodeBuilderParams {
463    pub max_decorators: usize,
464    pub max_decorator_id_u32: u32,
465}
466
467#[cfg(any(test, feature = "arbitrary"))]
468impl Default for LoopNodeBuilderParams {
469    fn default() -> Self {
470        Self {
471            max_decorators: 4,
472            max_decorator_id_u32: 10,
473        }
474    }
475}