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