1use alloc::{boxed::Box, vec::Vec};
2use core::fmt;
3
4use miden_crypto::{Felt, Word};
5use miden_formatting::prettier::PrettyPrint;
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9use super::{MastForestContributor, MastNodeErrorContext, MastNodeExt};
10use crate::{
11 Idx, OPCODE_LOOP,
12 chiplets::hasher,
13 mast::{
14 DecoratedOpLink, DecoratorId, DecoratorStore, MastForest, MastForestError, MastNode,
15 MastNodeId,
16 },
17};
18
19#[derive(Debug, Clone, PartialEq, Eq)]
29#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
30#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
31pub struct LoopNode {
32 body: MastNodeId,
33 digest: Word,
34 decorator_store: DecoratorStore,
35}
36
37impl LoopNode {
39 pub const DOMAIN: Felt = Felt::new(OPCODE_LOOP as u64);
41}
42
43impl LoopNode {
44 pub fn body(&self) -> MastNodeId {
46 self.body
47 }
48}
49
50impl MastNodeErrorContext for LoopNode {
51 fn decorators<'a>(
52 &'a self,
53 forest: &'a MastForest,
54 ) -> impl Iterator<Item = DecoratedOpLink> + 'a {
55 let before_enter = self.decorator_store.before_enter(forest);
57 let after_exit = self.decorator_store.after_exit(forest);
58
59 before_enter
61 .iter()
62 .map(|&deco_id| (0, deco_id))
63 .chain(after_exit.iter().map(|&deco_id| (1, deco_id)))
64 }
65}
66
67impl LoopNode {
71 pub(super) fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> impl fmt::Display + 'a {
72 LoopNodePrettyPrint { loop_node: self, mast_forest }
73 }
74
75 pub(super) fn to_pretty_print<'a>(
76 &'a self,
77 mast_forest: &'a MastForest,
78 ) -> impl PrettyPrint + 'a {
79 LoopNodePrettyPrint { loop_node: self, mast_forest }
80 }
81}
82
83struct LoopNodePrettyPrint<'a> {
84 loop_node: &'a LoopNode,
85 mast_forest: &'a MastForest,
86}
87
88impl crate::prettier::PrettyPrint for LoopNodePrettyPrint<'_> {
89 fn render(&self) -> crate::prettier::Document {
90 use crate::prettier::*;
91
92 let pre_decorators = {
93 let mut pre_decorators = self
94 .loop_node
95 .before_enter(self.mast_forest)
96 .iter()
97 .map(|&decorator_id| self.mast_forest[decorator_id].render())
98 .reduce(|acc, doc| acc + const_text(" ") + doc)
99 .unwrap_or_default();
100 if !pre_decorators.is_empty() {
101 pre_decorators += nl();
102 }
103
104 pre_decorators
105 };
106
107 let post_decorators = {
108 let mut post_decorators = self
109 .loop_node
110 .after_exit(self.mast_forest)
111 .iter()
112 .map(|&decorator_id| self.mast_forest[decorator_id].render())
113 .reduce(|acc, doc| acc + const_text(" ") + doc)
114 .unwrap_or_default();
115 if !post_decorators.is_empty() {
116 post_decorators = nl() + post_decorators;
117 }
118
119 post_decorators
120 };
121
122 let loop_body = self.mast_forest[self.loop_node.body].to_pretty_print(self.mast_forest);
123
124 pre_decorators
125 + indent(4, const_text("while.true") + nl() + loop_body.render())
126 + nl()
127 + const_text("end")
128 + post_decorators
129 }
130}
131
132impl fmt::Display for LoopNodePrettyPrint<'_> {
133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134 use crate::prettier::PrettyPrint;
135 self.pretty_print(f)
136 }
137}
138
139impl MastNodeExt for LoopNode {
143 fn digest(&self) -> Word {
154 self.digest
155 }
156
157 fn before_enter<'a>(&'a self, forest: &'a MastForest) -> &'a [DecoratorId] {
159 #[cfg(debug_assertions)]
160 self.verify_node_in_forest(forest);
161 self.decorator_store.before_enter(forest)
162 }
163
164 fn after_exit<'a>(&'a self, forest: &'a MastForest) -> &'a [DecoratorId] {
166 #[cfg(debug_assertions)]
167 self.verify_node_in_forest(forest);
168 self.decorator_store.after_exit(forest)
169 }
170
171 fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn fmt::Display + 'a> {
172 Box::new(LoopNode::to_display(self, mast_forest))
173 }
174
175 fn to_pretty_print<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn PrettyPrint + 'a> {
176 Box::new(LoopNode::to_pretty_print(self, mast_forest))
177 }
178
179 fn has_children(&self) -> bool {
180 true
181 }
182
183 fn append_children_to(&self, target: &mut Vec<MastNodeId>) {
184 target.push(self.body());
185 }
186
187 fn for_each_child<F>(&self, mut f: F)
188 where
189 F: FnMut(MastNodeId),
190 {
191 f(self.body());
192 }
193
194 fn domain(&self) -> Felt {
195 Self::DOMAIN
196 }
197
198 type Builder = LoopNodeBuilder;
199
200 fn to_builder(self, forest: &MastForest) -> Self::Builder {
201 match self.decorator_store {
203 DecoratorStore::Owned { before_enter, after_exit, .. } => {
204 let mut builder = LoopNodeBuilder::new(self.body);
205 builder = builder.with_before_enter(before_enter).with_after_exit(after_exit);
206 builder
207 },
208 DecoratorStore::Linked { id } => {
209 let before_enter = forest.before_enter_decorators(id).to_vec();
211 let after_exit = forest.after_exit_decorators(id).to_vec();
212 let mut builder = LoopNodeBuilder::new(self.body);
213 builder = builder.with_before_enter(before_enter).with_after_exit(after_exit);
214 builder
215 },
216 }
217 }
218
219 #[cfg(debug_assertions)]
220 fn verify_node_in_forest(&self, forest: &MastForest) {
221 if let Some(id) = self.decorator_store.linked_id() {
222 let self_ptr = self as *const Self;
224 let forest_node = &forest.nodes[id];
225 let forest_node_ptr = match forest_node {
226 MastNode::Loop(loop_node) => loop_node as *const LoopNode as *const (),
227 _ => panic!("Node type mismatch at {:?}", id),
228 };
229 let self_as_void = self_ptr as *const ();
230 debug_assert_eq!(
231 self_as_void, forest_node_ptr,
232 "Node pointer mismatch: expected node at {:?} to be self",
233 id
234 );
235 }
236 }
237}
238
239#[cfg(all(feature = "arbitrary", test))]
243impl proptest::prelude::Arbitrary for LoopNode {
244 type Parameters = ();
245
246 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
247 use proptest::prelude::*;
248
249 use crate::Felt;
250
251 (any::<MastNodeId>(), any::<[u64; 4]>())
253 .prop_map(|(body, digest_array)| {
254 let digest = Word::from(digest_array.map(Felt::new));
256 LoopNode {
258 body,
259 digest,
260 decorator_store: DecoratorStore::default(),
261 }
262 })
263 .no_shrink() .boxed()
265 }
266
267 type Strategy = proptest::prelude::BoxedStrategy<Self>;
268}
269
270#[derive(Debug)]
273pub struct LoopNodeBuilder {
274 body: MastNodeId,
275 before_enter: Vec<DecoratorId>,
276 after_exit: Vec<DecoratorId>,
277 digest: Option<Word>,
278}
279
280impl LoopNodeBuilder {
281 pub fn new(body: MastNodeId) -> Self {
283 Self {
284 body,
285 before_enter: Vec::new(),
286 after_exit: Vec::new(),
287 digest: None,
288 }
289 }
290
291 pub fn build(self, mast_forest: &MastForest) -> Result<LoopNode, MastForestError> {
293 if self.body.to_usize() >= mast_forest.nodes.len() {
294 return Err(MastForestError::NodeIdOverflow(self.body, mast_forest.nodes.len()));
295 }
296
297 let digest = if let Some(forced_digest) = self.digest {
299 forced_digest
300 } else {
301 let body_hash = mast_forest[self.body].digest();
302
303 hasher::merge_in_domain(&[body_hash, Word::default()], LoopNode::DOMAIN)
304 };
305
306 Ok(LoopNode {
307 body: self.body,
308 digest,
309 decorator_store: DecoratorStore::new_owned_with_decorators(
310 self.before_enter,
311 self.after_exit,
312 ),
313 })
314 }
315}
316
317impl MastForestContributor for LoopNodeBuilder {
318 fn add_to_forest(self, forest: &mut MastForest) -> Result<MastNodeId, MastForestError> {
319 let node = self.build(forest)?;
320
321 let LoopNode {
322 body,
323 digest,
324 decorator_store: DecoratorStore::Owned { before_enter, after_exit, .. },
325 } = node
326 else {
327 unreachable!("LoopNodeBuilder::build() should always return owned decorators");
328 };
329
330 let future_node_id = MastNodeId::new_unchecked(forest.nodes.len() as u32);
332
333 forest
335 .debug_info
336 .register_node_decorators(future_node_id, &before_enter, &after_exit);
337
338 let node_id = forest
341 .nodes
342 .push(
343 LoopNode {
344 body,
345 digest,
346 decorator_store: DecoratorStore::Linked { id: future_node_id },
347 }
348 .into(),
349 )
350 .map_err(|_| MastForestError::TooManyNodes)?;
351
352 Ok(node_id)
353 }
354
355 fn fingerprint_for_node(
356 &self,
357 forest: &MastForest,
358 hash_by_node_id: &impl crate::LookupByIdx<MastNodeId, crate::mast::MastNodeFingerprint>,
359 ) -> Result<crate::mast::MastNodeFingerprint, MastForestError> {
360 crate::mast::node_fingerprint::fingerprint_from_parts(
362 forest,
363 hash_by_node_id,
364 &self.before_enter,
365 &self.after_exit,
366 &[self.body],
367 if let Some(forced_digest) = self.digest {
369 forced_digest
370 } else {
371 let body_hash = forest[self.body].digest();
372
373 crate::chiplets::hasher::merge_in_domain(
374 &[body_hash, miden_crypto::Word::default()],
375 LoopNode::DOMAIN,
376 )
377 },
378 )
379 }
380
381 fn remap_children(
382 self,
383 remapping: &impl crate::LookupByIdx<crate::mast::MastNodeId, crate::mast::MastNodeId>,
384 ) -> Self {
385 LoopNodeBuilder {
386 body: *remapping.get(self.body).unwrap_or(&self.body),
387 before_enter: self.before_enter,
388 after_exit: self.after_exit,
389 digest: self.digest,
390 }
391 }
392
393 fn with_before_enter(mut self, decorators: impl Into<Vec<crate::mast::DecoratorId>>) -> Self {
394 self.before_enter = decorators.into();
395 self
396 }
397
398 fn with_after_exit(mut self, decorators: impl Into<Vec<crate::mast::DecoratorId>>) -> Self {
399 self.after_exit = decorators.into();
400 self
401 }
402
403 fn append_before_enter(
404 &mut self,
405 decorators: impl IntoIterator<Item = crate::mast::DecoratorId>,
406 ) {
407 self.before_enter.extend(decorators);
408 }
409
410 fn append_after_exit(
411 &mut self,
412 decorators: impl IntoIterator<Item = crate::mast::DecoratorId>,
413 ) {
414 self.after_exit.extend(decorators);
415 }
416
417 fn with_digest(mut self, digest: crate::Word) -> Self {
418 self.digest = Some(digest);
419 self
420 }
421}
422
423impl LoopNodeBuilder {
424 pub(in crate::mast) fn add_to_forest_relaxed(
434 self,
435 forest: &mut MastForest,
436 ) -> Result<MastNodeId, MastForestError> {
437 let Some(digest) = self.digest else {
440 return Err(MastForestError::DigestRequiredForDeserialization);
441 };
442
443 let future_node_id = MastNodeId::new_unchecked(forest.nodes.len() as u32);
444
445 forest.register_node_decorators(future_node_id, &self.before_enter, &self.after_exit);
447
448 let node_id = forest
451 .nodes
452 .push(
453 LoopNode {
454 body: self.body,
455 digest,
456 decorator_store: DecoratorStore::Linked { id: future_node_id },
457 }
458 .into(),
459 )
460 .map_err(|_| MastForestError::TooManyNodes)?;
461
462 Ok(node_id)
463 }
464}
465
466#[cfg(any(test, feature = "arbitrary"))]
467impl proptest::prelude::Arbitrary for LoopNodeBuilder {
468 type Parameters = LoopNodeBuilderParams;
469 type Strategy = proptest::strategy::BoxedStrategy<Self>;
470
471 fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
472 use proptest::prelude::*;
473
474 (
475 any::<crate::mast::MastNodeId>(),
476 proptest::collection::vec(
477 super::arbitrary::decorator_id_strategy(params.max_decorator_id_u32),
478 0..=params.max_decorators,
479 ),
480 proptest::collection::vec(
481 super::arbitrary::decorator_id_strategy(params.max_decorator_id_u32),
482 0..=params.max_decorators,
483 ),
484 )
485 .prop_map(|(body, before_enter, after_exit)| {
486 Self::new(body).with_before_enter(before_enter).with_after_exit(after_exit)
487 })
488 .boxed()
489 }
490}
491
492#[cfg(any(test, feature = "arbitrary"))]
494#[derive(Clone, Debug)]
495pub struct LoopNodeBuilderParams {
496 pub max_decorators: usize,
497 pub max_decorator_id_u32: u32,
498}
499
500#[cfg(any(test, feature = "arbitrary"))]
501impl Default for LoopNodeBuilderParams {
502 fn default() -> Self {
503 Self {
504 max_decorators: 4,
505 max_decorator_id_u32: 10,
506 }
507 }
508}