use crate::{
range::RangeChecker, system::ContextId, Chiplets, ChipletsLengths, Decoder, ExecutionError,
Felt, Host, Process, Stack, System, TraceLenSummary,
};
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt;
use vm_core::{AssemblyOp, Operation, StackOutputs, Word};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VmState {
pub clk: u32,
pub ctx: ContextId,
pub op: Option<Operation>,
pub asmop: Option<AsmOpInfo>,
pub fmp: Felt,
pub stack: Vec<Felt>,
pub memory: Vec<(u64, Word)>,
}
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();
let memory: Vec<(u64, [u64; 4])> =
self.memory.iter().map(|x| (x.0, word_to_ints(&x.1))).collect();
write!(
f,
"clk={}{}{}, fmp={}, stack={stack:?}, memory={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.fmp
)
}
}
pub struct VmStateIterator {
chiplets: Chiplets,
decoder: Decoder,
stack: Stack,
system: System,
error: Option<ExecutionError>,
clk: u32,
asmop_idx: usize,
forward: bool,
trace_len_summary: TraceLenSummary,
}
impl VmStateIterator {
pub(super) fn new<H>(process: Process<H>, result: Result<StackOutputs, ExecutionError>) -> Self
where
H: Host,
{
let (system, decoder, stack, mut range, chiplets, _) = 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: 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 = assembly_ops[self.asmop_idx - 1].0 as u32;
(
&assembly_ops[self.asmop_idx - 1],
(a.max(b) - a.min(b)) as u8,
)
} else {
(next_asmop, 0) };
if next_asmop.0 as u32 == self.clk - 1 {
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 as usize - 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,
fmp: self.system.get_fmp_at(self.clk),
stack: self.stack.get_state_at(self.clk),
memory: self.chiplets.get_mem_state_at(ctx, self.clk),
});
self.clk -= 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 as usize, 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;
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 as usize - 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,
fmp: self.system.get_fmp_at(self.clk),
stack: self.stack.get_state_at(self.clk),
memory: self.chiplets.get_mem_state_at(ctx, self.clk),
}));
self.clk += 1;
result
}
}
fn word_to_ints(word: &Word) -> [u64; 4] {
[word[0].as_int(), word[1].as_int(), word[2].as_int(), word[3].as_int()]
}
#[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)
}
}