use alloc::{collections::BTreeMap, sync::Arc};
use core::ops::RangeInclusive;
use proptest::{arbitrary::Arbitrary, prelude::*};
use super::*;
use crate::{
Felt, Word,
advice::AdviceMap,
mast::{
CallNodeBuilder, DecoratorId, DynNodeBuilder, ExternalNodeBuilder, JoinNodeBuilder,
LoopNodeBuilder, SplitNodeBuilder,
},
operations::{AssemblyOp, DebugOptions, Decorator, Operation},
program::{Kernel, Program},
};
pub fn op_no_imm_strategy() -> impl Strategy<Value = Operation> {
prop_oneof![
Just(Operation::Add),
Just(Operation::Mul),
Just(Operation::Neg),
Just(Operation::Inv),
Just(Operation::Incr),
Just(Operation::And),
Just(Operation::Or),
Just(Operation::Not),
Just(Operation::Eq),
Just(Operation::Eqz),
Just(Operation::Drop),
Just(Operation::Pad),
Just(Operation::Swap),
Just(Operation::SwapW),
Just(Operation::SwapW2),
Just(Operation::SwapW3),
Just(Operation::SwapDW),
Just(Operation::MovUp2),
Just(Operation::MovUp3),
Just(Operation::MovUp4),
Just(Operation::MovUp5),
Just(Operation::MovUp6),
Just(Operation::MovUp7),
Just(Operation::MovUp8),
Just(Operation::MovDn2),
Just(Operation::MovDn3),
Just(Operation::MovDn4),
Just(Operation::MovDn5),
Just(Operation::MovDn6),
Just(Operation::MovDn7),
Just(Operation::MovDn8),
Just(Operation::CSwap),
Just(Operation::CSwapW),
Just(Operation::Dup0),
Just(Operation::Dup1),
Just(Operation::Dup2),
Just(Operation::Dup3),
Just(Operation::Dup4),
Just(Operation::Dup5),
Just(Operation::Dup6),
Just(Operation::Dup7),
Just(Operation::Dup9),
Just(Operation::Dup11),
Just(Operation::Dup13),
Just(Operation::Dup15),
Just(Operation::MLoad),
Just(Operation::MStore),
Just(Operation::MLoadW),
Just(Operation::MStoreW),
Just(Operation::MStream),
Just(Operation::Pipe),
Just(Operation::AdvPop),
Just(Operation::AdvPopW),
Just(Operation::U32split),
Just(Operation::U32add),
Just(Operation::U32sub),
Just(Operation::U32mul),
Just(Operation::U32div),
Just(Operation::U32and),
Just(Operation::U32xor),
Just(Operation::U32add3),
Just(Operation::U32madd),
Just(Operation::SDepth),
Just(Operation::Caller),
Just(Operation::Clk),
Just(Operation::Emit),
Just(Operation::Ext2Mul),
Just(Operation::Expacc),
Just(Operation::HPerm),
]
}
pub fn op_with_imm_strategy() -> impl Strategy<Value = Operation> {
prop_oneof![any::<u64>().prop_map(Felt::new).prop_map(Operation::Push)]
}
pub fn op_non_control_strategy() -> impl Strategy<Value = Operation> {
prop_oneof![op_no_imm_strategy(), op_with_imm_strategy(),]
}
pub fn op_non_control_sequence_strategy(
max_length: usize,
) -> impl Strategy<Value = Vec<Operation>> {
prop::collection::vec(op_non_control_strategy(), 1..=max_length)
}
#[derive(Clone, Debug)]
pub struct BasicBlockNodeParams {
pub max_ops_len: usize,
pub max_pairs: usize,
pub max_decorator_id_u32: u32,
}
impl Default for BasicBlockNodeParams {
fn default() -> Self {
Self {
max_ops_len: 8,
max_pairs: 2,
max_decorator_id_u32: 3,
}
}
}
pub fn decorator_id_strategy(max_id: u32) -> impl Strategy<Value = DecoratorId> {
let upper = core::cmp::max(1, max_id);
(0..upper).prop_map(DecoratorId::new_unchecked)
}
pub fn decorator_pairs_strategy(
ops_len: usize,
max_id: u32,
max_pairs: usize,
) -> impl Strategy<Value = Vec<(usize, DecoratorId)>> {
prop::collection::vec((0..=ops_len, decorator_id_strategy(max_id)), 0..=max_pairs).prop_map(
|mut v| {
v.sort_by_key(|(i, _)| *i);
v
},
)
}
impl Arbitrary for BasicBlockNode {
type Parameters = BasicBlockNodeParams;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(p: Self::Parameters) -> Self::Strategy {
(op_non_control_sequence_strategy(p.max_ops_len),)
.prop_flat_map(move |(ops,)| {
let ops_len = ops.len().max(1); decorator_pairs_strategy(ops_len, p.max_decorator_id_u32, p.max_pairs)
.prop_map(move |decorators| (ops.clone(), decorators))
})
.prop_filter_map("non-empty ops", |(ops, decorators)| {
if ops.is_empty() { None } else { Some((ops, decorators)) }
})
.prop_map(|(ops, decorators)| {
BasicBlockNode::new_owned_with_decorators(ops, decorators)
.expect("non-empty ops; new() only errs on empty ops")
})
.boxed()
}
}
#[derive(Clone, Debug)]
pub struct MastForestParams {
pub decorators: u32,
pub blocks: RangeInclusive<usize>,
pub max_joins: usize,
pub max_splits: usize,
pub max_loops: usize,
pub max_calls: usize,
pub max_syscalls: usize,
pub max_externals: usize,
pub max_dyns: usize,
}
impl Default for MastForestParams {
fn default() -> Self {
Self {
decorators: 3,
blocks: 1..=3,
max_joins: 1,
max_splits: 1,
max_loops: 1,
max_calls: 1,
max_syscalls: 0, max_externals: 0, max_dyns: 0, }
}
}
impl Arbitrary for MastForest {
type Parameters = MastForestParams;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
let bb_params = BasicBlockNodeParams {
max_decorator_id_u32: params.decorators,
..Default::default()
};
(
prop::collection::vec(
any_with::<BasicBlockNode>(bb_params.clone()),
1..=*params.blocks.end(),
),
prop::collection::vec(
any::<Decorator>(),
params.decorators as usize..=params.decorators as usize,
),
(
0..=params.max_joins,
0..=params.max_splits,
0..=params.max_loops,
0..=params.max_calls,
0..=params.max_syscalls,
0..=params.max_externals,
0..=params.max_dyns,
),
)
.prop_flat_map(
move |(
basic_blocks,
decorators,
(
num_joins,
num_splits,
num_loops,
num_calls,
num_syscalls,
num_externals,
num_dyns,
),
)| {
let num_basic_blocks = basic_blocks.len();
let max_parent_nodes = num_basic_blocks.saturating_sub(1);
let num_joins = num_joins.min(max_parent_nodes);
let num_splits = num_splits.min(max_parent_nodes);
let num_loops = num_loops.min(num_basic_blocks);
let num_calls = num_calls.min(num_basic_blocks);
let num_syscalls = num_syscalls.min(num_basic_blocks);
(
Just(basic_blocks),
Just(decorators),
Just((
num_joins,
num_splits,
num_loops,
num_calls,
num_syscalls,
num_externals,
num_dyns,
)),
prop::collection::vec(any::<(usize, usize)>(), num_joins..=num_joins)
.prop_map(move |pairs| {
pairs
.into_iter()
.map(|(a, b)| (a % num_basic_blocks, b % num_basic_blocks))
.collect::<Vec<_>>()
}),
prop::collection::vec(any::<(usize, usize)>(), num_splits..=num_splits)
.prop_map(move |pairs| {
pairs
.into_iter()
.map(|(a, b)| (a % num_basic_blocks, b % num_basic_blocks))
.collect::<Vec<_>>()
}),
prop::collection::vec(any::<usize>(), num_loops..=num_loops).prop_map(
move |indices| {
indices
.into_iter()
.map(|i| i % num_basic_blocks)
.collect::<Vec<_>>()
},
),
prop::collection::vec(any::<usize>(), num_calls..=num_calls).prop_map(
move |indices| {
indices
.into_iter()
.map(|i| i % num_basic_blocks)
.collect::<Vec<_>>()
},
),
prop::collection::vec(any::<usize>(), num_syscalls..=num_syscalls)
.prop_map(move |indices| {
indices
.into_iter()
.map(|i| i % num_basic_blocks)
.collect::<Vec<_>>()
}),
prop::collection::vec(any::<[u64; 4]>(), num_externals..=num_externals)
.prop_map(move |digests| {
digests
.into_iter()
.map(|[a, b, c, d]| {
Word::from([
Felt::new(a),
Felt::new(b),
Felt::new(c),
Felt::new(d),
])
})
.collect::<Vec<_>>()
}),
)
},
)
.prop_map(
move |(
basic_blocks,
decorators,
(
_num_joins,
_num_splits,
_num_loops,
_num_calls,
_num_syscalls,
_num_externals,
num_dyns,
),
join_pairs,
split_pairs,
loop_indices,
call_indices,
syscall_indices,
external_digests,
)| {
let mut forest = MastForest::new();
for decorator in decorators {
forest.add_decorator(decorator).expect("Failed to add decorator");
}
let mut basic_block_ids = Vec::new();
for block in basic_blocks {
let builder = block.to_builder(&forest);
let node_id =
builder.add_to_forest(&mut forest).expect("Failed to add block");
basic_block_ids.push(node_id);
}
let mut all_node_ids = basic_block_ids.clone();
for &(left_idx, right_idx) in &join_pairs {
if left_idx < all_node_ids.len() && right_idx < all_node_ids.len() {
let left_id = all_node_ids[left_idx];
let right_id = all_node_ids[right_idx];
if let Ok(join_id) =
JoinNodeBuilder::new([left_id, right_id]).add_to_forest(&mut forest)
{
all_node_ids.push(join_id);
}
}
}
for &(true_idx, false_idx) in &split_pairs {
if true_idx < all_node_ids.len() && false_idx < all_node_ids.len() {
let true_id = all_node_ids[true_idx];
let false_id = all_node_ids[false_idx];
if let Ok(split_id) = SplitNodeBuilder::new([true_id, false_id])
.add_to_forest(&mut forest)
{
all_node_ids.push(split_id);
}
}
}
for &body_idx in &loop_indices {
if body_idx < all_node_ids.len() {
let body_id = all_node_ids[body_idx];
if let Ok(loop_id) =
LoopNodeBuilder::new(body_id).add_to_forest(&mut forest)
{
all_node_ids.push(loop_id);
}
}
}
for &callee_idx in &call_indices {
if callee_idx < all_node_ids.len() {
let callee_id = all_node_ids[callee_idx];
let call_id =
CallNodeBuilder::new(callee_id).add_to_forest(&mut forest).unwrap();
all_node_ids.push(call_id);
}
}
for &callee_idx in &syscall_indices {
if callee_idx < all_node_ids.len() {
let callee_id = all_node_ids[callee_idx];
let syscall_id = CallNodeBuilder::new_syscall(callee_id)
.add_to_forest(&mut forest)
.unwrap();
all_node_ids.push(syscall_id);
}
}
for digest in external_digests {
if let Ok(external_id) =
ExternalNodeBuilder::new(digest).add_to_forest(&mut forest)
{
all_node_ids.push(external_id);
}
}
for i in 0..num_dyns {
let dyn_id = if i % 2 == 0 {
DynNodeBuilder::new_dyn().add_to_forest(&mut forest).unwrap()
} else {
DynNodeBuilder::new_dyncall().add_to_forest(&mut forest).unwrap()
};
all_node_ids.push(dyn_id);
}
let num_roots = (all_node_ids.len() / 3).max(1); for (i, &node_id) in all_node_ids.iter().enumerate() {
if i % (all_node_ids.len() / num_roots.max(1)) == 0 {
forest.make_root(node_id);
}
}
forest
},
)
.boxed()
}
}
impl Arbitrary for DebugOptions {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
prop_oneof![
Just(DebugOptions::StackAll),
any::<u8>().prop_map(DebugOptions::StackTop),
Just(DebugOptions::MemAll),
(any::<u32>(), any::<u32>())
.prop_map(|(start, end)| DebugOptions::MemInterval(start, end)),
(any::<u16>(), any::<u16>(), any::<u16>()).prop_map(|(start, end, num_locals)| {
DebugOptions::LocalInterval(start, end, num_locals)
}),
any::<u16>().prop_map(DebugOptions::AdvStackTop),
]
.boxed()
}
}
impl Arbitrary for AssemblyOp {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(
any::<bool>(),
prop::collection::vec(any::<char>(), 1..=20)
.prop_map(|chars| chars.into_iter().collect()),
prop::collection::vec(any::<char>(), 1..=20)
.prop_map(|chars| chars.into_iter().collect()),
any::<u8>(),
)
.prop_map(|(has_location, context_name, op, num_cycles)| {
use miden_debug_types::{ByteIndex, Location, Uri};
let location = if has_location {
Some(Location::new(Uri::new("dummy.rs"), ByteIndex(0), ByteIndex(0)))
} else {
None
};
AssemblyOp::new(location, context_name, num_cycles, op)
})
.boxed()
}
}
impl Arbitrary for Decorator {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
prop_oneof![
any_with::<DebugOptions>(()).prop_map(Decorator::Debug),
any::<u32>().prop_map(Decorator::Trace),
]
.boxed()
}
}
impl Arbitrary for AdviceMap {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
let word_strategy = prop_oneof![
Just(Word::default()),
any::<[u64; 4]>().prop_map(|[a, b, c, d]| Word::new([
Felt::new(a),
Felt::new(b),
Felt::new(c),
Felt::new(d)
])),
];
let felt_array_strategy = prop::collection::vec(any::<u64>(), 1..=4).prop_map(|vals| {
let felts: Arc<[Felt]> = vals.into_iter().map(Felt::new).collect();
felts
});
let entry_strategy = (word_strategy, felt_array_strategy);
prop::collection::vec(entry_strategy, 0..=10)
.prop_map(|entries| {
let mut map = BTreeMap::new();
for (key, value) in entries {
map.insert(key, value);
}
AdviceMap::from(map)
})
.boxed()
}
}
impl Arbitrary for Program {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
any_with::<BasicBlockNode>(BasicBlockNodeParams {
max_ops_len: 4, max_pairs: 1, max_decorator_id_u32: 2,
})
.prop_map(|node| {
let mut forest = MastForest::new();
for i in 0..2 {
let decorator = Decorator::Trace(i as u32);
forest.add_decorator(decorator).expect("Failed to add decorator");
}
let builder = node.to_builder(&forest);
let node_id = builder.add_to_forest(&mut forest).expect("Failed to add node");
let entrypoint = if forest.num_procedures() > 0 {
forest.procedure_roots()[0]
} else {
forest.make_root(node_id);
if forest.num_procedures() == 0 {
panic!("Failed to create a valid procedure from node");
}
forest.procedure_roots()[0]
};
Program::new(Arc::new(forest), entrypoint)
})
.prop_filter("valid entrypoint", |program| {
program.mast_forest().is_procedure_root(program.entrypoint())
})
.boxed()
}
}
impl Arbitrary for Kernel {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
let word_strategy = any::<[u64; 4]>().prop_map(|[a, b, c, d]| {
Word::new([Felt::new(a), Felt::new(b), Felt::new(c), Felt::new(d)])
});
prop::collection::vec(word_strategy, 0..=3)
.prop_map(|words: Vec<Word>| {
Kernel::new(&words).expect("Generated kernel should be valid")
})
.boxed()
}
}