use alloc::{
boxed::Box,
string::{String, ToString},
vec::Vec,
};
use core::fmt;
use miden_air::RowIndex;
use miden_core::{AssemblyOp, FieldElement, Operation, StackOutputs};
use crate::{
Chiplets, ChipletsLengths, Decoder, ExecutionError, Felt, MemoryAddress, Process, Stack,
System, TraceLenSummary, range::RangeChecker, system::ContextId,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VmState {
pub clk: RowIndex,
pub ctx: ContextId,
pub op: Option<Operation>,
pub asmop: Option<AsmOpInfo>,
pub stack: Vec<Felt>,
pub memory: Vec<(MemoryAddress, Felt)>,
}
impl fmt::Display for VmState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let stack: Vec<u64> = self.stack.iter().map(|x| x.as_int()).collect();
write!(
f,
"clk={}{}{}, stack={stack:?}, memory={:?}",
self.clk,
match self.op {
Some(op) => format!(", op={op}"),
None => "".to_string(),
},
match &self.asmop {
Some(op) => format!(", {op}"),
None => "".to_string(),
},
self.memory
)
}
}
pub struct VmStateIterator {
chiplets: Chiplets,
decoder: Decoder,
stack: Stack,
system: System,
error: Option<ExecutionError>,
clk: RowIndex,
asmop_idx: usize,
forward: bool,
trace_len_summary: TraceLenSummary,
}
impl VmStateIterator {
pub fn new(process: Process, result: Result<StackOutputs, ExecutionError>) -> Self {
let (system, decoder, stack, mut range, chiplets, _final_pc_state) = process.into_parts();
let trace_len_summary = Self::build_trace_len_summary(&system, &mut range, &chiplets);
Self {
chiplets,
decoder,
stack,
system,
error: result.err(),
clk: RowIndex::from(0),
asmop_idx: 0,
forward: true,
trace_len_summary,
}
}
fn get_asmop(&self) -> (Option<AsmOpInfo>, bool) {
let assembly_ops = self.decoder.debug_info().assembly_ops();
if self.clk == 0 || assembly_ops.is_empty() || self.asmop_idx > assembly_ops.len() {
return (None, false);
}
let next_asmop = if self.forward && self.asmop_idx < assembly_ops.len() {
&assembly_ops[self.asmop_idx]
} else {
&assembly_ops[self.asmop_idx.saturating_sub(1)]
};
let (curr_asmop, cycle_idx) = if self.asmop_idx > 0 {
let a = self.clk;
let b = RowIndex::from(assembly_ops[self.asmop_idx - 1].0);
(
&assembly_ops[self.asmop_idx - 1],
(a.max(b) - a.min(b)) as u8,
)
} else {
(next_asmop, 0) };
if next_asmop.0 == (self.clk - 1).as_usize() {
let cycle_idx = 1;
let asmop = AsmOpInfo::new(next_asmop.1.clone(), cycle_idx);
(Some(asmop), true)
}
else if self.asmop_idx > 0 && cycle_idx <= curr_asmop.1.num_cycles() {
let asmop = AsmOpInfo::new(curr_asmop.1.clone(), cycle_idx);
(Some(asmop), false)
}
else {
(None, false)
}
}
pub fn back(&mut self) -> Option<VmState> {
if self.clk == 0 {
return None;
}
if self.forward {
self.clk = self.clk.saturating_sub(1);
self.forward = false;
}
let ctx = self.system.get_ctx_at(self.clk);
let op = if self.clk == 0 {
None
} else {
Some(self.decoder.debug_info().operations()[self.clk - 1])
};
let (asmop, is_start) = self.get_asmop();
if is_start {
self.asmop_idx -= 1;
}
let result = Some(VmState {
clk: self.clk,
ctx,
op,
asmop,
stack: self.stack.get_state_at(self.clk),
memory: self.chiplets.memory.get_state_at(ctx, self.clk),
});
self.clk = self.clk.saturating_sub(1);
result
}
pub fn into_parts(self) -> (System, Decoder, Stack, Chiplets, Option<ExecutionError>) {
(self.system, self.decoder, self.stack, self.chiplets, self.error)
}
pub fn trace_len_summary(&self) -> &TraceLenSummary {
&self.trace_len_summary
}
fn build_trace_len_summary(
system: &System,
range: &mut RangeChecker,
chiplets: &Chiplets,
) -> TraceLenSummary {
let clk = system.clk();
let range_table_len = range.get_number_range_checker_rows();
chiplets.append_range_checks(range);
TraceLenSummary::new(clk.into(), range_table_len, ChipletsLengths::new(chiplets))
}
}
impl Iterator for VmStateIterator {
type Item = Result<VmState, ExecutionError>;
fn next(&mut self) -> Option<Self::Item> {
if self.clk > self.system.clk() {
match &self.error {
Some(_) => {
let error = core::mem::take(&mut self.error);
return Some(Err(error.unwrap()));
},
None => return None,
}
}
if !self.forward && self.clk < self.system.clk() {
self.clk += 1_u32;
self.forward = true;
}
let ctx = self.system.get_ctx_at(self.clk);
let op = if self.clk == 0 {
None
} else {
Some(self.decoder.debug_info().operations()[self.clk - 1])
};
let (asmop, is_start) = self.get_asmop();
if is_start {
self.asmop_idx += 1;
}
let result = Some(Ok(VmState {
clk: self.clk,
ctx,
op,
asmop,
stack: self.stack.get_state_at(self.clk),
memory: self.chiplets.memory.get_state_at(ctx, self.clk),
}));
self.clk += 1_u32;
result
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AsmOpInfo {
asmop: AssemblyOp,
cycle_idx: u8,
}
impl AsmOpInfo {
pub fn new(asmop: AssemblyOp, cycle_idx: u8) -> Self {
Self { asmop, cycle_idx }
}
pub fn context_name(&self) -> &str {
self.asmop.context_name()
}
pub fn op(&self) -> &str {
self.asmop.op()
}
pub fn op_generalized(&self) -> String {
let op_vec: Vec<&str> = self.op().split('.').collect();
let keep_params = matches!(op_vec[0], "movdn" | "movup");
if !keep_params && op_vec.last().unwrap().parse::<usize>().is_ok() {
op_vec.split_last().unwrap().1.join(".")
} else {
self.op().to_string()
}
}
pub fn num_cycles(&self) -> u8 {
self.asmop.num_cycles()
}
pub fn cycle_idx(&self) -> u8 {
self.cycle_idx
}
pub const fn should_break(&self) -> bool {
self.asmop.should_break()
}
}
impl fmt::Display for AsmOpInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}, cycles={}", self.asmop, self.cycle_idx)
}
}
impl AsRef<AssemblyOp> for AsmOpInfo {
#[inline]
fn as_ref(&self) -> &AssemblyOp {
&self.asmop
}
}
pub(crate) trait BusMessage<E: FieldElement<BaseField = Felt>>: fmt::Display {
fn value(&self, alphas: &[E]) -> E;
fn source(&self) -> &str;
}
pub(crate) struct BusDebugger<E: FieldElement<BaseField = Felt>> {
pub bus_name: String,
pub outstanding_requests: Vec<(E, Box<dyn BusMessage<E>>)>,
pub outstanding_responses: Vec<(E, Box<dyn BusMessage<E>>)>,
}
impl<E> BusDebugger<E>
where
E: FieldElement<BaseField = Felt>,
{
pub fn new(bus_name: String) -> Self {
Self {
bus_name,
outstanding_requests: Vec::new(),
outstanding_responses: Vec::new(),
}
}
}
impl<E> BusDebugger<E>
where
E: FieldElement<BaseField = Felt>,
{
#[allow(dead_code)]
pub fn add_request(&mut self, request_msg: Box<dyn BusMessage<E>>, alphas: &[E]) {
let msg_value = request_msg.value(alphas);
if let Some(pos) =
self.outstanding_responses.iter().position(|(value, _)| *value == msg_value)
{
self.outstanding_responses.swap_remove(pos);
} else {
self.outstanding_requests.push((msg_value, request_msg));
}
}
#[allow(dead_code)]
pub fn add_response(&mut self, response_msg: Box<dyn BusMessage<E>>, alphas: &[E]) {
let msg_value = response_msg.value(alphas);
if let Some(pos) =
self.outstanding_requests.iter().position(|(value, _)| *value == msg_value)
{
self.outstanding_requests.swap_remove(pos);
} else {
self.outstanding_responses.push((msg_value, response_msg));
}
}
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.outstanding_requests.is_empty() && self.outstanding_responses.is_empty()
}
}
impl<E> fmt::Display for BusDebugger<E>
where
E: FieldElement<BaseField = Felt>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_empty() {
writeln!(f, "Bus '{}' is empty.", self.bus_name)?;
} else {
writeln!(f, "Bus '{}' construction failed.", self.bus_name)?;
if !self.outstanding_requests.is_empty() {
writeln!(f, "The following requests are still outstanding:")?;
for (_value, msg) in &self.outstanding_requests {
writeln!(f, "- {}: {}", msg.source(), msg)?;
}
}
if !self.outstanding_responses.is_empty() {
writeln!(f, "\nThe following responses are still outstanding:")?;
for (_value, msg) in &self.outstanding_responses {
writeln!(f, "- {}: {}", msg.source(), msg)?;
}
}
}
Ok(())
}
}