miden_core/mast/node/
loop_node.rs

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