1use alloc::{boxed::Box, vec::Vec};
2use core::fmt;
3
4use miden_crypto::{Felt, Word};
5use miden_formatting::prettier::{Document, PrettyPrint, const_text, nl};
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9use super::{MastForestContributor, MastNodeErrorContext, MastNodeExt};
10use crate::{
11 OPCODE_DYN, OPCODE_DYNCALL,
12 mast::{DecoratedOpLink, DecoratorId, DecoratorStore, MastForest, MastForestError, MastNodeId},
13};
14
15#[derive(Debug, Clone, PartialEq, Eq)]
20#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
21#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
22pub struct DynNode {
23 is_dyncall: bool,
24 digest: Word,
25 decorator_store: DecoratorStore,
26}
27
28impl DynNode {
30 pub const DYN_DOMAIN: Felt = Felt::new(OPCODE_DYN as u64);
32
33 pub const DYNCALL_DOMAIN: Felt = Felt::new(OPCODE_DYNCALL as u64);
35}
36
37impl DynNode {
39 pub const DYNCALL_DEFAULT_DIGEST: Word = Word::new([
41 Felt::new(8751004906421739448),
42 Felt::new(13469709002495534233),
43 Felt::new(12584249374630430826),
44 Felt::new(7624899870831503004),
45 ]);
46
47 pub const DYN_DEFAULT_DIGEST: Word = Word::new([
49 Felt::new(8115106948140260551),
50 Felt::new(13491227816952616836),
51 Felt::new(15015806788322198710),
52 Felt::new(16575543461540527115),
53 ]);
54}
55
56impl DynNode {
58 pub fn is_dyncall(&self) -> bool {
60 self.is_dyncall
61 }
62
63 pub fn domain(&self) -> Felt {
65 if self.is_dyncall() {
66 Self::DYNCALL_DOMAIN
67 } else {
68 Self::DYN_DOMAIN
69 }
70 }
71}
72
73impl MastNodeErrorContext for DynNode {
74 fn decorators<'a>(
75 &'a self,
76 forest: &'a MastForest,
77 ) -> impl Iterator<Item = DecoratedOpLink> + 'a {
78 let before_enter = self.decorator_store.before_enter(forest);
80 let after_exit = self.decorator_store.after_exit(forest);
81
82 before_enter
84 .iter()
85 .map(|&deco_id| (0, deco_id))
86 .chain(after_exit.iter().map(|&deco_id| (1, deco_id)))
87 }
88}
89
90impl DynNode {
94 pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a {
95 DynNodePrettyPrint { node: self, mast_forest }
96 }
97
98 pub(super) fn to_pretty_print<'a>(
99 &'a self,
100 mast_forest: &'a MastForest,
101 ) -> impl PrettyPrint + 'a {
102 DynNodePrettyPrint { node: self, mast_forest }
103 }
104}
105
106struct DynNodePrettyPrint<'a> {
107 node: &'a DynNode,
108 mast_forest: &'a MastForest,
109}
110
111impl DynNodePrettyPrint<'_> {
112 fn concatenate_decorators(
115 &self,
116 decorator_ids: &[DecoratorId],
117 prepend: Document,
118 append: Document,
119 ) -> Document {
120 let decorators = decorator_ids
121 .iter()
122 .map(|&decorator_id| self.mast_forest[decorator_id].render())
123 .reduce(|acc, doc| acc + const_text(" ") + doc)
124 .unwrap_or_default();
125
126 if decorators.is_empty() {
127 decorators
128 } else {
129 prepend + decorators + append
130 }
131 }
132
133 fn single_line_pre_decorators(&self) -> Document {
134 self.concatenate_decorators(
135 self.node.before_enter(self.mast_forest),
136 Document::Empty,
137 const_text(" "),
138 )
139 }
140
141 fn single_line_post_decorators(&self) -> Document {
142 self.concatenate_decorators(
143 self.node.after_exit(self.mast_forest),
144 const_text(" "),
145 Document::Empty,
146 )
147 }
148
149 fn multi_line_pre_decorators(&self) -> Document {
150 self.concatenate_decorators(self.node.before_enter(self.mast_forest), Document::Empty, nl())
151 }
152
153 fn multi_line_post_decorators(&self) -> Document {
154 self.concatenate_decorators(self.node.after_exit(self.mast_forest), nl(), Document::Empty)
155 }
156}
157
158impl crate::prettier::PrettyPrint for DynNodePrettyPrint<'_> {
159 fn render(&self) -> crate::prettier::Document {
160 let dyn_text = if self.node.is_dyncall() {
161 const_text("dyncall")
162 } else {
163 const_text("dyn")
164 };
165
166 let single_line = self.single_line_pre_decorators()
167 + dyn_text.clone()
168 + self.single_line_post_decorators();
169 let multi_line =
170 self.multi_line_pre_decorators() + dyn_text + self.multi_line_post_decorators();
171
172 single_line | multi_line
173 }
174}
175
176impl fmt::Display for DynNodePrettyPrint<'_> {
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 self.pretty_print(f)
179 }
180}
181
182impl MastNodeExt for DynNode {
186 fn digest(&self) -> Word {
188 self.digest
189 }
190
191 fn before_enter<'a>(&'a self, forest: &'a MastForest) -> &'a [DecoratorId] {
193 #[cfg(debug_assertions)]
194 self.verify_node_in_forest(forest);
195 self.decorator_store.before_enter(forest)
196 }
197
198 fn after_exit<'a>(&'a self, forest: &'a MastForest) -> &'a [DecoratorId] {
200 #[cfg(debug_assertions)]
201 self.verify_node_in_forest(forest);
202 self.decorator_store.after_exit(forest)
203 }
204
205 fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn fmt::Display + 'a> {
206 Box::new(DynNode::to_display(self, mast_forest))
207 }
208
209 fn to_pretty_print<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn PrettyPrint + 'a> {
210 Box::new(DynNode::to_pretty_print(self, mast_forest))
211 }
212
213 fn has_children(&self) -> bool {
214 false
215 }
216
217 fn append_children_to(&self, _target: &mut Vec<MastNodeId>) {
218 }
220
221 fn for_each_child<F>(&self, _f: F)
222 where
223 F: FnMut(MastNodeId),
224 {
225 }
227
228 fn domain(&self) -> Felt {
229 self.domain()
230 }
231
232 type Builder = DynNodeBuilder;
233
234 fn to_builder(self, forest: &MastForest) -> Self::Builder {
235 match self.decorator_store {
237 DecoratorStore::Owned { before_enter, after_exit, .. } => {
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 DecoratorStore::Linked { id } => {
247 let before_enter = forest.before_enter_decorators(id).to_vec();
249 let after_exit = forest.after_exit_decorators(id).to_vec();
250 let mut builder = if self.is_dyncall {
251 DynNodeBuilder::new_dyncall()
252 } else {
253 DynNodeBuilder::new_dyn()
254 };
255 builder = builder.with_before_enter(before_enter).with_after_exit(after_exit);
256 builder
257 },
258 }
259 }
260
261 #[cfg(debug_assertions)]
262 fn verify_node_in_forest(&self, forest: &MastForest) {
263 if let Some(id) = self.decorator_store.linked_id() {
264 let self_ptr = self as *const Self;
266 let forest_node = &forest.nodes[id];
267 let forest_node_ptr = match forest_node {
268 crate::mast::MastNode::Dyn(dyn_node) => dyn_node as *const DynNode as *const (),
269 _ => panic!("Node type mismatch at {:?}", id),
270 };
271 let self_as_void = self_ptr as *const ();
272 debug_assert_eq!(
273 self_as_void, forest_node_ptr,
274 "Node pointer mismatch: expected node at {:?} to be self",
275 id
276 );
277 }
278 }
279}
280
281#[cfg(all(feature = "arbitrary", test))]
285impl proptest::prelude::Arbitrary for DynNode {
286 type Parameters = ();
287
288 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
289 use proptest::prelude::*;
290
291 any::<bool>()
293 .prop_map(|is_dyncall| {
294 if is_dyncall {
295 DynNodeBuilder::new_dyncall().build()
296 } else {
297 DynNodeBuilder::new_dyn().build()
298 }
299 })
300 .no_shrink() .boxed()
302 }
303
304 type Strategy = proptest::prelude::BoxedStrategy<Self>;
305}
306
307#[derive(Debug)]
310pub struct DynNodeBuilder {
311 is_dyncall: bool,
312 before_enter: Vec<DecoratorId>,
313 after_exit: Vec<DecoratorId>,
314 digest: Option<Word>,
315}
316
317impl DynNodeBuilder {
318 pub fn new_dyn() -> Self {
320 Self {
321 is_dyncall: false,
322 before_enter: Vec::new(),
323 after_exit: Vec::new(),
324 digest: None,
325 }
326 }
327
328 pub fn new_dyncall() -> Self {
330 Self {
331 is_dyncall: true,
332 before_enter: Vec::new(),
333 after_exit: Vec::new(),
334 digest: None,
335 }
336 }
337
338 pub fn build(self) -> DynNode {
340 let digest = if let Some(forced_digest) = self.digest {
342 forced_digest
343 } else if self.is_dyncall {
344 DynNode::DYNCALL_DEFAULT_DIGEST
345 } else {
346 DynNode::DYN_DEFAULT_DIGEST
347 };
348
349 DynNode {
350 is_dyncall: self.is_dyncall,
351 digest,
352 decorator_store: DecoratorStore::new_owned_with_decorators(
353 self.before_enter,
354 self.after_exit,
355 ),
356 }
357 }
358}
359
360impl MastForestContributor for DynNodeBuilder {
361 fn add_to_forest(self, forest: &mut MastForest) -> Result<MastNodeId, MastForestError> {
362 let digest = if let Some(forced_digest) = self.digest {
364 forced_digest
365 } else if self.is_dyncall {
366 DynNode::DYNCALL_DEFAULT_DIGEST
367 } else {
368 DynNode::DYN_DEFAULT_DIGEST
369 };
370
371 let future_node_id = MastNodeId::new_unchecked(forest.nodes.len() as u32);
373
374 forest.register_node_decorators(future_node_id, &self.before_enter, &self.after_exit);
376
377 let node_id = forest
380 .nodes
381 .push(
382 DynNode {
383 is_dyncall: self.is_dyncall,
384 digest,
385 decorator_store: DecoratorStore::Linked { id: future_node_id },
386 }
387 .into(),
388 )
389 .map_err(|_| MastForestError::TooManyNodes)?;
390
391 Ok(node_id)
392 }
393
394 fn fingerprint_for_node(
395 &self,
396 forest: &MastForest,
397 _hash_by_node_id: &impl crate::LookupByIdx<MastNodeId, crate::mast::MastNodeFingerprint>,
398 ) -> Result<crate::mast::MastNodeFingerprint, MastForestError> {
399 crate::mast::node_fingerprint::fingerprint_from_parts(
402 forest,
403 _hash_by_node_id,
404 &self.before_enter,
405 &self.after_exit,
406 &[], if let Some(forced_digest) = self.digest {
409 forced_digest
410 } else if self.is_dyncall {
411 DynNode::DYNCALL_DEFAULT_DIGEST
412 } else {
413 DynNode::DYN_DEFAULT_DIGEST
414 },
415 )
416 }
417
418 fn remap_children(
419 self,
420 _remapping: &impl crate::LookupByIdx<crate::mast::MastNodeId, crate::mast::MastNodeId>,
421 ) -> Self {
422 self
424 }
425
426 fn with_before_enter(mut self, decorators: impl Into<Vec<crate::mast::DecoratorId>>) -> Self {
427 self.before_enter = decorators.into();
428 self
429 }
430
431 fn with_after_exit(mut self, decorators: impl Into<Vec<crate::mast::DecoratorId>>) -> Self {
432 self.after_exit = decorators.into();
433 self
434 }
435
436 fn append_before_enter(
437 &mut self,
438 decorators: impl IntoIterator<Item = crate::mast::DecoratorId>,
439 ) {
440 self.before_enter.extend(decorators);
441 }
442
443 fn append_after_exit(
444 &mut self,
445 decorators: impl IntoIterator<Item = crate::mast::DecoratorId>,
446 ) {
447 self.after_exit.extend(decorators);
448 }
449
450 fn with_digest(mut self, digest: crate::Word) -> Self {
451 self.digest = Some(digest);
452 self
453 }
454}
455
456impl DynNodeBuilder {
457 pub(in crate::mast) fn add_to_forest_relaxed(
470 self,
471 forest: &mut MastForest,
472 ) -> Result<MastNodeId, MastForestError> {
473 let digest = if let Some(forced_digest) = self.digest {
475 forced_digest
476 } else if self.is_dyncall {
477 DynNode::DYNCALL_DEFAULT_DIGEST
478 } else {
479 DynNode::DYN_DEFAULT_DIGEST
480 };
481
482 let future_node_id = MastNodeId::new_unchecked(forest.nodes.len() as u32);
483
484 forest.register_node_decorators(future_node_id, &self.before_enter, &self.after_exit);
486
487 let node_id = forest
490 .nodes
491 .push(
492 DynNode {
493 is_dyncall: self.is_dyncall,
494 digest,
495 decorator_store: DecoratorStore::Linked { id: future_node_id },
496 }
497 .into(),
498 )
499 .map_err(|_| MastForestError::TooManyNodes)?;
500
501 Ok(node_id)
502 }
503}
504
505#[cfg(any(test, feature = "arbitrary"))]
506impl proptest::prelude::Arbitrary for DynNodeBuilder {
507 type Parameters = DynNodeBuilderParams;
508 type Strategy = proptest::strategy::BoxedStrategy<Self>;
509
510 fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
511 use proptest::prelude::*;
512
513 (
514 any::<bool>(),
515 proptest::collection::vec(
516 super::arbitrary::decorator_id_strategy(params.max_decorator_id_u32),
517 0..=params.max_decorators,
518 ),
519 proptest::collection::vec(
520 super::arbitrary::decorator_id_strategy(params.max_decorator_id_u32),
521 0..=params.max_decorators,
522 ),
523 )
524 .prop_map(|(is_dyncall, before_enter, after_exit)| {
525 let builder = if is_dyncall {
526 Self::new_dyncall()
527 } else {
528 Self::new_dyn()
529 };
530 builder.with_before_enter(before_enter).with_after_exit(after_exit)
531 })
532 .boxed()
533 }
534}
535
536#[cfg(any(test, feature = "arbitrary"))]
538#[derive(Clone, Debug)]
539pub struct DynNodeBuilderParams {
540 pub max_decorators: usize,
541 pub max_decorator_id_u32: u32,
542}
543
544#[cfg(any(test, feature = "arbitrary"))]
545impl Default for DynNodeBuilderParams {
546 fn default() -> Self {
547 Self {
548 max_decorators: 4,
549 max_decorator_id_u32: 10,
550 }
551 }
552}
553
554#[cfg(test)]
555mod tests {
556 use miden_crypto::hash::rpo::Rpo256;
557
558 use super::*;
559
560 #[test]
563 pub fn test_dyn_node_digest() {
564 let mut forest = MastForest::new();
565 let dyn_node_id = DynNodeBuilder::new_dyn().add_to_forest(&mut forest).unwrap();
566 let dyn_node = forest.get_node_by_id(dyn_node_id).unwrap().unwrap_dyn();
567 assert_eq!(
568 dyn_node.digest(),
569 Rpo256::merge_in_domain(&[Word::default(), Word::default()], DynNode::DYN_DOMAIN)
570 );
571
572 let dyncall_node_id = DynNodeBuilder::new_dyncall().add_to_forest(&mut forest).unwrap();
573 let dyncall_node = forest.get_node_by_id(dyncall_node_id).unwrap().unwrap_dyn();
574 assert_eq!(
575 dyncall_node.digest(),
576 Rpo256::merge_in_domain(&[Word::default(), Word::default()], DynNode::DYNCALL_DOMAIN)
577 );
578 }
579}