Skip to main content

miden_debug_engine/exec/
host.rs

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