use alloc::vec::Vec;
use miden_crypto::{WORD_SIZE, rand::test_utils::prng_array};
use proptest::prelude::*;
use crate::{
Felt, Word,
chiplets::hasher,
mast::{
BasicBlockNodeBuilder, CallNodeBuilder, DynNode, DynNodeBuilder, JoinNodeBuilder,
MastForest, MastForestContributor, MastNodeExt, SplitNodeBuilder,
},
operations::{AssemblyOp, DebugOptions, Decorator, Operation},
program::{Kernel, ProgramInfo},
serde::{Deserializable, Serializable},
};
#[test]
fn dyn_hash_is_correct() {
let expected_constant =
hasher::merge_in_domain(&[Word::default(), Word::default()], DynNode::DYN_DOMAIN);
let mut forest = MastForest::new();
let dyn_node_id = DynNodeBuilder::new_dyn().add_to_forest(&mut forest).unwrap();
let dyn_node = forest.get_node_by_id(dyn_node_id).unwrap().unwrap_dyn();
assert_eq!(expected_constant, dyn_node.digest());
}
proptest! {
#[test]
fn arbitrary_program_info_serialization_works(
kernel_count in prop::num::u8::ANY,
ref seed in any::<[u8; 32]>()
) {
let program_hash = digest_from_seed(*seed);
let kernel: Vec<Word> = (0..kernel_count)
.scan(*seed, |seed, _| {
*seed = prng_array(*seed);
Some(digest_from_seed(*seed))
})
.collect();
let kernel = Kernel::new(&kernel).unwrap();
let program_info = ProgramInfo::new(program_hash, kernel);
let bytes = program_info.to_bytes();
let deser = ProgramInfo::read_from_bytes(&bytes).unwrap();
assert_eq!(program_info, deser);
}
}
#[test]
fn test_decorator_storage_consistency_with_block_iterator() {
let mut forest = MastForest::new();
let deco1 = forest.add_decorator(Decorator::Trace(1)).unwrap();
let deco2 = forest.add_decorator(Decorator::Trace(2)).unwrap();
let deco3 = forest.add_decorator(Decorator::Debug(DebugOptions::StackTop(42))).unwrap();
let operations = vec![
Operation::Push(Felt::new(1)),
Operation::Add,
Operation::Push(Felt::new(2)),
Operation::Mul,
];
let decorators = vec![
(0, deco1), (2, deco2), (3, deco3), ];
let block_id = BasicBlockNodeBuilder::new(operations.clone(), decorators.clone())
.add_to_forest(&mut forest)
.unwrap();
let block = if let crate::mast::MastNode::Block(block) = &forest[block_id] {
block
} else {
panic!("Expected a block node");
};
let forest_decorators: Vec<_> = forest
.debug_info
.op_decorator_storage()
.decorator_ids_for_node(block_id)
.unwrap()
.flat_map(|(op_idx, decorators)| decorators.iter().map(move |dec_id| (op_idx, *dec_id)))
.collect();
let block_decorators: Vec<_> = block.indexed_decorator_iter(&forest).collect();
assert_eq!(
forest_decorators, block_decorators,
"Decorators from forest storage should match block iterator"
);
for (op_idx, expected_decorator_id) in &decorators {
let forest_decos = forest
.debug_info
.op_decorator_storage()
.decorator_ids_for_operation(block_id, *op_idx)
.unwrap();
let block_decos: Vec<_> = block
.indexed_decorator_iter(&forest)
.filter(|(idx, _)| *idx == *op_idx)
.map(|(_, id)| id)
.collect();
assert_eq!(forest_decos, block_decos, "Decorators for operation {} should match", op_idx);
assert_eq!(
forest_decos,
&[*expected_decorator_id],
"Should have correct decorator for operation {}",
op_idx
);
}
let operations_without_decorators = [1]; for op_idx in operations_without_decorators {
let forest_decos = forest
.debug_info
.op_decorator_storage()
.decorator_ids_for_operation(block_id, op_idx)
.unwrap();
let block_decos: Vec<_> = block
.indexed_decorator_iter(&forest)
.filter(|(idx, _)| *idx == op_idx)
.map(|(_, id)| id)
.collect();
assert_eq!(forest_decos, [], "Operation {} should have no decorators", op_idx);
assert_eq!(block_decos, [], "Operation {} should have no decorators", op_idx);
}
}
#[test]
fn test_decorator_storage_consistency_with_empty_block() {
let mut forest = MastForest::new();
let operations = vec![Operation::Push(Felt::new(1)), Operation::Add];
let block_id = BasicBlockNodeBuilder::new(operations.clone(), vec![])
.add_to_forest(&mut forest)
.unwrap();
let block = if let crate::mast::MastNode::Block(block) = &forest[block_id] {
block
} else {
panic!("Expected a block node");
};
let forest_decorators: Vec<_> = forest
.debug_info
.op_decorator_storage()
.decorator_ids_for_node(block_id)
.unwrap()
.collect();
let block_decorators: Vec<_> = block.indexed_decorator_iter(&forest).collect();
assert_eq!(forest_decorators, []);
assert_eq!(block_decorators, []);
}
#[test]
fn test_decorator_storage_consistency_with_multiple_blocks() {
let mut forest = MastForest::new();
let deco1 = forest.add_decorator(Decorator::Trace(1)).unwrap();
let deco2 = forest.add_decorator(Decorator::Trace(2)).unwrap();
let operations1 = vec![Operation::Push(Felt::new(1)), Operation::Add];
let decorators1 = vec![(0, deco1), (1, deco2)];
let block_id1 = BasicBlockNodeBuilder::new(operations1, decorators1)
.add_to_forest(&mut forest)
.unwrap();
let deco3 = forest.add_decorator(Decorator::Debug(DebugOptions::StackTop(99))).unwrap();
let operations2 = vec![Operation::Push(Felt::new(2)), Operation::Mul];
let decorators2 = vec![(0, deco3)];
let block_id2 = BasicBlockNodeBuilder::new(operations2, decorators2)
.add_to_forest(&mut forest)
.unwrap();
let forest_decorators1: Vec<_> = forest
.debug_info
.op_decorator_storage()
.decorator_ids_for_node(block_id1)
.unwrap()
.flat_map(|(op_idx, decorators)| decorators.iter().map(move |dec_id| (op_idx, *dec_id)))
.collect();
let block1 = if let crate::mast::MastNode::Block(block) = &forest[block_id1] {
block
} else {
panic!("Expected a block node");
};
let block_decorators1: Vec<_> = block1.indexed_decorator_iter(&forest).collect();
assert_eq!(forest_decorators1, block_decorators1);
let forest_decorators2: Vec<_> = forest
.debug_info
.op_decorator_storage()
.decorator_ids_for_node(block_id2)
.unwrap()
.flat_map(|(op_idx, decorators)| decorators.iter().map(move |dec_id| (op_idx, *dec_id)))
.collect();
let block2 = if let crate::mast::MastNode::Block(block) = &forest[block_id2] {
block
} else {
panic!("Expected a block node");
};
let block_decorators2: Vec<_> = block2.indexed_decorator_iter(&forest).collect();
assert_eq!(forest_decorators2, block_decorators2);
assert_eq!(forest.debug_info.op_decorator_storage().num_nodes(), 2);
}
#[test]
fn test_decorator_storage_after_clear_debug_info() {
let mut forest = MastForest::new();
let deco1 = forest.add_decorator(Decorator::Trace(1)).unwrap();
let deco2 = forest.add_decorator(Decorator::Trace(2)).unwrap();
let operations = vec![Operation::Push(Felt::new(1)), Operation::Add];
let block_id = BasicBlockNodeBuilder::new(operations, vec![(0, deco1), (1, deco2)])
.add_to_forest(&mut forest)
.unwrap();
assert_eq!(forest.debug_info.num_decorators(), 2);
assert_eq!(forest.debug_info.op_decorator_storage().num_decorator_ids(), 2);
forest.clear_debug_info();
assert_eq!(forest.debug_info.num_decorators(), 0);
assert_eq!(forest.debug_info.op_decorator_storage().num_nodes(), 1);
assert!(forest.decorator_links_for_node(block_id).unwrap().into_iter().next().is_none());
}
#[test]
fn test_clear_debug_info_edge_cases() {
let mut forest = MastForest::new();
forest.clear_debug_info();
assert_eq!(forest.debug_info.num_decorators(), 0);
assert_eq!(forest.debug_info.op_decorator_storage().num_nodes(), 0);
let operations = vec![Operation::Push(Felt::new(1)), Operation::Add];
let block_id = BasicBlockNodeBuilder::new(operations, vec![])
.add_to_forest(&mut forest)
.unwrap();
forest.clear_debug_info();
forest.clear_debug_info();
assert_eq!(forest.debug_info.num_decorators(), 0);
assert_eq!(forest.debug_info.op_decorator_storage().num_nodes(), 1);
assert!(forest.decorator_links_for_node(block_id).unwrap().into_iter().next().is_none());
}
#[test]
fn test_clear_debug_info_multiple_node_types() {
let mut forest = MastForest::new();
let deco = forest.add_decorator(Decorator::Trace(1)).unwrap();
let block_id = BasicBlockNodeBuilder::new(
vec![Operation::Push(Felt::new(1)), Operation::Add],
vec![(0, deco)],
)
.add_to_forest(&mut forest)
.unwrap();
JoinNodeBuilder::new([block_id, block_id]).add_to_forest(&mut forest).unwrap();
SplitNodeBuilder::new([block_id, block_id]).add_to_forest(&mut forest).unwrap();
forest.clear_debug_info();
assert_eq!(forest.debug_info.op_decorator_storage().num_nodes(), 3);
assert!(forest.decorator_links_for_node(block_id).unwrap().into_iter().next().is_none());
}
#[test]
fn test_mast_forest_roundtrip_with_basic_blocks_and_decorators() {
use crate::mast::MastNode;
let mut original_forest = MastForest::new();
let trace_deco_0 = original_forest.add_decorator(Decorator::Trace(0)).unwrap();
let trace_deco_1 = original_forest.add_decorator(Decorator::Trace(1)).unwrap();
let trace_deco_2 = original_forest.add_decorator(Decorator::Trace(2)).unwrap();
let trace_deco_3 = original_forest.add_decorator(Decorator::Trace(3)).unwrap();
let trace_deco_4 = original_forest.add_decorator(Decorator::Trace(4)).unwrap();
let operations1 = vec![Operation::Add, Operation::Mul, Operation::Eq];
let decorators1 = vec![(0, trace_deco_0), (2, trace_deco_1)];
let block1_id = BasicBlockNodeBuilder::new(operations1, decorators1)
.with_before_enter(vec![trace_deco_2])
.with_after_exit(vec![trace_deco_3])
.add_to_forest(&mut original_forest)
.unwrap();
let operations2 = vec![
Operation::Push(Felt::new(1)),
Operation::Push(Felt::new(2)),
Operation::Mul,
Operation::Drop,
];
let decorators2 = vec![
(0, trace_deco_0),
(0, trace_deco_4),
(3, trace_deco_1),
(3, trace_deco_2),
(3, trace_deco_3),
];
let block2_id = BasicBlockNodeBuilder::new(operations2, decorators2)
.add_to_forest(&mut original_forest)
.unwrap();
let operations3 = vec![Operation::Incr, Operation::Neg];
let decorators3 = vec![];
let block3_id = BasicBlockNodeBuilder::new(operations3, decorators3)
.add_to_forest(&mut original_forest)
.unwrap();
assert_eq!(original_forest.num_nodes(), 3);
assert_eq!(original_forest.debug_info.op_decorator_storage().num_nodes(), 3);
let original_decorator_count =
original_forest.debug_info.op_decorator_storage().num_decorator_ids();
let original_bytes = original_forest.to_bytes();
let deserialized_forest = MastForest::read_from_bytes(&original_bytes).unwrap();
assert_eq!(deserialized_forest.num_nodes(), 3);
assert_eq!(deserialized_forest.debug_info.op_decorator_storage().num_nodes(), 3);
assert_eq!(
deserialized_forest.debug_info.op_decorator_storage().num_decorator_ids(),
original_decorator_count
);
assert!(
!deserialized_forest.debug_info.op_decorator_storage().is_empty(),
"Deserialized forest should have decorator storage"
);
for &block_id in &[block1_id, block2_id, block3_id] {
let original_block = match &original_forest[block_id] {
MastNode::Block(block) => block,
_ => panic!("Expected block node"),
};
let deserialized_block = match &deserialized_forest[block_id] {
MastNode::Block(block) => block,
_ => panic!("Expected block node"),
};
assert_eq!(original_block, deserialized_block);
let original_decorators: Vec<_> =
original_block.indexed_decorator_iter(&original_forest).collect();
let deserialized_decorators: Vec<_> =
deserialized_block.indexed_decorator_iter(&deserialized_forest).collect();
assert_eq!(original_decorators, deserialized_decorators);
assert_eq!(
original_block.before_enter(&original_forest),
deserialized_block.before_enter(&deserialized_forest)
);
assert_eq!(
original_block.after_exit(&original_forest),
deserialized_block.after_exit(&deserialized_forest)
);
}
let deserialized_block1 = match &deserialized_forest[block1_id] {
MastNode::Block(block) => block,
_ => panic!("Expected block node"),
};
let deserialized_block2 = match &deserialized_forest[block2_id] {
MastNode::Block(block) => block,
_ => panic!("Expected block node"),
};
assert_eq!(deserialized_block1.before_enter(&deserialized_forest), &[trace_deco_2]);
assert_eq!(deserialized_block1.after_exit(&deserialized_forest), &[trace_deco_3]);
let block2_decorators: Vec<_> =
deserialized_block2.indexed_decorator_iter(&deserialized_forest).collect();
assert_eq!(block2_decorators.len(), 5);
let mut op0_decorators = Vec::new();
let mut op3_decorators = Vec::new();
for (op_idx, decorator_id) in block2_decorators {
match op_idx {
0 => op0_decorators.push(decorator_id),
3 => op3_decorators.push(decorator_id),
_ => panic!("Unexpected decorator at operation index {}", op_idx),
}
}
assert_eq!(op0_decorators.len(), 2);
assert_eq!(op3_decorators.len(), 3);
}
#[test]
#[cfg(feature = "serde")]
fn test_mast_forest_serde_converts_linked_to_owned_decorators() {
let mut forest = MastForest::new();
let deco1 = forest.add_decorator(Decorator::Trace(1)).unwrap();
let deco2 = forest.add_decorator(Decorator::Trace(2)).unwrap();
let operations =
vec![Operation::Push(Felt::new(1)), Operation::Add, Operation::Push(Felt::new(2))];
let decorators = vec![(0, deco1), (2, deco2)];
let block_id = BasicBlockNodeBuilder::new(operations.clone(), decorators.clone())
.add_to_forest(&mut forest)
.unwrap();
let original_block = if let crate::mast::MastNode::Block(block) = &forest[block_id] {
block
} else {
panic!("Expected a block node");
};
let original_decorators: Vec<_> = original_block.indexed_decorator_iter(&forest).collect();
let expected_decorators = vec![(0, deco1), (2, deco2)];
assert_eq!(
original_decorators, expected_decorators,
"Decorators should be correct before serialization"
);
let serialized_bytes = forest.to_bytes();
let mut deserialized_forest: MastForest =
MastForest::read_from_bytes(&serialized_bytes).expect("Failed to deserialize MastForest");
let deserialized_block =
if let crate::mast::MastNode::Block(block) = &deserialized_forest[block_id] {
block
} else {
panic!("Expected a block node in deserialized forest");
};
let deserialized_decorators: Vec<_> =
deserialized_block.indexed_decorator_iter(&deserialized_forest).collect();
assert_eq!(
deserialized_decorators, expected_decorators,
"Decorator data should be preserved during round-trip"
);
let deserialized_via_links = deserialized_block
.indexed_decorator_iter(&deserialized_forest)
.collect::<Vec<_>>();
assert_eq!(
deserialized_via_links, expected_decorators,
"Deserialized block should use linked storage via forest borrowing"
);
assert_eq!(
original_block.indexed_decorator_iter(&forest).collect::<Vec<_>>(),
deserialized_block
.indexed_decorator_iter(&deserialized_forest)
.collect::<Vec<_>>(),
"Decorators should be functionally equal between original and deserialized forests"
);
let new_decorator_id = deserialized_forest.add_decorator(Decorator::Trace(99)).unwrap();
assert_eq!(deserialized_forest[new_decorator_id], Decorator::Trace(99));
}
#[test]
fn test_mast_forest_serializable_converts_linked_to_owned_decorators() {
let mut forest = MastForest::new();
let deco1 = forest.add_decorator(Decorator::Trace(1)).unwrap();
let deco2 = forest.add_decorator(Decorator::Trace(2)).unwrap();
let operations =
vec![Operation::Push(Felt::new(1)), Operation::Add, Operation::Push(Felt::new(2))];
let decorators = vec![(0, deco1), (2, deco2)];
let block_id = BasicBlockNodeBuilder::new(operations.clone(), decorators.clone())
.add_to_forest(&mut forest)
.unwrap();
let original_block = if let crate::mast::MastNode::Block(block) = &forest[block_id] {
block
} else {
panic!("Expected a block node");
};
let decorator_count_before = original_block.indexed_decorator_iter(&forest).count();
assert_eq!(
decorator_count_before, 2,
"Block should have 2 decorators accessible through forest borrowing"
);
let original_decorators: Vec<_> = original_block.indexed_decorator_iter(&forest).collect();
let expected_decorators = vec![(0, deco1), (2, deco2)];
assert_eq!(
original_decorators, expected_decorators,
"Decorators should be correct before serialization"
);
let serialized = forest.to_bytes();
let mut deserialized_forest: MastForest =
MastForest::read_from_bytes(&serialized).expect("Failed to deserialize MastForest");
let deserialized_decorators: Vec<_> = {
let block = if let crate::mast::MastNode::Block(block) = &deserialized_forest[block_id] {
block
} else {
panic!("Expected a block node in deserialized forest");
};
block.indexed_decorator_iter(&deserialized_forest).collect()
};
assert_eq!(
deserialized_decorators, expected_decorators,
"Decorator data should be preserved during round-trip"
);
let original_decorators_final =
original_block.indexed_decorator_iter(&forest).collect::<Vec<_>>();
assert_eq!(
original_decorators_final, deserialized_decorators,
"Decorators should be functionally equal despite different storage representations"
);
let new_decorator_id = deserialized_forest.add_decorator(Decorator::Trace(99)).unwrap();
let original_after_new = original_block.indexed_decorator_iter(&forest).collect::<Vec<_>>();
assert_eq!(
original_after_new, expected_decorators,
"Original decorators should remain unchanged after adding new decorator"
);
assert_eq!(deserialized_forest[new_decorator_id], Decorator::Trace(99));
}
#[test]
fn test_forest_borrowing_decorator_access() {
let mut forest = MastForest::new();
let decorator1 = forest.add_decorator(Decorator::Trace(1)).unwrap();
let decorator2 = forest.add_decorator(Decorator::Trace(2)).unwrap();
let decorator3 = forest.add_decorator(Decorator::Trace(3)).unwrap();
let operations =
vec![Operation::Add, Operation::Mul, Operation::Eq, Operation::Assert(Felt::ZERO)];
let decorators = vec![(0, decorator1), (1, decorator2), (3, decorator3)];
let builder = BasicBlockNodeBuilder::new(operations, decorators);
let node_id = builder.add_to_forest(&mut forest).unwrap();
let block = &forest[node_id];
if let crate::mast::MastNode::Block(block_node) = block {
let op0_decorators = forest.decorator_indices_for_op(node_id, 0);
assert_eq!(op0_decorators, &[decorator1]);
let op1_decorators = forest.decorator_indices_for_op(node_id, 1);
assert_eq!(op1_decorators, &[decorator2]);
let op2_decorators = forest.decorator_indices_for_op(node_id, 2);
assert_eq!(op2_decorators, &[]);
let op3_decorators = forest.decorator_indices_for_op(node_id, 3);
assert_eq!(op3_decorators, &[decorator3]);
let op0_decorator_refs: Vec<_> = forest.decorators_for_op(node_id, 0).collect();
assert_eq!(op0_decorator_refs, &[&Decorator::Trace(1)]);
let op1_decorator_refs: Vec<_> = forest.decorators_for_op(node_id, 1).collect();
assert_eq!(op1_decorator_refs, &[&Decorator::Trace(2)]);
let decorator_links = forest.decorator_links_for_node(node_id).unwrap();
let collected_links: Vec<_> = decorator_links.into_iter().collect();
assert_eq!(collected_links, vec![(0, decorator1), (1, decorator2), (3, decorator3)]);
let decorator_ids: Vec<_> =
forest.decorator_links_for_node(node_id).unwrap().into_iter().collect();
assert_eq!(decorator_ids, vec![(0, decorator1), (1, decorator2), (3, decorator3)]);
let forest_borrowed_iter: Vec<_> = block_node.indexed_decorator_iter(&forest).collect();
let expected_from_forest = vec![(0, decorator1), (1, decorator2), (3, decorator3)];
assert_eq!(forest_borrowed_iter, expected_from_forest);
let raw_forest_iter: Vec<_> = block_node.raw_decorator_iter(&forest).collect();
assert_eq!(raw_forest_iter.len(), 3);
let raw_op_decorators = block_node.raw_op_indexed_decorators(&forest);
assert_eq!(raw_op_decorators, vec![(0, decorator1), (1, decorator2), (3, decorator3)]);
let count_with_forest = block_node.num_operations_and_decorators(&forest);
let expected_count = 4 + 3; assert_eq!(count_with_forest, expected_count);
} else {
panic!("Expected a Block node");
}
assert!(
!forest.debug_info.op_decorator_storage().is_empty(),
"Decorator storage should be populated"
);
assert_eq!(
forest.debug_info.op_decorator_storage().num_nodes(),
1,
"Should have 1 node with decorators"
);
}
#[test]
fn test_mast_forest_compaction_comprehensive() {
let mut forest = MastForest::new();
let trace_deco = forest.add_decorator(Decorator::Trace(42)).unwrap();
let debug_deco = forest.add_decorator(Decorator::Debug(DebugOptions::StackTop(10))).unwrap();
let bb_no_deco = BasicBlockNodeBuilder::new(vec![Operation::Add, Operation::Mul], Vec::new())
.add_to_forest(&mut forest)
.unwrap();
let bb_with_op_deco =
BasicBlockNodeBuilder::new(vec![Operation::Add, Operation::Mul], vec![(0, trace_deco)])
.add_to_forest(&mut forest)
.unwrap();
forest.make_root(bb_no_deco);
forest.make_root(bb_with_op_deco);
let child1 = BasicBlockNodeBuilder::new(vec![Operation::Push(Felt::new(1))], Vec::new())
.add_to_forest(&mut forest)
.unwrap();
let child2 = BasicBlockNodeBuilder::new(vec![Operation::Push(Felt::new(2))], Vec::new())
.add_to_forest(&mut forest)
.unwrap();
let join_no_deco = crate::mast::JoinNodeBuilder::new([child1, child2])
.add_to_forest(&mut forest)
.unwrap();
let join_with_before_deco = crate::mast::JoinNodeBuilder::new([child1, child2])
.with_before_enter(vec![debug_deco])
.add_to_forest(&mut forest)
.unwrap();
forest.make_root(join_no_deco);
forest.make_root(join_with_before_deco);
let split_child1 = BasicBlockNodeBuilder::new(vec![Operation::Eq], Vec::new())
.add_to_forest(&mut forest)
.unwrap();
let split_child2 =
BasicBlockNodeBuilder::new(vec![Operation::Assert(Felt::new(1))], Vec::new())
.add_to_forest(&mut forest)
.unwrap();
let split_no_deco = crate::mast::SplitNodeBuilder::new([split_child1, split_child2])
.add_to_forest(&mut forest)
.unwrap();
let split_with_after_deco = crate::mast::SplitNodeBuilder::new([split_child1, split_child2])
.with_after_exit(vec![trace_deco])
.add_to_forest(&mut forest)
.unwrap();
forest.make_root(split_no_deco);
forest.make_root(split_with_after_deco);
let loop_body = BasicBlockNodeBuilder::new(vec![Operation::Add, Operation::Add], Vec::new())
.add_to_forest(&mut forest)
.unwrap();
let loop_no_deco =
crate::mast::LoopNodeBuilder::new(loop_body).add_to_forest(&mut forest).unwrap();
let loop_with_before_deco = crate::mast::LoopNodeBuilder::new(loop_body)
.with_before_enter(vec![debug_deco])
.add_to_forest(&mut forest)
.unwrap();
forest.make_root(loop_no_deco);
forest.make_root(loop_with_before_deco);
let call_target = BasicBlockNodeBuilder::new(vec![Operation::Mul], Vec::new())
.add_to_forest(&mut forest)
.unwrap();
let call_no_deco = crate::mast::CallNodeBuilder::new(call_target)
.add_to_forest(&mut forest)
.unwrap();
let call_with_after_deco = crate::mast::CallNodeBuilder::new(call_target)
.with_after_exit(vec![trace_deco])
.add_to_forest(&mut forest)
.unwrap();
forest.make_root(call_no_deco);
forest.make_root(call_with_after_deco);
let dyn_no_deco = crate::mast::DynNodeBuilder::new_dyn().add_to_forest(&mut forest).unwrap();
let dyn_with_before_deco = crate::mast::DynNodeBuilder::new_dyn()
.with_before_enter(vec![debug_deco])
.add_to_forest(&mut forest)
.unwrap();
forest.make_root(dyn_no_deco);
forest.make_root(dyn_with_before_deco);
let external_digest = BasicBlockNodeBuilder::new(vec![Operation::Neg], Vec::new())
.build()
.unwrap()
.digest();
let external_no_deco = crate::mast::ExternalNodeBuilder::new(external_digest)
.add_to_forest(&mut forest)
.unwrap();
let external_with_after_deco = crate::mast::ExternalNodeBuilder::new(external_digest)
.with_after_exit(vec![trace_deco])
.add_to_forest(&mut forest)
.unwrap();
forest.make_root(external_no_deco);
forest.make_root(external_with_after_deco);
assert_eq!(forest.num_procedures(), 14);
assert!(forest.num_nodes() > 14); assert!(!forest.debug_info.is_empty());
forest.clear_debug_info();
let (forest, _root_map) = forest.compact();
assert_eq!(forest.num_nodes(), 13); assert!(forest.num_procedures() >= 7);
assert!(forest.debug_info.is_empty());
}
#[test]
fn test_mast_forest_get_assembly_op_basic_block() {
let mut forest = MastForest::new();
let assembly_op = AssemblyOp::new(None, "test_context".into(), 1, "add".into());
let asm_op_id = forest.debug_info.add_asm_op(assembly_op.clone()).unwrap();
let operations = vec![Operation::Push(Felt::new(1)), Operation::Add];
let node_id = BasicBlockNodeBuilder::new(operations, vec![])
.add_to_forest(&mut forest)
.unwrap();
forest.debug_info.register_asm_ops(node_id, 2, vec![(0, asm_op_id)]).unwrap();
let result = forest.get_assembly_op(node_id, None);
assert!(result.is_some());
assert_eq!(result.unwrap(), &assembly_op);
}
#[test]
fn test_mast_forest_get_assembly_op_with_target_index() {
let mut forest = MastForest::new();
let assembly_op = AssemblyOp::new(
None,
"test_context".into(),
3, "complex_op".into(),
);
let asm_op_id = forest.debug_info.add_asm_op(assembly_op.clone()).unwrap();
let operations = vec![
Operation::Push(Felt::new(1)),
Operation::Push(Felt::new(2)),
Operation::Mul,
Operation::Add,
Operation::Drop,
];
let node_id = BasicBlockNodeBuilder::new(operations, vec![])
.add_to_forest(&mut forest)
.unwrap();
forest
.debug_info
.register_asm_ops(node_id, 5, vec![(2, asm_op_id), (3, asm_op_id), (4, asm_op_id)])
.unwrap();
let result2 = forest.get_assembly_op(node_id, Some(2));
assert!(result2.is_some());
assert_eq!(result2.unwrap(), &assembly_op);
let result3 = forest.get_assembly_op(node_id, Some(3));
assert!(result3.is_some());
assert_eq!(result3.unwrap(), &assembly_op);
let result4 = forest.get_assembly_op(node_id, Some(4));
assert!(result4.is_some());
assert_eq!(result4.unwrap(), &assembly_op);
let result5 = forest.get_assembly_op(node_id, Some(5));
assert!(result5.is_some());
assert_eq!(result5.unwrap(), &assembly_op);
}
#[test]
fn test_mast_forest_get_assembly_op_all_node_types() {
let mut forest = MastForest::new();
let assembly_op = AssemblyOp::new(None, "test_context".into(), 1, "test_op".into());
let asm_op_id = forest.debug_info.add_asm_op(assembly_op.clone()).unwrap();
let operations = vec![Operation::Push(Felt::new(1)), Operation::Add];
let bb_node_id = BasicBlockNodeBuilder::new(operations.clone(), vec![])
.add_to_forest(&mut forest)
.unwrap();
forest.debug_info.register_asm_ops(bb_node_id, 2, vec![(0, asm_op_id)]).unwrap();
let bb_result = forest.get_assembly_op(bb_node_id, Some(0));
assert!(bb_result.is_some());
assert_eq!(bb_result.unwrap(), &assembly_op);
let call_node = CallNodeBuilder::new(bb_node_id).add_to_forest(&mut forest).unwrap();
let join_child2 = BasicBlockNodeBuilder::new(vec![Operation::Push(Felt::new(2))], vec![])
.add_to_forest(&mut forest)
.unwrap();
let _join_node = JoinNodeBuilder::new([bb_node_id, join_child2])
.add_to_forest(&mut forest)
.unwrap();
let call_result = forest.get_assembly_op(call_node, None);
assert!(call_result.is_none());
let bb_result_again = forest.get_assembly_op(bb_node_id, None);
assert!(bb_result_again.is_some());
assert_eq!(bb_result_again.unwrap(), &assembly_op);
}
#[test]
fn test_mast_forest_get_assembly_comprehensive_edge_cases() {
let mut forest = MastForest::new();
let operations = vec![Operation::Push(Felt::new(1)), Operation::Add];
let node_id = BasicBlockNodeBuilder::new(operations.clone(), vec![])
.add_to_forest(&mut forest)
.unwrap();
let result = forest.get_assembly_op(node_id, None);
assert!(result.is_none(), "Node with no AssemblyOps registered should return None");
let asm_op1 = AssemblyOp::new(None, "context1".into(), 1, "op1".into());
let asm_op_id1 = forest.debug_info.add_asm_op(asm_op1.clone()).unwrap();
let node_id2 = BasicBlockNodeBuilder::new(operations.clone(), vec![])
.add_to_forest(&mut forest)
.unwrap();
forest.debug_info.register_asm_ops(node_id2, 2, vec![(0, asm_op_id1)]).unwrap();
let result2 = forest.get_assembly_op(node_id2, None);
assert!(result2.is_some());
assert_eq!(result2.unwrap(), &asm_op1);
let asm_op2 = AssemblyOp::new(None, "context2".into(), 1, "op2".into());
let asm_op3 = AssemblyOp::new(None, "context3".into(), 1, "op3".into());
let asm_op_id2 = forest.debug_info.add_asm_op(asm_op2.clone()).unwrap();
let asm_op_id3 = forest.debug_info.add_asm_op(asm_op3.clone()).unwrap();
let ops_multi = vec![Operation::Push(Felt::new(1)), Operation::Add, Operation::Mul];
let node_id3 = BasicBlockNodeBuilder::new(ops_multi, vec![])
.add_to_forest(&mut forest)
.unwrap();
forest
.debug_info
.register_asm_ops(node_id3, 3, vec![(0, asm_op_id2), (2, asm_op_id3)])
.unwrap();
let result3_none = forest.get_assembly_op(node_id3, None);
assert!(result3_none.is_some());
assert_eq!(result3_none.unwrap(), &asm_op2, "Should return first AssemblyOp");
let result3_idx0 = forest.get_assembly_op(node_id3, Some(0));
assert!(result3_idx0.is_some());
assert_eq!(result3_idx0.unwrap(), &asm_op2);
let result3_idx2 = forest.get_assembly_op(node_id3, Some(2));
assert!(result3_idx2.is_some());
assert_eq!(result3_idx2.unwrap(), &asm_op3);
let result3_idx1 = forest.get_assembly_op(node_id3, Some(1));
assert!(result3_idx1.is_some());
assert_eq!(
result3_idx1.unwrap(),
&asm_op2,
"Backward search should find AssemblyOp at index 0"
);
let asm_op_multi = AssemblyOp::new(None, "multi_cycle".into(), 3, "multi_op".into());
let asm_op_id_multi = forest.debug_info.add_asm_op(asm_op_multi.clone()).unwrap();
let ops4 = vec![Operation::Push(Felt::new(1)), Operation::Add, Operation::Mul, Operation::Neg];
let node_id4 = BasicBlockNodeBuilder::new(ops4, vec![]).add_to_forest(&mut forest).unwrap();
forest
.debug_info
.register_asm_ops(
node_id4,
4,
vec![(1, asm_op_id_multi), (2, asm_op_id_multi), (3, asm_op_id_multi)],
)
.unwrap();
for idx in 1..=3 {
let result = forest.get_assembly_op(node_id4, Some(idx));
assert!(result.is_some(), "Should find AssemblyOp at index {}", idx);
assert_eq!(result.unwrap(), &asm_op_multi);
}
let result4_idx0 = forest.get_assembly_op(node_id4, Some(0));
assert!(result4_idx0.is_none());
}
#[test]
fn test_clear_debug_info_independent() {
let mut forest = MastForest::new();
let decorator = forest.add_decorator(Decorator::Trace(42)).unwrap();
let node_with_deco = BasicBlockNodeBuilder::new(vec![Operation::Add], vec![(0, decorator)])
.add_to_forest(&mut forest)
.unwrap();
forest.make_root(node_with_deco);
assert!(!forest.debug_info.is_empty());
assert_eq!(forest.decorators().len(), 1);
forest.clear_debug_info();
assert!(forest.debug_info.is_empty());
assert_eq!(forest.num_nodes(), 1);
assert_eq!(forest.num_procedures(), 1);
}
#[test]
fn test_compaction_independent() {
let mut forest = MastForest::new();
let node1 = BasicBlockNodeBuilder::new(vec![Operation::Add, Operation::Mul], Vec::new())
.add_to_forest(&mut forest)
.unwrap();
let node2 = BasicBlockNodeBuilder::new(vec![Operation::Add, Operation::Mul], Vec::new())
.add_to_forest(&mut forest)
.unwrap();
forest.make_root(node1);
forest.make_root(node2);
assert_eq!(forest.num_nodes(), 2);
assert_eq!(forest.num_procedures(), 2);
assert!(forest.debug_info.is_empty());
let (forest, _root_map) = forest.compact();
assert_eq!(forest.num_nodes(), 1);
assert_eq!(forest.num_procedures(), 1);
assert!(forest.debug_info.is_empty());
}
#[test]
fn test_commitment_caching() {
let mut forest = MastForest::new();
let node1 = BasicBlockNodeBuilder::new(vec![Operation::Add], vec![])
.add_to_forest(&mut forest)
.unwrap();
let node2 = BasicBlockNodeBuilder::new(vec![Operation::Mul], vec![])
.add_to_forest(&mut forest)
.unwrap();
forest.make_root(node1);
let commitment1 = forest.commitment();
assert_ne!(commitment1, Word::from([Felt::ZERO; 4]));
let commitment2 = forest.commitment();
assert_eq!(commitment1, commitment2);
forest.make_root(node2);
let commitment3 = forest.commitment();
assert_ne!(commitment1, commitment3);
let commitment4 = forest.commitment();
assert_eq!(commitment3, commitment4);
forest.advice_map_mut().insert(Word::from([Felt::ZERO; 4]), vec![]);
let commitment5 = forest.commitment();
assert_eq!(
commitment3, commitment5,
"advice_map mutation should not invalidate commitment cache"
);
forest.clear_debug_info();
let commitment6 = forest.commitment();
assert_eq!(
commitment3, commitment6,
"clear_debug_info should not invalidate commitment cache"
);
let nodes_to_remove = alloc::collections::BTreeSet::new();
forest.remove_nodes(&nodes_to_remove); let commitment7 = forest.commitment();
assert_eq!(commitment3, commitment7);
}
fn digest_from_seed(seed: [u8; 32]) -> Word {
let mut digest = [Felt::ZERO; WORD_SIZE];
digest.iter_mut().enumerate().for_each(|(i, d)| {
*d = <[u8; 8]>::try_from(&seed[i * 8..(i + 1) * 8])
.map(u64::from_le_bytes)
.map(Felt::new)
.unwrap()
});
digest.into()
}
#[test]
fn test_asm_op_id_basic() {
use crate::{mast::AsmOpId, utils::Idx};
let id = AsmOpId::new(42);
assert_eq!(id.to_usize(), 42);
assert_eq!(u32::from(id), 42);
}
#[test]
fn test_debug_info_asm_op_storage() {
use alloc::string::ToString;
use crate::mast::{DebugInfo, MastNodeId};
let mut debug_info = DebugInfo::new();
let asm_op = AssemblyOp::new(None, "test".to_string(), 5, "add".to_string());
let asm_op_id = debug_info.add_asm_op(asm_op).unwrap();
let node_id = MastNodeId::new_unchecked(0);
debug_info.register_asm_ops(node_id, 5, vec![(2, asm_op_id)]).unwrap();
let retrieved = debug_info.asm_op_for_operation(node_id, 2);
assert!(retrieved.is_some());
assert_eq!(retrieved.unwrap().op(), "add");
assert!(debug_info.asm_op_for_operation(node_id, 0).is_none());
let first = debug_info.first_asm_op_for_node(node_id);
assert!(first.is_some());
assert_eq!(first.unwrap().op(), "add");
assert_eq!(debug_info.num_asm_ops(), 1);
assert_eq!(debug_info.asm_ops().len(), 1);
assert!(debug_info.asm_op(asm_op_id).is_some());
assert_eq!(debug_info.asm_op(asm_op_id).unwrap().op(), "add");
let non_existent_node = MastNodeId::new_unchecked(999);
assert!(debug_info.asm_op_for_operation(non_existent_node, 0).is_none());
assert!(debug_info.first_asm_op_for_node(non_existent_node).is_none());
}