use alloc::{
collections::{BTreeMap, BTreeSet},
sync::Arc,
vec::Vec,
};
use core::ops::{Index, IndexMut};
use miden_core::{
Felt, Word,
advice::AdviceMap,
mast::{
AsmOpId, BasicBlockNode, BasicBlockNodeBuilder, DecoratorFingerprint, DecoratorId,
ExternalNodeBuilder, JoinNodeBuilder, MastForest, MastForestContributor, MastForestError,
MastNode, MastNodeBuilder, MastNodeExt, MastNodeFingerprint, MastNodeId, Remapping,
SubtreeIterator,
},
operations::{AssemblyOp, Decorator, DecoratorList, Operation},
serde::Serializable,
};
use super::{GlobalItemIndex, LinkerError, Procedure};
use crate::{
diagnostics::{IntoDiagnostic, Report, WrapErr},
report,
};
const PROCEDURE_INLINING_THRESHOLD: usize = 32;
#[derive(Clone, Debug, Default)]
pub struct MastForestBuilder {
pub(crate) mast_forest: MastForest,
procedures: BTreeMap<GlobalItemIndex, Procedure>,
proc_gid_by_mast_root: BTreeMap<Word, GlobalItemIndex>,
node_id_by_fingerprint: BTreeMap<MastNodeFingerprint, MastNodeId>,
hash_by_node_id: BTreeMap<MastNodeId, MastNodeFingerprint>,
decorator_id_by_fingerprint: BTreeMap<DecoratorFingerprint, DecoratorId>,
merged_basic_block_ids: BTreeSet<MastNodeId>,
statically_linked_mast: Arc<MastForest>,
statically_linked_mast_remapping: Remapping,
statically_linked_decorator_remapping: BTreeMap<DecoratorId, DecoratorId>,
pending_asm_op_mappings: Vec<(MastNodeId, Vec<(usize, AsmOpId)>)>,
pending_debug_var_mappings: Vec<(MastNodeId, Vec<(usize, miden_core::mast::DebugVarId)>)>,
emit_debug_info: bool,
}
impl MastForestBuilder {
pub fn new<'a>(
static_libraries: impl IntoIterator<Item = &'a MastForest>,
) -> Result<Self, Report> {
let forests = static_libraries.into_iter();
let (statically_linked_mast, _remapping) = MastForest::merge(forests).into_diagnostic()?;
let mut mast_forest = MastForest::default();
*mast_forest.advice_map_mut() = statically_linked_mast.advice_map().clone();
Ok(MastForestBuilder {
mast_forest,
statically_linked_mast: Arc::new(statically_linked_mast),
emit_debug_info: true,
..Self::default()
})
}
pub fn set_emit_debug_info(&mut self, emit: bool) {
self.emit_debug_info = emit;
}
fn maybe_augment(&self, fp: MastNodeFingerprint, data: &[u8]) -> MastNodeFingerprint {
if self.emit_debug_info {
fp.augment_with_data(data)
} else {
fp
}
}
pub fn mast_forest(&self) -> &MastForest {
&self.mast_forest
}
pub fn build(mut self) -> (MastForest, BTreeMap<MastNodeId, MastNodeId>) {
let deduped_mappings =
deduplicate_asm_op_mappings(core::mem::take(&mut self.pending_asm_op_mappings));
for (node_id, asm_op_mappings) in deduped_mappings {
let (num_operations, adjusted_mappings) =
compute_operations_and_adjust_mappings(&self.mast_forest[node_id], asm_op_mappings);
self.mast_forest
.debug_info_mut()
.register_asm_ops(node_id, num_operations, adjusted_mappings)
.expect("failed to register AssemblyOps - internal ordering error");
}
let debug_var_mappings =
deduplicate_debug_var_mappings(core::mem::take(&mut self.pending_debug_var_mappings));
for (node_id, debug_vars) in debug_var_mappings {
self.mast_forest
.debug_info_mut()
.register_op_indexed_debug_vars(node_id, debug_vars)
.expect("failed to register debug variables - internal ordering error");
}
let nodes_to_remove = get_nodes_to_remove(self.merged_basic_block_ids, &self.mast_forest);
let id_remappings = self.mast_forest.remove_nodes(&nodes_to_remove);
(self.mast_forest, id_remappings)
}
}
fn compute_operations_and_adjust_mappings(
node: &MastNode,
asm_op_mappings: Vec<(usize, AsmOpId)>,
) -> (usize, Vec<(usize, AsmOpId)>) {
match node {
MastNode::Block(block) => (
block.num_operations() as usize,
BasicBlockNode::adjust_asm_op_indices(asm_op_mappings, block.op_batches()),
),
_ => {
let num_ops = asm_op_mappings.iter().map(|(idx, _)| idx + 1).max().unwrap_or(0);
(num_ops, asm_op_mappings)
},
}
}
fn deduplicate_asm_op_mappings(
mut mappings: Vec<(MastNodeId, Vec<(usize, AsmOpId)>)>,
) -> Vec<(MastNodeId, Vec<(usize, AsmOpId)>)> {
mappings.sort_by_key(|(node_id, _)| *node_id);
let mut seen_node_ids = BTreeSet::new();
mappings
.into_iter()
.filter(|(node_id, _)| seen_node_ids.insert(*node_id))
.collect()
}
fn append_serialized_asm_op(data: &mut Vec<u8>, op_idx: usize, asm_op: &AssemblyOp) {
data.extend_from_slice(&op_idx.to_le_bytes());
asm_op.context_name().write_into(data);
asm_op.op().write_into(data);
asm_op.num_cycles().write_into(data);
match asm_op.location() {
Some(location) => {
data.push(1);
location.uri.write_into(data);
data.extend_from_slice(&u32::from(location.start).to_le_bytes());
data.extend_from_slice(&u32::from(location.end).to_le_bytes());
},
None => data.push(0),
}
}
fn serialize_asm_ops(asm_ops: &[(usize, AssemblyOp)]) -> Vec<u8> {
let mut data = Vec::new();
for (op_idx, asm_op) in asm_ops {
append_serialized_asm_op(&mut data, *op_idx, asm_op);
}
data
}
fn serialize_asm_op_ids(forest: &MastForest, asm_op_ids: &[(usize, AsmOpId)]) -> Vec<u8> {
let mut data = Vec::new();
for (op_idx, asm_op_id) in asm_op_ids {
if let Some(asm_op) = forest.debug_info().asm_op(*asm_op_id) {
append_serialized_asm_op(&mut data, *op_idx, asm_op);
}
}
data
}
fn serialize_asm_ops_for_node(
forest: &MastForest,
pending: &[(MastNodeId, Vec<(usize, AsmOpId)>)],
node_id: MastNodeId,
) -> Vec<u8> {
for (id, asm_ops) in pending {
if *id == node_id {
return serialize_asm_op_ids(forest, asm_ops);
}
}
Vec::new()
}
fn serialize_asm_ops_from_forest_node(forest: &MastForest, node_id: MastNodeId) -> Vec<u8> {
let mut entries = forest.debug_info().asm_ops_for_node(node_id);
if entries.is_empty() {
return Vec::new();
}
if let MastNode::Block(block) = &forest[node_id] {
entries = BasicBlockNode::unadjust_asm_op_indices(entries, block.op_batches());
}
let mut data = Vec::new();
for (op_idx, asm_op_id) in entries {
if let Some(asm_op) = forest.debug_info().asm_op(asm_op_id) {
append_serialized_asm_op(&mut data, op_idx, asm_op);
}
}
data
}
fn serialize_debug_vars(
forest: &MastForest,
debug_vars: &[(usize, miden_core::mast::DebugVarId)],
) -> Vec<u8> {
let mut data = Vec::new();
for (op_idx, var_id) in debug_vars {
data.extend_from_slice(&op_idx.to_le_bytes());
if let Some(debug_var) = forest.debug_info().debug_var(*var_id) {
debug_var.write_into(&mut data);
}
}
data
}
fn serialize_debug_vars_for_node(
forest: &MastForest,
pending: &[(MastNodeId, Vec<(usize, miden_core::mast::DebugVarId)>)],
node_id: MastNodeId,
) -> Vec<u8> {
for (id, vars) in pending {
if *id == node_id {
return serialize_debug_vars(forest, vars);
}
}
Vec::new()
}
fn serialize_debug_vars_from_forest_node(forest: &MastForest, node_id: MastNodeId) -> Vec<u8> {
let entries = forest.debug_info().debug_vars_for_node(node_id);
if entries.is_empty() {
return Vec::new();
}
let mut data = Vec::new();
for (op_idx, var_id) in entries {
data.extend_from_slice(&op_idx.to_le_bytes());
if let Some(debug_var) = forest.debug_info().debug_var(var_id) {
debug_var.write_into(&mut data);
}
}
data
}
fn deduplicate_debug_var_mappings(
mut mappings: Vec<(MastNodeId, Vec<(usize, miden_core::mast::DebugVarId)>)>,
) -> Vec<(MastNodeId, Vec<(usize, miden_core::mast::DebugVarId)>)> {
mappings.sort_by_key(|(node_id, _)| *node_id);
let mut seen_node_ids = BTreeSet::new();
mappings
.into_iter()
.filter(|(node_id, _)| seen_node_ids.insert(*node_id))
.collect()
}
fn get_nodes_to_remove(
merged_node_ids: BTreeSet<MastNodeId>,
mast_forest: &MastForest,
) -> BTreeSet<MastNodeId> {
let mut nodes_to_remove: BTreeSet<MastNodeId> = merged_node_ids
.iter()
.filter(|&&mast_node_id| !mast_forest.is_procedure_root(mast_node_id))
.copied()
.collect();
for node in mast_forest.nodes() {
node.for_each_child(|child_id| {
if nodes_to_remove.contains(&child_id) {
nodes_to_remove.remove(&child_id);
}
});
}
nodes_to_remove
}
impl MastForestBuilder {
#[inline(always)]
pub fn get_procedure(&self, gid: GlobalItemIndex) -> Option<&Procedure> {
self.procedures.get(&gid)
}
#[inline(always)]
pub fn find_procedure_by_mast_root(&self, mast_root: &Word) -> Option<&Procedure> {
self.proc_gid_by_mast_root
.get(mast_root)
.and_then(|gid| self.get_procedure(*gid))
}
pub fn get_mast_node(&self, id: MastNodeId) -> Option<&MastNode> {
self.mast_forest.get_node_by_id(id)
}
}
impl MastForestBuilder {
pub fn insert_procedure(
&mut self,
gid: GlobalItemIndex,
procedure: Procedure,
) -> Result<(), Report> {
if self.procedures.contains_key(&gid) {
return Ok(());
}
if let Some(cached) = self.find_procedure_by_mast_root(&procedure.mast_root()) {
let cached_locals = cached.num_locals();
let procedure_locals = procedure.num_locals();
let mismatched_locals = cached_locals != procedure_locals;
let is_valid =
!mismatched_locals || core::cmp::min(cached_locals, procedure_locals) == 0;
if !is_valid {
let first = cached.path();
let second = procedure.path();
return Err(report!(
"two procedures found with same mast root, but conflicting definitions ('{}' and '{}')",
first,
second
));
}
}
self.mast_forest.make_root(procedure.body_node_id());
self.proc_gid_by_mast_root.insert(procedure.mast_root(), gid);
self.procedures.insert(gid, procedure);
Ok(())
}
}
impl MastForestBuilder {
pub fn join_nodes(
&mut self,
node_ids: Vec<MastNodeId>,
asm_op: Option<AssemblyOp>,
) -> Result<MastNodeId, Report> {
debug_assert!(!node_ids.is_empty(), "cannot combine empty MAST node id list");
let mut node_ids = self.merge_contiguous_basic_blocks(node_ids)?;
while node_ids.len() > 1 {
let last_mast_node_id = if node_ids.len().is_multiple_of(2) {
None
} else {
node_ids.pop()
};
let mut source_node_ids = Vec::new();
core::mem::swap(&mut node_ids, &mut source_node_ids);
let mut source_mast_node_iter = source_node_ids.drain(0..);
while let (Some(left), Some(right)) =
(source_mast_node_iter.next(), source_mast_node_iter.next())
{
let join_builder = JoinNodeBuilder::new([left, right])
.with_before_enter(vec![])
.with_after_exit(vec![]);
let join_mast_node_id = if let Some(ref asm_op) = asm_op {
self.ensure_node_with_asm_op(join_builder, asm_op.clone())?
} else {
self.ensure_node(join_builder)?
};
node_ids.push(join_mast_node_id);
}
if let Some(mast_node_id) = last_mast_node_id {
node_ids.push(mast_node_id);
}
}
Ok(node_ids.remove(0))
}
fn merge_contiguous_basic_blocks(
&mut self,
node_ids: Vec<MastNodeId>,
) -> Result<Vec<MastNodeId>, Report> {
let mut merged_node_ids = Vec::with_capacity(node_ids.len());
let mut contiguous_basic_block_ids: Vec<MastNodeId> = Vec::new();
for mast_node_id in node_ids {
if self.mast_forest[mast_node_id].is_basic_block() {
contiguous_basic_block_ids.push(mast_node_id);
} else {
merged_node_ids.extend(self.merge_basic_blocks(&contiguous_basic_block_ids)?);
contiguous_basic_block_ids.clear();
merged_node_ids.push(mast_node_id);
}
}
merged_node_ids.extend(self.merge_basic_blocks(&contiguous_basic_block_ids)?);
Ok(merged_node_ids)
}
fn merge_basic_blocks(
&mut self,
contiguous_basic_block_ids: &[MastNodeId],
) -> Result<Vec<MastNodeId>, Report> {
if contiguous_basic_block_ids.is_empty() {
return Ok(Vec::new());
}
if contiguous_basic_block_ids.len() == 1 {
return Ok(contiguous_basic_block_ids.to_vec());
}
let mut operations: Vec<Operation> = Vec::new();
let mut decorators = DecoratorList::new();
let mut merged_asm_ops: Vec<(usize, AsmOpId)> = Vec::new();
let mut merged_debug_vars: Vec<(usize, miden_core::mast::DebugVarId)> = Vec::new();
let mut merged_basic_blocks: Vec<MastNodeId> = Vec::new();
for &basic_block_id in contiguous_basic_block_ids {
if should_merge(
self.mast_forest.is_procedure_root(basic_block_id),
self.mast_forest[basic_block_id]
.get_basic_block()
.expect("merge_basic_blocks: expected BasicBlockNode")
.num_op_batches(),
) {
let (block_decorators, block_ops) = {
let basic_block_node =
self.mast_forest[basic_block_id].get_basic_block().unwrap();
let block_decorators: Vec<_> =
basic_block_node.raw_decorator_iter(&self.mast_forest).collect();
let block_ops: Vec<Operation> = basic_block_node
.op_batches()
.iter()
.flat_map(|b| b.raw_ops().copied())
.collect();
(block_decorators, block_ops)
};
let ops_offset = operations.len();
self.transfer_asm_ops_for_merge(basic_block_id, ops_offset, &mut merged_asm_ops);
self.transfer_debug_vars_for_merge(
basic_block_id,
ops_offset,
&mut merged_debug_vars,
);
for (op_idx, decorator) in block_decorators {
decorators.push((op_idx + ops_offset, decorator));
}
operations.extend(block_ops);
} else {
if !operations.is_empty() {
let block_ops = core::mem::take(&mut operations);
let block_decorators = core::mem::take(&mut decorators);
let block_asm_ops = core::mem::take(&mut merged_asm_ops);
let block_debug_vars = core::mem::take(&mut merged_debug_vars);
let merged_basic_block_id = self.ensure_block_with_asm_op_and_debug_var_ids(
block_ops,
block_decorators,
block_asm_ops,
block_debug_vars,
vec![],
vec![],
)?;
merged_basic_blocks.push(merged_basic_block_id);
}
merged_basic_blocks.push(basic_block_id);
}
}
self.merged_basic_block_ids.extend(contiguous_basic_block_ids.iter());
if !operations.is_empty() || !decorators.is_empty() {
let merged_basic_block = self.ensure_block_with_asm_op_and_debug_var_ids(
operations,
decorators,
merged_asm_ops,
merged_debug_vars,
vec![],
vec![],
)?;
merged_basic_blocks.push(merged_basic_block);
}
Ok(merged_basic_blocks)
}
fn transfer_asm_ops_for_merge(
&self,
source_block_id: MastNodeId,
ops_offset: usize,
merged_asm_ops: &mut Vec<(usize, AsmOpId)>,
) {
for (node_id, asm_ops) in &self.pending_asm_op_mappings {
if *node_id == source_block_id {
merged_asm_ops.extend(
asm_ops.iter().map(|(op_idx, asm_op_id)| (op_idx + ops_offset, *asm_op_id)),
);
}
}
}
fn transfer_debug_vars_for_merge(
&self,
source_block_id: MastNodeId,
ops_offset: usize,
merged_debug_vars: &mut Vec<(usize, miden_core::mast::DebugVarId)>,
) {
for (node_id, debug_vars) in &self.pending_debug_var_mappings {
if *node_id == source_block_id {
merged_debug_vars.extend(
debug_vars.iter().map(|(op_idx, var_id)| (op_idx + ops_offset, *var_id)),
);
}
}
}
fn ensure_block_with_asm_op_and_debug_var_ids(
&mut self,
operations: Vec<Operation>,
decorators: DecoratorList,
asm_op_ids: Vec<(usize, AsmOpId)>,
debug_vars: Vec<(usize, miden_core::mast::DebugVarId)>,
before_enter: Vec<DecoratorId>,
after_exit: Vec<DecoratorId>,
) -> Result<MastNodeId, Report> {
let block = BasicBlockNodeBuilder::new(operations, decorators)
.with_before_enter(before_enter)
.with_after_exit(after_exit);
let base_fingerprint = block
.fingerprint_for_node(&self.mast_forest, &self.hash_by_node_id)
.expect("hash_by_node_id should contain the fingerprints of all children of `node`");
let dedup_fingerprint = self.maybe_augment(
self.maybe_augment(
base_fingerprint,
&serialize_asm_op_ids(&self.mast_forest, &asm_op_ids),
),
&serialize_debug_vars(&self.mast_forest, &debug_vars),
);
let (node_id, is_new) =
if let Some(node_id) = self.node_id_by_fingerprint.get(&dedup_fingerprint) {
(*node_id, false)
} else {
let new_node_id = block
.add_to_forest(&mut self.mast_forest)
.into_diagnostic()
.wrap_err("assembler failed to add new node")?;
self.node_id_by_fingerprint.insert(dedup_fingerprint, new_node_id);
self.hash_by_node_id.insert(new_node_id, dedup_fingerprint);
(new_node_id, true)
};
if is_new && !asm_op_ids.is_empty() {
self.pending_asm_op_mappings.push((node_id, asm_op_ids));
}
if is_new && !debug_vars.is_empty() {
self.pending_debug_var_mappings.push((node_id, debug_vars));
}
Ok(node_id)
}
}
impl MastForestBuilder {
pub fn ensure_decorator(&mut self, decorator: Decorator) -> Result<DecoratorId, Report> {
let decorator_hash = decorator.fingerprint();
if let Some(decorator_id) = self.decorator_id_by_fingerprint.get(&decorator_hash) {
Ok(*decorator_id)
} else {
let new_decorator_id = self
.mast_forest
.add_decorator(decorator)
.into_diagnostic()
.wrap_err("assembler failed to add new decorator")?;
self.decorator_id_by_fingerprint.insert(decorator_hash, new_decorator_id);
Ok(new_decorator_id)
}
}
pub fn add_debug_var(
&mut self,
debug_var: miden_core::operations::DebugVarInfo,
) -> Result<miden_core::mast::DebugVarId, Report> {
self.mast_forest
.add_debug_var(debug_var)
.into_diagnostic()
.wrap_err("assembler failed to add debug variable")
}
pub(crate) fn ensure_node(
&mut self,
builder: impl MastForestContributor,
) -> Result<MastNodeId, Report> {
let (node_id, _is_new) = self.ensure_node_exists(builder)?;
Ok(node_id)
}
pub(crate) fn ensure_node_with_asm_op(
&mut self,
builder: impl MastForestContributor,
asm_op: AssemblyOp,
) -> Result<MastNodeId, Report> {
let base_fingerprint = builder
.fingerprint_for_node(&self.mast_forest, &self.hash_by_node_id)
.expect("hash_by_node_id should contain the fingerprints of all children of `node`");
let dedup_fingerprint =
self.maybe_augment(base_fingerprint, &serialize_asm_ops(&[(0, asm_op.clone())]));
if let Some(node_id) = self.node_id_by_fingerprint.get(&dedup_fingerprint) {
Ok(*node_id)
} else {
let new_node_id = builder
.add_to_forest(&mut self.mast_forest)
.into_diagnostic()
.wrap_err("assembler failed to add new node")?;
self.node_id_by_fingerprint.insert(dedup_fingerprint, new_node_id);
self.hash_by_node_id.insert(new_node_id, dedup_fingerprint);
let asm_op_id = self
.mast_forest
.debug_info_mut()
.add_asm_op(asm_op)
.into_diagnostic()
.wrap_err("failed to add AssemblyOp for control flow node")?;
self.pending_asm_op_mappings.push((new_node_id, vec![(0, asm_op_id)]));
Ok(new_node_id)
}
}
pub(crate) fn ensure_node_preserving_debug_vars(
&mut self,
builder: impl MastForestContributor,
source_node_id: MastNodeId,
) -> Result<MastNodeId, Report> {
let base_fingerprint = builder
.fingerprint_for_node(&self.mast_forest, &self.hash_by_node_id)
.expect("hash_by_node_id should contain the fingerprints of all children of `node`");
let asm_ops_data = serialize_asm_ops_for_node(
&self.mast_forest,
&self.pending_asm_op_mappings,
source_node_id,
);
let debug_vars_data = serialize_debug_vars_for_node(
&self.mast_forest,
&self.pending_debug_var_mappings,
source_node_id,
);
let dedup_fingerprint = self
.maybe_augment(self.maybe_augment(base_fingerprint, &asm_ops_data), &debug_vars_data);
if let Some(node_id) = self.node_id_by_fingerprint.get(&dedup_fingerprint) {
Ok(*node_id)
} else {
let new_node_id = builder
.add_to_forest(&mut self.mast_forest)
.into_diagnostic()
.wrap_err("assembler failed to add new node")?;
self.node_id_by_fingerprint.insert(dedup_fingerprint, new_node_id);
self.hash_by_node_id.insert(new_node_id, dedup_fingerprint);
let asm_ops: Option<Vec<_>> = self
.pending_asm_op_mappings
.iter()
.find(|(id, _)| *id == source_node_id)
.map(|(_, asm_ops)| asm_ops.clone());
if let Some(asm_ops) = asm_ops
&& !asm_ops.is_empty()
{
self.pending_asm_op_mappings.push((new_node_id, asm_ops));
}
let debug_vars: Option<Vec<_>> = self
.pending_debug_var_mappings
.iter()
.find(|(id, _)| *id == source_node_id)
.map(|(_, vars)| vars.clone());
if let Some(vars) = debug_vars
&& !vars.is_empty()
{
self.pending_debug_var_mappings.push((new_node_id, vars));
}
Ok(new_node_id)
}
}
fn ensure_node_from_statically_linked_source(
&mut self,
builder: impl MastForestContributor,
source_node_id: MastNodeId,
) -> Result<MastNodeId, Report> {
let base_fingerprint = builder
.fingerprint_for_node(&self.mast_forest, &self.hash_by_node_id)
.expect("hash_by_node_id should contain the fingerprints of all children of `node`");
let asm_ops_data =
serialize_asm_ops_from_forest_node(&self.statically_linked_mast, source_node_id);
let debug_vars_data =
serialize_debug_vars_from_forest_node(&self.statically_linked_mast, source_node_id);
let dedup_fingerprint = self
.maybe_augment(self.maybe_augment(base_fingerprint, &asm_ops_data), &debug_vars_data);
if let Some(node_id) = self.node_id_by_fingerprint.get(&dedup_fingerprint) {
return Ok(*node_id);
}
let new_node_id = builder
.add_to_forest(&mut self.mast_forest)
.into_diagnostic()
.wrap_err("assembler failed to add new statically linked node")?;
self.node_id_by_fingerprint.insert(dedup_fingerprint, new_node_id);
self.hash_by_node_id.insert(new_node_id, dedup_fingerprint);
let mut asm_ops = self.statically_linked_mast.debug_info().asm_ops_for_node(source_node_id);
if let MastNode::Block(block) = &self.statically_linked_mast[source_node_id] {
asm_ops = BasicBlockNode::unadjust_asm_op_indices(asm_ops, block.op_batches());
}
if !asm_ops.is_empty() {
let mut remapped_asm_ops = Vec::with_capacity(asm_ops.len());
for (op_idx, asm_op_id) in asm_ops {
if let Some(asm_op) = self.statically_linked_mast.debug_info().asm_op(asm_op_id) {
let new_asm_op_id = self
.mast_forest
.debug_info_mut()
.add_asm_op(asm_op.clone())
.into_diagnostic()
.wrap_err("failed to copy AssemblyOp from statically linked forest")?;
remapped_asm_ops.push((op_idx, new_asm_op_id));
}
}
if !remapped_asm_ops.is_empty() {
self.pending_asm_op_mappings.push((new_node_id, remapped_asm_ops));
}
}
let debug_vars =
self.statically_linked_mast.debug_info().debug_vars_for_node(source_node_id);
if !debug_vars.is_empty() {
let mut remapped_debug_vars = Vec::with_capacity(debug_vars.len());
for (op_idx, var_id) in debug_vars {
if let Some(debug_var) = self.statically_linked_mast.debug_info().debug_var(var_id)
{
let new_var_id = self
.mast_forest
.add_debug_var(debug_var.clone())
.into_diagnostic()
.wrap_err("failed to copy debug var from statically linked forest")?;
remapped_debug_vars.push((op_idx, new_var_id));
}
}
if !remapped_debug_vars.is_empty() {
self.pending_debug_var_mappings.push((new_node_id, remapped_debug_vars));
}
}
Ok(new_node_id)
}
fn ensure_node_exists(
&mut self,
builder: impl MastForestContributor,
) -> Result<(MastNodeId, bool), Report> {
let node_fingerprint = builder
.fingerprint_for_node(&self.mast_forest, &self.hash_by_node_id)
.expect("hash_by_node_id should contain the fingerprints of all children of `node`");
if let Some(node_id) = self.node_id_by_fingerprint.get(&node_fingerprint) {
Ok((*node_id, false))
} else {
let new_node_id = builder
.add_to_forest(&mut self.mast_forest)
.into_diagnostic()
.wrap_err("assembler failed to add new node")?;
self.node_id_by_fingerprint.insert(node_fingerprint, new_node_id);
self.hash_by_node_id.insert(new_node_id, node_fingerprint);
Ok((new_node_id, true))
}
}
pub fn ensure_block(
&mut self,
operations: Vec<Operation>,
decorators: DecoratorList,
asm_ops: Vec<(usize, AssemblyOp)>,
debug_vars: Vec<(usize, miden_core::mast::DebugVarId)>,
before_enter: Vec<DecoratorId>,
after_exit: Vec<DecoratorId>,
) -> Result<MastNodeId, Report> {
let block = BasicBlockNodeBuilder::new(operations, decorators)
.with_before_enter(before_enter)
.with_after_exit(after_exit);
let base_fingerprint = block
.fingerprint_for_node(&self.mast_forest, &self.hash_by_node_id)
.expect("hash_by_node_id should contain the fingerprints of all children of `node`");
let dedup_fingerprint = self.maybe_augment(
self.maybe_augment(base_fingerprint, &serialize_asm_ops(&asm_ops)),
&serialize_debug_vars(&self.mast_forest, &debug_vars),
);
let (node_id, is_new) =
if let Some(node_id) = self.node_id_by_fingerprint.get(&dedup_fingerprint) {
(*node_id, false)
} else {
let new_node_id = block
.add_to_forest(&mut self.mast_forest)
.into_diagnostic()
.wrap_err("assembler failed to add new node")?;
self.node_id_by_fingerprint.insert(dedup_fingerprint, new_node_id);
self.hash_by_node_id.insert(new_node_id, dedup_fingerprint);
(new_node_id, true)
};
if is_new && !asm_ops.is_empty() {
let mut asm_op_mappings = Vec::with_capacity(asm_ops.len());
for (op_idx, asm_op) in asm_ops {
let asm_op_id = self
.mast_forest
.debug_info_mut()
.add_asm_op(asm_op)
.into_diagnostic()
.wrap_err("failed to add AssemblyOp")?;
asm_op_mappings.push((op_idx, asm_op_id));
}
self.pending_asm_op_mappings.push((node_id, asm_op_mappings));
}
if is_new && !debug_vars.is_empty() {
self.pending_debug_var_mappings.push((node_id, debug_vars));
}
Ok(node_id)
}
fn collect_decorators_from_subtree(&mut self, root_id: &MastNodeId) -> Result<(), Report> {
self.statically_linked_decorator_remapping.clear();
for node_id in SubtreeIterator::new(root_id, &self.statically_linked_mast.clone()) {
let decorator_ids: Vec<DecoratorId> = {
let mut ids = Vec::new();
ids.extend(self.statically_linked_mast.before_enter_decorators(node_id));
ids.extend(self.statically_linked_mast.after_exit_decorators(node_id));
if let MastNode::Block(block_node) = &self.statically_linked_mast[node_id] {
for (_idx, decorator_id) in
block_node.indexed_decorator_iter(&self.statically_linked_mast)
{
ids.push(decorator_id);
}
}
ids
};
for old_decorator_id in decorator_ids {
if !self.statically_linked_decorator_remapping.contains_key(&old_decorator_id) {
let decorator = self.statically_linked_mast[old_decorator_id].clone();
let new_decorator_id = self.ensure_decorator(decorator)?;
self.statically_linked_decorator_remapping
.insert(old_decorator_id, new_decorator_id);
}
}
}
Ok(())
}
fn build_with_remapped_ids(
&self,
node_id: MastNodeId,
node: MastNode,
) -> Result<MastNodeBuilder, Report> {
miden_core::mast::build_node_with_remapped_ids(
node_id,
node,
&self.statically_linked_mast,
&self.statically_linked_mast_remapping,
&self.statically_linked_decorator_remapping,
)
.into_diagnostic()
}
pub fn ensure_external_link(&mut self, mast_root: Word) -> Result<MastNodeId, Report> {
if let Some(root_id) = self.statically_linked_mast.find_procedure_root(mast_root) {
self.copy_statically_linked_subtree(root_id)
} else {
self.ensure_node(ExternalNodeBuilder::new(mast_root))
}
}
fn copy_statically_linked_subtree(
&mut self,
root_id: MastNodeId,
) -> Result<MastNodeId, Report> {
self.collect_decorators_from_subtree(&root_id)?;
for old_id in SubtreeIterator::new(&root_id, &self.statically_linked_mast.clone()) {
let node = self.statically_linked_mast[old_id].clone();
let builder = self.build_with_remapped_ids(old_id, node)?;
let new_id = self.ensure_node_from_statically_linked_source(builder, old_id)?;
self.statically_linked_mast_remapping.insert(old_id, new_id);
}
Ok(root_id.remap(&self.statically_linked_mast_remapping))
}
pub fn append_before_enter(
&mut self,
node_id: MastNodeId,
decorator_ids: Vec<DecoratorId>,
) -> Result<(), MastForestError> {
let mut decorated_builder = self.mast_forest[node_id].clone().to_builder(&self.mast_forest);
decorated_builder.append_before_enter(decorator_ids);
let base_fingerprint =
decorated_builder.fingerprint_for_node(&self.mast_forest, &self.hash_by_node_id)?;
let asm_ops_data =
serialize_asm_ops_for_node(&self.mast_forest, &self.pending_asm_op_mappings, node_id);
let debug_vars_data = serialize_debug_vars_for_node(
&self.mast_forest,
&self.pending_debug_var_mappings,
node_id,
);
let new_node_fingerprint = self
.maybe_augment(self.maybe_augment(base_fingerprint, &asm_ops_data), &debug_vars_data);
self.mast_forest[node_id] = decorated_builder.build(&self.mast_forest)?;
self.hash_by_node_id.insert(node_id, new_node_fingerprint);
self.node_id_by_fingerprint.insert(new_node_fingerprint, node_id);
Ok(())
}
pub fn append_after_exit(
&mut self,
node_id: MastNodeId,
decorator_ids: Vec<DecoratorId>,
) -> Result<(), MastForestError> {
let mut decorated_builder = self.mast_forest[node_id].clone().to_builder(&self.mast_forest);
decorated_builder.append_after_exit(decorator_ids);
let base_fingerprint =
decorated_builder.fingerprint_for_node(&self.mast_forest, &self.hash_by_node_id)?;
let asm_ops_data =
serialize_asm_ops_for_node(&self.mast_forest, &self.pending_asm_op_mappings, node_id);
let debug_vars_data = serialize_debug_vars_for_node(
&self.mast_forest,
&self.pending_debug_var_mappings,
node_id,
);
let new_node_fingerprint = self
.maybe_augment(self.maybe_augment(base_fingerprint, &asm_ops_data), &debug_vars_data);
self.mast_forest[node_id] = decorated_builder.build(&self.mast_forest)?;
self.hash_by_node_id.insert(node_id, new_node_fingerprint);
self.node_id_by_fingerprint.insert(new_node_fingerprint, node_id);
Ok(())
}
}
impl MastForestBuilder {
pub fn register_error(&mut self, msg: Arc<str>) -> Felt {
self.mast_forest.register_error(msg)
}
}
impl Index<MastNodeId> for MastForestBuilder {
type Output = MastNode;
#[inline(always)]
fn index(&self, node_id: MastNodeId) -> &Self::Output {
&self.mast_forest[node_id]
}
}
impl Index<DecoratorId> for MastForestBuilder {
type Output = Decorator;
#[inline(always)]
fn index(&self, decorator_id: DecoratorId) -> &Self::Output {
&self.mast_forest[decorator_id]
}
}
impl IndexMut<DecoratorId> for MastForestBuilder {
#[inline(always)]
fn index_mut(&mut self, decorator_id: DecoratorId) -> &mut Self::Output {
&mut self.mast_forest[decorator_id]
}
}
impl MastForestBuilder {
pub fn merge_advice_map(&mut self, other: &AdviceMap) -> Result<(), Report> {
self.mast_forest
.advice_map_mut()
.merge(other)
.map_err(|((key, prev_values), new_values)| LinkerError::AdviceMapKeyAlreadyPresent {
key,
prev_values: prev_values.to_vec(),
new_values: new_values.to_vec(),
})
.into_diagnostic()
}
}
fn should_merge(is_procedure: bool, num_op_batches: usize) -> bool {
if is_procedure {
num_op_batches < PROCEDURE_INLINING_THRESHOLD
} else {
true
}
}
#[cfg(test)]
mod tests {
use miden_core::{mast::CallNodeBuilder, operations::Operation};
use super::*;
#[test]
fn test_merge_basic_blocks_preserves_decorator_links_with_padding() {
let mut builder = MastForestBuilder::new(&[]).unwrap();
let block1_ops = vec![
Operation::Push(Felt::new(1)),
Operation::Drop,
Operation::Drop,
Operation::Drop,
Operation::Drop,
Operation::Drop,
Operation::Drop,
Operation::Push(Felt::new(2)),
Operation::Push(Felt::new(3)),
]; let block1_raw_ops_len = block1_ops.len();
let block1_decorator1 = builder.ensure_decorator(Decorator::Trace(1)).unwrap();
let block1_decorator2 = builder.ensure_decorator(Decorator::Trace(2)).unwrap();
let block1_decorator3 = builder.ensure_decorator(Decorator::Trace(3)).unwrap();
let block1_decorators = vec![
(0, block1_decorator1), (7, block1_decorator2), (8, block1_decorator3), ];
let block1_id = builder
.ensure_block(block1_ops.clone(), block1_decorators, vec![], vec![], vec![], vec![])
.unwrap();
let block1 = builder.mast_forest[block1_id].get_basic_block().unwrap().clone();
assert!(block1.operations().count() > block1_raw_ops_len); assert_eq!(block1.raw_operations().count(), block1_raw_ops_len);
let block2_ops = vec![Operation::Push(Felt::new(4)), Operation::Mul];
let block2_decorator1 = builder.ensure_decorator(Decorator::Trace(4)).unwrap();
let block2_decorator2 = builder.ensure_decorator(Decorator::Trace(5)).unwrap();
let block2_decorators = vec![
(0, block2_decorator1), (1, block2_decorator2), ];
let block2_id = builder
.ensure_block(block2_ops.clone(), block2_decorators, vec![], vec![], vec![], vec![])
.unwrap();
let merged_blocks = builder.merge_basic_blocks(&[block1_id, block2_id]).unwrap();
assert_eq!(merged_blocks.len(), 1);
let merged_block_id = merged_blocks[0];
let merged_block = builder.mast_forest[merged_block_id].get_basic_block().unwrap();
let decorators = merged_block.indexed_decorator_iter(&builder.mast_forest);
let decorator_count = merged_block.indexed_decorator_iter(&builder.mast_forest).count();
assert_eq!(decorator_count, 5);
let mut found_traces = std::collections::HashSet::new();
for (op_idx, decorator_id) in decorators {
let decorator = &builder.mast_forest[decorator_id];
match decorator {
Decorator::Trace(trace_value) => {
found_traces.insert(*trace_value);
let merged_ops: Vec<Operation> = merged_block.operations().cloned().collect();
if op_idx < merged_ops.len() {
match op_idx {
0 => {
match &merged_ops[op_idx] {
Operation::Push(x) if *x == Felt::new(1) => {
assert_eq!(
*trace_value, 1,
"Decorator for Push(1) should have trace value 1"
);
},
_ => panic!("Expected Push operation at index 0"),
}
},
7 => {
match &merged_ops[op_idx] {
Operation::Push(x) if *x == Felt::new(2) => {
assert_eq!(
*trace_value, 2,
"Decorator for Push(2) should have trace value 2"
);
},
_ => panic!("Expected Push operation at index 7"),
}
},
9 => {
match &merged_ops[op_idx] {
Operation::Push(x) if *x == Felt::new(3) => {
assert_eq!(
*trace_value, 3,
"Decorator for Push(3) should have trace value 3"
);
},
_ => panic!("Expected Push operation at index 9"),
}
},
10 => {
match &merged_ops[op_idx] {
Operation::Push(x) if *x == Felt::new(4) => {
assert_eq!(
*trace_value, 4,
"Decorator for Push(4) should have trace value 4"
);
},
_ => panic!("Expected Push operation at index 10"),
}
},
11 => {
match &merged_ops[op_idx] {
Operation::Mul => {
assert_eq!(
*trace_value, 5,
"Decorator for Mul should have trace value 5"
);
},
_ => panic!("Expected Mul operation at index 11"),
}
},
_ => panic!(
"Unexpected operation index {} for {:?} pointing at {:?}",
op_idx, trace_value, merged_ops[op_idx]
),
}
} else {
panic!("Operation index {} is out of bounds", op_idx);
}
},
_ => panic!("Expected Trace decorator"),
}
}
let expected_traces = [1, 2, 3, 4, 5];
for expected_trace in expected_traces {
assert!(
found_traces.contains(&expected_trace),
"Missing trace value: {}",
expected_trace
);
}
assert_eq!(found_traces.len(), 5, "Should have found exactly 5 trace values");
}
#[test]
fn test_merge_basic_blocks_keeps_non_mergeable_block_standalone() {
let mut builder = MastForestBuilder::new(&[]).unwrap();
let num_ops = PROCEDURE_INLINING_THRESHOLD * 1024;
let large_ops = vec![Operation::Add; num_ops];
let large_block_id = builder
.ensure_block(large_ops, Vec::new(), vec![], vec![], vec![], vec![])
.unwrap();
builder.mast_forest.make_root(large_block_id);
let small_block_id = builder
.ensure_block(vec![Operation::Add], Vec::new(), vec![], vec![], vec![], vec![])
.unwrap();
let merged_blocks = builder.merge_basic_blocks(&[large_block_id, small_block_id]).unwrap();
assert_eq!(merged_blocks.len(), 2);
assert_eq!(merged_blocks[0], large_block_id);
assert_eq!(merged_blocks[1], small_block_id);
}
#[test]
fn test_ensure_node_preserving_debug_vars_on_cloned_block() {
use miden_core::operations::{DebugVarInfo, DebugVarLocation};
let mut builder = MastForestBuilder::new(&[]).unwrap();
let var_info = DebugVarInfo::new("x", DebugVarLocation::Stack(0));
let var_id = builder.add_debug_var(var_info).unwrap();
let block_id = builder
.ensure_block(
vec![Operation::Add],
Vec::new(),
vec![],
vec![(0, var_id)],
vec![],
vec![],
)
.unwrap();
let decorator_id = builder.ensure_decorator(Decorator::Trace(42)).unwrap();
let rebuilt_builder = builder.mast_forest[block_id]
.clone()
.to_builder(builder.mast_forest())
.with_before_enter(vec![decorator_id]);
let cloned_id =
builder.ensure_node_preserving_debug_vars(rebuilt_builder, block_id).unwrap();
assert_ne!(cloned_id, block_id);
let (forest, _remapping) = builder.build();
let cloned_vars = forest.debug_info().debug_vars_for_node(cloned_id);
assert_eq!(cloned_vars.len(), 1, "cloned node should have debug vars");
assert_eq!(cloned_vars[0].1, var_id);
let original_vars = forest.debug_info().debug_vars_for_node(block_id);
assert_eq!(original_vars.len(), 1, "original should keep its debug vars");
assert_eq!(original_vars[0].1, var_id);
}
#[test]
fn test_ensure_node_preserving_debug_vars_prevents_aliasing() {
use miden_core::operations::{DebugVarInfo, DebugVarLocation};
let mut builder = MastForestBuilder::new(&[]).unwrap();
let var_x_id = builder
.add_debug_var(DebugVarInfo::new("x", DebugVarLocation::Stack(0)))
.unwrap();
let var_y_id = builder
.add_debug_var(DebugVarInfo::new("y", DebugVarLocation::Stack(1)))
.unwrap();
let block_a = builder
.ensure_block(
vec![Operation::Add],
Vec::new(),
vec![],
vec![(0, var_x_id)],
vec![],
vec![],
)
.unwrap();
let block_b = builder
.ensure_block(
vec![Operation::Add],
Vec::new(),
vec![],
vec![(0, var_y_id)],
vec![],
vec![],
)
.unwrap();
assert_ne!(block_a, block_b);
let decorator_id = builder.ensure_decorator(Decorator::Trace(1)).unwrap();
let rebuilt_a = builder.mast_forest[block_a]
.clone()
.to_builder(builder.mast_forest())
.with_before_enter(vec![decorator_id]);
let cloned_a = builder.ensure_node_preserving_debug_vars(rebuilt_a, block_a).unwrap();
let rebuilt_b = builder.mast_forest[block_b]
.clone()
.to_builder(builder.mast_forest())
.with_before_enter(vec![decorator_id]);
let cloned_b = builder.ensure_node_preserving_debug_vars(rebuilt_b, block_b).unwrap();
assert_ne!(cloned_a, cloned_b, "different debug vars must prevent dedup");
let (forest, _remapping) = builder.build();
let vars_a = forest.debug_info().debug_vars_for_node(cloned_a);
let vars_b = forest.debug_info().debug_vars_for_node(cloned_b);
assert_eq!(vars_a.len(), 1);
assert_eq!(vars_a[0].1, var_x_id, "cloned_a should have var x");
assert_eq!(vars_b.len(), 1);
assert_eq!(vars_b[0].1, var_y_id, "cloned_b should have var y");
}
#[test]
fn test_ensure_block_dedups_identical_debug_var_payloads() {
use miden_core::operations::{DebugVarInfo, DebugVarLocation};
let mut builder = MastForestBuilder::new(&[]).unwrap();
let var_a = builder
.add_debug_var(DebugVarInfo::new("x", DebugVarLocation::Stack(0)))
.unwrap();
let var_b = builder
.add_debug_var(DebugVarInfo::new("x", DebugVarLocation::Stack(0)))
.unwrap();
let block_a = builder
.ensure_block(
vec![Operation::Add],
Vec::new(),
vec![],
vec![(0, var_a)],
vec![],
vec![],
)
.unwrap();
let block_b = builder
.ensure_block(
vec![Operation::Add],
Vec::new(),
vec![],
vec![(0, var_b)],
vec![],
vec![],
)
.unwrap();
assert_eq!(
block_a, block_b,
"same op stream plus same DebugVarInfo payload should dedup to one node"
);
}
#[test]
fn test_ensure_block_keeps_different_asm_ops_distinct() {
let mut builder = MastForestBuilder::new(&[]).unwrap();
let block_a = builder
.ensure_block(
vec![Operation::Add],
Vec::new(),
vec![(0, AssemblyOp::new(None, "ctx_a".into(), 1, "add".into()))],
vec![],
vec![],
vec![],
)
.unwrap();
let block_b = builder
.ensure_block(
vec![Operation::Add],
Vec::new(),
vec![(0, AssemblyOp::new(None, "ctx_b".into(), 1, "add".into()))],
vec![],
vec![],
vec![],
)
.unwrap();
assert_ne!(
block_a, block_b,
"same op stream plus different AssemblyOp payload must not dedup"
);
let (forest, _remapping) = builder.build();
assert_eq!(
forest.debug_info().first_asm_op_for_node(block_a).unwrap().context_name(),
"ctx_a"
);
assert_eq!(
forest.debug_info().first_asm_op_for_node(block_b).unwrap().context_name(),
"ctx_b"
);
}
#[test]
fn test_non_block_nodes_keep_different_asm_ops_distinct() {
let mut builder = MastForestBuilder::new(&[]).unwrap();
let callee = builder
.ensure_block(vec![Operation::Add], Vec::new(), vec![], vec![], vec![], vec![])
.unwrap();
let call_a = builder
.ensure_node_with_asm_op(
CallNodeBuilder::new(callee),
AssemblyOp::new(None, "ctx_a".into(), 1, "call.foo".into()),
)
.unwrap();
let call_b = builder
.ensure_node_with_asm_op(
CallNodeBuilder::new(callee),
AssemblyOp::new(None, "ctx_b".into(), 1, "call.foo".into()),
)
.unwrap();
assert_ne!(
call_a, call_b,
"same-structure non-block nodes with different AssemblyOps must not dedup"
);
let (forest, _remapping) = builder.build();
assert_eq!(
forest.debug_info().first_asm_op_for_node(call_a).unwrap().context_name(),
"ctx_a"
);
assert_eq!(
forest.debug_info().first_asm_op_for_node(call_b).unwrap().context_name(),
"ctx_b"
);
}
#[test]
fn test_ensure_node_preserving_debug_vars_on_cloned_block_keeps_asm_ops() {
let mut builder = MastForestBuilder::new(&[]).unwrap();
let block_id = builder
.ensure_block(
vec![Operation::Add],
Vec::new(),
vec![(0, AssemblyOp::new(None, "ctx".into(), 1, "add".into()))],
vec![],
vec![],
vec![],
)
.unwrap();
let decorator_id = builder.ensure_decorator(Decorator::Trace(7)).unwrap();
let rebuilt_builder = builder.mast_forest[block_id]
.clone()
.to_builder(builder.mast_forest())
.with_before_enter(vec![decorator_id]);
let cloned_id =
builder.ensure_node_preserving_debug_vars(rebuilt_builder, block_id).unwrap();
assert_ne!(cloned_id, block_id);
let (forest, _remapping) = builder.build();
assert_eq!(
forest.debug_info().first_asm_op_for_node(cloned_id).unwrap().context_name(),
"ctx"
);
assert_eq!(
forest.debug_info().first_asm_op_for_node(block_id).unwrap().context_name(),
"ctx"
);
}
#[test]
fn test_statically_linked_nodes_preserve_metadata_in_dedup() {
use miden_core::operations::{DebugVarInfo, DebugVarLocation};
let mut static_forest = MastForest::new();
let static_asm_op_id = static_forest
.debug_info_mut()
.add_asm_op(AssemblyOp::new(None, "lib_ctx".into(), 1, "add".into()))
.unwrap();
let static_var_id = static_forest
.add_debug_var(DebugVarInfo::new("x", DebugVarLocation::Stack(0)))
.unwrap();
let static_block_id = BasicBlockNodeBuilder::new(vec![Operation::Add], Vec::new())
.add_to_forest(&mut static_forest)
.unwrap();
static_forest
.debug_info_mut()
.register_asm_ops(static_block_id, 1, vec![(0, static_asm_op_id)])
.unwrap();
static_forest
.debug_info_mut()
.register_op_indexed_debug_vars(static_block_id, vec![(0, static_var_id)])
.unwrap();
static_forest.make_root(static_block_id);
let mut builder = MastForestBuilder::new([&static_forest]).unwrap();
let copied_block_id =
builder.ensure_external_link(static_forest[static_block_id].digest()).unwrap();
let local_var_id = builder
.add_debug_var(DebugVarInfo::new("y", DebugVarLocation::Stack(1)))
.unwrap();
let local_block_id = builder
.ensure_block(
vec![Operation::Add],
Vec::new(),
vec![(0, AssemblyOp::new(None, "local_ctx".into(), 1, "add".into()))],
vec![(0, local_var_id)],
vec![],
vec![],
)
.unwrap();
assert_ne!(
copied_block_id, local_block_id,
"statically linked nodes must not alias local nodes with different metadata"
);
let (forest, _remapping) = builder.build();
assert_eq!(
forest
.debug_info()
.first_asm_op_for_node(copied_block_id)
.unwrap()
.context_name(),
"lib_ctx"
);
assert_eq!(
forest
.debug_info()
.first_asm_op_for_node(local_block_id)
.unwrap()
.context_name(),
"local_ctx"
);
let copied_vars = forest.debug_info().debug_vars_for_node(copied_block_id);
let local_vars = forest.debug_info().debug_vars_for_node(local_block_id);
assert_eq!(forest.debug_info().debug_var(copied_vars[0].1).unwrap().name(), "x");
assert_eq!(forest.debug_info().debug_var(local_vars[0].1).unwrap().name(), "y");
}
#[test]
fn test_statically_linked_padded_block_dedups_with_equivalent_local_block() {
let mut source_builder = MastForestBuilder::new(&[]).unwrap();
let ops = vec![
Operation::Push(Felt::new(1)),
Operation::Drop,
Operation::Drop,
Operation::Drop,
Operation::Drop,
Operation::Drop,
Operation::Drop,
Operation::Push(Felt::new(2)),
Operation::Push(Felt::new(3)),
];
let asm_op = AssemblyOp::new(None, "padded_ctx".into(), 1, "push.3".into());
let static_block = source_builder
.ensure_block(
ops.clone(),
Vec::new(),
vec![(8, asm_op.clone())],
vec![],
vec![],
vec![],
)
.unwrap();
source_builder.mast_forest.make_root(static_block);
let (static_forest, _) = source_builder.build();
let expected_padded_idx = static_forest.debug_info().asm_ops_for_node(static_block)[0].0;
let mut builder = MastForestBuilder::new([&static_forest]).unwrap();
let copied_block_id =
builder.ensure_external_link(static_forest[static_block].digest()).unwrap();
let local_block_id = builder
.ensure_block(ops, Vec::new(), vec![(8, asm_op)], vec![], vec![], vec![])
.unwrap();
assert_eq!(
copied_block_id, local_block_id,
"copied padded blocks should dedup with equivalent local blocks",
);
let (forest, remapping) = builder.build();
let final_block_id = remapping.get(&copied_block_id).copied().unwrap_or(copied_block_id);
assert!(
forest
.debug_info()
.asm_op_for_operation(final_block_id, expected_padded_idx - 1)
.is_none(),
"the asm op must not be attached before its padded operation index",
);
assert_eq!(
forest
.debug_info()
.asm_op_for_operation(final_block_id, expected_padded_idx)
.unwrap()
.context_name(),
"padded_ctx",
);
}
#[test]
fn test_merged_root_block_keeps_metadata() {
use miden_core::operations::{AssemblyOp, DebugVarInfo, DebugVarLocation};
let mut builder = MastForestBuilder::new(&[]).unwrap();
let var_id = builder
.add_debug_var(DebugVarInfo::new("x", DebugVarLocation::Stack(0)))
.unwrap();
let asm_op = AssemblyOp::new(None, "test".into(), 1, "add".into());
let root_block = builder
.ensure_block(
vec![Operation::Add],
Vec::new(),
vec![(0, asm_op)],
vec![(0, var_id)],
vec![],
vec![],
)
.unwrap();
builder.mast_forest.make_root(root_block);
let other_block = builder
.ensure_block(vec![Operation::Mul], Vec::new(), vec![], vec![], vec![], vec![])
.unwrap();
let merged = builder.merge_basic_blocks(&[root_block, other_block]).unwrap();
assert_eq!(merged.len(), 1);
let merged_id = merged[0];
assert_ne!(merged_id, root_block);
let (forest, remapping) = builder.build();
let final_root_id = remapping.get(&root_block).copied().unwrap_or(root_block);
assert!(forest.is_procedure_root(final_root_id), "root should survive");
let root_vars = forest.debug_info().debug_vars_for_node(final_root_id);
assert_eq!(root_vars.len(), 1, "root must keep its debug vars after merge");
assert_eq!(root_vars[0].1, var_id);
let root_asm = forest.debug_info().first_asm_op_for_node(final_root_id);
assert!(root_asm.is_some(), "root must keep its asm op after merge");
}
#[test]
fn test_static_link_exact_node_preserves_alias_metadata() {
let mut source_builder = MastForestBuilder::new(&[]).unwrap();
let alias_a = source_builder
.ensure_block(
vec![Operation::Add],
Vec::new(),
vec![(0, AssemblyOp::new(None, "alias_a".into(), 1, "add".into()))],
vec![],
vec![],
vec![],
)
.unwrap();
let alias_b = source_builder
.ensure_block(
vec![Operation::Add],
Vec::new(),
vec![(0, AssemblyOp::new(None, "alias_b".into(), 1, "add".into()))],
vec![],
vec![],
vec![],
)
.unwrap();
source_builder.mast_forest.make_root(alias_a);
source_builder.mast_forest.make_root(alias_b);
let (static_forest, _remapping) = source_builder.build();
assert_eq!(static_forest[alias_a].digest(), static_forest[alias_b].digest());
let mut exact_builder = MastForestBuilder::new([&static_forest]).unwrap();
let exact_alias_b = {
let node = exact_builder.statically_linked_mast[alias_b].clone();
let builder = exact_builder.build_with_remapped_ids(alias_b, node).unwrap();
exact_builder
.ensure_node_from_statically_linked_source(builder, alias_b)
.unwrap()
};
let (exact_forest, _) = exact_builder.build();
assert_eq!(
exact_forest
.debug_info()
.first_asm_op_for_node(exact_alias_b)
.unwrap()
.context_name(),
"alias_b"
);
}
#[test]
fn test_static_link_by_digest_imports_only_selected_alias() {
let mut source_builder = MastForestBuilder::new(&[]).unwrap();
let alias_a = source_builder
.ensure_block(
vec![Operation::Add],
Vec::new(),
vec![(0, AssemblyOp::new(None, "alias_a".into(), 1, "add".into()))],
vec![],
vec![],
vec![],
)
.unwrap();
let alias_b = source_builder
.ensure_block(
vec![Operation::Add],
Vec::new(),
vec![(0, AssemblyOp::new(None, "alias_b".into(), 1, "add".into()))],
vec![],
vec![],
vec![],
)
.unwrap();
source_builder.mast_forest.make_root(alias_a);
source_builder.mast_forest.make_root(alias_b);
let (static_forest, _) = source_builder.build();
let mut builder = MastForestBuilder::new([&static_forest]).unwrap();
let linked = builder.ensure_external_link(static_forest[alias_a].digest()).unwrap();
builder.mast_forest.make_root(linked);
let (forest, _) = builder.build();
assert_eq!(forest.num_nodes(), 1, "only the selected alias should be imported");
assert_eq!(
forest.debug_info().first_asm_op_for_node(linked).unwrap().context_name(),
"alias_a",
);
}
#[test]
#[ignore = "requires #2990: source MastNodeId in ProcedureInfo"]
fn repro_statically_linked_same_digest_root_uses_first_alias_metadata() {
let mut source_builder = MastForestBuilder::new(&[]).unwrap();
let alias_a = source_builder
.ensure_block(
vec![Operation::Add],
Vec::new(),
vec![(0, AssemblyOp::new(None, "alias_a".into(), 1, "add".into()))],
vec![],
vec![],
vec![],
)
.unwrap();
let alias_b = source_builder
.ensure_block(
vec![Operation::Add],
Vec::new(),
vec![(0, AssemblyOp::new(None, "alias_b".into(), 1, "add".into()))],
vec![],
vec![],
vec![],
)
.unwrap();
source_builder.mast_forest.make_root(alias_a);
source_builder.mast_forest.make_root(alias_b);
let (static_forest, _remapping) = source_builder.build();
assert_eq!(static_forest[alias_a].digest(), static_forest[alias_b].digest());
let mut exact_builder = MastForestBuilder::new([&static_forest]).unwrap();
let exact_alias_b = {
let node = exact_builder.statically_linked_mast[alias_b].clone();
let builder = exact_builder.build_with_remapped_ids(alias_b, node).unwrap();
exact_builder
.ensure_node_from_statically_linked_source(builder, alias_b)
.unwrap()
};
let (exact_forest, _) = exact_builder.build();
let mut digest_builder = MastForestBuilder::new([&static_forest]).unwrap();
let linked_alias_b =
digest_builder.ensure_external_link(static_forest[alias_b].digest()).unwrap();
let (linked_forest, _) = digest_builder.build();
assert_eq!(
exact_forest
.debug_info()
.first_asm_op_for_node(exact_alias_b)
.unwrap()
.context_name(),
"alias_b"
);
assert_eq!(
linked_forest
.debug_info()
.first_asm_op_for_node(linked_alias_b)
.unwrap()
.context_name(),
"alias_b",
"linking the second same-digest root by digest should preserve that root's metadata",
);
}
}