Skip to main content

miden_core/mast/node/
loop_node.rs

1use alloc::{boxed::Box, vec::Vec};
2use core::fmt;
3
4use super::{
5    MastForestContributor, MastNodeContext, MastNodeExt, fingerprint_with_child_fingerprints,
6};
7use crate::{
8    Felt, Word,
9    chiplets::hasher,
10    mast::{MastForest, MastForestError, MastNodeId},
11    operations::opcodes,
12    prettier::PrettyPrint,
13    utils::LookupByIdx,
14};
15
16// LOOP NODE
17// ================================================================================================
18
19/// A Loop node defines condition-controlled iterative execution. When the VM encounters a Loop
20/// node, it will keep executing the body of the loop as long as the top of the stack is `1``,
21/// except for the encounter which it executes unconditionally.
22///
23/// The loop is exited when at the end of executing the loop body the top of the stack is `0``.
24/// If the top of the stack is neither `0` nor `1` when the condition is checked, the execution
25/// fails.
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct LoopNode {
28    body: MastNodeId,
29    digest: Word,
30}
31
32/// Constants
33impl LoopNode {
34    /// The domain of the loop node (used for control block hashing).
35    pub const DOMAIN: Felt = Felt::new_unchecked(opcodes::LOOP as u64);
36}
37
38impl LoopNode {
39    /// Returns the ID of the node presenting the body of the loop.
40    pub fn body(&self) -> MastNodeId {
41        self.body
42    }
43}
44
45// PRETTY PRINTING
46// ================================================================================================
47
48impl LoopNode {
49    pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a {
50        LoopNodePrettyPrint { loop_node: self, mast_forest }
51    }
52
53    pub(super) fn to_pretty_print<'a>(
54        &'a self,
55        mast_forest: &'a MastForest,
56    ) -> impl PrettyPrint + 'a {
57        LoopNodePrettyPrint { loop_node: self, mast_forest }
58    }
59}
60
61struct LoopNodePrettyPrint<'a> {
62    loop_node: &'a LoopNode,
63    mast_forest: &'a MastForest,
64}
65
66impl PrettyPrint for LoopNodePrettyPrint<'_> {
67    fn render(&self) -> crate::prettier::Document {
68        use crate::prettier::*;
69
70        let loop_body = self.mast_forest[self.loop_node.body].to_pretty_print(self.mast_forest);
71
72        indent(4, const_text("loop") + nl() + loop_body.render()) + nl() + const_text("end")
73    }
74}
75
76impl fmt::Display for LoopNodePrettyPrint<'_> {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        use crate::prettier::PrettyPrint;
79        self.pretty_print(f)
80    }
81}
82
83// MAST NODE TRAIT IMPLEMENTATION
84// ================================================================================================
85
86impl MastNodeExt for LoopNode {
87    /// Returns a commitment to this Loop node.
88    ///
89    /// The commitment is computed as a hash of the loop body and an empty word ([ZERO; 4]) in
90    /// the domain defined by [Self::DOMAIN] - i..e,:
91    /// ```
92    /// # use miden_core::mast::LoopNode;
93    /// # use miden_crypto::{Word, hash::poseidon2::Poseidon2 as Hasher};
94    /// # let body_digest = Word::default();
95    /// Hasher::merge_in_domain(&[body_digest, Word::default()], LoopNode::DOMAIN);
96    /// ```
97    fn digest(&self) -> Word {
98        self.digest
99    }
100
101    fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn fmt::Display + 'a> {
102        Box::new(LoopNode::to_display(self, mast_forest))
103    }
104
105    fn to_pretty_print<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn PrettyPrint + 'a> {
106        Box::new(LoopNode::to_pretty_print(self, mast_forest))
107    }
108
109    fn has_children(&self) -> bool {
110        true
111    }
112
113    fn append_children_to(&self, target: &mut Vec<MastNodeId>) {
114        target.push(self.body());
115    }
116
117    fn for_each_child<F>(&self, mut f: F)
118    where
119        F: FnMut(MastNodeId),
120    {
121        f(self.body());
122    }
123
124    fn domain(&self) -> Felt {
125        Self::DOMAIN
126    }
127
128    type Builder = LoopNodeBuilder;
129
130    fn to_builder(self, _forest: &MastForest) -> Self::Builder {
131        LoopNodeBuilder::new(self.body).with_digest(self.digest)
132    }
133}
134
135// ------------------------------------------------------------------------------------------------
136/// Builder for creating [`LoopNode`] instances.
137#[derive(Debug)]
138pub struct LoopNodeBuilder {
139    body: MastNodeId,
140    digest: Option<Word>,
141}
142
143impl LoopNodeBuilder {
144    /// Creates a new builder for a LoopNode with the specified body.
145    pub fn new(body: MastNodeId) -> Self {
146        Self { body, digest: None }
147    }
148
149    /// Builds the LoopNode.
150    pub fn build(self, context: &impl MastNodeContext) -> Result<LoopNode, MastForestError> {
151        let body = context
152            .get_node_by_id(self.body)
153            .ok_or_else(|| MastForestError::NodeIdOverflow(self.body, context.node_count()))?;
154
155        // Use the forced digest if provided, otherwise compute the digest
156        let digest = if let Some(forced_digest) = self.digest {
157            forced_digest
158        } else {
159            let body_hash = body.digest();
160
161            hasher::merge_in_domain(&[body_hash, Word::default()], LoopNode::DOMAIN)
162        };
163
164        Ok(LoopNode { body: self.body, digest })
165    }
166
167    pub(in crate::mast) fn build_linked(self) -> Result<LoopNode, MastForestError> {
168        Ok(LoopNode {
169            body: self.body,
170            digest: self.digest.ok_or(MastForestError::DigestRequiredForDeserialization)?,
171        })
172    }
173}
174
175#[cfg(any(test, feature = "arbitrary"))]
176impl LoopNodeBuilder {
177    /// Adds this builder to a mutable forest for test and arbitrary data construction.
178    pub fn add_to_forest(self, forest: &mut MastForest) -> Result<MastNodeId, MastForestError> {
179        let node = self.build(forest)?;
180        forest.nodes.push(node.into()).map_err(|_| MastForestError::TooManyNodes)
181    }
182}
183
184impl MastForestContributor for LoopNodeBuilder {
185    fn fingerprint_for_node(
186        &self,
187        context: &impl MastNodeContext,
188        hash_by_node_id: &impl LookupByIdx<MastNodeId, Word>,
189    ) -> Result<Word, MastForestError> {
190        let node_digest = if let Some(forced_digest) = self.digest {
191            forced_digest
192        } else {
193            let body_hash = context
194                .get_node_by_id(self.body)
195                .ok_or_else(|| MastForestError::NodeIdOverflow(self.body, context.node_count()))?
196                .digest();
197
198            hasher::merge_in_domain(&[body_hash, Word::default()], LoopNode::DOMAIN)
199        };
200
201        fingerprint_with_child_fingerprints(node_digest, &[self.body], context, hash_by_node_id)
202    }
203
204    fn remap_children(self, remapping: &impl LookupByIdx<MastNodeId, MastNodeId>) -> Self {
205        LoopNodeBuilder {
206            body: *remapping.get(self.body).unwrap_or(&self.body),
207            digest: self.digest,
208        }
209    }
210
211    fn with_digest(mut self, digest: Word) -> Self {
212        self.digest = Some(digest);
213        self
214    }
215}
216
217#[cfg(any(test, feature = "arbitrary"))]
218impl proptest::prelude::Arbitrary for LoopNodeBuilder {
219    type Parameters = ();
220    type Strategy = proptest::strategy::BoxedStrategy<Self>;
221
222    fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy {
223        use proptest::prelude::*;
224
225        any::<MastNodeId>().prop_map(Self::new).boxed()
226    }
227}