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::{MastNodeErrorContext, MastNodeExt};
10use crate::{
11    Idx, OPCODE_LOOP,
12    chiplets::hasher,
13    mast::{DecoratedOpLink, DecoratorId, MastForest, MastForestError, MastNodeId, Remapping},
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///
22/// The loop is exited when at the end of executing the loop body the top of the stack is `0``.
23/// If the top of the stack is neither `0` nor `1` when the condition is checked, the execution
24/// fails.
25#[derive(Debug, Clone, PartialEq, Eq)]
26#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
27pub struct LoopNode {
28    body: MastNodeId,
29    digest: Word,
30    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Vec::is_empty"))]
31    before_enter: Vec<DecoratorId>,
32    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Vec::is_empty"))]
33    after_exit: Vec<DecoratorId>,
34}
35
36/// Constants
37impl LoopNode {
38    /// The domain of the loop node (used for control block hashing).
39    pub const DOMAIN: Felt = Felt::new(OPCODE_LOOP as u64);
40}
41
42/// Constructors
43impl LoopNode {
44    /// Returns a new [`LoopNode`] instantiated with the specified body node.
45    pub fn new(body: MastNodeId, mast_forest: &MastForest) -> Result<Self, MastForestError> {
46        if body.to_usize() >= mast_forest.nodes.len() {
47            return Err(MastForestError::NodeIdOverflow(body, mast_forest.nodes.len()));
48        }
49        let digest = {
50            let body_hash = mast_forest[body].digest();
51
52            hasher::merge_in_domain(&[body_hash, Word::default()], Self::DOMAIN)
53        };
54
55        Ok(Self {
56            body,
57            digest,
58            before_enter: Vec::new(),
59            after_exit: Vec::new(),
60        })
61    }
62
63    /// Returns a new [`LoopNode`] from values that are assumed to be correct.
64    /// Should only be used when the source of the inputs is trusted (e.g. deserialization).
65    pub fn new_unsafe(body: MastNodeId, digest: Word) -> Self {
66        Self {
67            body,
68            digest,
69            before_enter: Vec::new(),
70            after_exit: Vec::new(),
71        }
72    }
73}
74
75impl LoopNode {
76    /// Returns the ID of the node presenting the body of the loop.
77    pub fn body(&self) -> MastNodeId {
78        self.body
79    }
80}
81
82impl MastNodeErrorContext for LoopNode {
83    fn decorators(&self) -> impl Iterator<Item = DecoratedOpLink> {
84        self.before_enter.iter().chain(&self.after_exit).copied().enumerate()
85    }
86}
87
88// PRETTY PRINTING
89// ================================================================================================
90
91impl LoopNode {
92    pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a {
93        LoopNodePrettyPrint { loop_node: self, mast_forest }
94    }
95
96    pub(super) fn to_pretty_print<'a>(
97        &'a self,
98        mast_forest: &'a MastForest,
99    ) -> impl PrettyPrint + 'a {
100        LoopNodePrettyPrint { loop_node: self, mast_forest }
101    }
102}
103
104struct LoopNodePrettyPrint<'a> {
105    loop_node: &'a LoopNode,
106    mast_forest: &'a MastForest,
107}
108
109impl crate::prettier::PrettyPrint for LoopNodePrettyPrint<'_> {
110    fn render(&self) -> crate::prettier::Document {
111        use crate::prettier::*;
112
113        let pre_decorators = {
114            let mut pre_decorators = self
115                .loop_node
116                .before_enter()
117                .iter()
118                .map(|&decorator_id| self.mast_forest[decorator_id].render())
119                .reduce(|acc, doc| acc + const_text(" ") + doc)
120                .unwrap_or_default();
121            if !pre_decorators.is_empty() {
122                pre_decorators += nl();
123            }
124
125            pre_decorators
126        };
127
128        let post_decorators = {
129            let mut post_decorators = self
130                .loop_node
131                .after_exit()
132                .iter()
133                .map(|&decorator_id| self.mast_forest[decorator_id].render())
134                .reduce(|acc, doc| acc + const_text(" ") + doc)
135                .unwrap_or_default();
136            if !post_decorators.is_empty() {
137                post_decorators = nl() + post_decorators;
138            }
139
140            post_decorators
141        };
142
143        let loop_body = self.mast_forest[self.loop_node.body].to_pretty_print(self.mast_forest);
144
145        pre_decorators
146            + indent(4, const_text("while.true") + nl() + loop_body.render())
147            + nl()
148            + const_text("end")
149            + post_decorators
150    }
151}
152
153impl fmt::Display for LoopNodePrettyPrint<'_> {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        use crate::prettier::PrettyPrint;
156        self.pretty_print(f)
157    }
158}
159
160// MAST NODE TRAIT IMPLEMENTATION
161// ================================================================================================
162
163impl MastNodeExt for LoopNode {
164    /// Returns a commitment to this Loop node.
165    ///
166    /// The commitment is computed as a hash of the loop body and an empty word ([ZERO; 4]) in
167    /// the domain defined by [Self::DOMAIN] - i..e,:
168    /// ```
169    /// # use miden_core::mast::LoopNode;
170    /// # use miden_crypto::{Word, hash::rpo::Rpo256 as Hasher};
171    /// # let body_digest = Word::default();
172    /// Hasher::merge_in_domain(&[body_digest, Word::default()], LoopNode::DOMAIN);
173    /// ```
174    fn digest(&self) -> Word {
175        self.digest
176    }
177
178    /// Returns the decorators to be executed before this node is executed.
179    fn before_enter(&self) -> &[DecoratorId] {
180        &self.before_enter
181    }
182
183    /// Returns the decorators to be executed after this node is executed.
184    fn after_exit(&self) -> &[DecoratorId] {
185        &self.after_exit
186    }
187    /// Sets the list of decorators to be executed before this node.
188    fn append_before_enter(&mut self, decorator_ids: &[DecoratorId]) {
189        self.before_enter.extend_from_slice(decorator_ids);
190    }
191
192    /// Sets the list of decorators to be executed after this node.
193    fn append_after_exit(&mut self, decorator_ids: &[DecoratorId]) {
194        self.after_exit.extend_from_slice(decorator_ids);
195    }
196
197    /// Removes all decorators from this node.
198    fn remove_decorators(&mut self) {
199        self.before_enter.truncate(0);
200        self.after_exit.truncate(0);
201    }
202
203    fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn fmt::Display + 'a> {
204        Box::new(LoopNode::to_display(self, mast_forest))
205    }
206
207    fn to_pretty_print<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn PrettyPrint + 'a> {
208        Box::new(LoopNode::to_pretty_print(self, mast_forest))
209    }
210
211    fn remap_children(&self, remapping: &Remapping) -> Self {
212        let mut node = self.clone();
213        node.body = node.body.remap(remapping);
214        node
215    }
216
217    fn has_children(&self) -> bool {
218        true
219    }
220
221    fn append_children_to(&self, target: &mut Vec<MastNodeId>) {
222        target.push(self.body());
223    }
224
225    fn for_each_child<F>(&self, mut f: F)
226    where
227        F: FnMut(MastNodeId),
228    {
229        f(self.body());
230    }
231
232    fn domain(&self) -> Felt {
233        Self::DOMAIN
234    }
235}