Skip to content

Commit

Permalink
chore: clean up naming in midenc-debug
Browse files Browse the repository at this point in the history
  • Loading branch information
bitwalker committed Aug 15, 2024
1 parent e1decee commit cc6c021
Show file tree
Hide file tree
Showing 20 changed files with 762 additions and 676 deletions.
6 changes: 3 additions & 3 deletions midenc-debug/src/runner.rs → midenc-debug/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use midenc_session::{

/// Run a compiled Miden program with the Miden VM
#[derive(Default, Debug, Parser)]
#[command(name = "run")]
pub struct Runner {
#[command(name = "debug")]
pub struct Debugger {
/// The working directory for the compiler
///
/// By default this will be the working directory the compiler is executed from
Expand Down Expand Up @@ -79,7 +79,7 @@ pub struct Runner {
pub link_libraries: Vec<LinkLibrary>,
}

impl Runner {
impl Debugger {
/// Construct a [Compiler] programatically
pub fn new_session<I, A, S>(inputs: I, emitter: Option<Arc<dyn Emitter>>, argv: A) -> Session
where
Expand Down
289 changes: 289 additions & 0 deletions midenc-debug/src/exec/executor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
use std::{
cell::RefCell,
collections::{BTreeMap, BTreeSet, VecDeque},
rc::Rc,
};

use miden_assembly::Library as CompiledLibrary;
use miden_core::{Program, StackInputs, Word};
use miden_processor::{
AdviceInputs, ContextId, ExecutionError, Felt, MastForest, MemAdviceProvider, Process,
ProcessState, RowIndex, StackOutputs, VmState, VmStateIterator,
};
use midenc_codegen_masm::NativePtr;
use midenc_hir::Type;
use midenc_session::Session;

use super::{DebugExecutor, DebuggerHost, ExecutionTrace, TraceEvent};
use crate::{debug::CallStack, felt::PopFromStack, TestFelt};

/// The [Executor] is responsible for executing a program with the Miden VM.
///
/// It is used by either converting it into a [DebugExecutor], and using that to
/// manage execution step-by-step, such as is done by the debugger; or by running
/// the program to completion and obtaining an [ExecutionTrace], which can be used
/// to introspect the final program state.
pub struct Executor {
stack: StackInputs,
advice: AdviceInputs,
libraries: Vec<MastForest>,
}
impl Executor {
/// Construct an executor with the given arguments on the operand stack
pub fn new(args: Vec<Felt>) -> Self {
Self {
stack: StackInputs::new(args).expect("invalid stack inputs"),
advice: AdviceInputs::default(),
libraries: Default::default(),
}
}

/// Set the contents of memory for the shadow stack frame of the entrypoint
pub fn with_advice_inputs(&mut self, advice: AdviceInputs) -> &mut Self {
self.advice.extend(advice);
self
}

/// Add a [CompiledLibrary] to the execution context
pub fn with_library(&mut self, lib: &CompiledLibrary) -> &mut Self {
self.libraries.push(lib.mast_forest().clone());
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(mut self, program: &Program, session: &Session) -> DebugExecutor {
let advice_provider = MemAdviceProvider::from(self.advice);
let mut host = DebuggerHost::new(advice_provider);
for lib in core::mem::take(&mut self.libraries) {
host.load_mast_forest(lib);
}

let trace_events: Rc<RefCell<BTreeMap<RowIndex, TraceEvent>>> = Rc::new(Default::default());
let frame_start_events = Rc::clone(&trace_events);
host.register_trace_handler(TraceEvent::FrameStart, move |clk, event| {
frame_start_events.borrow_mut().insert(clk, event);
});
let frame_end_events = Rc::clone(&trace_events);
host.register_trace_handler(TraceEvent::FrameEnd, move |clk, event| {
frame_end_events.borrow_mut().insert(clk, event);
});
let assertion_events = Rc::clone(&trace_events);
host.register_assert_failed_tracer(move |clk, event| {
assertion_events.borrow_mut().insert(clk, event);
});

let mut process = Process::new_debug(program.kernel().clone(), self.stack, host);
let root_context = process.ctx();
let result = process.execute(program);
let mut iter = VmStateIterator::new(process, result.clone());
let mut callstack = CallStack::new(trace_events);
DebugExecutor {
iter,
result,
contexts: Default::default(),
root_context,
current_context: root_context,
callstack,
recent: VecDeque::with_capacity(5),
last: None,
cycle: 0,
stopped: false,
}
}

/// Execute the given program until termination, producing a trace
pub fn capture_trace(mut self, program: &Program, session: &Session) -> ExecutionTrace {
let mut executor = self.into_debug(program, session);
while let Some(step) = executor.next() {
if step.is_err() {
return executor.into_execution_trace();
}
}
executor.into_execution_trace()
}

/// Execute the given program, producing a trace
#[track_caller]
pub fn execute(mut self, program: &Program, session: &Session) -> ExecutionTrace {
let mut executor = self.into_debug(program, session);
while let Some(step) = executor.next() {
if let Err(err) = step {
render_execution_error(err, &executor, session);
}

/*
if let Some(op) = state.op {
match op {
miden_core::Operation::MLoad => {
let load_addr = last_state
.as_ref()
.map(|state| state.stack[0].as_int())
.unwrap();
let loaded = match state
.memory
.binary_search_by_key(&load_addr, |&(addr, _)| addr)
{
Ok(index) => state.memory[index].1[0].as_int(),
Err(_) => 0,
};
//dbg!(load_addr, loaded, format!("{loaded:08x}"));
}
miden_core::Operation::MLoadW => {
let load_addr = last_state
.as_ref()
.map(|state| state.stack[0].as_int())
.unwrap();
let loaded = match state
.memory
.binary_search_by_key(&load_addr, |&(addr, _)| addr)
{
Ok(index) => {
let word = state.memory[index].1;
[
word[0].as_int(),
word[1].as_int(),
word[2].as_int(),
word[3].as_int(),
]
}
Err(_) => [0; 4],
};
let loaded_bytes = {
let word = loaded;
let a = (word[0] as u32).to_be_bytes();
let b = (word[1] as u32).to_be_bytes();
let c = (word[2] as u32).to_be_bytes();
let d = (word[3] as u32).to_be_bytes();
let bytes = [
a[0], a[1], a[2], a[3], b[0], b[1], b[2], b[3], c[0], c[1],
c[2], c[3], d[0], d[1], d[2], d[3],
];
u128::from_be_bytes(bytes)
};
//dbg!(load_addr, loaded, format!("{loaded_bytes:032x}"));
}
miden_core::Operation::MStore => {
let store_addr = last_state
.as_ref()
.map(|state| state.stack[0].as_int())
.unwrap();
let stored = match state
.memory
.binary_search_by_key(&store_addr, |&(addr, _)| addr)
{
Ok(index) => state.memory[index].1[0].as_int(),
Err(_) => 0,
};
//dbg!(store_addr, stored, format!("{stored:08x}"));
}
miden_core::Operation::MStoreW => {
let store_addr = last_state
.as_ref()
.map(|state| state.stack[0].as_int())
.unwrap();
let stored = {
let memory = state
.memory
.iter()
.find_map(|(addr, word)| {
if addr == &store_addr {
Some(word)
} else {
None
}
})
.unwrap();
let a = memory[0].as_int();
let b = memory[1].as_int();
let c = memory[2].as_int();
let d = memory[3].as_int();
[a, b, c, d]
};
let stored_bytes = {
let word = stored;
let a = (word[0] as u32).to_be_bytes();
let b = (word[1] as u32).to_be_bytes();
let c = (word[2] as u32).to_be_bytes();
let d = (word[3] as u32).to_be_bytes();
let bytes = [
a[0], a[1], a[2], a[3], b[0], b[1], b[2], b[3], c[0], c[1],
c[2], c[3], d[0], d[1], d[2], d[3],
];
u128::from_be_bytes(bytes)
};
//dbg!(store_addr, stored, format!("{stored_bytes:032x}"));
}
_ => (),
}
}
*/
}

executor.into_execution_trace()
}

/// Execute a program, parsing the operand stack outputs as a value of type `T`
pub fn execute_into<T>(self, program: &Program, session: &Session) -> T
where
T: PopFromStack + PartialEq,
{
let out = self.execute(program, session);
out.parse_result().expect("invalid result")
}
}

#[track_caller]
fn render_execution_error(
err: ExecutionError,
execution_state: &DebugExecutor,
session: &Session,
) -> ! {
use midenc_hir::diagnostics::{miette::miette, reporting::PrintDiagnostic, LabeledSpan};

let stacktrace = execution_state.callstack.stacktrace(&execution_state.recent, session);

eprintln!("{stacktrace}");

if let Some(last_state) = execution_state.last.as_ref() {
let stack = last_state.stack.iter().map(|elem| elem.as_int());
let stack = midenc_hir::DisplayValues::new(stack);
let fmp = last_state.fmp.as_int();
eprintln!(
"\nLast Known State (at most recent instruction which succeeded):
| Frame Pointer: {fmp} (starts at 2^30)
| Operand Stack: [{stack}]
"
);

let mut labels = vec![];
if let Some(span) = stacktrace
.current_frame()
.and_then(|frame| frame.location.as_ref())
.map(|loc| loc.span)
{
labels.push(LabeledSpan::new_with_span(
None,
span.start().to_usize()..span.end().to_usize(),
));
}
let report = miette!(
labels = labels,
"program execution failed at step {step} (cycle {cycle}): {err}",
step = execution_state.cycle,
cycle = last_state.clk,
);
let report = match stacktrace
.current_frame()
.and_then(|frame| frame.location.as_ref())
.map(|loc| loc.source_file.clone())
{
Some(source) => report.with_source_code(source),
None => report,
};

panic!("{}", PrintDiagnostic::new(report));
} else {
panic!("program execution failed at step {step}: {err}", step = execution_state.cycle);
}
}
13 changes: 10 additions & 3 deletions midenc-debug/src/host.rs → midenc-debug/src/exec/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@ use miden_processor::{

use super::{TraceEvent, TraceHandler};

/// This is an implementation of [Host] which is essentially [miden_processor::DefaultHost],
/// but extended with additional functionality for debugging, in particular it manages trace
/// events that record the entry or exit of a procedure call frame.
#[derive(Default)]
pub struct TestHost {
pub struct DebuggerHost {
adv_provider: MemAdviceProvider,
store: MemMastForestStore,
tracing_callbacks: BTreeMap<u32, Vec<Box<TraceHandler>>>,
on_assert_failed: Option<Box<TraceHandler>>,
}
impl TestHost {
impl DebuggerHost {
/// Construct a new instance of [DebuggerHost] with the given advice provider.
pub fn new(adv_provider: MemAdviceProvider) -> Self {
Self {
adv_provider,
Expand All @@ -25,6 +29,7 @@ impl TestHost {
}
}

/// Register a trace handler for `event`
pub fn register_trace_handler<F>(&mut self, event: TraceEvent, callback: F)
where
F: FnMut(RowIndex, TraceEvent) + 'static,
Expand All @@ -36,19 +41,21 @@ impl TestHost {
self.tracing_callbacks.entry(key).or_default().push(Box::new(callback));
}

/// Register a handler to be called when an assertion in the VM fails
pub fn register_assert_failed_tracer<F>(&mut self, callback: F)
where
F: FnMut(RowIndex, TraceEvent) + 'static,
{
self.on_assert_failed = Some(Box::new(callback));
}

/// Load `forest` into the MAST store for this host
pub fn load_mast_forest(&mut self, forest: MastForest) {
self.store.insert(forest);
}
}

impl Host for TestHost {
impl Host for DebuggerHost {
fn get_advice<P: ProcessState>(
&mut self,
process: &P,
Expand Down
11 changes: 11 additions & 0 deletions midenc-debug/src/exec/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
mod executor;
mod host;
mod state;
mod trace;

pub use self::{
executor::Executor,
host::DebuggerHost,
state::{Chiplets, DebugExecutor},
trace::{ExecutionTrace, TraceEvent, TraceHandler},
};
Loading

0 comments on commit cc6c021

Please sign in to comment.