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};
12use crate::{
13    Felt, Word,
14    chiplets::hasher,
15    mast::{
16        DecoratorId, DecoratorStore, MastForest, MastForestError, MastNode, MastNodeFingerprint,
17        MastNodeId,
18    },
19    operations::{OPCODE_CALL, OPCODE_SYSCALL},
20    utils::{Idx, LookupByIdx},
21};
22
23// CALL NODE
24// ================================================================================================
25
26/// A Call node describes a function call such that the callee is executed in a different execution
27/// context from the currently executing code.
28///
29/// A call node can be of two types:
30/// - A simple call: the callee is executed in the new user context.
31/// - A syscall: the callee is executed in the root context.
32#[derive(Debug, Clone, PartialEq, Eq)]
33#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
34#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
35pub struct CallNode {
36    callee: MastNodeId,
37    is_syscall: bool,
38    digest: Word,
39    decorator_store: DecoratorStore,
40}
41
42//-------------------------------------------------------------------------------------------------
43/// Constants
44impl CallNode {
45    /// The domain of the call block (used for control block hashing).
46    pub const CALL_DOMAIN: Felt = Felt::new(OPCODE_CALL as u64);
47    /// The domain of the syscall block (used for control block hashing).
48    pub const SYSCALL_DOMAIN: Felt = Felt::new(OPCODE_SYSCALL as u64);
49}
50
51//-------------------------------------------------------------------------------------------------
52/// Public accessors
53impl CallNode {
54    /// Returns the ID of the node to be invoked by this call node.
55    pub fn callee(&self) -> MastNodeId {
56        self.callee
57    }
58
59    /// Returns true if this call node represents a syscall.
60    pub fn is_syscall(&self) -> bool {
61        self.is_syscall
62    }
63
64    /// Returns the domain of this call node.
65    pub fn domain(&self) -> Felt {
66        if self.is_syscall() {
67            Self::SYSCALL_DOMAIN
68        } else {
69            Self::CALL_DOMAIN
70        }
71    }
72}
73
74// PRETTY PRINTING
75// ================================================================================================
76
77impl CallNode {
78    pub(super) fn to_pretty_print<'a>(
79        &'a self,
80        mast_forest: &'a MastForest,
81    ) -> impl PrettyPrint + 'a {
82        CallNodePrettyPrint { node: self, mast_forest }
83    }
84
85    pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a {
86        CallNodePrettyPrint { node: self, mast_forest }
87    }
88}
89
90struct CallNodePrettyPrint<'a> {
91    node: &'a CallNode,
92    mast_forest: &'a MastForest,
93}
94
95impl CallNodePrettyPrint<'_> {
96    /// Concatenates the provided decorators in a single line. If the list of decorators is not
97    /// empty, prepends `prepend` and appends `append` to the decorator document.
98    fn concatenate_decorators(
99        &self,
100        decorator_ids: &[DecoratorId],
101        prepend: Document,
102        append: Document,
103    ) -> Document {
104        let decorators = decorator_ids
105            .iter()
106            .map(|&decorator_id| self.mast_forest[decorator_id].render())
107            .reduce(|acc, doc| acc + const_text(" ") + doc)
108            .unwrap_or_default();
109
110        if decorators.is_empty() {
111            decorators
112        } else {
113            prepend + decorators + append
114        }
115    }
116
117    fn single_line_pre_decorators(&self) -> Document {
118        self.concatenate_decorators(
119            self.node.before_enter(self.mast_forest),
120            Document::Empty,
121            const_text(" "),
122        )
123    }
124
125    fn single_line_post_decorators(&self) -> Document {
126        self.concatenate_decorators(
127            self.node.after_exit(self.mast_forest),
128            const_text(" "),
129            Document::Empty,
130        )
131    }
132
133    fn multi_line_pre_decorators(&self) -> Document {
134        self.concatenate_decorators(self.node.before_enter(self.mast_forest), Document::Empty, nl())
135    }
136
137    fn multi_line_post_decorators(&self) -> Document {
138        self.concatenate_decorators(self.node.after_exit(self.mast_forest), nl(), Document::Empty)
139    }
140}
141
142impl PrettyPrint for CallNodePrettyPrint<'_> {
143    fn render(&self) -> Document {
144        let call_or_syscall = {
145            let callee_digest = self.mast_forest[self.node.callee].digest();
146            if self.node.is_syscall {
147                const_text("syscall")
148                    + const_text(".")
149                    + text(callee_digest.as_bytes().to_hex_with_prefix())
150            } else {
151                const_text("call")
152                    + const_text(".")
153                    + text(callee_digest.as_bytes().to_hex_with_prefix())
154            }
155        };
156
157        let single_line = self.single_line_pre_decorators()
158            + call_or_syscall.clone()
159            + self.single_line_post_decorators();
160        let multi_line =
161            self.multi_line_pre_decorators() + call_or_syscall + self.multi_line_post_decorators();
162
163        single_line | multi_line
164    }
165}
166
167impl fmt::Display for CallNodePrettyPrint<'_> {
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        use crate::prettier::PrettyPrint;
170        self.pretty_print(f)
171    }
172}
173
174// MAST NODE TRAIT IMPLEMENTATION
175// ================================================================================================
176
177impl MastNodeExt for CallNode {
178    /// Returns a commitment to this Call node.
179    ///
180    /// The commitment is computed as a hash of the callee and an empty word ([ZERO; 4]) in the
181    /// domain defined by either [Self::CALL_DOMAIN] or [Self::SYSCALL_DOMAIN], depending on
182    /// whether the node represents a simple call or a syscall - i.e.,:
183    /// ```
184    /// # use miden_core::mast::CallNode;
185    /// # use miden_crypto::{Word, hash::poseidon2::Poseidon2 as Hasher};
186    /// # let callee_digest = Word::default();
187    /// Hasher::merge_in_domain(&[callee_digest, Word::default()], CallNode::CALL_DOMAIN);
188    /// ```
189    /// or
190    /// ```
191    /// # use miden_core::mast::CallNode;
192    /// # use miden_crypto::{Word, hash::poseidon2::Poseidon2 as Hasher};
193    /// # let callee_digest = Word::default();
194    /// Hasher::merge_in_domain(&[callee_digest, Word::default()], CallNode::SYSCALL_DOMAIN);
195    /// ```
196    fn digest(&self) -> Word {
197        self.digest
198    }
199
200    /// Returns the decorators to be executed before this node is executed.
201    fn before_enter<'a>(&'a self, forest: &'a MastForest) -> &'a [DecoratorId] {
202        #[cfg(debug_assertions)]
203        self.verify_node_in_forest(forest);
204        self.decorator_store.before_enter(forest)
205    }
206
207    /// Returns the decorators to be executed after this node is executed.
208    fn after_exit<'a>(&'a self, forest: &'a MastForest) -> &'a [DecoratorId] {
209        #[cfg(debug_assertions)]
210        self.verify_node_in_forest(forest);
211        self.decorator_store.after_exit(forest)
212    }
213
214    fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn fmt::Display + 'a> {
215        Box::new(CallNode::to_display(self, mast_forest))
216    }
217
218    fn to_pretty_print<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn PrettyPrint + 'a> {
219        Box::new(CallNode::to_pretty_print(self, mast_forest))
220    }
221
222    fn has_children(&self) -> bool {
223        true
224    }
225
226    fn append_children_to(&self, target: &mut Vec<MastNodeId>) {
227        target.push(self.callee());
228    }
229
230    fn for_each_child<F>(&self, mut f: F)
231    where
232        F: FnMut(MastNodeId),
233    {
234        f(self.callee());
235    }
236
237    fn domain(&self) -> Felt {
238        self.domain()
239    }
240
241    type Builder = CallNodeBuilder;
242
243    fn to_builder(self, forest: &MastForest) -> Self::Builder {
244        // Extract decorators from decorator_store if in Owned state
245        match self.decorator_store {
246            DecoratorStore::Owned { before_enter, after_exit, .. } => {
247                let mut builder = if self.is_syscall {
248                    CallNodeBuilder::new_syscall(self.callee)
249                } else {
250                    CallNodeBuilder::new(self.callee)
251                };
252                builder = builder.with_before_enter(before_enter).with_after_exit(after_exit);
253                builder
254            },
255            DecoratorStore::Linked { id } => {
256                // Extract decorators from forest storage when in Linked state
257                let before_enter = forest.before_enter_decorators(id).to_vec();
258                let after_exit = forest.after_exit_decorators(id).to_vec();
259                let mut builder = if self.is_syscall {
260                    CallNodeBuilder::new_syscall(self.callee)
261                } else {
262                    CallNodeBuilder::new(self.callee)
263                };
264                builder = builder.with_before_enter(before_enter).with_after_exit(after_exit);
265                builder
266            },
267        }
268    }
269
270    #[cfg(debug_assertions)]
271    fn verify_node_in_forest(&self, forest: &MastForest) {
272        if let Some(id) = self.decorator_store.linked_id() {
273            // Verify that this node is the one stored at the given ID in the forest
274            let self_ptr = self as *const Self;
275            let forest_node = &forest.nodes[id];
276            let forest_node_ptr = match forest_node {
277                MastNode::Call(call_node) => call_node as *const CallNode as *const (),
278                _ => panic!("Node type mismatch at {:?}", id),
279            };
280            let self_as_void = self_ptr as *const ();
281            debug_assert_eq!(
282                self_as_void, forest_node_ptr,
283                "Node pointer mismatch: expected node at {:?} to be self",
284                id
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));
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                crate::chiplets::hasher::merge_in_domain(
456                    &[callee_digest, miden_crypto::Word::default()],
457                    domain,
458                )
459            },
460        )
461    }
462
463    fn remap_children(self, remapping: &impl LookupByIdx<MastNodeId, MastNodeId>) -> Self {
464        CallNodeBuilder {
465            callee: *remapping.get(self.callee).unwrap_or(&self.callee),
466            is_syscall: self.is_syscall,
467            before_enter: self.before_enter,
468            after_exit: self.after_exit,
469            digest: self.digest,
470        }
471    }
472
473    fn with_before_enter(mut self, decorators: impl Into<Vec<DecoratorId>>) -> Self {
474        self.before_enter = decorators.into();
475        self
476    }
477
478    fn with_after_exit(mut self, decorators: impl Into<Vec<DecoratorId>>) -> Self {
479        self.after_exit = decorators.into();
480        self
481    }
482
483    fn append_before_enter(&mut self, decorators: impl IntoIterator<Item = DecoratorId>) {
484        self.before_enter.extend(decorators);
485    }
486
487    fn append_after_exit(&mut self, decorators: impl IntoIterator<Item = DecoratorId>) {
488        self.after_exit.extend(decorators);
489    }
490
491    fn with_digest(mut self, digest: crate::Word) -> Self {
492        self.digest = Some(digest);
493        self
494    }
495}
496
497impl CallNodeBuilder {
498    /// Add this node to a forest using relaxed validation.
499    ///
500    /// This method is used during deserialization where nodes may reference child nodes
501    /// that haven't been added to the forest yet. The child node IDs have already been
502    /// validated against the expected final node count during the `try_into_mast_node_builder`
503    /// step, so we can safely skip validation here.
504    ///
505    /// Note: This is not part of the `MastForestContributor` trait because it's only
506    /// intended for internal use during deserialization.
507    pub(in crate::mast) fn add_to_forest_relaxed(
508        self,
509        forest: &mut MastForest,
510    ) -> Result<MastNodeId, MastForestError> {
511        // Use the forced digest if provided, otherwise use a default digest
512        // The actual digest computation will be handled when the forest is complete
513        let Some(digest) = self.digest else {
514            return Err(MastForestError::DigestRequiredForDeserialization);
515        };
516
517        let future_node_id = MastNodeId::new_unchecked(forest.nodes.len() as u32);
518
519        // Create the node in the forest with Linked variant from the start
520        // Note: Decorators are already in forest.debug_info from deserialization
521        // Move the data directly without intermediate cloning
522        let node_id = forest
523            .nodes
524            .push(
525                CallNode {
526                    callee: self.callee,
527                    is_syscall: self.is_syscall,
528                    digest,
529                    decorator_store: DecoratorStore::Linked { id: future_node_id },
530                }
531                .into(),
532            )
533            .map_err(|_| MastForestError::TooManyNodes)?;
534
535        Ok(node_id)
536    }
537}
538
539#[cfg(any(test, feature = "arbitrary"))]
540impl proptest::prelude::Arbitrary for CallNodeBuilder {
541    type Parameters = CallNodeBuilderParams;
542    type Strategy = proptest::strategy::BoxedStrategy<Self>;
543
544    fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
545        use proptest::prelude::*;
546
547        (
548            any::<MastNodeId>(),
549            any::<bool>(),
550            proptest::collection::vec(
551                super::arbitrary::decorator_id_strategy(params.max_decorator_id_u32),
552                0..=params.max_decorators,
553            ),
554            proptest::collection::vec(
555                super::arbitrary::decorator_id_strategy(params.max_decorator_id_u32),
556                0..=params.max_decorators,
557            ),
558        )
559            .prop_map(|(callee, is_syscall, before_enter, after_exit)| {
560                let mut builder = if is_syscall {
561                    Self::new_syscall(callee)
562                } else {
563                    Self::new(callee)
564                };
565                builder = builder.with_before_enter(before_enter).with_after_exit(after_exit);
566                builder
567            })
568            .boxed()
569    }
570}
571
572/// Parameters for generating CallNodeBuilder instances
573#[cfg(any(test, feature = "arbitrary"))]
574#[derive(Clone, Debug)]
575pub struct CallNodeBuilderParams {
576    pub max_decorators: usize,
577    pub max_decorator_id_u32: u32,
578}
579
580#[cfg(any(test, feature = "arbitrary"))]
581impl Default for CallNodeBuilderParams {
582    fn default() -> Self {
583        Self {
584            max_decorators: 4,
585            max_decorator_id_u32: 10,
586        }
587    }
588}