1use alloc::{boxed::Box, vec::Vec};
2use core::fmt;
3
4use miden_formatting::{
5 hex::ToHex,
6 prettier::{Document, PrettyPrint, const_text, text},
7};
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11use super::{
12 MastForestContributor, MastNodeContext, MastNodeExt, fingerprint_with_child_fingerprints,
13};
14use crate::{
15 Felt, Word,
16 chiplets::hasher,
17 mast::{MastForest, MastForestError, MastNodeId},
18 operations::opcodes,
19 utils::LookupByIdx,
20};
21
22#[derive(Debug, Clone, PartialEq, Eq)]
32#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
33#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
34pub struct CallNode {
35 callee: MastNodeId,
36 is_syscall: bool,
37 digest: Word,
38}
39
40impl CallNode {
43 pub const CALL_DOMAIN: Felt = Felt::new_unchecked(opcodes::CALL as u64);
45 pub const SYSCALL_DOMAIN: Felt = Felt::new_unchecked(opcodes::SYSCALL as u64);
47}
48
49impl CallNode {
52 pub fn callee(&self) -> MastNodeId {
54 self.callee
55 }
56
57 pub fn is_syscall(&self) -> bool {
59 self.is_syscall
60 }
61
62 pub fn domain(&self) -> Felt {
64 if self.is_syscall() {
65 Self::SYSCALL_DOMAIN
66 } else {
67 Self::CALL_DOMAIN
68 }
69 }
70}
71
72impl CallNode {
76 pub(super) fn to_pretty_print<'a>(
77 &'a self,
78 mast_forest: &'a MastForest,
79 ) -> impl PrettyPrint + 'a {
80 CallNodePrettyPrint { node: self, mast_forest }
81 }
82
83 pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a {
84 CallNodePrettyPrint { node: self, mast_forest }
85 }
86}
87
88struct CallNodePrettyPrint<'a> {
89 node: &'a CallNode,
90 mast_forest: &'a MastForest,
91}
92
93impl PrettyPrint for CallNodePrettyPrint<'_> {
94 fn render(&self) -> Document {
95 let callee_digest = self.mast_forest[self.node.callee].digest();
96 if self.node.is_syscall {
97 const_text("syscall")
98 + const_text(".")
99 + text(callee_digest.as_bytes().to_hex_with_prefix())
100 } else {
101 const_text("call")
102 + const_text(".")
103 + text(callee_digest.as_bytes().to_hex_with_prefix())
104 }
105 }
106}
107
108impl fmt::Display for CallNodePrettyPrint<'_> {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 use crate::prettier::PrettyPrint;
111 self.pretty_print(f)
112 }
113}
114
115impl MastNodeExt for CallNode {
119 fn digest(&self) -> Word {
138 self.digest
139 }
140
141 fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn fmt::Display + 'a> {
142 Box::new(CallNode::to_display(self, mast_forest))
143 }
144
145 fn to_pretty_print<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn PrettyPrint + 'a> {
146 Box::new(CallNode::to_pretty_print(self, mast_forest))
147 }
148
149 fn has_children(&self) -> bool {
150 true
151 }
152
153 fn append_children_to(&self, target: &mut Vec<MastNodeId>) {
154 target.push(self.callee());
155 }
156
157 fn for_each_child<F>(&self, mut f: F)
158 where
159 F: FnMut(MastNodeId),
160 {
161 f(self.callee());
162 }
163
164 fn domain(&self) -> Felt {
165 self.domain()
166 }
167
168 type Builder = CallNodeBuilder;
169
170 fn to_builder(self, _forest: &MastForest) -> Self::Builder {
171 let builder = if self.is_syscall {
172 CallNodeBuilder::new_syscall(self.callee)
173 } else {
174 CallNodeBuilder::new(self.callee)
175 };
176 builder.with_digest(self.digest)
177 }
178}
179
180#[cfg(all(feature = "arbitrary", test))]
184impl proptest::prelude::Arbitrary for CallNode {
185 type Parameters = ();
186
187 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
188 use proptest::prelude::*;
189
190 use crate::Felt;
191
192 (any::<MastNodeId>(), any::<[u64; 4]>(), any::<bool>())
194 .prop_map(|(callee, digest_array, is_syscall)| {
195 let digest = Word::from(digest_array.map(Felt::new_unchecked));
197 CallNode {
199 callee,
200 is_syscall,
201 digest,
202 }
203 })
204 .no_shrink() .boxed()
206 }
207
208 type Strategy = proptest::prelude::BoxedStrategy<Self>;
209}
210
211#[derive(Debug)]
214pub struct CallNodeBuilder {
215 callee: MastNodeId,
216 is_syscall: bool,
217 digest: Option<Word>,
218}
219
220impl CallNodeBuilder {
221 pub fn new(callee: MastNodeId) -> Self {
223 Self { callee, is_syscall: false, digest: None }
224 }
225
226 pub fn new_syscall(callee: MastNodeId) -> Self {
228 Self { callee, is_syscall: true, digest: None }
229 }
230
231 pub fn build(self, context: &impl MastNodeContext) -> Result<CallNode, MastForestError> {
233 let callee = context
234 .get_node_by_id(self.callee)
235 .ok_or_else(|| MastForestError::NodeIdOverflow(self.callee, context.node_count()))?;
236
237 let digest = if let Some(forced_digest) = self.digest {
239 forced_digest
240 } else {
241 let callee_digest = callee.digest();
242 let domain = if self.is_syscall {
243 CallNode::SYSCALL_DOMAIN
244 } else {
245 CallNode::CALL_DOMAIN
246 };
247
248 hasher::merge_in_domain(&[callee_digest, Word::default()], domain)
249 };
250
251 Ok(CallNode {
252 callee: self.callee,
253 is_syscall: self.is_syscall,
254 digest,
255 })
256 }
257
258 pub(in crate::mast) fn build_linked(self) -> Result<CallNode, MastForestError> {
259 Ok(CallNode {
260 callee: self.callee,
261 is_syscall: self.is_syscall,
262 digest: self.digest.ok_or(MastForestError::DigestRequiredForDeserialization)?,
263 })
264 }
265}
266
267#[cfg(any(test, feature = "arbitrary"))]
268impl CallNodeBuilder {
269 pub fn add_to_forest(self, forest: &mut MastForest) -> Result<MastNodeId, MastForestError> {
271 let node = self.build(forest)?;
272 forest.nodes.push(node.into()).map_err(|_| MastForestError::TooManyNodes)
273 }
274}
275
276impl MastForestContributor for CallNodeBuilder {
277 fn fingerprint_for_node(
278 &self,
279 context: &impl MastNodeContext,
280 hash_by_node_id: &impl LookupByIdx<MastNodeId, Word>,
281 ) -> Result<Word, MastForestError> {
282 let node_digest = if let Some(forced_digest) = self.digest {
283 forced_digest
284 } else {
285 let callee_digest = context
286 .get_node_by_id(self.callee)
287 .ok_or_else(|| MastForestError::NodeIdOverflow(self.callee, context.node_count()))?
288 .digest();
289 let domain = if self.is_syscall {
290 CallNode::SYSCALL_DOMAIN
291 } else {
292 CallNode::CALL_DOMAIN
293 };
294
295 hasher::merge_in_domain(&[callee_digest, Word::default()], domain)
296 };
297
298 fingerprint_with_child_fingerprints(node_digest, &[self.callee], context, hash_by_node_id)
299 }
300
301 fn remap_children(self, remapping: &impl LookupByIdx<MastNodeId, MastNodeId>) -> Self {
302 CallNodeBuilder {
303 callee: *remapping.get(self.callee).unwrap_or(&self.callee),
304 is_syscall: self.is_syscall,
305 digest: self.digest,
306 }
307 }
308
309 fn with_digest(mut self, digest: Word) -> Self {
310 self.digest = Some(digest);
311 self
312 }
313}
314
315#[cfg(any(test, feature = "arbitrary"))]
316impl proptest::prelude::Arbitrary for CallNodeBuilder {
317 type Parameters = ();
318 type Strategy = proptest::strategy::BoxedStrategy<Self>;
319
320 fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy {
321 use proptest::prelude::*;
322
323 (any::<MastNodeId>(), any::<bool>())
324 .prop_map(|(callee, is_syscall)| {
325 if is_syscall {
326 Self::new_syscall(callee)
327 } else {
328 Self::new(callee)
329 }
330 })
331 .boxed()
332 }
333}