Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 6 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ crossterm = { version = "0.28.1", optional = true, features = ["event-stream"] }
env_logger = { version = "0.11", optional = true }
log = "0.4"
glob = { version = "0.3.1", optional = true }
miden-assembly = { version = "=0.21.1", default-features = false }
miden-assembly-syntax = { version = "=0.21.1", default-features = false }
miden-core = { version = "=0.21.1", default-features = false }
miden-debug-types = { version = "=0.21.1", default-features = false }
miden-mast-package = { version = "=0.21.1", default-features = false }
miden-processor = { version = "=0.21.1", default-features = false }
miden-assembly = { version = "0.21", default-features = false }
miden-assembly-syntax = { version = "0.21", default-features = false }
miden-core = { version = "0.21", default-features = false }
miden-debug-types = { version = "0.21", default-features = false }
miden-mast-package = { version = "0.21", default-features = false }
miden-processor = { version = "0.21", default-features = false }
num-traits = "0.2"
ratatui = { version = "0.29.0", optional = true }
rustc-demangle = { version = "0.1", features = ["std"] }
Expand Down Expand Up @@ -72,5 +72,3 @@ tokio-util = "0.7.11"
futures = "0.3.30"
proptest = { version = "1.4", optional = true }

# Pin miden-crypto to match what miden-vm v0.21.1 requires
miden-crypto = { version = "=0.22.3", default-features = false }
25 changes: 25 additions & 0 deletions src/debug/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,28 @@ impl clap::builder::TypedValueParser for FormatTypeParser {
value.parse().map_err(|err| Error::raw(ErrorKind::InvalidValue, err))
}
}

#[cfg(test)]
mod tests {
use super::FormatType;
use crate::test_utils::write_scalar_bytes;

#[test]
fn write_scalar_bytes_reads_little_endian_u64() {
let mut output = String::new();

write_scalar_bytes(&mut output, "u64", FormatType::Decimal, &[1, 2, 3, 4, 5, 6, 7, 8])
.unwrap();

assert_eq!(output, u64::from_le_bytes([1, 2, 3, 4, 5, 6, 7, 8]).to_string());
}

#[test]
fn write_scalar_bytes_reads_little_endian_u16_hex() {
let mut output = String::new();

write_scalar_bytes(&mut output, "u16", FormatType::Hex, &[0x34, 0x12]).unwrap();

assert_eq!(output, "1234");
}
}
6 changes: 3 additions & 3 deletions src/exec/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl ExecutionConfig {
let inputs =
StackInputs::new(&felts).map_err(|err| format!("invalid value for 'stack': {err}"))?;
let advice_inputs = AdviceInputs::default()
.with_stack(file.inputs.advice.stack.into_iter().rev().map(|felt| felt.0))
.with_stack(file.inputs.advice.stack.into_iter().map(|felt| felt.0))
.with_map(file.inputs.advice.map.into_iter().map(|entry| {
(entry.digest.0, entry.values.into_iter().map(|felt| felt.0).collect::<Vec<_>>())
}));
Expand Down Expand Up @@ -182,7 +182,7 @@ mod tests {
use miden_processor::Felt as RawFelt;
use toml::toml;

use super::*;
use super::{ExecutionConfig, *};

#[test]
fn execution_config_empty() {
Expand Down Expand Up @@ -270,7 +270,7 @@ mod tests {
assert_eq!(file.inputs.as_ref(), expected_inputs.as_ref());
assert_eq!(
file.advice_inputs.stack,
&[RawFelt::new(4), RawFelt::new(3), RawFelt::new(2), RawFelt::new(1)]
&[RawFelt::new(1), RawFelt::new(2), RawFelt::new(3), RawFelt::new(4)]
);
assert_eq!(
file.advice_inputs.map.get(&digest).map(|value| value.as_ref()),
Expand Down
20 changes: 19 additions & 1 deletion src/exec/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ use miden_mast_package::{
MemDependencyResolverByDigest, ResolvedDependency,
};
use miden_processor::{
ContextId, ExecutionError, ExecutionOptions, FastProcessor, Felt, advice::AdviceInputs,
ContextId, ExecutionError, ExecutionOptions, FastProcessor, Felt,
advice::AdviceInputs,
event::{EventHandler, EventName},
trace::RowIndex,
};

Expand All @@ -33,6 +35,7 @@ pub struct Executor {
advice: AdviceInputs,
options: ExecutionOptions,
libraries: Vec<Arc<Library>>,
event_handlers: Vec<(EventName, Arc<dyn EventHandler>)>,
dependency_resolver: MemDependencyResolverByDigest,
}
impl Executor {
Expand Down Expand Up @@ -63,6 +66,7 @@ impl Executor {
advice: advice_inputs,
options,
libraries: Default::default(),
event_handlers: Default::default(),
dependency_resolver,
}
}
Expand Down Expand Up @@ -132,6 +136,16 @@ impl Executor {
self
}

/// Register a VM event handler to be available during execution.
pub fn register_event_handler(
&mut self,
event: EventName,
handler: Arc<dyn EventHandler>,
) -> Result<&mut Self, ExecutionError> {
self.event_handlers.push((event, handler));
Ok(self)
}

/// Convert this [Executor] into a [DebugExecutor], which captures much more information
/// about the program being executed, and must be stepped manually.
pub fn into_debug(
Expand All @@ -145,6 +159,10 @@ impl Executor {
for lib in core::mem::take(&mut self.libraries) {
host.load_mast_forest(lib.mast_forest().clone());
}
for (event, handler) in core::mem::take(&mut self.event_handlers) {
host.register_event_handler(event, handler)
.expect("failed to register debug executor event handler");
}

let trace_events: Rc<RefCell<BTreeMap<RowIndex, TraceEvent>>> = Rc::new(Default::default());
let frame_start_events = Rc::clone(&trace_events);
Expand Down
44 changes: 39 additions & 5 deletions src/exec/host.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
use std::{collections::BTreeMap, num::NonZeroU32, sync::Arc};

use miden_assembly::SourceManager;
use miden_core::Word;
use miden_core::{
Word,
events::{EventId, EventName},
};
use miden_debug_types::{Location, SourceFile, SourceSpan};
use miden_processor::{
FutureMaybeSend, Host, MastForestStore, MemMastForestStore, ProcessorState, TraceError,
advice::AdviceMutation, event::EventError, mast::MastForest, trace::RowIndex,
ExecutionError, FutureMaybeSend, Host, MastForestStore, MemMastForestStore, ProcessorState,
TraceError,
advice::AdviceMutation,
event::{EventError, EventHandler, EventHandlerRegistry},
mast::MastForest,
trace::RowIndex,
};

use super::{TraceEvent, TraceHandler};
Expand All @@ -15,6 +22,7 @@ use super::{TraceEvent, TraceHandler};
/// events that record the entry or exit of a procedure call frame.
pub struct DebuggerHost<S: SourceManager + ?Sized> {
store: MemMastForestStore,
event_handlers: EventHandlerRegistry,
tracing_callbacks: BTreeMap<u32, Vec<Box<TraceHandler>>>,
on_assert_failed: Option<Box<TraceHandler>>,
source_manager: Arc<S>,
Expand All @@ -27,6 +35,7 @@ where
pub fn new(source_manager: Arc<S>) -> Self {
Self {
store: Default::default(),
event_handlers: EventHandlerRegistry::default(),
tracing_callbacks: Default::default(),
on_assert_failed: None,
source_manager,
Expand Down Expand Up @@ -67,6 +76,15 @@ where
pub fn load_mast_forest(&mut self, forest: Arc<MastForest>) {
self.store.insert(forest);
}

/// Registers an event handler for use during program execution.
pub fn register_event_handler(
&mut self,
event: EventName,
handler: Arc<dyn EventHandler>,
) -> Result<(), ExecutionError> {
self.event_handlers.register(event, handler)
}
}

impl<S> Host for DebuggerHost<S>
Expand All @@ -88,9 +106,21 @@ where

fn on_event(
&mut self,
_process: &ProcessorState<'_>,
process: &ProcessorState<'_>,
) -> impl FutureMaybeSend<Result<Vec<AdviceMutation>, EventError>> {
std::future::ready(Ok(Vec::new()))
let event_id = EventId::from_felt(process.get_stack_item(0));
let result = match self.event_handlers.handle_event(event_id, process) {
Ok(Some(mutations)) => Ok(mutations),
Ok(None) => {
#[derive(Debug, thiserror::Error)]
#[error("no event handler registered")]
struct UnhandledEvent;

Err(UnhandledEvent.into())
}
Err(err) => Err(err),
};
std::future::ready(result)
}

fn on_trace(&mut self, process: &ProcessorState<'_>, trace_id: u32) -> Result<(), TraceError> {
Expand All @@ -103,4 +133,8 @@ where
}
Ok(())
}

fn resolve_event(&self, event_id: EventId) -> Option<&EventName> {
self.event_handlers.resolve_event(event_id)
}
}
117 changes: 81 additions & 36 deletions src/exec/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ impl ExecutionTrace {

let mut needed = size - buf.len();
for elem in elems {
let bytes = ((elem.as_canonical_u64() & U32_MASK) as u32).to_be_bytes();
let bytes = ((elem.as_canonical_u64() & U32_MASK) as u32).to_le_bytes();
let take = core::cmp::min(needed, 4);
buf.extend(&bytes[0..take]);
needed -= take;
Expand Down Expand Up @@ -154,43 +154,88 @@ impl ExecutionTrace {
where
T: core::any::Any + FromMidenRepr,
{
use core::any::TypeId;

let ptr = NativePtr::from_ptr(addr);
if TypeId::of::<T>() == TypeId::of::<Felt>() {
assert_eq!(ptr.offset, 0, "cannot read values of type Felt from unaligned addresses");
}
assert_eq!(ptr.offset, 0, "support for unaligned reads is not yet implemented");
match <T as FromMidenRepr>::size_in_felts() {
1 => {
let felt = self.read_memory_element_in_context(ptr.addr, ctx, clk)?;
Some(T::from_felts(&[felt]))
}
2 => {
let lo = self.read_memory_element_in_context(ptr.addr, ctx, clk)?;
let hi = self.read_memory_element_in_context(ptr.addr + 1, ctx, clk)?;
Some(T::from_felts(&[lo, hi]))
}
3 => {
let lo_l = self.read_memory_element_in_context(ptr.addr, ctx, clk)?;
let lo_h = self.read_memory_element_in_context(ptr.addr + 1, ctx, clk)?;
let hi_l = self.read_memory_element_in_context(ptr.addr + 2, ctx, clk)?;
Some(T::from_felts(&[lo_l, lo_h, hi_l]))
}
n => {
assert_ne!(n, 0);
let num_words = n.next_multiple_of(4) / 4;
let mut words = SmallVec::<[_; 2]>::with_capacity(num_words);
for word_index in 0..(num_words as u32) {
let addr = ptr.addr + (word_index * 4);
let mut word = self.read_memory_word(addr)?;
word.reverse();
dbg!(word_index, word);
words.push(word);
}
words.resize(num_words, Word::new([Felt::ZERO; 4]));
Some(T::from_words(&words))
}
let size = <T as FromMidenRepr>::size_in_felts();
let mut felts = SmallVec::<[_; 4]>::with_capacity(size);
for index in 0..(size as u32) {
felts.push(self.read_memory_element_in_context(ptr.addr + index, ctx, clk)?);
}
Some(T::from_felts(&felts))
}
}

#[cfg(test)]
mod tests {
use std::sync::Arc;

use miden_assembly::DefaultSourceManager;
use miden_assembly_syntax::ast::types::Type;
use miden_processor::{ContextId, trace::RowIndex};

use super::ExecutionTrace;
use crate::{Executor, debug::NativePtr, felt::ToMidenRepr};

fn empty_trace() -> ExecutionTrace {
ExecutionTrace {
root_context: ContextId::root(),
last_cycle: RowIndex::from(0_u32),
processor: miden_processor::FastProcessor::new(miden_processor::StackInputs::default()),
outputs: miden_processor::StackOutputs::default(),
}
}

fn execute_trace(source: &str) -> ExecutionTrace {
let source_manager = Arc::new(DefaultSourceManager::default());
let program = miden_assembly::Assembler::new(source_manager.clone())
.assemble_program(source)
.unwrap();

Executor::new(vec![]).capture_trace(&program, source_manager)
}

#[test]
fn parse_result_reads_multi_felt_outputs_in_stack_order() {
let outputs = 0x0807_0605_0403_0201_u64.to_felts();
let trace = ExecutionTrace {
outputs: miden_processor::StackOutputs::new(&outputs).unwrap(),
..empty_trace()
};

let result = trace.parse_result::<u64>().unwrap();

assert_eq!(result, 0x0807_0605_0403_0201_u64);
}

#[test]
fn read_bytes_for_type_preserves_little_endian_bytes() {
let trace = execute_trace(
r#"
begin
push.4660
push.8
mem_store

push.67305985
push.12
mem_store

push.134678021
push.13
mem_store
end
"#,
);
let ctx = ContextId::root();

let u16_bytes = trace
.read_bytes_for_type(NativePtr::new(8, 0), &Type::U16, ctx, RowIndex::from(0_u32))
.unwrap();
let u64_bytes = trace
.read_bytes_for_type(NativePtr::new(12, 0), &Type::U64, ctx, RowIndex::from(0_u32))
.unwrap();

assert_eq!(u16_bytes, vec![0x34, 0x12]);
assert_eq!(u64_bytes, vec![1, 2, 3, 4, 5, 6, 7, 8]);
}
}
Loading