miden_processor/host/
debug.rs

1use alloc::{
2    string::{String, ToString},
3    vec::Vec,
4};
5use core::{fmt, ops::RangeInclusive};
6
7use miden_core::{DebugOptions, FMP_ADDR};
8
9use crate::{DebugHandler, ExecutionError, Felt, ProcessState};
10
11// WRITER IMPLEMENTATIONS
12// ================================================================================================
13
14/// A wrapper that implements [`fmt::Write`] for `stdout` when the `std` feature is enabled.
15#[derive(Default)]
16pub struct StdoutWriter;
17
18impl fmt::Write for StdoutWriter {
19    fn write_str(&mut self, s: &str) -> fmt::Result {
20        #[cfg(feature = "std")]
21        std::print!("{}", s);
22        Ok(())
23    }
24}
25
26// DEFAULT DEBUG HANDLER IMPLEMENTATION
27// ================================================================================================
28
29/// Default implementation of [`DebugHandler`] that writes debug information to `stdout` when
30/// available.
31pub struct DefaultDebugHandler<W: fmt::Write + Sync = StdoutWriter> {
32    writer: W,
33}
34
35impl Default for DefaultDebugHandler<StdoutWriter> {
36    fn default() -> Self {
37        Self { writer: StdoutWriter }
38    }
39}
40
41impl<W: fmt::Write + Sync> DefaultDebugHandler<W> {
42    /// Creates a new [`DefaultDebugHandler`] with the specified writer.
43    pub fn new(writer: W) -> Self {
44        Self { writer }
45    }
46
47    /// Returns a reference to the writer for accessing writer-specific methods.
48    pub fn writer(&self) -> &W {
49        &self.writer
50    }
51}
52
53impl<W: fmt::Write + Sync> DebugHandler for DefaultDebugHandler<W> {
54    fn on_debug(
55        &mut self,
56        process: &ProcessState,
57        options: &DebugOptions,
58    ) -> Result<(), ExecutionError> {
59        let _ = match *options {
60            DebugOptions::StackAll => {
61                let stack = process.get_stack_state();
62                self.print_stack(&stack, None, "Stack", process)
63            },
64            DebugOptions::StackTop(n) => {
65                let stack = process.get_stack_state();
66                let count = if n == 0 { None } else { Some(n as usize) };
67                self.print_stack(&stack, count, "Stack", process)
68            },
69            DebugOptions::MemAll => self.print_mem_all(process),
70            DebugOptions::MemInterval(n, m) => self.print_mem_interval(process, n..=m),
71            DebugOptions::LocalInterval(n, m, num_locals) => {
72                self.print_local_interval(process, n..=m, num_locals as u32)
73            },
74            DebugOptions::AdvStackTop(n) => {
75                // Reverse the advice stack so last element becomes index 0
76                let stack = process.advice_provider().stack();
77                let reversed_stack: Vec<_> = stack.iter().copied().rev().collect();
78
79                let count = if n == 0 { None } else { Some(n as usize) };
80                self.print_stack(&reversed_stack, count, "Advice stack", process)
81            },
82        };
83        Ok(())
84    }
85
86    fn on_trace(&mut self, process: &ProcessState, trace_id: u32) -> Result<(), ExecutionError> {
87        let _ = writeln!(
88            self.writer,
89            "Trace with id {} emitted at step {} in context {}",
90            trace_id,
91            process.clk(),
92            process.ctx()
93        );
94        Ok(())
95    }
96}
97
98impl<W: fmt::Write + Sync> DefaultDebugHandler<W> {
99    /// Generic stack printing.
100    fn print_stack(
101        &mut self,
102        stack: &[Felt],
103        n: Option<usize>,
104        stack_type: &str,
105        process: &ProcessState,
106    ) -> fmt::Result {
107        if stack.is_empty() {
108            writeln!(self.writer, "{stack_type} empty before step {}.", process.clk())?;
109            return Ok(());
110        }
111
112        // Determine how many items to show
113        let num_items = n.unwrap_or(stack.len());
114
115        // Write header
116        let is_partial = num_items < stack.len();
117        if is_partial {
118            writeln!(
119                self.writer,
120                "{stack_type} state in interval [0, {}] before step {}:",
121                num_items - 1,
122                process.clk()
123            )?
124        } else {
125            writeln!(self.writer, "{stack_type} state before step {}:", process.clk())?
126        }
127
128        // Build stack items for display
129        let mut stack_items = Vec::new();
130        for (i, element) in stack.iter().enumerate().take(num_items) {
131            stack_items.push((i.to_string(), Some(element.to_string())));
132        }
133        // Add extra EMPTY slots if requested more than available
134        for i in stack.len()..num_items {
135            stack_items.push((i.to_string(), None));
136        }
137
138        // Calculate remaining items for partial views
139        let remaining = if num_items < stack.len() {
140            Some(stack.len() - num_items)
141        } else {
142            None
143        };
144
145        self.print_interval(stack_items, remaining)
146    }
147
148    /// Writes the whole memory state at the cycle `clk` in context `ctx`.
149    fn print_mem_all(&mut self, process: &ProcessState) -> fmt::Result {
150        let mem = process.get_mem_state(process.ctx());
151
152        writeln!(
153            self.writer,
154            "Memory state before step {} for the context {}:",
155            process.clk(),
156            process.ctx()
157        )?;
158
159        let mem_items: Vec<_> = mem
160            .into_iter()
161            .map(|(addr, value)| (format!("{addr:#010x}"), Some(value.to_string())))
162            .collect();
163
164        self.print_interval(mem_items, None)?;
165        Ok(())
166    }
167
168    /// Writes memory values in the provided addresses interval.
169    fn print_mem_interval(
170        &mut self,
171        process: &ProcessState,
172        range: RangeInclusive<u32>,
173    ) -> fmt::Result {
174        let start = *range.start();
175        let end = *range.end();
176
177        if start == end {
178            let value = process.get_mem_value(process.ctx(), start);
179            let value_str = format_value(value);
180            writeln!(
181                self.writer,
182                "Memory state before step {} for the context {} at address {:#010x}: {value_str}",
183                process.clk(),
184                process.ctx(),
185                start
186            )
187        } else {
188            writeln!(
189                self.writer,
190                "Memory state before step {} for the context {} in the interval [{}, {}]:",
191                process.clk(),
192                process.ctx(),
193                start,
194                end
195            )?;
196            let mem_items: Vec<_> = range
197                .map(|addr| {
198                    let value = process.get_mem_value(process.ctx(), addr);
199                    let addr_str = format!("{addr:#010x}");
200                    let value_str = value.map(|v| v.to_string());
201                    (addr_str, value_str)
202                })
203                .collect();
204
205            self.print_interval(mem_items, None)
206        }
207    }
208
209    /// Writes locals in provided indexes interval.
210    ///
211    /// The interval given is inclusive on *both* ends.
212    fn print_local_interval(
213        &mut self,
214        process: &ProcessState,
215        range: RangeInclusive<u16>,
216        num_locals: u32,
217    ) -> fmt::Result {
218        let local_memory_offset = {
219            let fmp = process
220                .get_mem_value(process.ctx(), FMP_ADDR.as_int() as u32)
221                .expect("FMP address is empty");
222
223            fmp.as_int() as u32 - num_locals
224        };
225
226        let start = *range.start() as u32;
227        let end = *range.end() as u32;
228
229        if start == end {
230            let addr = local_memory_offset + start;
231            let value = process.get_mem_value(process.ctx(), addr);
232            let value_str = format_value(value);
233
234            writeln!(
235                self.writer,
236                "State of procedure local {start} before step {}: {value_str}",
237                process.clk(),
238            )
239        } else {
240            writeln!(
241                self.writer,
242                "State of procedure locals [{start}, {end}] before step {}:",
243                process.clk()
244            )?;
245            let local_items: Vec<_> = range
246                .map(|local_idx| {
247                    let addr = local_memory_offset + local_idx as u32;
248                    let value = process.get_mem_value(process.ctx(), addr);
249                    let addr_str = local_idx.to_string();
250                    let value_str = value.map(|v| v.to_string());
251                    (addr_str, value_str)
252                })
253                .collect();
254
255            self.print_interval(local_items, None)
256        }
257    }
258
259    /// Writes a generic interval with proper alignment and optional remaining count.
260    ///
261    /// Takes a vector of (address_string, optional_value_string) pairs where:
262    /// - address_string: The address as a string (not pre-padded)
263    /// - optional_value_string: Some(value) or None (prints "EMPTY")
264    /// - remaining: Optional count of remaining items to show as "(N more items)"
265    fn print_interval(
266        &mut self,
267        items: Vec<(String, Option<String>)>,
268        remaining: Option<usize>,
269    ) -> fmt::Result {
270        // Find the maximum address width for proper alignment
271        let max_addr_width = items.iter().map(|(addr, _)| addr.len()).max().unwrap_or(0);
272
273        // Collect formatted items
274        let mut formatted_items: Vec<String> = items
275            .into_iter()
276            .map(|(addr, value_opt)| {
277                let value_string = format_value(value_opt);
278                format!("{addr:>width$}: {value_string}", width = max_addr_width)
279            })
280            .collect();
281
282        // Add remaining count if specified
283        if let Some(count) = remaining {
284            formatted_items.push(format!("({count} more items)"));
285        }
286
287        // Prints a list of items with proper tree-style indentation.
288        // All items except the last are prefixed with "├── ", and the last item with "└── ".
289        if let Some((last, front)) = formatted_items.split_last() {
290            // Print all items except the last with "├── " prefix
291            for item in front {
292                writeln!(self.writer, "├── {item}")?;
293            }
294            // Print the last item with "└── " prefix
295            writeln!(self.writer, "└── {last}")?;
296        }
297
298        Ok(())
299    }
300}
301
302// HELPER FUNCTIONS
303// ================================================================================================
304
305/// Formats a value as a string, using "EMPTY" for None values.
306fn format_value<T: ToString>(value: Option<T>) -> String {
307    value.map(|v| v.to_string()).unwrap_or_else(|| "EMPTY".to_string())
308}