use alloc::{
format,
string::{String, ToString},
sync::Arc,
vec,
vec::Vec,
};
use core::fmt;
use miden_core::{Felt, Word};
use miden_processor::{
MemoryError, ProcessorState, StdoutWriter,
advice::AdviceMutation,
event::{EventError, EventHandler, EventId, EventName},
write_interval, write_stack,
};
use miden_utils_sync::RwLock;
pub const PRINT_STACK_EVENT_NAME: EventName = EventName::new("miden::core::debug::print_stack");
pub const PRINT_MEM_EVENT_NAME: EventName = EventName::new("miden::core::debug::print_mem");
pub const PRINT_MEM_ALL_EVENT_NAME: EventName = EventName::new("miden::core::debug::print_mem_all");
pub const PRINT_ADV_STACK_EVENT_NAME: EventName =
EventName::new("miden::core::debug::print_adv_stack");
pub const PRINT_ADV_MAP_EVENT_NAME: EventName = EventName::new("miden::core::debug::print_adv_map");
pub const PRINT_ADV_MAP_ITEM_EVENT_NAME: EventName =
EventName::new("miden::core::debug::print_adv_map_item");
const MAX_PRINT_MEM_RANGE: u64 = 1024;
pub fn default_debug_handlers() -> Vec<(EventName, Arc<dyn EventHandler>)> {
let printer: Arc<dyn EventHandler> = Arc::new(DebugPrinter::default());
vec![
(PRINT_STACK_EVENT_NAME, printer.clone()),
(PRINT_MEM_EVENT_NAME, printer.clone()),
(PRINT_MEM_ALL_EVENT_NAME, printer),
]
}
pub fn noop_debug_handlers() -> Vec<(EventName, Arc<dyn EventHandler>)> {
let handler: Arc<dyn EventHandler> = Arc::new(NoopDebugHandler);
vec![
(PRINT_STACK_EVENT_NAME, handler.clone()),
(PRINT_MEM_EVENT_NAME, handler.clone()),
(PRINT_MEM_ALL_EVENT_NAME, handler.clone()),
(PRINT_ADV_STACK_EVENT_NAME, handler.clone()),
(PRINT_ADV_MAP_EVENT_NAME, handler.clone()),
(PRINT_ADV_MAP_ITEM_EVENT_NAME, handler),
]
}
pub fn debug_handlers() -> Vec<(EventName, Arc<dyn EventHandler>)> {
let printer: Arc<dyn EventHandler> = Arc::new(DebugPrinter::default());
vec![
(PRINT_STACK_EVENT_NAME, printer.clone()),
(PRINT_MEM_EVENT_NAME, printer.clone()),
(PRINT_MEM_ALL_EVENT_NAME, printer.clone()),
(PRINT_ADV_STACK_EVENT_NAME, printer.clone()),
(PRINT_ADV_MAP_EVENT_NAME, printer.clone()),
(PRINT_ADV_MAP_ITEM_EVENT_NAME, printer),
]
}
pub fn advice_debug_handlers() -> Vec<(EventName, Arc<dyn EventHandler>)> {
let printer: Arc<dyn EventHandler> = Arc::new(DebugPrinter::default());
vec![
(PRINT_ADV_STACK_EVENT_NAME, printer.clone()),
(PRINT_ADV_MAP_EVENT_NAME, printer.clone()),
(PRINT_ADV_MAP_ITEM_EVENT_NAME, printer),
]
}
pub struct DebugPrinter<W: fmt::Write + Send + Sync = StdoutWriter> {
writer: RwLock<W>,
}
impl Default for DebugPrinter<StdoutWriter> {
fn default() -> Self {
Self { writer: RwLock::new(StdoutWriter) }
}
}
impl<W: fmt::Write + Send + Sync> DebugPrinter<W> {
pub fn new(writer: W) -> Self {
Self { writer: RwLock::new(writer) }
}
}
impl<W: fmt::Write + Send + Sync + 'static> EventHandler for DebugPrinter<W> {
fn on_event(&self, process: &ProcessorState) -> Result<Vec<AdviceMutation>, EventError> {
let id = EventId::from_felt(process.get_stack_item(0));
let mut writer = self.writer.write();
let w: &mut W = &mut writer;
if id == PRINT_STACK_EVENT_NAME.to_event_id() {
let stack = process.get_stack_state();
let operand_stack = stack.get(1..).unwrap_or(&[]);
write_stack(w, operand_stack, None, "Stack", process.clock())?;
} else if id == PRINT_MEM_EVENT_NAME.to_event_id() {
let bounds = read_mem_print_range(process, 1, 2)?;
if let Some((first, last)) = bounds {
let len = u64::from(last - first) + 1;
if len > MAX_PRINT_MEM_RANGE {
return Err(format!(
"print_mem range length {len} exceeds maximum of {MAX_PRINT_MEM_RANGE}"
)
.into());
}
}
write_mem_range(w, process, bounds)?;
} else if id == PRINT_MEM_ALL_EVENT_NAME.to_event_id() {
write_mem_all(w, process)?;
} else if id == PRINT_ADV_STACK_EVENT_NAME.to_event_id() {
let start = stack_item_as_usize(process, 1);
let end = stack_item_as_usize(process, 2);
let adv_stack = process.advice_provider().stack();
let slice = slice_range(&adv_stack, start, end);
write_stack(w, slice, None, "Advice stack", process.clock())?;
} else if id == PRINT_ADV_MAP_EVENT_NAME.to_event_id() {
write_adv_map(w, process)?;
} else if id == PRINT_ADV_MAP_ITEM_EVENT_NAME.to_event_id() {
write_adv_map_entry(w, process)?;
}
Ok(Vec::new())
}
}
struct NoopDebugHandler;
impl EventHandler for NoopDebugHandler {
fn on_event(&self, _process: &ProcessorState) -> Result<Vec<AdviceMutation>, EventError> {
Ok(Vec::new())
}
}
fn stack_item_as_usize(process: &ProcessorState, pos: usize) -> usize {
usize::try_from(process.get_stack_item(pos).as_canonical_u64()).unwrap_or(usize::MAX)
}
fn slice_range(slice: &[Felt], start: usize, end: usize) -> &[Felt] {
let len = slice.len();
let start = start.min(len);
let end = end.clamp(start, len);
&slice[start..end]
}
fn read_mem_print_range(
process: &ProcessorState,
start_idx: usize,
end_idx: usize,
) -> Result<Option<(u32, u32)>, MemoryError> {
let start_addr = process.get_stack_item(start_idx).as_canonical_u64();
let end_addr = process.get_stack_item(end_idx).as_canonical_u64();
if start_addr > u32::MAX as u64 {
return Err(MemoryError::AddressOutOfBounds { addr: start_addr });
}
if end_addr > u32::MAX as u64 + 1 {
return Err(MemoryError::AddressOutOfBounds { addr: end_addr });
}
if start_addr > end_addr {
return Err(MemoryError::InvalidMemoryRange { start_addr, end_addr });
}
if start_addr == end_addr {
Ok(None)
} else {
Ok(Some((start_addr as u32, (end_addr - 1) as u32)))
}
}
fn write_mem_range<W: fmt::Write>(
w: &mut W,
process: &ProcessorState,
bounds: Option<(u32, u32)>,
) -> fmt::Result {
let (ctx, clk) = (process.ctx(), process.clock());
let Some((start, end)) = bounds else {
return writeln!(w, "Memory state before step {clk} for context {ctx}: range is empty.");
};
writeln!(
w,
"Memory state before step {clk} for context {ctx} in the range [{start}, {end}]:",
)?;
let items: Vec<_> = (start..=end)
.map(|addr| {
let value = process.get_mem_value(ctx, addr).map(|v| v.to_string());
(format!("{addr:#010x}"), value)
})
.collect();
write_interval(w, items, None)
}
fn write_mem_all<W: fmt::Write>(w: &mut W, process: &ProcessorState) -> fmt::Result {
let (ctx, clk) = (process.ctx(), process.clock());
writeln!(w, "Memory state before step {clk} for context {ctx}:")?;
let items: Vec<_> = process
.get_mem_state(ctx)
.into_iter()
.map(|(addr, value)| (format!("{addr:#010x}"), Some(value.to_string())))
.collect();
write_interval(w, items, None)
}
fn write_adv_map<W: fmt::Write>(w: &mut W, process: &ProcessorState) -> fmt::Result {
let clk = process.clock();
let map = process.advice_provider().map();
if map.is_empty() {
return writeln!(w, "Advice map before step {clk}: empty.");
}
writeln!(w, "Advice map before step {clk}:")?;
let items: Vec<_> = map
.iter()
.map(|(key, values)| (format_word(key), Some(format_felt_slice(values))))
.collect();
write_interval(w, items, None)
}
fn write_adv_map_entry<W: fmt::Write>(w: &mut W, process: &ProcessorState) -> fmt::Result {
let key = process.get_stack_word(1);
let key_str = format_word(&key);
let clk = process.clock();
match process.advice_provider().get_mapped_values(&key) {
Some(values) => {
writeln!(w, "Advice map entry for key {key_str} before step {clk}:")?;
let items: Vec<_> = values
.iter()
.enumerate()
.map(|(i, v)| (i.to_string(), Some(v.to_string())))
.collect();
write_interval(w, items, None)
},
None => writeln!(w, "No advice map entry for key {key_str} before step {clk}."),
}
}
fn format_word(word: &Word) -> String {
format!("[{}, {}, {}, {}]", word[0], word[1], word[2], word[3])
}
fn format_felt_slice(values: &[Felt]) -> String {
let mut out = String::from("[");
for (idx, value) in values.iter().enumerate() {
if idx > 0 {
out.push_str(", ");
}
out.push_str(&value.to_string());
}
out.push(']');
out
}