miden_core/mast/node/
dyn_node.rs

1use alloc::{boxed::Box, vec::Vec};
2use core::fmt;
3
4use miden_crypto::{Felt, Word};
5use miden_formatting::prettier::{Document, PrettyPrint, const_text, nl};
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9use super::{MastNodeErrorContext, MastNodeExt};
10use crate::{
11    OPCODE_DYN, OPCODE_DYNCALL,
12    mast::{DecoratorId, MastForest, MastNodeId, Remapping},
13};
14
15// DYN NODE
16// ================================================================================================
17
18/// A Dyn node specifies that the node to be executed next is defined dynamically via the stack.
19#[derive(Debug, Clone, PartialEq, Eq)]
20#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
21pub struct DynNode {
22    is_dyncall: bool,
23    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Vec::is_empty"))]
24    before_enter: Vec<DecoratorId>,
25    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Vec::is_empty"))]
26    after_exit: Vec<DecoratorId>,
27}
28
29/// Constants
30impl DynNode {
31    /// The domain of the Dyn block (used for control block hashing).
32    pub const DYN_DOMAIN: Felt = Felt::new(OPCODE_DYN as u64);
33
34    /// The domain of the Dyncall block (used for control block hashing).
35    pub const DYNCALL_DOMAIN: Felt = Felt::new(OPCODE_DYNCALL as u64);
36}
37
38/// Public accessors
39impl DynNode {
40    /// Creates a new [`DynNode`] representing a dynexec operation.
41    pub fn new_dyn() -> Self {
42        Self {
43            is_dyncall: false,
44            before_enter: Vec::new(),
45            after_exit: Vec::new(),
46        }
47    }
48
49    /// Creates a new [`DynNode`] representing a dyncall operation.
50    pub fn new_dyncall() -> Self {
51        Self {
52            is_dyncall: true,
53            before_enter: Vec::new(),
54            after_exit: Vec::new(),
55        }
56    }
57
58    /// Returns true if the [`DynNode`] represents a dyncall operation, and false for dynexec.
59    pub fn is_dyncall(&self) -> bool {
60        self.is_dyncall
61    }
62
63    /// Returns the domain of this dyn node.
64    pub fn domain(&self) -> Felt {
65        if self.is_dyncall() {
66            Self::DYNCALL_DOMAIN
67        } else {
68            Self::DYN_DOMAIN
69        }
70    }
71}
72
73impl MastNodeErrorContext for DynNode {
74    fn decorators(&self) -> impl Iterator<Item = (usize, DecoratorId)> {
75        self.before_enter.iter().chain(&self.after_exit).copied().enumerate()
76    }
77}
78
79// PRETTY PRINTING
80// ================================================================================================
81
82impl DynNode {
83    pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a {
84        DynNodePrettyPrint { node: self, mast_forest }
85    }
86
87    pub(super) fn to_pretty_print<'a>(
88        &'a self,
89        mast_forest: &'a MastForest,
90    ) -> impl PrettyPrint + 'a {
91        DynNodePrettyPrint { node: self, mast_forest }
92    }
93}
94
95struct DynNodePrettyPrint<'a> {
96    node: &'a DynNode,
97    mast_forest: &'a MastForest,
98}
99
100impl DynNodePrettyPrint<'_> {
101    /// Concatenates the provided decorators in a single line. If the list of decorators is not
102    /// empty, prepends `prepend` and appends `append` to the decorator document.
103    fn concatenate_decorators(
104        &self,
105        decorator_ids: &[DecoratorId],
106        prepend: Document,
107        append: Document,
108    ) -> Document {
109        let decorators = decorator_ids
110            .iter()
111            .map(|&decorator_id| self.mast_forest[decorator_id].render())
112            .reduce(|acc, doc| acc + const_text(" ") + doc)
113            .unwrap_or_default();
114
115        if decorators.is_empty() {
116            decorators
117        } else {
118            prepend + decorators + append
119        }
120    }
121
122    fn single_line_pre_decorators(&self) -> Document {
123        self.concatenate_decorators(self.node.before_enter(), Document::Empty, const_text(" "))
124    }
125
126    fn single_line_post_decorators(&self) -> Document {
127        self.concatenate_decorators(self.node.after_exit(), const_text(" "), Document::Empty)
128    }
129
130    fn multi_line_pre_decorators(&self) -> Document {
131        self.concatenate_decorators(self.node.before_enter(), Document::Empty, nl())
132    }
133
134    fn multi_line_post_decorators(&self) -> Document {
135        self.concatenate_decorators(self.node.after_exit(), nl(), Document::Empty)
136    }
137}
138
139impl crate::prettier::PrettyPrint for DynNodePrettyPrint<'_> {
140    fn render(&self) -> crate::prettier::Document {
141        let dyn_text = if self.node.is_dyncall() {
142            const_text("dyncall")
143        } else {
144            const_text("dyn")
145        };
146
147        let single_line = self.single_line_pre_decorators()
148            + dyn_text.clone()
149            + self.single_line_post_decorators();
150        let multi_line =
151            self.multi_line_pre_decorators() + dyn_text + self.multi_line_post_decorators();
152
153        single_line | multi_line
154    }
155}
156
157impl fmt::Display for DynNodePrettyPrint<'_> {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        self.pretty_print(f)
160    }
161}
162
163// MAST NODE TRAIT IMPLEMENTATION
164// ================================================================================================
165
166impl MastNodeExt for DynNode {
167    /// Returns a commitment to a Dyn node.
168    ///
169    /// The commitment is computed by hashing two empty words ([ZERO; 4]) in the domain defined
170    /// by [Self::DYN_DOMAIN] or [Self::DYNCALL_DOMAIN], i.e.:
171    ///
172    /// ```
173    /// # use miden_core::mast::DynNode;
174    /// # use miden_crypto::{Word, hash::rpo::Rpo256 as Hasher};
175    /// Hasher::merge_in_domain(&[Word::default(), Word::default()], DynNode::DYN_DOMAIN);
176    /// Hasher::merge_in_domain(&[Word::default(), Word::default()], DynNode::DYNCALL_DOMAIN);
177    /// ```
178    fn digest(&self) -> Word {
179        if self.is_dyncall {
180            Word::new([
181                Felt::new(8751004906421739448),
182                Felt::new(13469709002495534233),
183                Felt::new(12584249374630430826),
184                Felt::new(7624899870831503004),
185            ])
186        } else {
187            Word::new([
188                Felt::new(8115106948140260551),
189                Felt::new(13491227816952616836),
190                Felt::new(15015806788322198710),
191                Felt::new(16575543461540527115),
192            ])
193        }
194    }
195
196    /// Returns the decorators to be executed before this node is executed.
197    fn before_enter(&self) -> &[DecoratorId] {
198        &self.before_enter
199    }
200
201    /// Returns the decorators to be executed after this node is executed.
202    fn after_exit(&self) -> &[DecoratorId] {
203        &self.after_exit
204    }
205
206    /// Sets the list of decorators to be executed before this node.
207    fn append_before_enter(&mut self, decorator_ids: &[DecoratorId]) {
208        self.before_enter.extend_from_slice(decorator_ids);
209    }
210
211    /// Sets the list of decorators to be executed after this node.
212    fn append_after_exit(&mut self, decorator_ids: &[DecoratorId]) {
213        self.after_exit.extend_from_slice(decorator_ids);
214    }
215
216    /// Removes all decorators from this node.
217    fn remove_decorators(&mut self) {
218        self.before_enter.truncate(0);
219        self.after_exit.truncate(0);
220    }
221
222    fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn fmt::Display + 'a> {
223        Box::new(DynNode::to_display(self, mast_forest))
224    }
225
226    fn to_pretty_print<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn PrettyPrint + 'a> {
227        Box::new(DynNode::to_pretty_print(self, mast_forest))
228    }
229
230    fn remap_children(&self, _remapping: &Remapping) -> Self {
231        self.clone()
232    }
233
234    fn has_children(&self) -> bool {
235        false
236    }
237
238    fn append_children_to(&self, _target: &mut Vec<MastNodeId>) {
239        // No children for dyn nodes
240    }
241
242    fn domain(&self) -> Felt {
243        self.domain()
244    }
245}
246
247#[cfg(test)]
248mod tests {
249    use miden_crypto::hash::rpo::Rpo256;
250
251    use super::*;
252
253    /// Ensures that the hash of `DynNode` is indeed the hash of 2 empty words, in the `DynNode`
254    /// domain.
255    #[test]
256    pub fn test_dyn_node_digest() {
257        assert_eq!(
258            DynNode::new_dyn().digest(),
259            Rpo256::merge_in_domain(&[Word::default(), Word::default()], DynNode::DYN_DOMAIN)
260        );
261
262        assert_eq!(
263            DynNode::new_dyncall().digest(),
264            Rpo256::merge_in_domain(&[Word::default(), Word::default()], DynNode::DYNCALL_DOMAIN)
265        );
266    }
267}