Skip to main content

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}