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::{MastForestContributor, MastNodeErrorContext, MastNodeExt};
10use crate::{
11    OPCODE_DYN, OPCODE_DYNCALL,
12    mast::{DecoratedOpLink, DecoratorId, DecoratorStore, MastForest, MastForestError, MastNodeId},
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))]
21#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
22pub struct DynNode {
23    is_dyncall: bool,
24    digest: Word,
25    decorator_store: DecoratorStore,
26}
27
28/// Constants
29impl DynNode {
30    /// The domain of the Dyn block (used for control block hashing).
31    pub const DYN_DOMAIN: Felt = Felt::new(OPCODE_DYN as u64);
32
33    /// The domain of the Dyncall block (used for control block hashing).
34    pub const DYNCALL_DOMAIN: Felt = Felt::new(OPCODE_DYNCALL as u64);
35}
36
37/// Default digest constants
38impl DynNode {
39    /// The default digest for a DynNode representing a dyncall operation.
40    pub const DYNCALL_DEFAULT_DIGEST: Word = Word::new([
41        Felt::new(8751004906421739448),
42        Felt::new(13469709002495534233),
43        Felt::new(12584249374630430826),
44        Felt::new(7624899870831503004),
45    ]);
46
47    /// The default digest for a DynNode representing a dynexec operation.
48    pub const DYN_DEFAULT_DIGEST: Word = Word::new([
49        Felt::new(8115106948140260551),
50        Felt::new(13491227816952616836),
51        Felt::new(15015806788322198710),
52        Felt::new(16575543461540527115),
53    ]);
54}
55
56/// Public accessors
57impl DynNode {
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<'a>(
75        &'a self,
76        forest: &'a MastForest,
77    ) -> impl Iterator<Item = DecoratedOpLink> + 'a {
78        // Use the decorator_store for efficient O(1) decorator access
79        let before_enter = self.decorator_store.before_enter(forest);
80        let after_exit = self.decorator_store.after_exit(forest);
81
82        // Convert decorators to DecoratedOpLink tuples
83        before_enter
84            .iter()
85            .map(|&deco_id| (0, deco_id))
86            .chain(after_exit.iter().map(|&deco_id| (1, deco_id)))
87    }
88}
89
90// PRETTY PRINTING
91// ================================================================================================
92
93impl DynNode {
94    pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a {
95        DynNodePrettyPrint { node: self, mast_forest }
96    }
97
98    pub(super) fn to_pretty_print<'a>(
99        &'a self,
100        mast_forest: &'a MastForest,
101    ) -> impl PrettyPrint + 'a {
102        DynNodePrettyPrint { node: self, mast_forest }
103    }
104}
105
106struct DynNodePrettyPrint<'a> {
107    node: &'a DynNode,
108    mast_forest: &'a MastForest,
109}
110
111impl DynNodePrettyPrint<'_> {
112    /// Concatenates the provided decorators in a single line. If the list of decorators is not
113    /// empty, prepends `prepend` and appends `append` to the decorator document.
114    fn concatenate_decorators(
115        &self,
116        decorator_ids: &[DecoratorId],
117        prepend: Document,
118        append: Document,
119    ) -> Document {
120        let decorators = decorator_ids
121            .iter()
122            .map(|&decorator_id| self.mast_forest[decorator_id].render())
123            .reduce(|acc, doc| acc + const_text(" ") + doc)
124            .unwrap_or_default();
125
126        if decorators.is_empty() {
127            decorators
128        } else {
129            prepend + decorators + append
130        }
131    }
132
133    fn single_line_pre_decorators(&self) -> Document {
134        self.concatenate_decorators(
135            self.node.before_enter(self.mast_forest),
136            Document::Empty,
137            const_text(" "),
138        )
139    }
140
141    fn single_line_post_decorators(&self) -> Document {
142        self.concatenate_decorators(
143            self.node.after_exit(self.mast_forest),
144            const_text(" "),
145            Document::Empty,
146        )
147    }
148
149    fn multi_line_pre_decorators(&self) -> Document {
150        self.concatenate_decorators(self.node.before_enter(self.mast_forest), Document::Empty, nl())
151    }
152
153    fn multi_line_post_decorators(&self) -> Document {
154        self.concatenate_decorators(self.node.after_exit(self.mast_forest), nl(), Document::Empty)
155    }
156}
157
158impl crate::prettier::PrettyPrint for DynNodePrettyPrint<'_> {
159    fn render(&self) -> crate::prettier::Document {
160        let dyn_text = if self.node.is_dyncall() {
161            const_text("dyncall")
162        } else {
163            const_text("dyn")
164        };
165
166        let single_line = self.single_line_pre_decorators()
167            + dyn_text.clone()
168            + self.single_line_post_decorators();
169        let multi_line =
170            self.multi_line_pre_decorators() + dyn_text + self.multi_line_post_decorators();
171
172        single_line | multi_line
173    }
174}
175
176impl fmt::Display for DynNodePrettyPrint<'_> {
177    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178        self.pretty_print(f)
179    }
180}
181
182// MAST NODE TRAIT IMPLEMENTATION
183// ================================================================================================
184
185impl MastNodeExt for DynNode {
186    /// Returns a commitment to a Dyn node.
187    fn digest(&self) -> Word {
188        self.digest
189    }
190
191    /// Returns the decorators to be executed before this node is executed.
192    fn before_enter<'a>(&'a self, forest: &'a MastForest) -> &'a [DecoratorId] {
193        #[cfg(debug_assertions)]
194        self.verify_node_in_forest(forest);
195        self.decorator_store.before_enter(forest)
196    }
197
198    /// Returns the decorators to be executed after this node is executed.
199    fn after_exit<'a>(&'a self, forest: &'a MastForest) -> &'a [DecoratorId] {
200        #[cfg(debug_assertions)]
201        self.verify_node_in_forest(forest);
202        self.decorator_store.after_exit(forest)
203    }
204
205    fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn fmt::Display + 'a> {
206        Box::new(DynNode::to_display(self, mast_forest))
207    }
208
209    fn to_pretty_print<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn PrettyPrint + 'a> {
210        Box::new(DynNode::to_pretty_print(self, mast_forest))
211    }
212
213    fn has_children(&self) -> bool {
214        false
215    }
216
217    fn append_children_to(&self, _target: &mut Vec<MastNodeId>) {
218        // No children for dyn nodes
219    }
220
221    fn for_each_child<F>(&self, _f: F)
222    where
223        F: FnMut(MastNodeId),
224    {
225        // DynNode has no children
226    }
227
228    fn domain(&self) -> Felt {
229        self.domain()
230    }
231
232    type Builder = DynNodeBuilder;
233
234    fn to_builder(self, forest: &MastForest) -> Self::Builder {
235        // Extract decorators from decorator_store if in Owned state
236        match self.decorator_store {
237            DecoratorStore::Owned { before_enter, after_exit, .. } => {
238                let mut builder = if self.is_dyncall {
239                    DynNodeBuilder::new_dyncall()
240                } else {
241                    DynNodeBuilder::new_dyn()
242                };
243                builder = builder.with_before_enter(before_enter).with_after_exit(after_exit);
244                builder
245            },
246            DecoratorStore::Linked { id } => {
247                // Extract decorators from forest storage when in Linked state
248                let before_enter = forest.before_enter_decorators(id).to_vec();
249                let after_exit = forest.after_exit_decorators(id).to_vec();
250                let mut builder = if self.is_dyncall {
251                    DynNodeBuilder::new_dyncall()
252                } else {
253                    DynNodeBuilder::new_dyn()
254                };
255                builder = builder.with_before_enter(before_enter).with_after_exit(after_exit);
256                builder
257            },
258        }
259    }
260
261    #[cfg(debug_assertions)]
262    fn verify_node_in_forest(&self, forest: &MastForest) {
263        if let Some(id) = self.decorator_store.linked_id() {
264            // Verify that this node is the one stored at the given ID in the forest
265            let self_ptr = self as *const Self;
266            let forest_node = &forest.nodes[id];
267            let forest_node_ptr = match forest_node {
268                crate::mast::MastNode::Dyn(dyn_node) => dyn_node as *const DynNode as *const (),
269                _ => panic!("Node type mismatch at {:?}", id),
270            };
271            let self_as_void = self_ptr as *const ();
272            debug_assert_eq!(
273                self_as_void, forest_node_ptr,
274                "Node pointer mismatch: expected node at {:?} to be self",
275                id
276            );
277        }
278    }
279}
280
281// ARBITRARY IMPLEMENTATION
282// ================================================================================================
283
284#[cfg(all(feature = "arbitrary", test))]
285impl proptest::prelude::Arbitrary for DynNode {
286    type Parameters = ();
287
288    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
289        use proptest::prelude::*;
290
291        // Generate whether it's a dyncall or dynexec
292        any::<bool>()
293            .prop_map(|is_dyncall| {
294                if is_dyncall {
295                    DynNodeBuilder::new_dyncall().build()
296                } else {
297                    DynNodeBuilder::new_dyn().build()
298                }
299            })
300            .no_shrink()  // Pure random values, no meaningful shrinking pattern
301            .boxed()
302    }
303
304    type Strategy = proptest::prelude::BoxedStrategy<Self>;
305}
306
307// ------------------------------------------------------------------------------------------------
308/// Builder for creating [`DynNode`] instances with decorators.
309#[derive(Debug)]
310pub struct DynNodeBuilder {
311    is_dyncall: bool,
312    before_enter: Vec<DecoratorId>,
313    after_exit: Vec<DecoratorId>,
314    digest: Option<Word>,
315}
316
317impl DynNodeBuilder {
318    /// Creates a new builder for a DynNode representing a dynexec operation.
319    pub fn new_dyn() -> Self {
320        Self {
321            is_dyncall: false,
322            before_enter: Vec::new(),
323            after_exit: Vec::new(),
324            digest: None,
325        }
326    }
327
328    /// Creates a new builder for a DynNode representing a dyncall operation.
329    pub fn new_dyncall() -> Self {
330        Self {
331            is_dyncall: true,
332            before_enter: Vec::new(),
333            after_exit: Vec::new(),
334            digest: None,
335        }
336    }
337
338    /// Builds the DynNode with the specified decorators.
339    pub fn build(self) -> DynNode {
340        // Use the forced digest if provided, otherwise use the default digest
341        let digest = if let Some(forced_digest) = self.digest {
342            forced_digest
343        } else if self.is_dyncall {
344            DynNode::DYNCALL_DEFAULT_DIGEST
345        } else {
346            DynNode::DYN_DEFAULT_DIGEST
347        };
348
349        DynNode {
350            is_dyncall: self.is_dyncall,
351            digest,
352            decorator_store: DecoratorStore::new_owned_with_decorators(
353                self.before_enter,
354                self.after_exit,
355            ),
356        }
357    }
358}
359
360impl MastForestContributor for DynNodeBuilder {
361    fn add_to_forest(self, forest: &mut MastForest) -> Result<MastNodeId, MastForestError> {
362        // Use the forced digest if provided, otherwise use the default digest
363        let digest = if let Some(forced_digest) = self.digest {
364            forced_digest
365        } else if self.is_dyncall {
366            DynNode::DYNCALL_DEFAULT_DIGEST
367        } else {
368            DynNode::DYN_DEFAULT_DIGEST
369        };
370
371        // Determine the node ID that will be assigned
372        let future_node_id = MastNodeId::new_unchecked(forest.nodes.len() as u32);
373
374        // Store node-level decorators in the centralized NodeToDecoratorIds for efficient access
375        forest.register_node_decorators(future_node_id, &self.before_enter, &self.after_exit);
376
377        // Create the node in the forest with Linked variant from the start
378        // Move the data directly without intermediate cloning
379        let node_id = forest
380            .nodes
381            .push(
382                DynNode {
383                    is_dyncall: self.is_dyncall,
384                    digest,
385                    decorator_store: DecoratorStore::Linked { id: future_node_id },
386                }
387                .into(),
388            )
389            .map_err(|_| MastForestError::TooManyNodes)?;
390
391        Ok(node_id)
392    }
393
394    fn fingerprint_for_node(
395        &self,
396        forest: &MastForest,
397        _hash_by_node_id: &impl crate::LookupByIdx<MastNodeId, crate::mast::MastNodeFingerprint>,
398    ) -> Result<crate::mast::MastNodeFingerprint, MastForestError> {
399        // DynNode has no children, so we don't need hash_by_node_id
400        // Use the fingerprint_from_parts helper function with empty children array
401        crate::mast::node_fingerprint::fingerprint_from_parts(
402            forest,
403            _hash_by_node_id,
404            &self.before_enter,
405            &self.after_exit,
406            &[], // DynNode has no children
407            // Use the forced digest if available, otherwise use the default digest values
408            if let Some(forced_digest) = self.digest {
409                forced_digest
410            } else if self.is_dyncall {
411                DynNode::DYNCALL_DEFAULT_DIGEST
412            } else {
413                DynNode::DYN_DEFAULT_DIGEST
414            },
415        )
416    }
417
418    fn remap_children(
419        self,
420        _remapping: &impl crate::LookupByIdx<crate::mast::MastNodeId, crate::mast::MastNodeId>,
421    ) -> Self {
422        // DynNode has no children to remap, but preserve the digest
423        self
424    }
425
426    fn with_before_enter(mut self, decorators: impl Into<Vec<crate::mast::DecoratorId>>) -> Self {
427        self.before_enter = decorators.into();
428        self
429    }
430
431    fn with_after_exit(mut self, decorators: impl Into<Vec<crate::mast::DecoratorId>>) -> Self {
432        self.after_exit = decorators.into();
433        self
434    }
435
436    fn append_before_enter(
437        &mut self,
438        decorators: impl IntoIterator<Item = crate::mast::DecoratorId>,
439    ) {
440        self.before_enter.extend(decorators);
441    }
442
443    fn append_after_exit(
444        &mut self,
445        decorators: impl IntoIterator<Item = crate::mast::DecoratorId>,
446    ) {
447        self.after_exit.extend(decorators);
448    }
449
450    fn with_digest(mut self, digest: crate::Word) -> Self {
451        self.digest = Some(digest);
452        self
453    }
454}
455
456impl DynNodeBuilder {
457    /// Add this node to a forest using relaxed validation.
458    ///
459    /// This method is used during deserialization where nodes may reference child nodes
460    /// that haven't been added to the forest yet. The child node IDs have already been
461    /// validated against the expected final node count during the `try_into_mast_node_builder`
462    /// step, so we can safely skip validation here.
463    ///
464    /// Note: This is not part of the `MastForestContributor` trait because it's only
465    /// intended for internal use during deserialization.
466    ///
467    /// For DynNode, this is equivalent to the normal `add_to_forest` since dyn nodes
468    /// don't have child nodes to validate.
469    pub(in crate::mast) fn add_to_forest_relaxed(
470        self,
471        forest: &mut MastForest,
472    ) -> Result<MastNodeId, MastForestError> {
473        // Use the same digest computation as in build()
474        let digest = if let Some(forced_digest) = self.digest {
475            forced_digest
476        } else if self.is_dyncall {
477            DynNode::DYNCALL_DEFAULT_DIGEST
478        } else {
479            DynNode::DYN_DEFAULT_DIGEST
480        };
481
482        let future_node_id = MastNodeId::new_unchecked(forest.nodes.len() as u32);
483
484        // Store node-level decorators in the centralized NodeToDecoratorIds for efficient access
485        forest.register_node_decorators(future_node_id, &self.before_enter, &self.after_exit);
486
487        // Create the node in the forest with Linked variant from the start
488        // Move the data directly without intermediate cloning
489        let node_id = forest
490            .nodes
491            .push(
492                DynNode {
493                    is_dyncall: self.is_dyncall,
494                    digest,
495                    decorator_store: DecoratorStore::Linked { id: future_node_id },
496                }
497                .into(),
498            )
499            .map_err(|_| MastForestError::TooManyNodes)?;
500
501        Ok(node_id)
502    }
503}
504
505#[cfg(any(test, feature = "arbitrary"))]
506impl proptest::prelude::Arbitrary for DynNodeBuilder {
507    type Parameters = DynNodeBuilderParams;
508    type Strategy = proptest::strategy::BoxedStrategy<Self>;
509
510    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
511        use proptest::prelude::*;
512
513        (
514            any::<bool>(),
515            proptest::collection::vec(
516                super::arbitrary::decorator_id_strategy(params.max_decorator_id_u32),
517                0..=params.max_decorators,
518            ),
519            proptest::collection::vec(
520                super::arbitrary::decorator_id_strategy(params.max_decorator_id_u32),
521                0..=params.max_decorators,
522            ),
523        )
524            .prop_map(|(is_dyncall, before_enter, after_exit)| {
525                let builder = if is_dyncall {
526                    Self::new_dyncall()
527                } else {
528                    Self::new_dyn()
529                };
530                builder.with_before_enter(before_enter).with_after_exit(after_exit)
531            })
532            .boxed()
533    }
534}
535
536/// Parameters for generating DynNodeBuilder instances
537#[cfg(any(test, feature = "arbitrary"))]
538#[derive(Clone, Debug)]
539pub struct DynNodeBuilderParams {
540    pub max_decorators: usize,
541    pub max_decorator_id_u32: u32,
542}
543
544#[cfg(any(test, feature = "arbitrary"))]
545impl Default for DynNodeBuilderParams {
546    fn default() -> Self {
547        Self {
548            max_decorators: 4,
549            max_decorator_id_u32: 10,
550        }
551    }
552}
553
554#[cfg(test)]
555mod tests {
556    use miden_crypto::hash::rpo::Rpo256;
557
558    use super::*;
559
560    /// Ensures that the hash of `DynNode` is indeed the hash of 2 empty words, in the `DynNode`
561    /// domain.
562    #[test]
563    pub fn test_dyn_node_digest() {
564        let mut forest = MastForest::new();
565        let dyn_node_id = DynNodeBuilder::new_dyn().add_to_forest(&mut forest).unwrap();
566        let dyn_node = forest.get_node_by_id(dyn_node_id).unwrap().unwrap_dyn();
567        assert_eq!(
568            dyn_node.digest(),
569            Rpo256::merge_in_domain(&[Word::default(), Word::default()], DynNode::DYN_DOMAIN)
570        );
571
572        let dyncall_node_id = DynNodeBuilder::new_dyncall().add_to_forest(&mut forest).unwrap();
573        let dyncall_node = forest.get_node_by_id(dyncall_node_id).unwrap().unwrap_dyn();
574        assert_eq!(
575            dyncall_node.digest(),
576            Rpo256::merge_in_domain(&[Word::default(), Word::default()], DynNode::DYNCALL_DOMAIN)
577        );
578    }
579}