Skip to main content

miden_processor/host/
debug.rs

1use alloc::{
2    string::{String, ToString},
3    vec::Vec,
4};
5use core::{fmt, ops::RangeInclusive};
6
7use miden_core::{FMP_ADDR, Felt, operations::DebugOptions};
8
9use crate::{DebugError, ProcessorState, TraceError, host::handlers::DebugHandler};
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: &ProcessorState,
57        options: &DebugOptions,
58    ) -> Result<(), DebugError> {
59        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                // .stack() already returns elements from top (index 0) to bottom
76                let stack = process.advice_provider().stack();
77                let count = if n == 0 { None } else { Some(n as usize) };
78                self.print_stack(&stack, count, "Advice stack", process)
79            },
80        }
81        .map_err(DebugError::from)
82    }
83
84    fn on_trace(&mut self, process: &ProcessorState, trace_id: u32) -> Result<(), TraceError> {
85        writeln!(
86            self.writer,
87            "Trace with id {} emitted at step {} in context {}",
88            trace_id,
89            process.clock(),
90            process.ctx()
91        )
92        .map_err(TraceError::from)
93    }
94}
95
96impl<W: fmt::Write + Sync> DefaultDebugHandler<W> {
97    /// Generic stack printing.
98    fn print_stack(
99        &mut self,
100        stack: &[Felt],
101        n: Option<usize>,
102        stack_type: &str,
103        process: &ProcessorState,
104    ) -> fmt::Result {
105        if stack.is_empty() {
106            writeln!(self.writer, "{stack_type} empty before step {}.", process.clock())?;
107            return Ok(());
108        }
109
110        // Determine how many items to show
111        let num_items = n.unwrap_or(stack.len());
112
113        // Write header
114        let is_partial = num_items < stack.len();
115        if is_partial {
116            writeln!(
117                self.writer,
118                "{stack_type} state in interval [0, {}] before step {}:",
119                num_items - 1,
120                process.clock()
121            )?
122        } else {
123            writeln!(self.writer, "{stack_type} state before step {}:", process.clock())?
124        }
125
126        // Build stack items for display
127        let mut stack_items = Vec::new();
128        for (i, element) in stack.iter().enumerate().take(num_items) {
129            stack_items.push((i.to_string(), Some(element.to_string())));
130        }
131        // Add extra EMPTY slots if requested more than available
132        for i in stack.len()..num_items {
133            stack_items.push((i.to_string(), None));
134        }
135
136        // Calculate remaining items for partial views
137        let remaining = if num_items < stack.len() {
138            Some(stack.len() - num_items)
139        } else {
140            None
141        };
142
143        self.print_interval(stack_items, remaining)
144    }
145
146    /// Writes the whole memory state at the cycle `clk` in context `ctx`.
147    fn print_mem_all(&mut self, process: &ProcessorState) -> fmt::Result {
148        let mem = process.get_mem_state(process.ctx());
149
150        writeln!(
151            self.writer,
152            "Memory state before step {} for the context {}:",
153            process.clock(),
154            process.ctx()
155        )?;
156
157        let mem_items: Vec<_> = mem
158            .into_iter()
159            .map(|(addr, value)| (format!("{addr:#010x}"), Some(value.to_string())))
160            .collect();
161
162        self.print_interval(mem_items, None)?;
163        Ok(())
164    }
165
166    /// Writes memory values in the provided addresses interval.
167    fn print_mem_interval(
168        &mut self,
169        process: &ProcessorState,
170        range: RangeInclusive<u32>,
171    ) -> fmt::Result {
172        let start = *range.start();
173        let end = *range.end();
174
175        if start == end {
176            let value = process.get_mem_value(process.ctx(), start);
177            let value_str = format_value(value);
178            writeln!(
179                self.writer,
180                "Memory state before step {} for the context {} at address {:#010x}: {value_str}",
181                process.clock(),
182                process.ctx(),
183                start
184            )
185        } else {
186            writeln!(
187                self.writer,
188                "Memory state before step {} for the context {} in the interval [{}, {}]:",
189                process.clock(),
190                process.ctx(),
191                start,
192                end
193            )?;
194            let mem_items: Vec<_> = range
195                .map(|addr| {
196                    let value = process.get_mem_value(process.ctx(), addr);
197                    let addr_str = format!("{addr:#010x}");
198                    let value_str = value.map(|v| v.to_string());
199                    (addr_str, value_str)
200                })
201                .collect();
202
203            self.print_interval(mem_items, None)
204        }
205    }
206
207    /// Writes locals in provided indexes interval.
208    ///
209    /// The interval given is inclusive on *both* ends.
210    fn print_local_interval(
211        &mut self,
212        process: &ProcessorState,
213        range: RangeInclusive<u16>,
214        num_locals: u32,
215    ) -> fmt::Result {
216        let local_memory_offset = {
217            let fmp = process
218                .get_mem_value(process.ctx(), FMP_ADDR.as_canonical_u64() as u32)
219                .expect("FMP address is empty");
220
221            fmp.as_canonical_u64() as u32 - num_locals
222        };
223
224        let start = *range.start() as u32;
225        let end = *range.end() as u32;
226
227        if start == end {
228            let addr = local_memory_offset + start;
229            let value = process.get_mem_value(process.ctx(), addr);
230            let value_str = format_value(value);
231
232            writeln!(
233                self.writer,
234                "State of procedure local {start} before step {}: {value_str}",
235                process.clock(),
236            )
237        } else {
238            writeln!(
239                self.writer,
240                "State of procedure locals [{start}, {end}] before step {}:",
241                process.clock()
242            )?;
243            let local_items: Vec<_> = range
244                .map(|local_idx| {
245                    let addr = local_memory_offset + local_idx as u32;
246                    let value = process.get_mem_value(process.ctx(), addr);
247                    let addr_str = local_idx.to_string();
248                    let value_str = value.map(|v| v.to_string());
249                    (addr_str, value_str)
250                })
251                .collect();
252
253            self.print_interval(local_items, None)
254        }
255    }
256
257    /// Writes a generic interval with proper alignment and optional remaining count.
258    ///
259    /// Takes a vector of (address_string, optional_value_string) pairs where:
260    /// - address_string: The address as a string (not pre-padded)
261    /// - optional_value_string: Some(value) or None (prints "EMPTY")
262    /// - remaining: Optional count of remaining items to show as "(N more items)"
263    fn print_interval(
264        &mut self,
265        items: Vec<(String, Option<String>)>,
266        remaining: Option<usize>,
267    ) -> fmt::Result {
268        // Find the maximum address width for proper alignment
269        let max_addr_width = items.iter().map(|(addr, _)| addr.len()).max().unwrap_or(0);
270
271        // Collect formatted items
272        let mut formatted_items: Vec<String> = items
273            .into_iter()
274            .map(|(addr, value_opt)| {
275                let value_string = format_value(value_opt);
276                format!("{addr:>width$}: {value_string}", width = max_addr_width)
277            })
278            .collect();
279
280        // Add remaining count if specified
281        if let Some(count) = remaining {
282            formatted_items.push(format!("({count} more items)"));
283        }
284
285        // Prints a list of items with proper tree-style indentation.
286        // All items except the last are prefixed with "├── ", and the last item with "└── ".
287        if let Some((last, front)) = formatted_items.split_last() {
288            // Print all items except the last with "├── " prefix
289            for item in front {
290                writeln!(self.writer, "├── {item}")?;
291            }
292            // Print the last item with "└── " prefix
293            writeln!(self.writer, "└── {last}")?;
294        }
295
296        Ok(())
297    }
298}
299
300// HELPER FUNCTIONS
301// ================================================================================================
302
303/// Formats a value as a string, using "EMPTY" for None values.
304fn format_value<T: ToString>(value: Option<T>) -> String {
305    value.map(|v| v.to_string()).unwrap_or_else(|| "EMPTY".to_string())
306}