use alloc::sync::Arc;
use super::*;
use crate::{
Felt, ONE, Word,
mast::{
BasicBlockNodeBuilder, CallNodeBuilder, DecoratorId, DynNodeBuilder, ExternalNodeBuilder,
LoopNodeBuilder,
node::{MastForestContributor, MastNodeExt},
},
operations::{AssemblyOp, DebugOptions, DebugVarInfo, DebugVarLocation, Decorator, Operation},
utils::Idx,
};
fn block_foo() -> BasicBlockNodeBuilder {
BasicBlockNodeBuilder::new(vec![Operation::Mul, Operation::Add], Vec::new())
}
fn block_foo_with_decorators(
before_enter: &[DecoratorId],
after_exit: &[DecoratorId],
) -> BasicBlockNodeBuilder {
BasicBlockNodeBuilder::new(vec![Operation::Mul, Operation::Add], Vec::new())
.with_before_enter(before_enter.to_vec())
.with_after_exit(after_exit.to_vec())
}
fn block_bar() -> BasicBlockNodeBuilder {
BasicBlockNodeBuilder::new(vec![Operation::And, Operation::Eq], Vec::new())
}
fn block_qux() -> BasicBlockNodeBuilder {
BasicBlockNodeBuilder::new(
vec![Operation::Swap, Operation::Push(ONE), Operation::Eq],
Vec::new(),
)
}
fn loop_with_decorators(
body_id: MastNodeId,
before_enter: &[DecoratorId],
after_exit: &[DecoratorId],
) -> LoopNodeBuilder {
LoopNodeBuilder::new(body_id)
.with_before_enter(before_enter.to_vec())
.with_after_exit(after_exit.to_vec())
}
fn external_with_decorators(
procedure_hash: Word,
before_enter: &[DecoratorId],
after_exit: &[DecoratorId],
) -> ExternalNodeBuilder {
ExternalNodeBuilder::new(procedure_hash)
.with_before_enter(before_enter.to_vec())
.with_after_exit(after_exit.to_vec())
}
fn assert_contains_node_once(forest: &MastForest, digest: Word) -> Result<(), &str> {
if forest.nodes.iter().filter(|node| node.digest() == digest).count() != 1 {
return Err("node digest contained more than once in the forest");
}
Ok(())
}
fn assert_root_mapping(
root_map: &MastForestRootMap,
original_roots: Vec<&[MastNodeId]>,
merged_roots: &[MastNodeId],
) -> Result<(), &'static str> {
for (forest_idx, original_root) in original_roots.into_iter().enumerate() {
for root in original_root {
let mapped_root = root_map.map_root(forest_idx, root).unwrap();
if !merged_roots.contains(&mapped_root) {
return Err("merged root does not contain mapped root");
}
}
}
Ok(())
}
fn assert_child_id_lt_parent_id(forest: &MastForest) -> Result<(), &str> {
for (mast_node_id, node) in forest.nodes().iter().enumerate() {
node.for_each_child(|child_id| {
if child_id.to_usize() >= mast_node_id {
panic!("child id {} is not < parent id {}", child_id.to_usize(), mast_node_id);
}
});
}
Ok(())
}
#[test]
fn mast_forest_merge_preserves_dyn_callness_and_digest() {
let mut forest = MastForest::new();
let dynexec_id = DynNodeBuilder::new_dyn().add_to_forest(&mut forest).unwrap();
let dyncall_id = DynNodeBuilder::new_dyncall().add_to_forest(&mut forest).unwrap();
forest.make_root(dynexec_id);
forest.make_root(dyncall_id);
let dynexec_digest = forest[dynexec_id].digest();
let dyncall_digest = forest[dyncall_id].digest();
let (merged, root_maps) = MastForest::merge([&forest]).unwrap();
let merged_dynexec_id = root_maps.map_root(0, &dynexec_id).unwrap();
let merged_dyncall_id = root_maps.map_root(0, &dyncall_id).unwrap();
assert_ne!(
merged_dynexec_id, merged_dyncall_id,
"dynexec and dyncall nodes should not be deduplicated"
);
let merged_dynexec = merged[merged_dynexec_id].unwrap_dyn();
let merged_dyncall = merged[merged_dyncall_id].unwrap_dyn();
assert!(!merged_dynexec.is_dyncall(), "dynexec node should remain dynexec after merge");
assert!(merged_dyncall.is_dyncall(), "dyncall node should remain dyncall after merge");
assert_eq!(merged_dynexec.digest(), dynexec_digest, "dynexec digest should be preserved");
assert_eq!(merged_dyncall.digest(), dyncall_digest, "dyncall digest should be preserved");
}
#[test]
fn mast_forest_merge_remap() {
let mut forest_a = MastForest::new();
let id_foo = block_foo().add_to_forest(&mut forest_a).unwrap();
let id_call_a = CallNodeBuilder::new(id_foo).add_to_forest(&mut forest_a).unwrap();
forest_a.make_root(id_call_a);
let mut forest_b = MastForest::new();
let id_bar = block_bar().add_to_forest(&mut forest_b).unwrap();
let id_call_b = CallNodeBuilder::new(id_bar).add_to_forest(&mut forest_b).unwrap();
forest_b.make_root(id_call_b);
let (mut merged, root_maps) = MastForest::merge([&forest_a, &forest_b]).unwrap();
assert_eq!(merged.nodes().len(), 4);
let expected_foo_id = block_foo().add_to_forest(&mut merged).unwrap();
let expected_foo_block = merged.get_node_by_id(expected_foo_id).unwrap().unwrap_basic_block();
assert_matches!(&merged.nodes()[0], MastNode::Block(merged_block)
if merged_block.semantic_eq(expected_foo_block, &merged));
assert_matches!(&merged.nodes()[1], MastNode::Call(call_node) if 0u32 == u32::from(call_node.callee()));
let expected_bar_id = block_bar().add_to_forest(&mut merged).unwrap();
let expected_bar_block = merged.get_node_by_id(expected_bar_id).unwrap().unwrap_basic_block();
assert_matches!(&merged.nodes()[2], MastNode::Block(merged_block)
if merged_block.semantic_eq(expected_bar_block, &merged));
assert_matches!(&merged.nodes()[3], MastNode::Call(call_node) if 2u32 == u32::from(call_node.callee()));
assert_eq!(u32::from(root_maps.map_root(0, &id_call_a).unwrap()), 1u32);
assert_eq!(u32::from(root_maps.map_root(1, &id_call_b).unwrap()), 3u32);
assert_child_id_lt_parent_id(&merged).unwrap();
}
#[test]
fn mast_forest_merge_duplicate() {
let mut forest_a = MastForest::new();
forest_a.add_decorator(Decorator::Debug(DebugOptions::MemAll)).unwrap();
forest_a.add_decorator(Decorator::Trace(25)).unwrap();
let bar_block_id = block_bar().add_to_forest(&mut forest_a).unwrap();
let bar_block = forest_a.get_node_by_id(bar_block_id).unwrap().unwrap_basic_block();
let id_external = ExternalNodeBuilder::new(bar_block.digest())
.add_to_forest(&mut forest_a)
.unwrap();
let id_foo = block_foo().add_to_forest(&mut forest_a).unwrap();
let id_call = CallNodeBuilder::new(id_foo).add_to_forest(&mut forest_a).unwrap();
let id_loop = LoopNodeBuilder::new(id_external).add_to_forest(&mut forest_a).unwrap();
forest_a.make_root(id_call);
forest_a.make_root(id_loop);
let (merged, root_maps) = MastForest::merge([&forest_a, &forest_a]).unwrap();
for merged_root in merged.procedure_digests() {
forest_a.procedure_digests().find(|root| root == &merged_root).unwrap();
}
for original_root in forest_a.procedure_roots() {
assert_eq!(&root_maps.map_root(0, original_root), &root_maps.map_root(1, original_root));
}
for merged_node in merged.nodes().iter().map(MastNode::digest) {
forest_a.nodes.iter().find(|node| node.digest() == merged_node).unwrap();
}
for merged_decorator in merged.decorators().iter() {
assert!(forest_a.decorators().contains(merged_decorator));
}
assert_child_id_lt_parent_id(&merged).unwrap();
}
#[test]
fn mast_forest_merge_replace_external() {
let mut forest_a = MastForest::new();
let foo_block_a = block_foo().build().unwrap();
let id_foo_a = ExternalNodeBuilder::new(foo_block_a.digest())
.add_to_forest(&mut forest_a)
.unwrap();
let id_call_a = CallNodeBuilder::new(id_foo_a).add_to_forest(&mut forest_a).unwrap();
forest_a.make_root(id_call_a);
let mut forest_b = MastForest::new();
let id_foo_b = block_foo().add_to_forest(&mut forest_b).unwrap();
let id_call_b = CallNodeBuilder::new(id_foo_b).add_to_forest(&mut forest_b).unwrap();
forest_b.make_root(id_call_b);
let (merged_ab, root_maps_ab) = MastForest::merge([&forest_a, &forest_b]).unwrap();
let (merged_ba, root_maps_ba) = MastForest::merge([&forest_b, &forest_a]).unwrap();
for (mut merged, root_map) in [(merged_ab, root_maps_ab), (merged_ba, root_maps_ba)] {
assert_eq!(merged.nodes().len(), 2);
let expected_foo_id = block_foo().add_to_forest(&mut merged).unwrap();
let expected_foo_block =
merged.get_node_by_id(expected_foo_id).unwrap().unwrap_basic_block();
assert_matches!(&merged.nodes()[0], MastNode::Block(merged_block)
if merged_block.semantic_eq(expected_foo_block, &merged));
assert_matches!(&merged.nodes()[1], MastNode::Call(call_node) if 0u32 == u32::from(call_node.callee()));
assert_eq!(merged.roots.len(), 1);
assert_eq!(root_map.map_root(0, &id_call_a).unwrap().to_usize(), 1);
assert_eq!(root_map.map_root(1, &id_call_b).unwrap().to_usize(), 1);
assert_child_id_lt_parent_id(&merged).unwrap();
}
}
#[test]
fn mast_forest_merge_roots() {
let mut forest_a = MastForest::new();
let id_foo_a = block_foo().add_to_forest(&mut forest_a).unwrap();
let call_a = CallNodeBuilder::new(id_foo_a).add_to_forest(&mut forest_a).unwrap();
forest_a.make_root(call_a);
let mut forest_b = MastForest::new();
let id_foo_b = block_foo().add_to_forest(&mut forest_b).unwrap();
let id_bar_b = block_bar().add_to_forest(&mut forest_b).unwrap();
let call_b = CallNodeBuilder::new(id_foo_b).add_to_forest(&mut forest_b).unwrap();
forest_b.make_root(id_bar_b);
forest_b.make_root(call_b);
let root_digest_call_a = forest_a.get_node_by_id(call_a).unwrap().digest();
let root_digest_bar_b = forest_b.get_node_by_id(id_bar_b).unwrap().digest();
let root_digest_call_b = forest_b.get_node_by_id(call_b).unwrap().digest();
let (merged, root_maps) = MastForest::merge([&forest_a, &forest_b]).unwrap();
assert_eq!(merged.procedure_roots().len(), 2);
let root_digests = merged.procedure_digests().collect::<Vec<_>>();
assert!(root_digests.contains(&root_digest_call_a));
assert!(root_digests.contains(&root_digest_bar_b));
assert!(root_digests.contains(&root_digest_call_b));
assert_root_mapping(&root_maps, vec![&forest_a.roots, &forest_b.roots], &merged.roots).unwrap();
assert_child_id_lt_parent_id(&merged).unwrap();
}
#[test]
fn mast_forest_merge_multiple() {
let mut forest_a = MastForest::new();
let id_foo_a = block_foo().add_to_forest(&mut forest_a).unwrap();
let call_a = CallNodeBuilder::new(id_foo_a).add_to_forest(&mut forest_a).unwrap();
forest_a.make_root(call_a);
let mut forest_b = MastForest::new();
let id_foo_b = block_foo().add_to_forest(&mut forest_b).unwrap();
let id_bar_b = block_bar().add_to_forest(&mut forest_b).unwrap();
let call_b = CallNodeBuilder::new(id_foo_b).add_to_forest(&mut forest_b).unwrap();
forest_b.make_root(id_bar_b);
forest_b.make_root(call_b);
let mut forest_c = MastForest::new();
let id_foo_c = block_foo().add_to_forest(&mut forest_c).unwrap();
let id_qux_c = block_qux().add_to_forest(&mut forest_c).unwrap();
let call_c = CallNodeBuilder::new(id_foo_c).add_to_forest(&mut forest_c).unwrap();
forest_c.make_root(id_qux_c);
forest_c.make_root(call_c);
let (merged, root_maps) = MastForest::merge([&forest_a, &forest_b, &forest_c]).unwrap();
let block_foo_digest = forest_b.get_node_by_id(id_foo_b).unwrap().digest();
let block_bar_digest = forest_b.get_node_by_id(id_bar_b).unwrap().digest();
let call_foo_digest = forest_b.get_node_by_id(call_b).unwrap().digest();
let block_qux_digest = forest_c.get_node_by_id(id_qux_c).unwrap().digest();
assert_eq!(merged.procedure_roots().len(), 3);
let root_digests = merged.procedure_digests().collect::<Vec<_>>();
assert!(root_digests.contains(&call_foo_digest));
assert!(root_digests.contains(&block_bar_digest));
assert!(root_digests.contains(&block_qux_digest));
assert_contains_node_once(&merged, block_foo_digest).unwrap();
assert_contains_node_once(&merged, block_bar_digest).unwrap();
assert_contains_node_once(&merged, block_qux_digest).unwrap();
assert_contains_node_once(&merged, call_foo_digest).unwrap();
assert_root_mapping(
&root_maps,
vec![&forest_a.roots, &forest_b.roots, &forest_c.roots],
&merged.roots,
)
.unwrap();
assert_child_id_lt_parent_id(&merged).unwrap();
}
#[test]
fn mast_forest_merge_decorators() {
let mut forest_a = MastForest::new();
let trace0 = Decorator::Trace(0);
let trace1 = Decorator::Trace(1);
let trace2 = Decorator::Trace(2);
let trace3 = Decorator::Trace(3);
let deco0_a = forest_a.add_decorator(trace0.clone()).unwrap();
let deco1_a = forest_a.add_decorator(trace1.clone()).unwrap();
let deco2_a = forest_a.add_decorator(trace2.clone()).unwrap();
let foo_node_a = block_foo_with_decorators(&[deco1_a, deco2_a], &[]);
let id_foo_a = foo_node_a.add_to_forest(&mut forest_a).unwrap();
let loop_node_a = loop_with_decorators(id_foo_a, &[], &[deco0_a, deco2_a]);
let id_loop_a = loop_node_a.add_to_forest(&mut forest_a).unwrap();
forest_a.make_root(id_loop_a);
let mut forest_b = MastForest::new();
let deco1_b = forest_b.add_decorator(trace1.clone()).unwrap();
let deco2_b = forest_b.add_decorator(trace2.clone()).unwrap();
let deco3_b = forest_b.add_decorator(trace3.clone()).unwrap();
let foo_node_b = block_foo_with_decorators(&[deco1_b, deco2_b], &[]);
let id_foo_b = foo_node_b.add_to_forest(&mut forest_b).unwrap();
let loop_node_b = loop_with_decorators(id_foo_b, &[], &[deco1_b, deco3_b]);
let id_loop_b = loop_node_b.add_to_forest(&mut forest_b).unwrap();
forest_b.make_root(id_loop_b);
let (merged, root_maps) = MastForest::merge([&forest_a, &forest_b]).unwrap();
assert_eq!(merged.decorators().len(), 4);
assert!(merged.decorators().contains(&trace0));
assert!(merged.decorators().contains(&trace1));
assert!(merged.decorators().contains(&trace2));
assert!(merged.decorators().contains(&trace3));
let find_decorator_id = |deco: &Decorator| {
let idx = merged
.decorators()
.iter()
.enumerate()
.find_map(
|(deco_id, forest_deco)| if forest_deco == deco { Some(deco_id) } else { None },
)
.unwrap();
DecoratorId::from_u32_safe(idx as u32, &merged).unwrap()
};
let merged_deco0 = find_decorator_id(&trace0);
let merged_deco1 = find_decorator_id(&trace1);
let merged_deco2 = find_decorator_id(&trace2);
let merged_deco3 = find_decorator_id(&trace3);
assert_eq!(merged.nodes.len(), 3);
let merged_foo_block = merged.nodes.iter().find(|node| node.is_basic_block()).unwrap();
let MastNode::Block(merged_foo_block) = merged_foo_block else {
panic!("expected basic block node");
};
let merged_foo_block_id = merged_foo_block.linked_id().unwrap();
let all_decorators = merged.all_decorators(merged_foo_block_id);
assert_eq!(all_decorators, vec![(0, merged_deco1), (0, merged_deco2)]);
assert_eq!(
merged
.nodes
.iter()
.filter(|node| {
if let MastNode::Loop(loop_node) = node {
loop_node.after_exit(&merged) == [merged_deco0, merged_deco2]
} else {
false
}
})
.count(),
1
);
assert_eq!(
merged
.nodes
.iter()
.filter(|node| {
if let MastNode::Loop(loop_node) = node {
loop_node.after_exit(&merged) == [merged_deco1, merged_deco3]
} else {
false
}
})
.count(),
1
);
assert_root_mapping(&root_maps, vec![&forest_a.roots, &forest_b.roots], &merged.roots).unwrap();
assert_child_id_lt_parent_id(&merged).unwrap();
}
#[test]
fn mast_forest_merge_external_node_reference_with_decorator() {
let mut forest_a = MastForest::new();
let trace = Decorator::Trace(1);
let deco = forest_a.add_decorator(trace.clone()).unwrap();
let foo_node_a = block_foo_with_decorators(&[deco], &[]);
let foo_node_digest = block_foo_with_decorators(&[deco], &[]).build().unwrap().digest();
let id_foo_a = foo_node_a.add_to_forest(&mut forest_a).unwrap();
forest_a.make_root(id_foo_a);
let mut forest_b = MastForest::new();
let id_external_b =
ExternalNodeBuilder::new(foo_node_digest).add_to_forest(&mut forest_b).unwrap();
forest_b.make_root(id_external_b);
for (idx, (merged, root_maps)) in [
MastForest::merge([&forest_a, &forest_b]).unwrap(),
MastForest::merge([&forest_b, &forest_a]).unwrap(),
]
.into_iter()
.enumerate()
{
let id_foo_a_digest = forest_a[id_foo_a].digest();
let digests: Vec<_> = merged.nodes().iter().map(|node| node.digest()).collect();
assert_eq!(merged.nodes.len(), 1);
assert!(digests.contains(&id_foo_a_digest));
if idx == 0 {
assert_root_mapping(&root_maps, vec![&forest_a.roots, &forest_b.roots], &merged.roots)
.unwrap();
} else {
assert_root_mapping(&root_maps, vec![&forest_b.roots, &forest_a.roots], &merged.roots)
.unwrap();
}
assert_child_id_lt_parent_id(&merged).unwrap();
}
}
#[test]
fn mast_forest_merge_external_node_with_decorator() {
let mut forest_a = MastForest::new();
let trace1 = Decorator::Trace(1);
let trace2 = Decorator::Trace(2);
let deco1 = forest_a.add_decorator(trace1.clone()).unwrap();
let deco2 = forest_a.add_decorator(trace2.clone()).unwrap();
let external_node_a =
external_with_decorators(block_foo().build().unwrap().digest(), &[deco1], &[deco2]);
let id_external_a = external_node_a.add_to_forest(&mut forest_a).unwrap();
forest_a.make_root(id_external_a);
let mut forest_b = MastForest::new();
let id_foo_b = block_foo().add_to_forest(&mut forest_b).unwrap();
forest_b.make_root(id_foo_b);
for (idx, (merged, root_maps)) in [
MastForest::merge([&forest_a, &forest_b]).unwrap(),
MastForest::merge([&forest_b, &forest_a]).unwrap(),
]
.into_iter()
.enumerate()
{
assert_eq!(merged.nodes.len(), 1);
let id_foo_b_digest = forest_b[id_foo_b].digest();
let digests: Vec<_> = merged.nodes().iter().map(|node| node.digest()).collect();
assert!(digests.contains(&id_foo_b_digest));
if idx == 0 {
assert_root_mapping(&root_maps, vec![&forest_a.roots, &forest_b.roots], &merged.roots)
.unwrap();
} else {
assert_root_mapping(&root_maps, vec![&forest_b.roots, &forest_a.roots], &merged.roots)
.unwrap();
}
assert_child_id_lt_parent_id(&merged).unwrap();
}
}
#[test]
fn mast_forest_merge_external_node_and_referenced_node_have_decorators() {
let mut forest_a = MastForest::new();
let trace1 = Decorator::Trace(1);
let trace2 = Decorator::Trace(2);
let deco1_a = forest_a.add_decorator(trace1.clone()).unwrap();
let external_node_a =
external_with_decorators(block_foo().build().unwrap().digest(), &[deco1_a], &[]);
let id_external_a = external_node_a.add_to_forest(&mut forest_a).unwrap();
forest_a.make_root(id_external_a);
let mut forest_b = MastForest::new();
let deco2_b = forest_b.add_decorator(trace2.clone()).unwrap();
let foo_node_b = block_foo_with_decorators(&[deco2_b], &[]);
let id_foo_b = foo_node_b.add_to_forest(&mut forest_b).unwrap();
forest_b.make_root(id_foo_b);
for (idx, (merged, root_maps)) in [
MastForest::merge([&forest_a, &forest_b]).unwrap(),
MastForest::merge([&forest_b, &forest_a]).unwrap(),
]
.into_iter()
.enumerate()
{
assert_eq!(merged.nodes.len(), 1);
let id_foo_b_digest = forest_b[id_foo_b].digest();
let digests: Vec<_> = merged.nodes().iter().map(|node| node.digest()).collect();
assert!(digests.contains(&id_foo_b_digest));
if idx == 0 {
assert_root_mapping(&root_maps, vec![&forest_a.roots, &forest_b.roots], &merged.roots)
.unwrap();
} else {
assert_root_mapping(&root_maps, vec![&forest_b.roots, &forest_a.roots], &merged.roots)
.unwrap();
}
assert_child_id_lt_parent_id(&merged).unwrap();
}
}
#[test]
fn mast_forest_merge_multiple_external_nodes_with_decorator() {
let mut forest_a = MastForest::new();
let trace1 = Decorator::Trace(1);
let trace2 = Decorator::Trace(2);
let deco1_a = forest_a.add_decorator(trace1.clone()).unwrap();
let deco2_a = forest_a.add_decorator(trace2.clone()).unwrap();
let external_node_a =
external_with_decorators(block_foo().build().unwrap().digest(), &[deco1_a], &[deco2_a]);
let id_external_a = external_node_a.add_to_forest(&mut forest_a).unwrap();
let external_node_b =
external_with_decorators(block_foo().build().unwrap().digest(), &[deco1_a], &[]);
let id_external_b = external_node_b.add_to_forest(&mut forest_a).unwrap();
forest_a.make_root(id_external_a);
forest_a.make_root(id_external_b);
let mut forest_b = MastForest::new();
let deco1_b = forest_b.add_decorator(trace1).unwrap();
let block_foo_b = block_foo_with_decorators(&[deco1_b], &[]);
let id_foo_b = block_foo_b.add_to_forest(&mut forest_b).unwrap();
forest_b.make_root(id_foo_b);
for (idx, (merged, root_maps)) in [
MastForest::merge([&forest_a, &forest_b]).unwrap(),
MastForest::merge([&forest_b, &forest_a]).unwrap(),
]
.into_iter()
.enumerate()
{
assert_eq!(merged.nodes.len(), 1);
let id_foo_b_digest = forest_b[id_foo_b].digest();
let digests: Vec<_> = merged.nodes().iter().map(|node| node.digest()).collect();
assert!(digests.contains(&id_foo_b_digest));
if idx == 0 {
assert_root_mapping(&root_maps, vec![&forest_a.roots, &forest_b.roots], &merged.roots)
.unwrap();
} else {
assert_root_mapping(&root_maps, vec![&forest_b.roots, &forest_a.roots], &merged.roots)
.unwrap();
}
assert_child_id_lt_parent_id(&merged).unwrap();
}
}
#[test]
fn mast_forest_merge_external_dependencies() {
let mut forest_a = MastForest::new();
let id_foo_a = ExternalNodeBuilder::new(block_qux().build().unwrap().digest())
.add_to_forest(&mut forest_a)
.unwrap();
let id_call_a = CallNodeBuilder::new(id_foo_a).add_to_forest(&mut forest_a).unwrap();
forest_a.make_root(id_call_a);
let mut forest_b = MastForest::new();
let id_ext_b = ExternalNodeBuilder::new(forest_a[id_call_a].digest())
.add_to_forest(&mut forest_b)
.unwrap();
let id_call_b = CallNodeBuilder::new(id_ext_b).add_to_forest(&mut forest_b).unwrap();
let id_qux_b = block_qux().add_to_forest(&mut forest_b).unwrap();
forest_b.make_root(id_call_b);
forest_b.make_root(id_qux_b);
for (merged, _) in [
MastForest::merge([&forest_a, &forest_b]).unwrap(),
MastForest::merge([&forest_b, &forest_a]).unwrap(),
]
.into_iter()
{
let digests = merged.nodes().iter().map(|node| node.digest()).collect::<Vec<_>>();
assert_eq!(merged.nodes().len(), 3);
assert!(digests.contains(&forest_b[id_ext_b].digest()));
assert!(digests.contains(&forest_b[id_call_b].digest()));
assert!(digests.contains(&forest_a[id_foo_a].digest()));
assert!(digests.contains(&forest_a[id_call_a].digest()));
assert!(digests.contains(&forest_b[id_qux_b].digest()));
assert_eq!(merged.nodes().iter().filter(|node| node.is_external()).count(), 0);
assert_child_id_lt_parent_id(&merged).unwrap();
}
}
#[test]
fn mast_forest_merge_invalid_decorator_index() {
let trace1 = Decorator::Trace(1);
let trace2 = Decorator::Trace(2);
let mut forest_a = MastForest::new();
let deco1_a = forest_a.add_decorator(trace1.clone()).unwrap();
let deco2_a = forest_a.add_decorator(trace2.clone()).unwrap();
let id_bar_a = block_bar().add_to_forest(&mut forest_a).unwrap();
forest_a.make_root(id_bar_a);
let mut forest_b = MastForest::new();
let block_b = block_foo_with_decorators(&[deco1_a, deco2_a], &[]);
let id_foo_b = block_b.add_to_forest(&mut forest_b).unwrap();
forest_b.make_root(id_foo_b);
let err = MastForest::merge([&forest_a, &forest_b]).unwrap_err();
assert_matches!(err, MastForestError::DecoratorIdOverflow(_, _));
}
#[test]
fn mast_forest_merge_advice_maps_merged() {
let mut forest_a = MastForest::new();
let id_foo = block_foo().add_to_forest(&mut forest_a).unwrap();
let id_call_a = CallNodeBuilder::new(id_foo).add_to_forest(&mut forest_a).unwrap();
forest_a.make_root(id_call_a);
let key_a = Word::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]);
let value_a = vec![ONE, ONE];
forest_a.advice_map_mut().insert(key_a, value_a.clone());
let mut forest_b = MastForest::new();
let id_bar = block_bar().add_to_forest(&mut forest_b).unwrap();
let id_call_b = CallNodeBuilder::new(id_bar).add_to_forest(&mut forest_b).unwrap();
forest_b.make_root(id_call_b);
let key_b = Word::new([Felt::new(1), Felt::new(3), Felt::new(2), Felt::new(1)]);
let value_b = vec![Felt::new(2), Felt::new(2)];
forest_b.advice_map_mut().insert(key_b, value_b.clone());
let (merged, _root_maps) = MastForest::merge([&forest_a, &forest_b]).unwrap();
let merged_advice_map = merged.advice_map();
assert_eq!(merged_advice_map.len(), 2);
assert_eq!(merged_advice_map.get(&key_a).unwrap().as_ref(), value_a);
assert_eq!(merged_advice_map.get(&key_b).unwrap().as_ref(), value_b);
}
#[test]
fn mast_forest_merge_advice_maps_collision() {
let mut forest_a = MastForest::new();
let id_foo = block_foo().add_to_forest(&mut forest_a).unwrap();
let id_call_a = CallNodeBuilder::new(id_foo).add_to_forest(&mut forest_a).unwrap();
forest_a.make_root(id_call_a);
let key_a = Word::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]);
let value_a = vec![ONE, ONE];
forest_a.advice_map_mut().insert(key_a, value_a.clone());
let mut forest_b = MastForest::new();
let id_bar = block_bar().add_to_forest(&mut forest_b).unwrap();
let id_call_b = CallNodeBuilder::new(id_bar).add_to_forest(&mut forest_b).unwrap();
forest_b.make_root(id_call_b);
let key_b = key_a;
let value_b = vec![Felt::new(2), Felt::new(2)];
forest_b.advice_map_mut().insert(key_b, value_b.clone());
let err = MastForest::merge([&forest_a, &forest_b]).unwrap_err();
assert_matches!(err, MastForestError::AdviceMapKeyCollisionOnMerge(_));
}
#[test]
fn mast_forest_merge_op_indexed_decorators_preservation() {
let mut forest_a = MastForest::new();
let before_enter_a = forest_a.add_decorator(Decorator::Trace(0)).unwrap();
let op0_a = forest_a.add_decorator(Decorator::Trace(1)).unwrap();
let op1_a = forest_a.add_decorator(Decorator::Trace(2)).unwrap();
let after_exit_a = forest_a.add_decorator(Decorator::Trace(3)).unwrap();
let shared_deco_a = forest_a.add_decorator(Decorator::Trace(99)).unwrap();
let ops_a = vec![Operation::Add, Operation::Mul, Operation::Or];
let block_id_a = BasicBlockNodeBuilder::new(
ops_a.clone(),
vec![(0, op0_a), (1, op1_a)], )
.with_before_enter(vec![before_enter_a, shared_deco_a]) .with_after_exit(vec![after_exit_a])
.add_to_forest(&mut forest_a)
.unwrap();
forest_a.make_root(block_id_a);
let mut forest_b = MastForest::new();
let before_enter_b = forest_b.add_decorator(Decorator::Trace(10)).unwrap();
let op0_b = forest_b.add_decorator(Decorator::Trace(11)).unwrap();
let op2_b = forest_b.add_decorator(Decorator::Trace(12)).unwrap();
let after_exit_b = forest_b.add_decorator(Decorator::Trace(13)).unwrap();
let shared_deco_b = forest_b.add_decorator(Decorator::Trace(99)).unwrap(); let unique_b = forest_b.add_decorator(Decorator::Trace(20)).unwrap();
let ops_b = vec![Operation::Add, Operation::Mul, Operation::Or];
let block_id_b = BasicBlockNodeBuilder::new(
ops_b.clone(),
vec![(0, op0_b), (2, op2_b)], )
.with_before_enter(vec![before_enter_b, shared_deco_b]) .with_after_exit(vec![after_exit_b, unique_b]) .add_to_forest(&mut forest_b)
.unwrap();
forest_b.make_root(block_id_b);
let (merged, root_maps) = MastForest::merge([&forest_a, &forest_b]).unwrap();
let find_decorator = |trace_value: u32| {
let idx = merged
.decorators()
.iter()
.enumerate()
.find_map(|(id, deco)| {
if let Decorator::Trace(v) = deco {
if *v == trace_value { Some(id) } else { None }
} else {
None
}
})
.expect("decorator not found");
DecoratorId::from_u32_safe(idx as u32, &merged).unwrap()
};
let merged_before_enter_a = find_decorator(0);
let merged_op0_a = find_decorator(1);
let merged_op1_a = find_decorator(2);
let merged_after_exit_a = find_decorator(3);
let merged_shared = find_decorator(99);
let merged_before_enter_b = find_decorator(10);
let merged_op0_b = find_decorator(11);
let merged_op2_b = find_decorator(12);
let merged_after_exit_b = find_decorator(13);
let merged_unique_b = find_decorator(20);
assert!(
merged
.decorators()
.iter()
.enumerate()
.find_map(|(i, deco)| {
if let Decorator::Trace(v) = deco
&& i > merged_shared.0 as usize
{
if *v == 99 { Some(i) } else { None }
} else {
None
}
})
.is_none(),
"Shared decorator should map to single ID"
);
let mut decorator_ref_counts = alloc::collections::BTreeMap::new();
for node in &merged.nodes {
for &deco_id in node.before_enter(&merged) {
*decorator_ref_counts.entry(deco_id).or_insert(0) += 1;
}
for &deco_id in node.after_exit(&merged) {
*decorator_ref_counts.entry(deco_id).or_insert(0) += 1;
}
if let MastNode::Block(block) = node {
for (_, deco_id) in block.indexed_decorator_iter(&merged) {
*decorator_ref_counts.entry(deco_id).or_insert(0) += 1;
}
}
}
for (i, decorator) in merged.decorators().iter().enumerate() {
let deco_id = DecoratorId::from_u32_safe(i as u32, &merged).unwrap();
let ref_count = decorator_ref_counts.get(&deco_id).unwrap_or(&0);
if ref_count == &0 {
panic!(
"Decorator at index {} (value: {:?}) is not referenced anywhere in the merged forest (orphan)",
i, decorator
);
}
}
let mapped_root_a = root_maps.map_root(0, &block_id_a).unwrap();
if let MastNode::Block(block_a) = &merged[mapped_root_a] {
assert_eq!(
block_a.before_enter(&merged),
&[merged_before_enter_a, merged_shared],
"Forest A's before_enter decorators should be preserved (including shared decorator)"
);
let indexed_decs: alloc::collections::BTreeMap<usize, DecoratorId> =
block_a.indexed_decorator_iter(&merged).collect();
assert_eq!(
indexed_decs.get(&0),
Some(&merged_op0_a),
"Forest A's op[0] decorator should be preserved at position 0"
);
assert_eq!(
indexed_decs.get(&1),
Some(&merged_op1_a),
"Forest A's op[1] decorator should be preserved at position 1"
);
assert_eq!(indexed_decs.get(&2), None, "Forest A's block doesn't have op[2] decorator");
assert_eq!(
block_a.after_exit(&merged),
&[merged_after_exit_a],
"Forest A's after_exit decorator should be preserved"
);
} else {
panic!("Expected a basic block node");
}
let mapped_root_b = root_maps.map_root(1, &block_id_b).unwrap();
if let MastNode::Block(block_b) = &merged[mapped_root_b] {
assert_eq!(
block_b.before_enter(&merged),
&[merged_before_enter_b, merged_shared],
"Forest B's before_enter decorators should be preserved (including shared decorator)"
);
let indexed_decs: alloc::collections::BTreeMap<usize, DecoratorId> =
block_b.indexed_decorator_iter(&merged).collect();
assert_eq!(
indexed_decs.get(&0),
Some(&merged_op0_b),
"Forest B's op[0] decorator should be preserved at position 0"
);
assert_eq!(indexed_decs.get(&1), None, "Forest B's block doesn't have op[1] decorator");
assert_eq!(
indexed_decs.get(&2),
Some(&merged_op2_b),
"Forest B's op[2] decorator should be preserved at position 2"
);
assert_eq!(
block_b.after_exit(&merged),
&[merged_after_exit_b, merged_unique_b],
"Forest B's after_exit decorators should be preserved (including unique decorator)"
);
} else {
panic!("Expected a basic block node");
}
let shared_ref_count = decorator_ref_counts.get(&merged_shared).unwrap_or(&0);
assert!(shared_ref_count > &0, "Shared decorator should be referenced at least once");
assert_eq!(
decorator_ref_counts.len(),
merged.decorators().len(),
"Every decorator in merged forest should be referenced at least once (no orphans)"
);
}
#[test]
fn merge_preserves_debug_metadata() {
let mut forest_a = MastForest::new();
let asm_op = AssemblyOp::new(None, "test".into(), 1, "add".into());
let asm_id_a = forest_a.debug_info_mut().add_asm_op(asm_op).unwrap();
let dvar_a = forest_a
.add_debug_var(DebugVarInfo::new("x", DebugVarLocation::Stack(0)))
.unwrap();
let block_a_id = block_foo().add_to_forest(&mut forest_a).unwrap();
let num_ops_a = forest_a[block_a_id].get_basic_block().unwrap().num_operations() as usize;
forest_a
.debug_info_mut()
.register_asm_ops(block_a_id, num_ops_a, vec![(0, asm_id_a)])
.unwrap();
forest_a
.debug_info_mut()
.register_op_indexed_debug_vars(block_a_id, vec![(0, dvar_a)])
.unwrap();
forest_a.make_root(block_a_id);
let digest_a = forest_a[block_a_id].digest();
forest_a.insert_procedure_name(digest_a, Arc::from("proc_a"));
let mut forest_b = MastForest::new();
let asm_op_b = AssemblyOp::new(None, "test".into(), 1, "and".into());
let asm_id_b = forest_b.debug_info_mut().add_asm_op(asm_op_b).unwrap();
let dvar_b = forest_b
.add_debug_var(DebugVarInfo::new("y", DebugVarLocation::Stack(1)))
.unwrap();
let block_b_id = block_bar().add_to_forest(&mut forest_b).unwrap();
let num_ops_b = forest_b[block_b_id].get_basic_block().unwrap().num_operations() as usize;
forest_b
.debug_info_mut()
.register_asm_ops(block_b_id, num_ops_b, vec![(0, asm_id_b)])
.unwrap();
forest_b
.debug_info_mut()
.register_op_indexed_debug_vars(block_b_id, vec![(0, dvar_b)])
.unwrap();
forest_b.make_root(block_b_id);
let digest_b = forest_b[block_b_id].digest();
forest_b.insert_procedure_name(digest_b, Arc::from("proc_b"));
let (merged, root_map) = MastForest::merge([&forest_a, &forest_b]).unwrap();
assert_eq!(merged.procedure_name(&digest_a), Some("proc_a"));
assert_eq!(merged.procedure_name(&digest_b), Some("proc_b"));
let new_a = root_map.map_root(0, &block_a_id).unwrap();
let new_b = root_map.map_root(1, &block_b_id).unwrap();
assert!(
merged.debug_info().first_asm_op_for_node(new_a).is_some(),
"merged node A must have asm op"
);
assert!(
merged.debug_info().first_asm_op_for_node(new_b).is_some(),
"merged node B must have asm op"
);
let vars_a = merged.debug_info().debug_vars_for_node(new_a);
let vars_b = merged.debug_info().debug_vars_for_node(new_b);
assert_eq!(vars_a.len(), 1, "merged node A must have debug var");
assert_eq!(vars_b.len(), 1, "merged node B must have debug var");
}
#[test]
fn compact_preserves_debug_metadata() {
let mut forest = MastForest::new();
let asm_op = AssemblyOp::new(None, "test".into(), 1, "add".into());
let asm_id = forest.debug_info_mut().add_asm_op(asm_op).unwrap();
let dvar = forest
.add_debug_var(DebugVarInfo::new("z", DebugVarLocation::Stack(2)))
.unwrap();
let block_id = block_foo().add_to_forest(&mut forest).unwrap();
let num_ops = forest[block_id].get_basic_block().unwrap().num_operations() as usize;
forest
.debug_info_mut()
.register_asm_ops(block_id, num_ops, vec![(0, asm_id)])
.unwrap();
forest
.debug_info_mut()
.register_op_indexed_debug_vars(block_id, vec![(0, dvar)])
.unwrap();
forest.make_root(block_id);
let digest = forest[block_id].digest();
forest.insert_procedure_name(digest, Arc::from("my_proc"));
let (compacted, _root_map) = forest.compact();
let compacted_id = compacted.find_procedure_root(digest).expect("root should survive compact");
assert_eq!(compacted.procedure_name(&digest), Some("my_proc"));
assert!(
compacted.debug_info().first_asm_op_for_node(compacted_id).is_some(),
"compacted node must keep asm op"
);
let vars = compacted.debug_info().debug_vars_for_node(compacted_id);
assert_eq!(vars.len(), 1, "compacted node must keep debug var");
}
#[test]
fn merge_keeps_blocks_with_different_debug_vars_distinct() {
let mut forest_a = MastForest::new();
let dvar_a = forest_a
.add_debug_var(DebugVarInfo::new("x", DebugVarLocation::Stack(0)))
.unwrap();
let block_a = block_foo().add_to_forest(&mut forest_a).unwrap();
forest_a
.debug_info_mut()
.register_op_indexed_debug_vars(block_a, vec![(0, dvar_a)])
.unwrap();
forest_a.make_root(block_a);
let mut forest_b = MastForest::new();
let dvar_b = forest_b
.add_debug_var(DebugVarInfo::new("y", DebugVarLocation::Stack(1)))
.unwrap();
let block_b = block_foo().add_to_forest(&mut forest_b).unwrap();
forest_b
.debug_info_mut()
.register_op_indexed_debug_vars(block_b, vec![(0, dvar_b)])
.unwrap();
forest_b.make_root(block_b);
let (merged, root_map) = MastForest::merge([&forest_a, &forest_b]).unwrap();
let new_a = root_map.map_root(0, &block_a).unwrap();
let new_b = root_map.map_root(1, &block_b).unwrap();
assert_ne!(new_a, new_b, "same-ops blocks with different debug vars must not be collapsed");
let vars_a = merged.debug_info().debug_vars_for_node(new_a);
let vars_b = merged.debug_info().debug_vars_for_node(new_b);
assert_eq!(vars_a.len(), 1);
assert_eq!(vars_b.len(), 1);
let info_a = merged.debug_info().debug_var(vars_a[0].1).unwrap();
let info_b = merged.debug_info().debug_var(vars_b[0].1).unwrap();
assert_eq!(info_a.name(), "x");
assert_eq!(info_b.name(), "y");
}
#[test]
fn merge_keeps_blocks_with_different_asm_ops_distinct() {
let mut forest_a = MastForest::new();
let asm_id_a = forest_a
.debug_info_mut()
.add_asm_op(AssemblyOp::new(None, "ctx_a".into(), 1, "mul add".into()))
.unwrap();
let dvar_a = forest_a
.add_debug_var(DebugVarInfo::new("x", DebugVarLocation::Stack(0)))
.unwrap();
let block_a = block_foo().add_to_forest(&mut forest_a).unwrap();
let num_ops_a = forest_a[block_a].get_basic_block().unwrap().num_operations() as usize;
forest_a
.debug_info_mut()
.register_asm_ops(block_a, num_ops_a, vec![(0, asm_id_a)])
.unwrap();
forest_a
.debug_info_mut()
.register_op_indexed_debug_vars(block_a, vec![(0, dvar_a)])
.unwrap();
forest_a.make_root(block_a);
let mut forest_b = MastForest::new();
let asm_id_b = forest_b
.debug_info_mut()
.add_asm_op(AssemblyOp::new(None, "ctx_b".into(), 1, "mul add".into()))
.unwrap();
let dvar_b = forest_b
.add_debug_var(DebugVarInfo::new("x", DebugVarLocation::Stack(0)))
.unwrap();
let block_b = block_foo().add_to_forest(&mut forest_b).unwrap();
let num_ops_b = forest_b[block_b].get_basic_block().unwrap().num_operations() as usize;
forest_b
.debug_info_mut()
.register_asm_ops(block_b, num_ops_b, vec![(0, asm_id_b)])
.unwrap();
forest_b
.debug_info_mut()
.register_op_indexed_debug_vars(block_b, vec![(0, dvar_b)])
.unwrap();
forest_b.make_root(block_b);
let (merged, root_map) = MastForest::merge([&forest_a, &forest_b]).unwrap();
let new_a = root_map.map_root(0, &block_a).unwrap();
let new_b = root_map.map_root(1, &block_b).unwrap();
assert_ne!(
new_a, new_b,
"same-structure blocks with different AssemblyOps must not collapse"
);
assert_eq!(
merged.debug_info().first_asm_op_for_node(new_a).unwrap().context_name(),
"ctx_a"
);
assert_eq!(
merged.debug_info().first_asm_op_for_node(new_b).unwrap().context_name(),
"ctx_b"
);
}
#[test]
fn non_basic_block_nodes_have_no_debug_vars() {
use crate::mast::{JoinNodeBuilder, SplitNodeBuilder};
let mut forest = MastForest::new();
let block_a = block_foo().add_to_forest(&mut forest).unwrap();
let block_b = block_bar().add_to_forest(&mut forest).unwrap();
let join = JoinNodeBuilder::new([block_a, block_b]);
let join_id = join.add_to_forest(&mut forest).unwrap();
let split = SplitNodeBuilder::new([block_a, block_b]);
let split_id = split.add_to_forest(&mut forest).unwrap();
let loop_node = LoopNodeBuilder::new(block_a);
let loop_id = loop_node.add_to_forest(&mut forest).unwrap();
forest.make_root(join_id);
forest.make_root(split_id);
forest.make_root(loop_id);
assert!(
forest.debug_info().debug_vars_for_node(join_id).is_empty(),
"join node must not carry debug vars"
);
assert!(
forest.debug_info().debug_vars_for_node(split_id).is_empty(),
"split node must not carry debug vars"
);
assert!(
forest.debug_info().debug_vars_for_node(loop_id).is_empty(),
"loop node must not carry debug vars"
);
}
#[test]
fn merge_deduplicates_blocks_with_same_debug_vars() {
let mut forest_a = MastForest::new();
let dvar_a = forest_a
.add_debug_var(DebugVarInfo::new("x", DebugVarLocation::Stack(0)))
.unwrap();
let block_a = block_foo().add_to_forest(&mut forest_a).unwrap();
forest_a
.debug_info_mut()
.register_op_indexed_debug_vars(block_a, vec![(0, dvar_a)])
.unwrap();
forest_a.make_root(block_a);
let mut forest_b = MastForest::new();
let dvar_b = forest_b
.add_debug_var(DebugVarInfo::new("x", DebugVarLocation::Stack(0)))
.unwrap();
let block_b = block_foo().add_to_forest(&mut forest_b).unwrap();
forest_b
.debug_info_mut()
.register_op_indexed_debug_vars(block_b, vec![(0, dvar_b)])
.unwrap();
forest_b.make_root(block_b);
let (merged, root_map) = MastForest::merge([&forest_a, &forest_b]).unwrap();
let new_a = root_map.map_root(0, &block_a).unwrap();
let new_b = root_map.map_root(1, &block_b).unwrap();
assert_eq!(new_a, new_b, "identical content must dedup to one node");
assert_eq!(merged.debug_info().debug_vars_for_node(new_a).len(), 1);
}
#[test]
fn compact_keeps_blocks_with_different_debug_vars_distinct() {
let mut forest = MastForest::new();
let var_x = forest
.add_debug_var(DebugVarInfo::new("x", DebugVarLocation::Stack(0)))
.unwrap();
let var_y = forest
.add_debug_var(DebugVarInfo::new("y", DebugVarLocation::Stack(1)))
.unwrap();
let block_a = block_foo().add_to_forest(&mut forest).unwrap();
forest
.debug_info_mut()
.register_op_indexed_debug_vars(block_a, vec![(0, var_x)])
.unwrap();
forest.make_root(block_a);
let block_b = block_foo().add_to_forest(&mut forest).unwrap();
forest
.debug_info_mut()
.register_op_indexed_debug_vars(block_b, vec![(0, var_y)])
.unwrap();
forest.make_root(block_b);
let (compacted, root_map) = forest.compact();
let new_a = root_map.map_root(0, &block_a).unwrap();
let new_b = root_map.map_root(0, &block_b).unwrap();
assert_ne!(new_a, new_b, "different debug vars must survive compact");
let info_a = compacted
.debug_info()
.debug_var(compacted.debug_info().debug_vars_for_node(new_a)[0].1)
.unwrap();
let info_b = compacted
.debug_info()
.debug_var(compacted.debug_info().debug_vars_for_node(new_b)[0].1)
.unwrap();
assert_eq!(info_a.name(), "x");
assert_eq!(info_b.name(), "y");
}
#[test]
fn compact_preserves_procedure_names() {
let mut forest = MastForest::new();
let block_id = block_foo().add_to_forest(&mut forest).unwrap();
forest.make_root(block_id);
let digest = forest[block_id].digest();
forest.insert_procedure_name(digest, Arc::from("my_fn"));
let (compacted, _) = forest.compact();
assert_eq!(compacted.procedure_name(&digest), Some("my_fn"));
}
#[test]
fn merge_three_forests_preserves_all_metadata() {
let blocks = [block_foo, block_bar, block_qux];
let var_names = ["a", "b", "c"];
let ctx_names = ["ctx_1", "ctx_2", "ctx_3"];
let mut forests = Vec::new();
for i in 0..3 {
let mut f = MastForest::new();
let dvar = f
.add_debug_var(DebugVarInfo::new(var_names[i], DebugVarLocation::Stack(i as u8)))
.unwrap();
let asm = AssemblyOp::new(None, ctx_names[i].into(), 1, "op".into());
let asm_id = f.debug_info_mut().add_asm_op(asm).unwrap();
let block = blocks[i]().add_to_forest(&mut f).unwrap();
let num_ops = f[block].get_basic_block().unwrap().num_operations() as usize;
f.debug_info_mut().register_asm_ops(block, num_ops, vec![(0, asm_id)]).unwrap();
f.debug_info_mut()
.register_op_indexed_debug_vars(block, vec![(0, dvar)])
.unwrap();
f.make_root(block);
forests.push(f);
}
let refs: Vec<&MastForest> = forests.iter().collect();
let (merged, _) = MastForest::merge(refs).unwrap();
assert_eq!(merged.procedure_roots().len(), 3);
for root_id in merged.procedure_roots() {
assert_eq!(merged.debug_info().debug_vars_for_node(*root_id).len(), 1);
assert!(merged.debug_info().first_asm_op_for_node(*root_id).is_some());
}
}
#[test]
fn merge_concrete_metadata_survives_external_placeholder() {
let mut forest_concrete = MastForest::new();
let asm = AssemblyOp::new(None, "real_ctx".into(), 1, "mul".into());
let asm_id = forest_concrete.debug_info_mut().add_asm_op(asm).unwrap();
let dvar = forest_concrete
.add_debug_var(DebugVarInfo::new("v", DebugVarLocation::Stack(0)))
.unwrap();
let block_id = block_foo().add_to_forest(&mut forest_concrete).unwrap();
let num_ops = forest_concrete[block_id].get_basic_block().unwrap().num_operations() as usize;
forest_concrete
.debug_info_mut()
.register_asm_ops(block_id, num_ops, vec![(0, asm_id)])
.unwrap();
forest_concrete
.debug_info_mut()
.register_op_indexed_debug_vars(block_id, vec![(0, dvar)])
.unwrap();
forest_concrete.make_root(block_id);
let digest = forest_concrete[block_id].digest();
let mut forest_external = MastForest::new();
let ext_id = ExternalNodeBuilder::new(digest).add_to_forest(&mut forest_external).unwrap();
forest_external.make_root(ext_id);
let (merged, root_map) = MastForest::merge([&forest_external, &forest_concrete]).unwrap();
let merged_id = root_map.map_root(1, &block_id).unwrap();
assert!(
merged.debug_info().first_asm_op_for_node(merged_id).is_some(),
"concrete asm-op must survive merge with external placeholder"
);
assert_eq!(
merged.debug_info().debug_vars_for_node(merged_id).len(),
1,
"concrete debug var must survive merge with external placeholder"
);
}
#[test]
fn merge_procedure_names_first_name_wins() {
let mut forest_a = MastForest::new();
let block_a = block_foo().add_to_forest(&mut forest_a).unwrap();
forest_a.make_root(block_a);
let digest = forest_a[block_a].digest();
forest_a.insert_procedure_name(digest, Arc::from("alias_a"));
let mut forest_b = MastForest::new();
let block_b = block_foo().add_to_forest(&mut forest_b).unwrap();
forest_b.make_root(block_b);
assert_eq!(forest_b[block_b].digest(), digest);
forest_b.insert_procedure_name(digest, Arc::from("alias_b"));
let (merged, root_map) = MastForest::merge([&forest_a, &forest_b]).unwrap();
let new_a = root_map.map_root(0, &block_a).unwrap();
let new_b = root_map.map_root(1, &block_b).unwrap();
assert_eq!(new_a, new_b);
assert_eq!(
merged.procedure_name(&digest),
Some("alias_a"),
"first forest's name must stick"
);
}