1use alloc::{
2 boxed::Box,
3 string::{String, ToString},
4 vec::Vec,
5};
6use core::fmt;
7
8use miden_air::RowIndex;
9use miden_core::{AssemblyOp, FieldElement, Operation, StackOutputs};
10
11use crate::{
12 Chiplets, ChipletsLengths, Decoder, ExecutionError, Felt, MemoryAddress, Process, Stack,
13 System, TraceLenSummary, range::RangeChecker, system::ContextId,
14};
15
16#[derive(Clone, Debug, Eq, PartialEq)]
18pub struct VmState {
19 pub clk: RowIndex,
20 pub ctx: ContextId,
21 pub op: Option<Operation>,
22 pub asmop: Option<AsmOpInfo>,
23 pub stack: Vec<Felt>,
24 pub memory: Vec<(MemoryAddress, Felt)>,
25}
26
27impl fmt::Display for VmState {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 let stack: Vec<u64> = self.stack.iter().map(|x| x.as_int()).collect();
30 write!(
31 f,
32 "clk={}{}{}, stack={stack:?}, memory={:?}",
33 self.clk,
34 match self.op {
35 Some(op) => format!(", op={op}"),
36 None => "".to_string(),
37 },
38 match &self.asmop {
39 Some(op) => format!(", {op}"),
40 None => "".to_string(),
41 },
42 self.memory
43 )
44 }
45}
46
47pub struct VmStateIterator {
52 chiplets: Chiplets,
53 decoder: Decoder,
54 stack: Stack,
55 system: System,
56 error: Option<ExecutionError>,
57 clk: RowIndex,
58 asmop_idx: usize,
59 forward: bool,
60 trace_len_summary: TraceLenSummary,
61}
62
63impl VmStateIterator {
64 pub fn new(process: Process, result: Result<StackOutputs, ExecutionError>) -> Self {
65 let (system, decoder, stack, mut range, chiplets, _final_pc_state) = process.into_parts();
66 let trace_len_summary = Self::build_trace_len_summary(&system, &mut range, &chiplets);
67
68 Self {
69 chiplets,
70 decoder,
71 stack,
72 system,
73 error: result.err(),
74 clk: RowIndex::from(0),
75 asmop_idx: 0,
76 forward: true,
77 trace_len_summary,
78 }
79 }
80
81 fn get_asmop(&self) -> (Option<AsmOpInfo>, bool) {
84 let assembly_ops = self.decoder.debug_info().assembly_ops();
85
86 if self.clk == 0 || assembly_ops.is_empty() || self.asmop_idx > assembly_ops.len() {
87 return (None, false);
88 }
89
90 let next_asmop = if self.forward && self.asmop_idx < assembly_ops.len() {
93 &assembly_ops[self.asmop_idx]
94 } else {
95 &assembly_ops[self.asmop_idx.saturating_sub(1)]
96 };
97
98 let (curr_asmop, cycle_idx) = if self.asmop_idx > 0 {
101 let a = self.clk;
102 let b = RowIndex::from(assembly_ops[self.asmop_idx - 1].0);
103 (
104 &assembly_ops[self.asmop_idx - 1],
105 (a.max(b) - a.min(b)) as u8,
108 )
109 } else {
110 (next_asmop, 0) };
112
113 if next_asmop.0 == (self.clk - 1).as_usize() {
116 let cycle_idx = 1;
118 let asmop = AsmOpInfo::new(next_asmop.1.clone(), cycle_idx);
119 (Some(asmop), true)
120 }
121 else if self.asmop_idx > 0 && cycle_idx <= curr_asmop.1.num_cycles() {
125 let asmop = AsmOpInfo::new(curr_asmop.1.clone(), cycle_idx);
127 (Some(asmop), false)
128 }
129 else {
132 (None, false)
133 }
134 }
135
136 pub fn back(&mut self) -> Option<VmState> {
137 if self.clk == 0 {
138 return None;
139 }
140
141 if self.forward {
143 self.clk = self.clk.saturating_sub(1);
144 self.forward = false;
145 }
146
147 let ctx = self.system.get_ctx_at(self.clk);
148
149 let op = if self.clk == 0 {
150 None
151 } else {
152 Some(self.decoder.debug_info().operations()[self.clk - 1])
153 };
154
155 let (asmop, is_start) = self.get_asmop();
156 if is_start {
157 self.asmop_idx -= 1;
158 }
159
160 let result = Some(VmState {
161 clk: self.clk,
162 ctx,
163 op,
164 asmop,
165 stack: self.stack.get_state_at(self.clk),
166 memory: self.chiplets.memory.get_state_at(ctx, self.clk),
167 });
168
169 self.clk = self.clk.saturating_sub(1);
171
172 result
173 }
174
175 pub fn into_parts(self) -> (System, Decoder, Stack, Chiplets, Option<ExecutionError>) {
176 (self.system, self.decoder, self.stack, self.chiplets, self.error)
177 }
178
179 pub fn trace_len_summary(&self) -> &TraceLenSummary {
180 &self.trace_len_summary
181 }
182
183 fn build_trace_len_summary(
185 system: &System,
186 range: &mut RangeChecker,
187 chiplets: &Chiplets,
188 ) -> TraceLenSummary {
189 let clk = system.clk();
190 let range_table_len = range.get_number_range_checker_rows();
191 chiplets.append_range_checks(range);
192
193 TraceLenSummary::new(clk.into(), range_table_len, ChipletsLengths::new(chiplets))
194 }
195}
196
197impl Iterator for VmStateIterator {
198 type Item = Result<VmState, ExecutionError>;
199
200 fn next(&mut self) -> Option<Self::Item> {
201 if self.clk > self.system.clk() {
202 match &self.error {
203 Some(_) => {
204 let error = core::mem::take(&mut self.error);
205 return Some(Err(error.unwrap()));
206 },
207 None => return None,
208 }
209 }
210
211 if !self.forward && self.clk < self.system.clk() {
213 self.clk += 1_u32;
214 self.forward = true;
215 }
216
217 let ctx = self.system.get_ctx_at(self.clk);
218
219 let op = if self.clk == 0 {
220 None
221 } else {
222 Some(self.decoder.debug_info().operations()[self.clk - 1])
223 };
224
225 let (asmop, is_start) = self.get_asmop();
226 if is_start {
227 self.asmop_idx += 1;
228 }
229
230 let result = Some(Ok(VmState {
231 clk: self.clk,
232 ctx,
233 op,
234 asmop,
235 stack: self.stack.get_state_at(self.clk),
236 memory: self.chiplets.memory.get_state_at(ctx, self.clk),
237 }));
238
239 self.clk += 1_u32;
240
241 result
242 }
243}
244
245#[derive(Clone, Debug, Eq, PartialEq)]
248pub struct AsmOpInfo {
249 asmop: AssemblyOp,
250 cycle_idx: u8,
251}
252
253impl AsmOpInfo {
257 pub fn new(asmop: AssemblyOp, cycle_idx: u8) -> Self {
261 Self { asmop, cycle_idx }
262 }
263
264 pub fn context_name(&self) -> &str {
266 self.asmop.context_name()
267 }
268
269 pub fn op(&self) -> &str {
271 self.asmop.op()
272 }
273
274 pub fn op_generalized(&self) -> String {
276 let op_vec: Vec<&str> = self.op().split('.').collect();
277 let keep_params = matches!(op_vec[0], "movdn" | "movup");
278 if !keep_params && op_vec.last().unwrap().parse::<usize>().is_ok() {
279 op_vec.split_last().unwrap().1.join(".")
280 } else {
281 self.op().to_string()
282 }
283 }
284
285 pub fn num_cycles(&self) -> u8 {
287 self.asmop.num_cycles()
288 }
289
290 pub fn cycle_idx(&self) -> u8 {
293 self.cycle_idx
294 }
295
296 pub const fn should_break(&self) -> bool {
298 self.asmop.should_break()
299 }
300}
301
302impl fmt::Display for AsmOpInfo {
303 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304 write!(f, "{}, cycles={}", self.asmop, self.cycle_idx)
305 }
306}
307
308impl AsRef<AssemblyOp> for AsmOpInfo {
309 #[inline]
310 fn as_ref(&self) -> &AssemblyOp {
311 &self.asmop
312 }
313}
314
315pub(crate) trait BusMessage<E: FieldElement<BaseField = Felt>>: fmt::Display {
320 fn value(&self, alphas: &[E]) -> E;
322
323 fn source(&self) -> &str;
325}
326
327pub(crate) struct BusDebugger<E: FieldElement<BaseField = Felt>> {
333 pub bus_name: String,
334 pub outstanding_requests: Vec<(E, Box<dyn BusMessage<E>>)>,
335 pub outstanding_responses: Vec<(E, Box<dyn BusMessage<E>>)>,
336}
337
338impl<E> BusDebugger<E>
339where
340 E: FieldElement<BaseField = Felt>,
341{
342 pub fn new(bus_name: String) -> Self {
343 Self {
344 bus_name,
345 outstanding_requests: Vec::new(),
346 outstanding_responses: Vec::new(),
347 }
348 }
349}
350
351impl<E> BusDebugger<E>
352where
353 E: FieldElement<BaseField = Felt>,
354{
355 #[allow(dead_code)]
359 pub fn add_request(&mut self, request_msg: Box<dyn BusMessage<E>>, alphas: &[E]) {
360 let msg_value = request_msg.value(alphas);
361
362 if let Some(pos) =
363 self.outstanding_responses.iter().position(|(value, _)| *value == msg_value)
364 {
365 self.outstanding_responses.swap_remove(pos);
366 } else {
367 self.outstanding_requests.push((msg_value, request_msg));
368 }
369 }
370
371 #[allow(dead_code)]
375 pub fn add_response(&mut self, response_msg: Box<dyn BusMessage<E>>, alphas: &[E]) {
376 let msg_value = response_msg.value(alphas);
377
378 if let Some(pos) =
379 self.outstanding_requests.iter().position(|(value, _)| *value == msg_value)
380 {
381 self.outstanding_requests.swap_remove(pos);
382 } else {
383 self.outstanding_responses.push((msg_value, response_msg));
384 }
385 }
386
387 #[allow(dead_code)]
394 pub fn is_empty(&self) -> bool {
395 self.outstanding_requests.is_empty() && self.outstanding_responses.is_empty()
396 }
397}
398
399impl<E> fmt::Display for BusDebugger<E>
400where
401 E: FieldElement<BaseField = Felt>,
402{
403 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404 if self.is_empty() {
405 writeln!(f, "Bus '{}' is empty.", self.bus_name)?;
406 } else {
407 writeln!(f, "Bus '{}' construction failed.", self.bus_name)?;
408
409 if !self.outstanding_requests.is_empty() {
410 writeln!(f, "The following requests are still outstanding:")?;
411 for (_value, msg) in &self.outstanding_requests {
412 writeln!(f, "- {}: {}", msg.source(), msg)?;
413 }
414 }
415
416 if !self.outstanding_responses.is_empty() {
417 writeln!(f, "\nThe following responses are still outstanding:")?;
418 for (_value, msg) in &self.outstanding_responses {
419 writeln!(f, "- {}: {}", msg.source(), msg)?;
420 }
421 }
422 }
423
424 Ok(())
425 }
426}