Skip to main content

miden_debug_engine/exec/
host.rs

1use alloc::{boxed::Box, collections::VecDeque, sync::Arc, vec::Vec};
2use core::num::NonZeroU32;
3
4use miden_assembly_syntax::debuginfo::SourceManager;
5use miden_core::{
6    Word,
7    events::{EventId, EventName},
8};
9use miden_debug_types::{Location, SourceFile, SourceSpan};
10use miden_mast_package::Package;
11use miden_processor::{
12    BaseHost, ExecutionError, LoadedMastForest, MastForestStore, MemMastForestStore,
13    ProcessorState, SyncHost,
14    advice::AdviceMutation,
15    event::{EventError, EventHandler, EventHandlerRegistry},
16};
17
18use super::advice::clone_advice_mutations;
19use crate::Event;
20
21/// This is an implementation of [Host] which is essentially [miden_processor::DefaultHost],
22/// but extended with additional functionality for debugging, in particular it manages trace
23/// events that record the entry or exit of a procedure call frame.
24pub struct DebuggerHost<S: SourceManager + ?Sized> {
25    store: MemMastForestStore,
26    event_handlers: EventHandlerRegistry,
27    #[allow(clippy::type_complexity)]
28    on_assert_failed: Option<Box<dyn FnMut(&ProcessorState<'_>, u32)>>,
29    source_manager: Arc<S>,
30    event_replay: VecDeque<Vec<AdviceMutation>>,
31    event_recording: Option<Vec<Vec<AdviceMutation>>>,
32}
33impl<S> DebuggerHost<S>
34where
35    S: SourceManager + ?Sized,
36{
37    /// Construct a new instance of [DebuggerHost] with the given source manager.
38    pub fn new(source_manager: Arc<S>) -> Self {
39        Self {
40            store: Default::default(),
41            event_handlers: EventHandlerRegistry::default(),
42            on_assert_failed: None,
43            source_manager,
44            event_replay: VecDeque::new(),
45            event_recording: None,
46        }
47    }
48
49    /// Set the event replay queue.
50    ///
51    /// When non-empty, `on_event()` will pop mutations from this queue instead of
52    /// returning empty results. This is used for transaction debugging where events
53    /// were recorded during a prior execution.
54    pub fn set_event_replay(&mut self, events: VecDeque<Vec<AdviceMutation>>) {
55        self.event_replay = events;
56    }
57
58    /// Record the advice mutations produced by each event handler invocation.
59    ///
60    /// One entry is recorded per `on_event` invocation, in execution order, **including empty
61    /// mutation sets**, so the recorded log can be fed directly back into
62    /// [DebuggerHost::set_event_replay] to replay this execution later. Take the log with
63    /// [DebuggerHost::take_recorded_event_mutations] once execution completes.
64    ///
65    /// Mutations are only recorded for live event handling; nothing is recorded while an event
66    /// replay queue is being consumed.
67    pub fn with_event_advice_mutations_recording(mut self) -> Self {
68        self.event_recording = Some(Vec::new());
69        self
70    }
71
72    /// Returns the advice mutations recorded so far, leaving the recording empty.
73    ///
74    /// Returns an empty log when recording was not enabled via
75    /// [DebuggerHost::with_event_advice_mutations_recording].
76    pub fn take_recorded_event_mutations(&mut self) -> Vec<Vec<AdviceMutation>> {
77        self.event_recording.as_mut().map(core::mem::take).unwrap_or_default()
78    }
79
80    /// Register a handler to be called when an assertion in the VM fails
81    pub fn register_assert_failed_tracer<F>(&mut self, callback: F)
82    where
83        F: FnMut(&ProcessorState<'_>, u32) + 'static,
84    {
85        self.on_assert_failed = Some(Box::new(callback));
86    }
87
88    /// Invoke the assert-failed handler, if registered.
89    ///
90    /// This is called externally when `step()` returns an assertion error, since
91    /// `on_assert_failed` no longer exists on the Host trait in 0.21.
92    pub fn handle_assert_failed(
93        &mut self,
94        process: &ProcessorState<'_>,
95        err_code: Option<NonZeroU32>,
96    ) {
97        if let Some(handler) = self.on_assert_failed.as_mut() {
98            handler(process, err_code.map(|nz| nz.get()).unwrap_or_default());
99        }
100    }
101
102    /// Load `package` into the MAST store for this host
103    pub fn load_package(&mut self, package: Arc<Package>) {
104        let mast = package.mast_forest().clone();
105        let debug_info = package.debug_info();
106        self.store
107            .insert_loaded(LoadedMastForest::with_package_debug_info(mast, debug_info));
108    }
109
110    /// Load `forest` into the MAST store for this host
111    pub fn load_mast_forest(&mut self, forest: LoadedMastForest) {
112        self.store.insert_loaded(forest);
113    }
114
115    /// Registers an event handler for use during program execution.
116    pub fn register_event_handler(
117        &mut self,
118        event: EventName,
119        handler: Arc<dyn EventHandler>,
120    ) -> Result<(), ExecutionError> {
121        self.event_handlers.register(event, handler)
122    }
123}
124
125impl<S> BaseHost for DebuggerHost<S>
126where
127    S: SourceManager + ?Sized,
128{
129    fn get_label_and_source_file(
130        &self,
131        location: &Location,
132    ) -> (SourceSpan, Option<Arc<SourceFile>>) {
133        let maybe_file = self.source_manager.get_by_uri(location.uri());
134        let span = self.source_manager.location_to_span(location.clone()).unwrap_or_default();
135        (span, maybe_file)
136    }
137
138    fn resolve_event(&self, event_id: EventId) -> Option<&EventName> {
139        self.event_handlers.resolve_event(event_id)
140    }
141}
142
143impl<S> SyncHost for DebuggerHost<S>
144where
145    S: SourceManager + ?Sized,
146{
147    fn get_mast_forest(&self, node_digest: &Word) -> Option<LoadedMastForest> {
148        self.store.get(node_digest)
149    }
150
151    fn on_event(
152        &mut self,
153        process: &ProcessorState<'_>,
154    ) -> Result<Vec<AdviceMutation>, EventError> {
155        let event_id = EventId::from_felt(process.get_stack_item(0));
156        let is_builtin_event = Event::from(event_id).has_builtin_handler();
157        let replay_mutations = self.event_replay.pop_front();
158        let is_replaying = replay_mutations.is_some();
159
160        if let Some(mutations) = replay_mutations {
161            if !is_builtin_event {
162                // Non-debug events without builtin handler: return mutations from replay.
163                return Ok(mutations);
164            }
165
166            // Even in replay mode we want to forward builtin events to the builtin handlers.
167            // Debugger functionality relies on the side effects of the handlers.
168            if is_builtin_event {
169                assert!(
170                    mutations.is_empty(),
171                    "debug events must not be associated with mutations from replay"
172                );
173            }
174        }
175
176        let result = match self.event_handlers.handle_event(event_id, process) {
177            Ok(Some(mutations)) => Ok(mutations),
178            Ok(None) => {
179                #[derive(Debug, thiserror::Error)]
180                #[error("no event handler registered")]
181                struct UnhandledEvent;
182
183                Err(UnhandledEvent.into())
184            }
185            Err(err) => Err(err),
186        };
187
188        if !is_replaying
189            && let (Some(log), Ok(mutations)) = (self.event_recording.as_mut(), &result)
190        {
191            log.push(clone_advice_mutations(mutations));
192        }
193        result
194    }
195}