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#[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
42impl CallNode {
45 pub const CALL_DOMAIN: Felt = Felt::new(OPCODE_CALL as u64);
47 pub const SYSCALL_DOMAIN: Felt = Felt::new(OPCODE_SYSCALL as u64);
49}
50
51impl CallNode {
54 pub fn callee(&self) -> MastNodeId {
56 self.callee
57 }
58
59 pub fn is_syscall(&self) -> bool {
61 self.is_syscall
62 }
63
64 pub fn domain(&self) -> Felt {
66 if self.is_syscall() {
67 Self::SYSCALL_DOMAIN
68 } else {
69 Self::CALL_DOMAIN
70 }
71 }
72}
73
74impl 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 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
174impl MastNodeExt for CallNode {
178 fn digest(&self) -> Word {
197 self.digest
198 }
199
200 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 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 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 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 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#[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 (any::<MastNodeId>(), any::<[u64; 4]>(), any::<bool>())
304 .prop_map(|(callee, digest_array, is_syscall)| {
305 let digest = Word::from(digest_array.map(Felt::new));
307 CallNode {
309 callee,
310 is_syscall,
311 digest,
312 decorator_store: DecoratorStore::default(),
313 }
314 })
315 .no_shrink() .boxed()
317 }
318
319 type Strategy = proptest::prelude::BoxedStrategy<Self>;
320}
321
322#[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 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 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 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 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 let future_node_id = MastNodeId::new_unchecked(forest.nodes.len() as u32);
396
397 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 forest.register_node_decorators(future_node_id, &self.before_enter, &self.after_exit);
413
414 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 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 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 pub(in crate::mast) fn add_to_forest_relaxed(
508 self,
509 forest: &mut MastForest,
510 ) -> Result<MastNodeId, MastForestError> {
511 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 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#[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}