miden_core/mast/node/basic_block_node/arbitrary.rs
1use alloc::{collections::BTreeMap, sync::Arc};
2use core::ops::RangeInclusive;
3
4use proptest::{arbitrary::Arbitrary, prelude::*};
5
6use super::*;
7use crate::{
8 Felt, Word,
9 advice::AdviceMap,
10 mast::{
11 CallNodeBuilder, DecoratorId, DynNodeBuilder, ExternalNodeBuilder, JoinNodeBuilder,
12 LoopNodeBuilder, SplitNodeBuilder,
13 },
14 operations::{AssemblyOp, DebugOptions, Decorator, Operation},
15 program::{Kernel, Program},
16};
17
18// Strategy for operations without immediate values (non-control flow)
19pub fn op_no_imm_strategy() -> impl Strategy<Value = Operation> {
20 prop_oneof![
21 Just(Operation::Add),
22 Just(Operation::Mul),
23 Just(Operation::Neg),
24 Just(Operation::Inv),
25 Just(Operation::Incr),
26 Just(Operation::And),
27 Just(Operation::Or),
28 Just(Operation::Not),
29 Just(Operation::Eq),
30 Just(Operation::Eqz),
31 Just(Operation::Drop),
32 Just(Operation::Pad),
33 Just(Operation::Swap),
34 Just(Operation::SwapW),
35 Just(Operation::SwapW2),
36 Just(Operation::SwapW3),
37 Just(Operation::SwapDW),
38 Just(Operation::MovUp2),
39 Just(Operation::MovUp3),
40 Just(Operation::MovUp4),
41 Just(Operation::MovUp5),
42 Just(Operation::MovUp6),
43 Just(Operation::MovUp7),
44 Just(Operation::MovUp8),
45 Just(Operation::MovDn2),
46 Just(Operation::MovDn3),
47 Just(Operation::MovDn4),
48 Just(Operation::MovDn5),
49 Just(Operation::MovDn6),
50 Just(Operation::MovDn7),
51 Just(Operation::MovDn8),
52 Just(Operation::CSwap),
53 Just(Operation::CSwapW),
54 Just(Operation::Dup0),
55 Just(Operation::Dup1),
56 Just(Operation::Dup2),
57 Just(Operation::Dup3),
58 Just(Operation::Dup4),
59 Just(Operation::Dup5),
60 Just(Operation::Dup6),
61 Just(Operation::Dup7),
62 Just(Operation::Dup9),
63 Just(Operation::Dup11),
64 Just(Operation::Dup13),
65 Just(Operation::Dup15),
66 Just(Operation::MLoad),
67 Just(Operation::MStore),
68 Just(Operation::MLoadW),
69 Just(Operation::MStoreW),
70 Just(Operation::MStream),
71 Just(Operation::Pipe),
72 Just(Operation::AdvPop),
73 Just(Operation::AdvPopW),
74 Just(Operation::U32split),
75 Just(Operation::U32add),
76 Just(Operation::U32sub),
77 Just(Operation::U32mul),
78 Just(Operation::U32div),
79 Just(Operation::U32and),
80 Just(Operation::U32xor),
81 Just(Operation::U32add3),
82 Just(Operation::U32madd),
83 Just(Operation::SDepth),
84 Just(Operation::Caller),
85 Just(Operation::Clk),
86 Just(Operation::Emit),
87 Just(Operation::Ext2Mul),
88 Just(Operation::Expacc),
89 Just(Operation::HPerm),
90 // Note: We exclude Assert here because it has an immediate value (error code)
91 ]
92}
93
94// Strategy for operations with immediate values
95pub fn op_with_imm_strategy() -> impl Strategy<Value = Operation> {
96 prop_oneof![any::<u64>().prop_map(Felt::new).prop_map(Operation::Push)]
97}
98
99// Strategy for all non-control flow operations
100pub fn op_non_control_strategy() -> impl Strategy<Value = Operation> {
101 prop_oneof![op_no_imm_strategy(), op_with_imm_strategy(),]
102}
103
104// Strategy for sequences of operations
105pub fn op_non_control_sequence_strategy(
106 max_length: usize,
107) -> impl Strategy<Value = Vec<Operation>> {
108 prop::collection::vec(op_non_control_strategy(), 1..=max_length)
109}
110
111// ---------- Parameters ----------
112
113/// Parameters for generating BasicBlockNode instances
114#[derive(Clone, Debug)]
115pub struct BasicBlockNodeParams {
116 /// Maximum number of operations in a generated basic block
117 pub max_ops_len: usize,
118 /// Maximum number of decorator pairs in a generated basic block
119 pub max_pairs: usize,
120 /// Maximum value for decorator IDs (u32)
121 pub max_decorator_id_u32: u32,
122}
123
124impl Default for BasicBlockNodeParams {
125 fn default() -> Self {
126 Self {
127 max_ops_len: 8,
128 max_pairs: 2,
129 max_decorator_id_u32: 3,
130 }
131 }
132}
133
134// ---------- DecoratorId strategy ----------
135
136/// Strategy for generating DecoratorId values
137pub fn decorator_id_strategy(max_id: u32) -> impl Strategy<Value = DecoratorId> {
138 // max_id == 0 would be degenerate; clamp to at least 1
139 let upper = core::cmp::max(1, max_id);
140 (0..upper).prop_map(DecoratorId::new_unchecked)
141}
142
143// ---------- Decorator pairs strategy ----------
144
145/// Strategy for generating decorator pairs (usize, DecoratorId)
146pub fn decorator_pairs_strategy(
147 ops_len: usize,
148 max_id: u32,
149 max_pairs: usize,
150) -> impl Strategy<Value = Vec<(usize, DecoratorId)>> {
151 // indices in [0, ops_len] inclusive; size 0..=max_pairs
152 // Generate, then sort by index to match validation expectations
153 prop::collection::vec((0..=ops_len, decorator_id_strategy(max_id)), 0..=max_pairs).prop_map(
154 |mut v| {
155 v.sort_by_key(|(i, _)| *i);
156 v
157 },
158 )
159}
160
161// ---------- Arbitrary for BasicBlockNode ----------
162
163impl Arbitrary for BasicBlockNode {
164 type Parameters = BasicBlockNodeParams;
165 type Strategy = BoxedStrategy<Self>;
166
167 fn arbitrary_with(p: Self::Parameters) -> Self::Strategy {
168 // ensure at least 1 op to satisfy BasicBlockNode::new
169 (op_non_control_sequence_strategy(p.max_ops_len),)
170 .prop_flat_map(move |(ops,)| {
171 let ops_len = ops.len().max(1); // defensive; strategy should ensure ≥1
172 decorator_pairs_strategy(ops_len, p.max_decorator_id_u32, p.max_pairs)
173 .prop_map(move |decorators| (ops.clone(), decorators))
174 })
175 .prop_filter_map("non-empty ops", |(ops, decorators)| {
176 if ops.is_empty() { None } else { Some((ops, decorators)) }
177 })
178 .prop_map(|(ops, decorators)| {
179 // BasicBlockNode::new_owned_with_decorators will adjust indices for padding and set
180 // be/ae empty.
181 BasicBlockNode::new_owned_with_decorators(ops, decorators)
182 .expect("non-empty ops; new() only errs on empty ops")
183 })
184 .boxed()
185 }
186}
187
188// ---------- Optional: MastForest strategy (behind feature gate) ----------
189
190/// Parameters for generating MastForest instances
191///
192/// # Execution Compatibility
193///
194/// Generated forests will only be executable if certain node types are excluded:
195/// - **Syscalls**: Require matching kernel procedures. Set `max_syscalls = 0` for executable
196/// forests.
197/// - **Externals**: Use random digests that won't match valid procedures. Set `max_externals = 0`
198/// for executable forests.
199/// - **Dyn nodes**: Leave junk on the stack and cannot execute properly. Set `max_dyns = 0` for
200/// executable forests.
201///
202/// The default parameters create executable forests by setting all three of the above to 0.
203///
204/// # Non-executable Forests
205///
206/// If you want to generate forests for testing assembly or parsing without execution:
207/// - Set `max_syscalls > 0` to include syscall nodes
208/// - Set `max_externals > 0` to include external nodes
209/// - Set `max_dyns > 0` to include dynamic nodes
210///
211/// These forests are useful for testing MAST structure and serialization but will fail during
212/// execution.
213#[derive(Clone, Debug)]
214pub struct MastForestParams {
215 /// Number of decorators to generate
216 pub decorators: u32,
217 /// Range of number of blocks to generate
218 pub blocks: RangeInclusive<usize>,
219 /// Maximum number of join nodes to generate
220 pub max_joins: usize,
221 /// Maximum number of split nodes to generate
222 pub max_splits: usize,
223 /// Maximum number of loop nodes to generate
224 pub max_loops: usize,
225 /// Maximum number of call nodes to generate
226 pub max_calls: usize,
227 /// Maximum number of syscall nodes to generate
228 ///
229 /// **Warning**: Syscalls require a properly configured kernel with matching procedure hashes.
230 /// Generated syscalls use random procedure digests and will not execute without providing
231 /// a matching kernel. Set to 0 for executable forests.
232 pub max_syscalls: usize,
233 /// Maximum number of external nodes to generate
234 ///
235 /// **Warning**: External nodes use random digests that won't correspond to valid procedures.
236 /// Any program with external nodes will fail to execute. Set to 0 for executable forests.
237 pub max_externals: usize,
238 /// Maximum number of dyn/dyncall nodes to generate
239 ///
240 /// **Warning**: Dyn nodes leave junk on the stack and cannot execute properly.
241 /// These nodes are primarily for testing MAST structure, not execution. Set to 0 for
242 /// executable forests.
243 pub max_dyns: usize,
244}
245
246impl Default for MastForestParams {
247 fn default() -> Self {
248 Self {
249 decorators: 3,
250 blocks: 1..=3,
251 max_joins: 1,
252 max_splits: 1,
253 max_loops: 1,
254 max_calls: 1,
255 max_syscalls: 0, // Default to 0 for executable forests
256 max_externals: 0, // Default to 0 for executable forests
257 max_dyns: 0, // Default to 0 for executable forests
258 }
259 }
260}
261
262impl Arbitrary for MastForest {
263 type Parameters = MastForestParams;
264 type Strategy = BoxedStrategy<Self>;
265
266 /// Generates a MastForest with the specified parameters.
267 ///
268 /// # Generated Forest Properties
269 ///
270 /// - **Basic blocks**: Always generated (1..=blocks.end()) with operations and decorators
271 /// - **Control flow nodes**: Generated according to max_* parameters, may be 0
272 /// - **Root nodes**: ~1/3 of generated nodes are marked as roots
273 ///
274 /// # Execution Limitations
275 ///
276 /// Generated forests may not be executable depending on parameters:
277 ///
278 /// ## Syscalls (`max_syscalls`)
279 /// - Generated syscalls reference random procedure digests
280 /// - Execution requires a kernel containing matching procedure hashes
281 /// - For executable forests, set `max_syscalls = 0`
282 ///
283 /// ## External Nodes (`max_externals`)
284 /// - Generated with random digests that won't match valid procedures
285 /// - Any program with external nodes will fail during execution
286 /// - For executable forests, set `max_externals = 0`
287 ///
288 /// ## Dynamic Nodes (`max_dyns`)
289 /// - Dyn and dyncall nodes leave junk on the stack
290 /// - These nodes cannot execute properly in practice
291 /// - For executable forests, set `max_dyns = 0`
292 ///
293 /// # Example Usage
294 ///
295 /// ```rust
296 /// use miden_core::mast::{MastForest, arbitrary::MastForestParams};
297 /// use proptest::arbitrary::Arbitrary;
298 ///
299 /// // Generate executable forest (default)
300 /// let forest = MastForest::arbitrary_with(MastForestParams::default());
301 ///
302 /// // Generate forest with non-executable nodes for testing
303 /// let params = MastForestParams {
304 /// max_syscalls: 2,
305 /// max_externals: 1,
306 /// max_dyns: 1,
307 /// ..Default::default()
308 /// };
309 /// let forest = MastForest::arbitrary_with(params);
310 /// ```
311 fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
312 // BasicBlockNode generation must reference decorator IDs in [0, decorators)
313 let bb_params = BasicBlockNodeParams {
314 max_decorator_id_u32: params.decorators,
315 ..Default::default()
316 };
317
318 // Generate nodes in a way that respects topological ordering
319 (
320 // Generate basic blocks first (they have no dependencies)
321 prop::collection::vec(
322 any_with::<BasicBlockNode>(bb_params.clone()),
323 1..=*params.blocks.end(),
324 ),
325 // Generate decorators
326 prop::collection::vec(
327 any::<Decorator>(),
328 params.decorators as usize..=params.decorators as usize,
329 ),
330 // Generate control flow node counts within the specified limits
331 (
332 // Generate number of join nodes (0 to max_joins)
333 0..=params.max_joins,
334 // Generate number of split nodes (0 to max_splits)
335 0..=params.max_splits,
336 // Generate number of loop nodes (0 to max_loops)
337 0..=params.max_loops,
338 // Generate number of call nodes (0 to max_calls)
339 0..=params.max_calls,
340 // Generate number of syscall nodes (0 to max_syscalls)
341 0..=params.max_syscalls,
342 // Generate number of external nodes (0 to max_externals)
343 0..=params.max_externals,
344 // Generate number of dyn nodes (0 to max_dyns)
345 0..=params.max_dyns,
346 ),
347 )
348 .prop_flat_map(
349 move |(
350 basic_blocks,
351 decorators,
352 (
353 num_joins,
354 num_splits,
355 num_loops,
356 num_calls,
357 num_syscalls,
358 num_externals,
359 num_dyns,
360 ),
361 )| {
362 let num_basic_blocks = basic_blocks.len();
363
364 // Ensure we have enough basic blocks for parents to reference
365 let max_parent_nodes = num_basic_blocks.saturating_sub(1);
366 let num_joins = num_joins.min(max_parent_nodes);
367 let num_splits = num_splits.min(max_parent_nodes);
368 let num_loops = num_loops.min(num_basic_blocks);
369 let num_calls = num_calls.min(num_basic_blocks);
370 let num_syscalls = num_syscalls.min(num_basic_blocks);
371
372 // Generate indices for creating parent nodes
373 (
374 Just(basic_blocks),
375 Just(decorators),
376 Just((
377 num_joins,
378 num_splits,
379 num_loops,
380 num_calls,
381 num_syscalls,
382 num_externals,
383 num_dyns,
384 )),
385 // Generate indices for join nodes (need 2 children each)
386 prop::collection::vec(any::<(usize, usize)>(), num_joins..=num_joins)
387 .prop_map(move |pairs| {
388 pairs
389 .into_iter()
390 .map(|(a, b)| (a % num_basic_blocks, b % num_basic_blocks))
391 .collect::<Vec<_>>()
392 }),
393 // Generate indices for split nodes (need 2 children each)
394 prop::collection::vec(any::<(usize, usize)>(), num_splits..=num_splits)
395 .prop_map(move |pairs| {
396 pairs
397 .into_iter()
398 .map(|(a, b)| (a % num_basic_blocks, b % num_basic_blocks))
399 .collect::<Vec<_>>()
400 }),
401 // Generate indices for loop nodes (need 1 child each)
402 prop::collection::vec(any::<usize>(), num_loops..=num_loops).prop_map(
403 move |indices| {
404 indices
405 .into_iter()
406 .map(|i| i % num_basic_blocks)
407 .collect::<Vec<_>>()
408 },
409 ),
410 // Generate indices for call nodes (need 1 child each)
411 prop::collection::vec(any::<usize>(), num_calls..=num_calls).prop_map(
412 move |indices| {
413 indices
414 .into_iter()
415 .map(|i| i % num_basic_blocks)
416 .collect::<Vec<_>>()
417 },
418 ),
419 // Generate indices for syscall nodes (need 1 child each)
420 prop::collection::vec(any::<usize>(), num_syscalls..=num_syscalls)
421 .prop_map(move |indices| {
422 indices
423 .into_iter()
424 .map(|i| i % num_basic_blocks)
425 .collect::<Vec<_>>()
426 }),
427 // Generate digests for external nodes
428 prop::collection::vec(any::<[u64; 4]>(), num_externals..=num_externals)
429 .prop_map(move |digests| {
430 digests
431 .into_iter()
432 .map(|[a, b, c, d]| {
433 Word::from([
434 Felt::new(a),
435 Felt::new(b),
436 Felt::new(c),
437 Felt::new(d),
438 ])
439 })
440 .collect::<Vec<_>>()
441 }),
442 )
443 },
444 )
445 .prop_map(
446 move |(
447 basic_blocks,
448 decorators,
449 (
450 _num_joins,
451 _num_splits,
452 _num_loops,
453 _num_calls,
454 _num_syscalls,
455 _num_externals,
456 num_dyns,
457 ),
458 join_pairs,
459 split_pairs,
460 loop_indices,
461 call_indices,
462 syscall_indices,
463 external_digests,
464 )| {
465 let mut forest = MastForest::new();
466
467 // 1) Add all decorators first
468 for decorator in decorators {
469 forest.add_decorator(decorator).expect("Failed to add decorator");
470 }
471
472 // 2) Add basic blocks and collect their IDs
473 let mut basic_block_ids = Vec::new();
474 for block in basic_blocks {
475 let builder = block.to_builder(&forest);
476 let node_id =
477 builder.add_to_forest(&mut forest).expect("Failed to add block");
478 basic_block_ids.push(node_id);
479 }
480
481 // 3) Add control flow nodes in topological order (children already exist)
482 let mut all_node_ids = basic_block_ids.clone();
483
484 // Add join nodes
485 for &(left_idx, right_idx) in &join_pairs {
486 if left_idx < all_node_ids.len() && right_idx < all_node_ids.len() {
487 let left_id = all_node_ids[left_idx];
488 let right_id = all_node_ids[right_idx];
489 if let Ok(join_id) =
490 JoinNodeBuilder::new([left_id, right_id]).add_to_forest(&mut forest)
491 {
492 all_node_ids.push(join_id);
493 }
494 }
495 }
496
497 // Add split nodes
498 for &(true_idx, false_idx) in &split_pairs {
499 if true_idx < all_node_ids.len() && false_idx < all_node_ids.len() {
500 let true_id = all_node_ids[true_idx];
501 let false_id = all_node_ids[false_idx];
502 if let Ok(split_id) = SplitNodeBuilder::new([true_id, false_id])
503 .add_to_forest(&mut forest)
504 {
505 all_node_ids.push(split_id);
506 }
507 }
508 }
509
510 // Add loop nodes
511 for &body_idx in &loop_indices {
512 if body_idx < all_node_ids.len() {
513 let body_id = all_node_ids[body_idx];
514 if let Ok(loop_id) =
515 LoopNodeBuilder::new(body_id).add_to_forest(&mut forest)
516 {
517 all_node_ids.push(loop_id);
518 }
519 }
520 }
521
522 // Add call nodes
523 for &callee_idx in &call_indices {
524 if callee_idx < all_node_ids.len() {
525 let callee_id = all_node_ids[callee_idx];
526 let call_id =
527 CallNodeBuilder::new(callee_id).add_to_forest(&mut forest).unwrap();
528 all_node_ids.push(call_id);
529 }
530 }
531
532 // Add syscall nodes
533 // WARNING: These use random procedure digests and will not execute without a
534 // matching kernel
535 for &callee_idx in &syscall_indices {
536 if callee_idx < all_node_ids.len() {
537 let callee_id = all_node_ids[callee_idx];
538 let syscall_id = CallNodeBuilder::new_syscall(callee_id)
539 .add_to_forest(&mut forest)
540 .unwrap();
541 all_node_ids.push(syscall_id);
542 }
543 }
544
545 // Add external nodes
546 // WARNING: These use random digests that won't match any valid procedures
547 for digest in external_digests {
548 if let Ok(external_id) =
549 ExternalNodeBuilder::new(digest).add_to_forest(&mut forest)
550 {
551 all_node_ids.push(external_id);
552 }
553 }
554
555 // Add dyn nodes (mix of dyn and dyncall)
556 // WARNING: These leave junk on the stack and cannot execute properly
557 for i in 0..num_dyns {
558 let dyn_id = if i % 2 == 0 {
559 DynNodeBuilder::new_dyn().add_to_forest(&mut forest).unwrap()
560 } else {
561 DynNodeBuilder::new_dyncall().add_to_forest(&mut forest).unwrap()
562 };
563 all_node_ids.push(dyn_id);
564 }
565
566 // 4) Make some nodes roots (but not all, to test internal nodes)
567 let num_roots = (all_node_ids.len() / 3).max(1); // Make roughly 1/3 of nodes roots
568 for (i, &node_id) in all_node_ids.iter().enumerate() {
569 if i % (all_node_ids.len() / num_roots.max(1)) == 0 {
570 forest.make_root(node_id);
571 }
572 }
573
574 forest
575 },
576 )
577 .boxed()
578 }
579}
580
581// ---------- Arbitrary implementations for missing types ----------
582
583impl Arbitrary for DebugOptions {
584 type Parameters = ();
585 type Strategy = BoxedStrategy<Self>;
586
587 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
588 prop_oneof![
589 Just(DebugOptions::StackAll),
590 any::<u8>().prop_map(DebugOptions::StackTop),
591 Just(DebugOptions::MemAll),
592 (any::<u32>(), any::<u32>())
593 .prop_map(|(start, end)| DebugOptions::MemInterval(start, end)),
594 (any::<u16>(), any::<u16>(), any::<u16>()).prop_map(|(start, end, num_locals)| {
595 DebugOptions::LocalInterval(start, end, num_locals)
596 }),
597 any::<u16>().prop_map(DebugOptions::AdvStackTop),
598 ]
599 .boxed()
600 }
601}
602
603impl Arbitrary for AssemblyOp {
604 type Parameters = ();
605 type Strategy = BoxedStrategy<Self>;
606
607 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
608 (
609 any::<bool>(),
610 prop::collection::vec(any::<char>(), 1..=20)
611 .prop_map(|chars| chars.into_iter().collect()),
612 prop::collection::vec(any::<char>(), 1..=20)
613 .prop_map(|chars| chars.into_iter().collect()),
614 any::<u8>(),
615 )
616 .prop_map(|(has_location, context_name, op, num_cycles)| {
617 use miden_debug_types::{ByteIndex, Location, Uri};
618
619 let location = if has_location {
620 Some(Location::new(Uri::new("dummy.rs"), ByteIndex(0), ByteIndex(0)))
621 } else {
622 None
623 };
624
625 AssemblyOp::new(location, context_name, num_cycles, op)
626 })
627 .boxed()
628 }
629}
630
631impl Arbitrary for Decorator {
632 type Parameters = ();
633 type Strategy = BoxedStrategy<Self>;
634
635 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
636 prop_oneof![
637 any_with::<DebugOptions>(()).prop_map(Decorator::Debug),
638 any::<u32>().prop_map(Decorator::Trace),
639 ]
640 .boxed()
641 }
642}
643
644impl Arbitrary for AdviceMap {
645 type Parameters = ();
646 type Strategy = BoxedStrategy<Self>;
647
648 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
649 // Strategy for generating Word keys
650 let word_strategy = prop_oneof![
651 Just(Word::default()),
652 any::<[u64; 4]>().prop_map(|[a, b, c, d]| Word::new([
653 Felt::new(a),
654 Felt::new(b),
655 Felt::new(c),
656 Felt::new(d)
657 ])),
658 ];
659
660 // Strategy for generating Arc<[Felt]> values
661 let felt_array_strategy = prop::collection::vec(any::<u64>(), 1..=4).prop_map(|vals| {
662 let felts: Arc<[Felt]> = vals.into_iter().map(Felt::new).collect();
663 felts
664 });
665
666 // Strategy for generating map entries
667 let entry_strategy = (word_strategy, felt_array_strategy);
668
669 // Strategy for generating the map itself (0 to 10 entries)
670 prop::collection::vec(entry_strategy, 0..=10)
671 .prop_map(|entries| {
672 let mut map = BTreeMap::new();
673 for (key, value) in entries {
674 map.insert(key, value);
675 }
676 AdviceMap::from(map)
677 })
678 .boxed()
679 }
680}
681
682impl Arbitrary for Program {
683 type Parameters = ();
684 type Strategy = BoxedStrategy<Self>;
685
686 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
687 // Create a simple strategy that generates a basic block and creates a program from it
688 any_with::<BasicBlockNode>(BasicBlockNodeParams {
689 max_ops_len: 4, // Keep it small
690 max_pairs: 1, // Fewer decorators
691 max_decorator_id_u32: 2,
692 })
693 .prop_map(|node| {
694 // Create a new MastForest
695 let mut forest = MastForest::new();
696
697 // Add some basic decorators
698 for i in 0..2 {
699 let decorator = Decorator::Trace(i as u32);
700 forest.add_decorator(decorator).expect("Failed to add decorator");
701 }
702
703 // Add the node to the forest using builder
704 let builder = node.to_builder(&forest);
705 let node_id = builder.add_to_forest(&mut forest).expect("Failed to add node");
706
707 // Since we added a node, it should be available as a procedure root
708 // If not, we need to make it a root manually
709 let entrypoint = if forest.num_procedures() > 0 {
710 forest.procedure_roots()[0]
711 } else {
712 // Make the node a root manually
713 forest.make_root(node_id);
714 // After making it a root, it should be a procedure
715 if forest.num_procedures() == 0 {
716 panic!("Failed to create a valid procedure from node");
717 }
718 forest.procedure_roots()[0]
719 };
720
721 Program::new(Arc::new(forest), entrypoint)
722 })
723 .prop_filter("valid entrypoint", |program| {
724 // Ensure the generated program has a valid procedure entrypoint
725 program.mast_forest().is_procedure_root(program.entrypoint())
726 })
727 .boxed()
728 }
729}
730
731impl Arbitrary for Kernel {
732 type Parameters = ();
733 type Strategy = BoxedStrategy<Self>;
734
735 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
736 // Strategy for generating Word vectors
737 let word_strategy = any::<[u64; 4]>().prop_map(|[a, b, c, d]| {
738 Word::new([Felt::new(a), Felt::new(b), Felt::new(c), Felt::new(d)])
739 });
740
741 // Strategy for generating kernel (0 to 3 words to avoid hitting MAX_NUM_PROCEDURES limit)
742 prop::collection::vec(word_strategy, 0..=3)
743 .prop_map(|words: Vec<Word>| {
744 Kernel::new(&words).expect("Generated kernel should be valid")
745 })
746 .boxed()
747 }
748}