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