Skip to main content

miden_core/mast/node/
call_node.rs

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