use alloc::{
borrow::Borrow,
string::{String, ToString},
sync::Arc,
vec::Vec,
};
use miden_assembly_syntax::{
ast::{DebugVarInfo, Instruction},
debuginfo::{Location, Span},
diagnostics::Report,
};
use miden_core::{
Felt,
events::SystemEvent,
operations::{AssemblyOp, Operation},
};
use miden_mast_package::debug_info::{DebugSourceAsmOp, DebugSourceVar};
use crate::{
ProcedureContext,
assembler::BodyWrapper,
mast_forest_builder::{MastForestBuilder, MastNodeRef},
};
#[derive(Debug)]
struct PendingAsmOp {
op_start: usize,
location: Option<Location>,
context_name: String,
op: String,
}
#[derive(Debug)]
pub struct BasicBlockBuilder<'a> {
ops: Vec<Operation>,
epilogue: Vec<Operation>,
pending_asm_op: Option<PendingAsmOp>,
asm_ops: Vec<DebugSourceAsmOp>,
debug_vars: Vec<DebugSourceVar>,
mast_forest_builder: &'a mut MastForestBuilder,
}
impl<'a> BasicBlockBuilder<'a> {
pub(super) fn new(
wrapper: Option<BodyWrapper>,
mast_forest_builder: &'a mut MastForestBuilder,
) -> Self {
match wrapper {
Some(wrapper) => Self {
ops: wrapper.prologue,
epilogue: wrapper.epilogue,
pending_asm_op: None,
asm_ops: Vec::new(),
debug_vars: Vec::new(),
mast_forest_builder,
},
None => Self {
ops: Default::default(),
epilogue: Default::default(),
pending_asm_op: None,
asm_ops: Vec::new(),
debug_vars: Default::default(),
mast_forest_builder,
},
}
}
}
impl BasicBlockBuilder<'_> {
pub fn mast_forest_builder(&self) -> &MastForestBuilder {
self.mast_forest_builder
}
pub fn mast_forest_builder_mut(&mut self) -> &mut MastForestBuilder {
self.mast_forest_builder
}
}
impl BasicBlockBuilder<'_> {
pub fn push_op(&mut self, op: Operation) {
self.ops.push(op);
}
pub fn push_ops<I, O>(&mut self, ops: I)
where
I: IntoIterator<Item = O>,
O: Borrow<Operation>,
{
self.ops.extend(ops.into_iter().map(|o| *o.borrow()));
}
pub fn push_op_many(&mut self, op: Operation, n: usize) {
let new_len = self.ops.len() + n;
self.ops.resize(new_len, op);
}
pub fn push_system_event(&mut self, sys_event: SystemEvent) {
let event_id = sys_event.event_id();
self.push_ops([Operation::Push(event_id.as_felt()), Operation::Emit, Operation::Drop]);
}
}
impl BasicBlockBuilder<'_> {
pub fn track_instruction(
&mut self,
instruction: &Span<Instruction>,
proc_ctx: &ProcedureContext,
) {
let span = instruction.span();
self.pending_asm_op = Some(PendingAsmOp {
op_start: self.ops.len(),
location: proc_ctx.source_manager().location(span).ok(),
context_name: proc_ctx.path().to_string(),
op: instruction.to_string(),
});
}
pub fn set_instruction_cycle_count(&mut self) -> Option<AssemblyOp> {
let pending = self.pending_asm_op.take().expect("no pending asm op to finalize");
let cycle_count = self.ops.len() - pending.op_start;
match cycle_count {
0 => {
let asm_op = AssemblyOp::new(
pending.location,
pending.context_name,
cycle_count as u8,
pending.op,
);
Some(asm_op)
},
_ => {
let debug_info = self.mast_forest_builder.debug_info_mut();
let location_idx = pending.location.map(|loc| debug_info.add_location(loc));
let context_name_idx = debug_info.add_string(pending.context_name);
let op_name_idx = debug_info.add_string(pending.op);
let asm_op = DebugSourceAsmOp::new(
pending.op_start as u32,
location_idx,
context_name_idx,
op_name_idx,
cycle_count as u8,
);
self.asm_ops.push(asm_op);
None
},
}
}
pub fn push_debug_var(&mut self, debug_var: DebugVarInfo) -> Result<(), Report> {
let debug_info = self.mast_forest_builder.debug_info_mut();
let name_idx = debug_info.add_string(debug_var.name().clone());
let location_idx = debug_var.location().cloned().map(|loc| debug_info.add_location(loc));
let type_id = if let Some(ty) = debug_var.ty() {
let declared_ty = debug_var.declared_type();
Some(debug_info.register_debug_type(None, declared_ty.as_deref(), ty)?)
} else {
None
};
let debug_var = DebugSourceVar {
op_idx: self.ops.len() as u32,
name_idx,
type_id,
arg_idx: debug_var.arg_index(),
location_idx,
value_location: debug_var.value_location().clone(),
};
self.debug_vars.push(debug_var);
Ok(())
}
}
impl BasicBlockBuilder<'_> {
pub(crate) fn make_basic_block(&mut self) -> Result<Option<MastNodeRef>, Report> {
if !self.ops.is_empty() {
let ops = self.ops.drain(..).collect();
let asm_ops = core::mem::take(&mut self.asm_ops);
let debug_vars = self.debug_vars.drain(..).collect();
let basic_block_node_ref = self.mast_forest_builder.ensure_block_ref(
ops,
asm_ops,
debug_vars,
vec![],
vec![],
)?;
Ok(Some(basic_block_node_ref))
} else {
Ok(None)
}
}
pub fn try_into_basic_block(mut self) -> Result<Option<MastNodeRef>, Report> {
self.ops.append(&mut self.epilogue);
self.make_basic_block()
}
}
impl BasicBlockBuilder<'_> {
pub fn register_error(&mut self, msg: Arc<str>) -> Felt {
self.mast_forest_builder.register_error(msg)
}
}