miden_core/mast/node/
dyn_node.rs

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