1use alloc::{boxed::Box, vec::Vec};
2use core::fmt;
3
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Serialize};
6
7use super::{MastForestContributor, MastNodeExt};
8#[cfg(debug_assertions)]
9use crate::mast::MastNode;
10use crate::{
11 Felt, Word,
12 mast::{
13 DecoratorId, DecoratorStore, MastForest, MastForestError, MastNodeFingerprint, MastNodeId,
14 },
15 operations::opcodes,
16 prettier::{Document, PrettyPrint, const_text, nl},
17 utils::LookupByIdx,
18};
19
20#[derive(Debug, Clone, PartialEq, Eq)]
25#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
26#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
27pub struct DynNode {
28 is_dyncall: bool,
29 digest: Word,
30 decorator_store: DecoratorStore,
31}
32
33impl DynNode {
35 pub const DYN_DOMAIN: Felt = Felt::new_unchecked(opcodes::DYN as u64);
37
38 pub const DYNCALL_DOMAIN: Felt = Felt::new_unchecked(opcodes::DYNCALL as u64);
40}
41
42impl DynNode {
44 pub const DYNCALL_DEFAULT_DIGEST: Word = Word::new([
46 Felt::new_unchecked(16830415514927835337),
47 Felt::new_unchecked(12164645914672292987),
48 Felt::new_unchecked(13192574193032437705),
49 Felt::new_unchecked(4604554596675732269),
50 ]);
51
52 pub const DYN_DEFAULT_DIGEST: Word = Word::new([
54 Felt::new_unchecked(16952228088962355159),
55 Felt::new_unchecked(5793482471479538911),
56 Felt::new_unchecked(14446299416172848527),
57 Felt::new_unchecked(13522295374716441620),
58 ]);
59}
60
61impl DynNode {
63 pub fn is_dyncall(&self) -> bool {
65 self.is_dyncall
66 }
67
68 pub fn domain(&self) -> Felt {
70 if self.is_dyncall() {
71 Self::DYNCALL_DOMAIN
72 } else {
73 Self::DYN_DOMAIN
74 }
75 }
76}
77
78impl DynNode {
82 pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a {
83 DynNodePrettyPrint { node: self, mast_forest }
84 }
85
86 pub(super) fn to_pretty_print<'a>(
87 &'a self,
88 mast_forest: &'a MastForest,
89 ) -> impl PrettyPrint + 'a {
90 DynNodePrettyPrint { node: self, mast_forest }
91 }
92}
93
94struct DynNodePrettyPrint<'a> {
95 node: &'a DynNode,
96 mast_forest: &'a MastForest,
97}
98
99impl DynNodePrettyPrint<'_> {
100 fn concatenate_decorators(
103 &self,
104 decorator_ids: &[DecoratorId],
105 prepend: Document,
106 append: Document,
107 ) -> Document {
108 let decorators = decorator_ids
109 .iter()
110 .map(|&decorator_id| self.mast_forest[decorator_id].render())
111 .reduce(|acc, doc| acc + const_text(" ") + doc)
112 .unwrap_or_default();
113
114 if decorators.is_empty() {
115 decorators
116 } else {
117 prepend + decorators + append
118 }
119 }
120
121 fn single_line_pre_decorators(&self) -> Document {
122 self.concatenate_decorators(
123 self.node.before_enter(self.mast_forest),
124 Document::Empty,
125 const_text(" "),
126 )
127 }
128
129 fn single_line_post_decorators(&self) -> Document {
130 self.concatenate_decorators(
131 self.node.after_exit(self.mast_forest),
132 const_text(" "),
133 Document::Empty,
134 )
135 }
136
137 fn multi_line_pre_decorators(&self) -> Document {
138 self.concatenate_decorators(self.node.before_enter(self.mast_forest), Document::Empty, nl())
139 }
140
141 fn multi_line_post_decorators(&self) -> Document {
142 self.concatenate_decorators(self.node.after_exit(self.mast_forest), nl(), Document::Empty)
143 }
144}
145
146impl PrettyPrint for DynNodePrettyPrint<'_> {
147 fn render(&self) -> Document {
148 let dyn_text = if self.node.is_dyncall() {
149 const_text("dyncall")
150 } else {
151 const_text("dyn")
152 };
153
154 let single_line = self.single_line_pre_decorators()
155 + dyn_text.clone()
156 + self.single_line_post_decorators();
157 let multi_line =
158 self.multi_line_pre_decorators() + dyn_text + self.multi_line_post_decorators();
159
160 single_line | multi_line
161 }
162}
163
164impl fmt::Display for DynNodePrettyPrint<'_> {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 self.pretty_print(f)
167 }
168}
169
170impl MastNodeExt for DynNode {
174 fn digest(&self) -> Word {
176 self.digest
177 }
178
179 fn before_enter<'a>(&'a self, forest: &'a MastForest) -> &'a [DecoratorId] {
181 #[cfg(debug_assertions)]
182 self.verify_node_in_forest(forest);
183 self.decorator_store.before_enter(forest)
184 }
185
186 fn after_exit<'a>(&'a self, forest: &'a MastForest) -> &'a [DecoratorId] {
188 #[cfg(debug_assertions)]
189 self.verify_node_in_forest(forest);
190 self.decorator_store.after_exit(forest)
191 }
192
193 fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn fmt::Display + 'a> {
194 Box::new(DynNode::to_display(self, mast_forest))
195 }
196
197 fn to_pretty_print<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn PrettyPrint + 'a> {
198 Box::new(DynNode::to_pretty_print(self, mast_forest))
199 }
200
201 fn has_children(&self) -> bool {
202 false
203 }
204
205 fn append_children_to(&self, _target: &mut Vec<MastNodeId>) {
206 }
208
209 fn for_each_child<F>(&self, _f: F)
210 where
211 F: FnMut(MastNodeId),
212 {
213 }
215
216 fn domain(&self) -> Felt {
217 self.domain()
218 }
219
220 type Builder = DynNodeBuilder;
221
222 fn to_builder(self, forest: &MastForest) -> Self::Builder {
223 match self.decorator_store {
225 DecoratorStore::Owned { before_enter, after_exit, .. } => {
226 let mut builder = if self.is_dyncall {
227 DynNodeBuilder::new_dyncall()
228 } else {
229 DynNodeBuilder::new_dyn()
230 };
231 builder = builder.with_before_enter(before_enter).with_after_exit(after_exit);
232 builder
233 },
234 DecoratorStore::Linked { id } => {
235 let before_enter = forest.before_enter_decorators(id).to_vec();
237 let after_exit = forest.after_exit_decorators(id).to_vec();
238 let mut builder = if self.is_dyncall {
239 DynNodeBuilder::new_dyncall()
240 } else {
241 DynNodeBuilder::new_dyn()
242 };
243 builder = builder.with_before_enter(before_enter).with_after_exit(after_exit);
244 builder
245 },
246 }
247 }
248
249 #[cfg(debug_assertions)]
250 fn verify_node_in_forest(&self, forest: &MastForest) {
251 if let Some(id) = self.decorator_store.linked_id() {
252 let self_ptr = self as *const Self;
254 let forest_node = &forest.nodes[id];
255 let forest_node_ptr = match forest_node {
256 MastNode::Dyn(dyn_node) => dyn_node as *const DynNode as *const (),
257 _ => panic!("Node type mismatch at {id:?}"),
258 };
259 let self_as_void = self_ptr as *const ();
260 debug_assert_eq!(
261 self_as_void, forest_node_ptr,
262 "Node pointer mismatch: expected node at {id:?} to be self"
263 );
264 }
265 }
266}
267
268#[cfg(all(feature = "arbitrary", test))]
272impl proptest::prelude::Arbitrary for DynNode {
273 type Parameters = ();
274
275 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
276 use proptest::prelude::*;
277
278 any::<bool>()
280 .prop_map(|is_dyncall| {
281 if is_dyncall {
282 DynNodeBuilder::new_dyncall().build()
283 } else {
284 DynNodeBuilder::new_dyn().build()
285 }
286 })
287 .no_shrink() .boxed()
289 }
290
291 type Strategy = proptest::prelude::BoxedStrategy<Self>;
292}
293
294#[derive(Debug)]
297pub struct DynNodeBuilder {
298 is_dyncall: bool,
299 before_enter: Vec<DecoratorId>,
300 after_exit: Vec<DecoratorId>,
301 digest: Option<Word>,
302}
303
304impl DynNodeBuilder {
305 pub fn new_dyn() -> Self {
307 Self {
308 is_dyncall: false,
309 before_enter: Vec::new(),
310 after_exit: Vec::new(),
311 digest: None,
312 }
313 }
314
315 pub fn new_dyncall() -> Self {
317 Self {
318 is_dyncall: true,
319 before_enter: Vec::new(),
320 after_exit: Vec::new(),
321 digest: None,
322 }
323 }
324
325 pub fn build(self) -> DynNode {
327 let digest = if let Some(forced_digest) = self.digest {
329 forced_digest
330 } else if self.is_dyncall {
331 DynNode::DYNCALL_DEFAULT_DIGEST
332 } else {
333 DynNode::DYN_DEFAULT_DIGEST
334 };
335
336 DynNode {
337 is_dyncall: self.is_dyncall,
338 digest,
339 decorator_store: DecoratorStore::new_owned_with_decorators(
340 self.before_enter,
341 self.after_exit,
342 ),
343 }
344 }
345}
346
347impl MastForestContributor for DynNodeBuilder {
348 fn add_to_forest(self, forest: &mut MastForest) -> Result<MastNodeId, MastForestError> {
349 let digest = if let Some(forced_digest) = self.digest {
351 forced_digest
352 } else if self.is_dyncall {
353 DynNode::DYNCALL_DEFAULT_DIGEST
354 } else {
355 DynNode::DYN_DEFAULT_DIGEST
356 };
357
358 let future_node_id = MastNodeId::new_unchecked(forest.nodes.len() as u32);
360
361 forest.register_node_decorators(future_node_id, &self.before_enter, &self.after_exit);
363
364 let node_id = forest
367 .nodes
368 .push(
369 DynNode {
370 is_dyncall: self.is_dyncall,
371 digest,
372 decorator_store: DecoratorStore::Linked { id: future_node_id },
373 }
374 .into(),
375 )
376 .map_err(|_| MastForestError::TooManyNodes)?;
377
378 Ok(node_id)
379 }
380
381 fn fingerprint_for_node(
382 &self,
383 forest: &MastForest,
384 _hash_by_node_id: &impl LookupByIdx<MastNodeId, MastNodeFingerprint>,
385 ) -> Result<MastNodeFingerprint, MastForestError> {
386 crate::mast::node_fingerprint::fingerprint_from_parts(
389 forest,
390 _hash_by_node_id,
391 &self.before_enter,
392 &self.after_exit,
393 &[], if let Some(forced_digest) = self.digest {
396 forced_digest
397 } else if self.is_dyncall {
398 DynNode::DYNCALL_DEFAULT_DIGEST
399 } else {
400 DynNode::DYN_DEFAULT_DIGEST
401 },
402 )
403 }
404
405 fn remap_children(self, _remapping: &impl LookupByIdx<MastNodeId, MastNodeId>) -> Self {
406 self
408 }
409
410 fn with_before_enter(mut self, decorators: impl Into<Vec<DecoratorId>>) -> Self {
411 self.before_enter = decorators.into();
412 self
413 }
414
415 fn with_after_exit(mut self, decorators: impl Into<Vec<DecoratorId>>) -> Self {
416 self.after_exit = decorators.into();
417 self
418 }
419
420 fn append_before_enter(&mut self, decorators: impl IntoIterator<Item = DecoratorId>) {
421 self.before_enter.extend(decorators);
422 }
423
424 fn append_after_exit(&mut self, decorators: impl IntoIterator<Item = DecoratorId>) {
425 self.after_exit.extend(decorators);
426 }
427
428 fn with_digest(mut self, digest: Word) -> Self {
429 self.digest = Some(digest);
430 self
431 }
432}
433
434impl DynNodeBuilder {
435 pub(in crate::mast) fn add_to_forest_relaxed(
445 self,
446 forest: &mut MastForest,
447 ) -> Result<MastNodeId, MastForestError> {
448 let digest = if let Some(forced_digest) = self.digest {
450 forced_digest
451 } else if self.is_dyncall {
452 DynNode::DYNCALL_DEFAULT_DIGEST
453 } else {
454 DynNode::DYN_DEFAULT_DIGEST
455 };
456
457 let future_node_id = MastNodeId::new_unchecked(forest.nodes.len() as u32);
459
460 let node_id = forest
464 .nodes
465 .push(
466 DynNode {
467 is_dyncall: self.is_dyncall,
468 digest,
469 decorator_store: DecoratorStore::Linked { id: future_node_id },
470 }
471 .into(),
472 )
473 .map_err(|_| MastForestError::TooManyNodes)?;
474
475 Ok(node_id)
476 }
477}
478
479#[cfg(any(test, feature = "arbitrary"))]
480impl proptest::prelude::Arbitrary for DynNodeBuilder {
481 type Parameters = DynNodeBuilderParams;
482 type Strategy = proptest::strategy::BoxedStrategy<Self>;
483
484 fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
485 use proptest::prelude::*;
486
487 (
488 any::<bool>(),
489 proptest::collection::vec(
490 super::arbitrary::decorator_id_strategy(params.max_decorator_id_u32),
491 0..=params.max_decorators,
492 ),
493 proptest::collection::vec(
494 super::arbitrary::decorator_id_strategy(params.max_decorator_id_u32),
495 0..=params.max_decorators,
496 ),
497 )
498 .prop_map(|(is_dyncall, before_enter, after_exit)| {
499 let builder = if is_dyncall {
500 Self::new_dyncall()
501 } else {
502 Self::new_dyn()
503 };
504 builder.with_before_enter(before_enter).with_after_exit(after_exit)
505 })
506 .boxed()
507 }
508}
509
510#[cfg(any(test, feature = "arbitrary"))]
512#[derive(Clone, Debug)]
513pub struct DynNodeBuilderParams {
514 pub max_decorators: usize,
515 pub max_decorator_id_u32: u32,
516}
517
518#[cfg(any(test, feature = "arbitrary"))]
519impl Default for DynNodeBuilderParams {
520 fn default() -> Self {
521 Self {
522 max_decorators: 4,
523 max_decorator_id_u32: 10,
524 }
525 }
526}
527
528#[cfg(test)]
529mod tests {
530 use miden_crypto::hash::poseidon2::Poseidon2;
531
532 use super::*;
533
534 #[test]
537 pub fn test_dyn_node_digest() {
538 let mut forest = MastForest::new();
539 let dyn_node_id = DynNodeBuilder::new_dyn().add_to_forest(&mut forest).unwrap();
540 let dyn_node = forest.get_node_by_id(dyn_node_id).unwrap().unwrap_dyn();
541 assert_eq!(
542 dyn_node.digest(),
543 Poseidon2::merge_in_domain(&[Word::default(), Word::default()], DynNode::DYN_DOMAIN)
544 );
545
546 let dyncall_node_id = DynNodeBuilder::new_dyncall().add_to_forest(&mut forest).unwrap();
547 let dyncall_node = forest.get_node_by_id(dyncall_node_id).unwrap().unwrap_dyn();
548 assert_eq!(
549 dyncall_node.digest(),
550 Poseidon2::merge_in_domain(
551 &[Word::default(), Word::default()],
552 DynNode::DYNCALL_DOMAIN
553 )
554 );
555 }
556}