miden_core/mast/node/
dyn_node.rs

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