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