miden_core/mast/node/
call_node.rs1use alloc::{boxed::Box, vec::Vec};
2use core::fmt;
3
4use miden_formatting::{
5 hex::ToHex,
6 prettier::{Document, PrettyPrint, const_text, text},
7};
8
9use super::{
10 MastForestContributor, MastNodeContext, MastNodeExt, fingerprint_with_child_fingerprints,
11};
12use crate::{
13 Felt, Word,
14 chiplets::hasher,
15 mast::{MastForest, MastForestError, MastNodeId},
16 operations::opcodes,
17 utils::LookupByIdx,
18};
19
20#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct CallNode {
31 callee: MastNodeId,
32 is_syscall: bool,
33 digest: Word,
34}
35
36impl CallNode {
39 pub const CALL_DOMAIN: Felt = Felt::new_unchecked(opcodes::CALL as u64);
41 pub const SYSCALL_DOMAIN: Felt = Felt::new_unchecked(opcodes::SYSCALL as u64);
43}
44
45impl CallNode {
48 pub fn callee(&self) -> MastNodeId {
50 self.callee
51 }
52
53 pub fn is_syscall(&self) -> bool {
55 self.is_syscall
56 }
57
58 pub fn domain(&self) -> Felt {
60 if self.is_syscall() {
61 Self::SYSCALL_DOMAIN
62 } else {
63 Self::CALL_DOMAIN
64 }
65 }
66}
67
68impl CallNode {
72 pub(super) fn to_pretty_print<'a>(
73 &'a self,
74 mast_forest: &'a MastForest,
75 ) -> impl PrettyPrint + 'a {
76 CallNodePrettyPrint { node: self, mast_forest }
77 }
78
79 pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a {
80 CallNodePrettyPrint { node: self, mast_forest }
81 }
82}
83
84struct CallNodePrettyPrint<'a> {
85 node: &'a CallNode,
86 mast_forest: &'a MastForest,
87}
88
89impl PrettyPrint for CallNodePrettyPrint<'_> {
90 fn render(&self) -> Document {
91 let callee_digest = self.mast_forest[self.node.callee].digest();
92 if self.node.is_syscall {
93 const_text("syscall")
94 + const_text(".")
95 + text(callee_digest.as_bytes().to_hex_with_prefix())
96 } else {
97 const_text("call")
98 + const_text(".")
99 + text(callee_digest.as_bytes().to_hex_with_prefix())
100 }
101 }
102}
103
104impl fmt::Display for CallNodePrettyPrint<'_> {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 use crate::prettier::PrettyPrint;
107 self.pretty_print(f)
108 }
109}
110
111impl MastNodeExt for CallNode {
115 fn digest(&self) -> Word {
134 self.digest
135 }
136
137 fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn fmt::Display + 'a> {
138 Box::new(CallNode::to_display(self, mast_forest))
139 }
140
141 fn to_pretty_print<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn PrettyPrint + 'a> {
142 Box::new(CallNode::to_pretty_print(self, mast_forest))
143 }
144
145 fn has_children(&self) -> bool {
146 true
147 }
148
149 fn append_children_to(&self, target: &mut Vec<MastNodeId>) {
150 target.push(self.callee());
151 }
152
153 fn for_each_child<F>(&self, mut f: F)
154 where
155 F: FnMut(MastNodeId),
156 {
157 f(self.callee());
158 }
159
160 fn domain(&self) -> Felt {
161 self.domain()
162 }
163
164 type Builder = CallNodeBuilder;
165
166 fn to_builder(self, _forest: &MastForest) -> Self::Builder {
167 let builder = if self.is_syscall {
168 CallNodeBuilder::new_syscall(self.callee)
169 } else {
170 CallNodeBuilder::new(self.callee)
171 };
172 builder.with_digest(self.digest)
173 }
174}
175
176#[derive(Debug)]
179pub struct CallNodeBuilder {
180 callee: MastNodeId,
181 is_syscall: bool,
182 digest: Option<Word>,
183}
184
185impl CallNodeBuilder {
186 pub fn new(callee: MastNodeId) -> Self {
188 Self { callee, is_syscall: false, digest: None }
189 }
190
191 pub fn new_syscall(callee: MastNodeId) -> Self {
193 Self { callee, is_syscall: true, digest: None }
194 }
195
196 pub fn build(self, context: &impl MastNodeContext) -> Result<CallNode, MastForestError> {
198 let callee = context
199 .get_node_by_id(self.callee)
200 .ok_or_else(|| MastForestError::NodeIdOverflow(self.callee, context.node_count()))?;
201
202 let digest = if let Some(forced_digest) = self.digest {
204 forced_digest
205 } else {
206 let callee_digest = callee.digest();
207 let domain = if self.is_syscall {
208 CallNode::SYSCALL_DOMAIN
209 } else {
210 CallNode::CALL_DOMAIN
211 };
212
213 hasher::merge_in_domain(&[callee_digest, Word::default()], domain)
214 };
215
216 Ok(CallNode {
217 callee: self.callee,
218 is_syscall: self.is_syscall,
219 digest,
220 })
221 }
222
223 pub(in crate::mast) fn build_linked(self) -> Result<CallNode, MastForestError> {
224 Ok(CallNode {
225 callee: self.callee,
226 is_syscall: self.is_syscall,
227 digest: self.digest.ok_or(MastForestError::DigestRequiredForDeserialization)?,
228 })
229 }
230}
231
232#[cfg(any(test, feature = "arbitrary"))]
233impl CallNodeBuilder {
234 pub fn add_to_forest(self, forest: &mut MastForest) -> Result<MastNodeId, MastForestError> {
236 let node = self.build(forest)?;
237 forest.nodes.push(node.into()).map_err(|_| MastForestError::TooManyNodes)
238 }
239}
240
241impl MastForestContributor for CallNodeBuilder {
242 fn fingerprint_for_node(
243 &self,
244 context: &impl MastNodeContext,
245 hash_by_node_id: &impl LookupByIdx<MastNodeId, Word>,
246 ) -> Result<Word, MastForestError> {
247 let node_digest = if let Some(forced_digest) = self.digest {
248 forced_digest
249 } else {
250 let callee_digest = context
251 .get_node_by_id(self.callee)
252 .ok_or_else(|| MastForestError::NodeIdOverflow(self.callee, context.node_count()))?
253 .digest();
254 let domain = if self.is_syscall {
255 CallNode::SYSCALL_DOMAIN
256 } else {
257 CallNode::CALL_DOMAIN
258 };
259
260 hasher::merge_in_domain(&[callee_digest, Word::default()], domain)
261 };
262
263 fingerprint_with_child_fingerprints(node_digest, &[self.callee], context, hash_by_node_id)
264 }
265
266 fn remap_children(self, remapping: &impl LookupByIdx<MastNodeId, MastNodeId>) -> Self {
267 CallNodeBuilder {
268 callee: *remapping.get(self.callee).unwrap_or(&self.callee),
269 is_syscall: self.is_syscall,
270 digest: self.digest,
271 }
272 }
273
274 fn with_digest(mut self, digest: Word) -> Self {
275 self.digest = Some(digest);
276 self
277 }
278}
279
280#[cfg(any(test, feature = "arbitrary"))]
281impl proptest::prelude::Arbitrary for CallNodeBuilder {
282 type Parameters = ();
283 type Strategy = proptest::strategy::BoxedStrategy<Self>;
284
285 fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy {
286 use proptest::prelude::*;
287
288 (any::<MastNodeId>(), any::<bool>())
289 .prop_map(|(callee, is_syscall)| {
290 if is_syscall {
291 Self::new_syscall(callee)
292 } else {
293 Self::new(callee)
294 }
295 })
296 .boxed()
297 }
298}