miden_core/mast/node/
dyn_node.rs

1use alloc::vec::Vec;
2use core::fmt;
3
4use miden_crypto::{Felt, hash::rpo::RpoDigest};
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::{hash::rpo::{RpoDigest as Digest, Rpo256 as Hasher}};
75    /// Hasher::merge_in_domain(&[Digest::default(), Digest::default()], DynNode::DYN_DOMAIN);
76    /// Hasher::merge_in_domain(&[Digest::default(), Digest::default()], DynNode::DYNCALL_DOMAIN);
77    /// ```
78    pub fn digest(&self) -> RpoDigest {
79        if self.is_dyncall {
80            RpoDigest::new([
81                Felt::new(8751004906421739448),
82                Felt::new(13469709002495534233),
83                Felt::new(12584249374630430826),
84                Felt::new(7624899870831503004),
85            ])
86        } else {
87            RpoDigest::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/// Mutators
108impl DynNode {
109    /// Sets the list of decorators to be executed before this node.
110    pub fn append_before_enter(&mut self, decorator_ids: &[DecoratorId]) {
111        self.before_enter.extend_from_slice(decorator_ids);
112    }
113
114    /// Sets the list of decorators to be executed after this node.
115    pub fn append_after_exit(&mut self, decorator_ids: &[DecoratorId]) {
116        self.after_exit.extend_from_slice(decorator_ids);
117    }
118}
119
120impl MastNodeExt for DynNode {
121    fn decorators(&self) -> impl Iterator<Item = (usize, DecoratorId)> {
122        self.before_enter.iter().chain(&self.after_exit).copied().enumerate()
123    }
124}
125
126// PRETTY PRINTING
127// ================================================================================================
128
129impl DynNode {
130    pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a {
131        DynNodePrettyPrint { node: self, mast_forest }
132    }
133
134    pub(super) fn to_pretty_print<'a>(
135        &'a self,
136        mast_forest: &'a MastForest,
137    ) -> impl PrettyPrint + 'a {
138        DynNodePrettyPrint { node: self, mast_forest }
139    }
140}
141
142struct DynNodePrettyPrint<'a> {
143    node: &'a DynNode,
144    mast_forest: &'a MastForest,
145}
146
147impl DynNodePrettyPrint<'_> {
148    /// Concatenates the provided decorators in a single line. If the list of decorators is not
149    /// empty, prepends `prepend` and appends `append` to the decorator document.
150    fn concatenate_decorators(
151        &self,
152        decorator_ids: &[DecoratorId],
153        prepend: Document,
154        append: Document,
155    ) -> Document {
156        let decorators = decorator_ids
157            .iter()
158            .map(|&decorator_id| self.mast_forest[decorator_id].render())
159            .reduce(|acc, doc| acc + const_text(" ") + doc)
160            .unwrap_or_default();
161
162        if decorators.is_empty() {
163            decorators
164        } else {
165            prepend + decorators + append
166        }
167    }
168
169    fn single_line_pre_decorators(&self) -> Document {
170        self.concatenate_decorators(self.node.before_enter(), Document::Empty, const_text(" "))
171    }
172
173    fn single_line_post_decorators(&self) -> Document {
174        self.concatenate_decorators(self.node.after_exit(), const_text(" "), Document::Empty)
175    }
176
177    fn multi_line_pre_decorators(&self) -> Document {
178        self.concatenate_decorators(self.node.before_enter(), Document::Empty, nl())
179    }
180
181    fn multi_line_post_decorators(&self) -> Document {
182        self.concatenate_decorators(self.node.after_exit(), nl(), Document::Empty)
183    }
184}
185
186impl crate::prettier::PrettyPrint for DynNodePrettyPrint<'_> {
187    fn render(&self) -> crate::prettier::Document {
188        let dyn_text = if self.node.is_dyncall() {
189            const_text("dyncall")
190        } else {
191            const_text("dyn")
192        };
193
194        let single_line = self.single_line_pre_decorators()
195            + dyn_text.clone()
196            + self.single_line_post_decorators();
197        let multi_line =
198            self.multi_line_pre_decorators() + dyn_text + self.multi_line_post_decorators();
199
200        single_line | multi_line
201    }
202}
203
204impl fmt::Display for DynNodePrettyPrint<'_> {
205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206        self.pretty_print(f)
207    }
208}
209
210// TESTS
211// ================================================================================================
212
213#[cfg(test)]
214mod tests {
215    use miden_crypto::hash::rpo::Rpo256;
216
217    use super::*;
218
219    /// Ensures that the hash of `DynNode` is indeed the hash of 2 empty words, in the `DynNode`
220    /// domain.
221    #[test]
222    pub fn test_dyn_node_digest() {
223        assert_eq!(
224            DynNode::new_dyn().digest(),
225            Rpo256::merge_in_domain(
226                &[RpoDigest::default(), RpoDigest::default()],
227                DynNode::DYN_DOMAIN
228            )
229        );
230
231        assert_eq!(
232            DynNode::new_dyncall().digest(),
233            Rpo256::merge_in_domain(
234                &[RpoDigest::default(), RpoDigest::default()],
235                DynNode::DYNCALL_DOMAIN
236            )
237        );
238    }
239}