midenc_debug/exec/
executor.rs

1use std::{
2    cell::RefCell,
3    collections::{BTreeMap, BTreeSet, VecDeque},
4    rc::Rc,
5};
6
7use miden_assembly::Library as CompiledLibrary;
8use miden_core::{Program, StackInputs, Word};
9use miden_processor::{
10    AdviceInputs, ContextId, ExecutionError, Felt, MastForest, MemAdviceProvider, Process,
11    ProcessState, RowIndex, StackOutputs, VmState, VmStateIterator,
12};
13use midenc_codegen_masm::{NativePtr, Package};
14use midenc_hir::Type;
15use midenc_session::{
16    diagnostics::{IntoDiagnostic, Report},
17    Session,
18};
19
20use super::{DebugExecutor, DebuggerHost, ExecutionTrace, TraceEvent};
21use crate::{debug::CallStack, felt::PopFromStack, TestFelt};
22
23/// The [Executor] is responsible for executing a program with the Miden VM.
24///
25/// It is used by either converting it into a [DebugExecutor], and using that to
26/// manage execution step-by-step, such as is done by the debugger; or by running
27/// the program to completion and obtaining an [ExecutionTrace], which can be used
28/// to introspect the final program state.
29pub struct Executor {
30    stack: StackInputs,
31    advice: AdviceInputs,
32    libraries: Vec<MastForest>,
33}
34impl Executor {
35    /// Construct an executor with the given arguments on the operand stack
36    pub fn new(args: Vec<Felt>) -> Self {
37        Self {
38            stack: StackInputs::new(args).expect("invalid stack inputs"),
39            advice: AdviceInputs::default(),
40            libraries: Default::default(),
41        }
42    }
43
44    pub fn for_package(
45        package: &Package,
46        args: Vec<Felt>,
47        session: &Session,
48    ) -> Result<Self, Report> {
49        use midenc_hir::formatter::DisplayHex;
50        log::debug!(
51            "creating executor for package '{}' (digest={})",
52            package.name,
53            DisplayHex::new(&package.digest.as_bytes())
54        );
55
56        let mut exec = Self::new(args);
57
58        for link_library in package.manifest.link_libraries.iter() {
59            log::debug!(
60                "loading link library from package manifest: {} (kind = {}, from = {:#?})",
61                link_library.name.as_ref(),
62                link_library.kind,
63                link_library.path.as_ref().map(|p| p.display())
64            );
65            let library = link_library.load(session)?;
66            log::debug!("library loaded succesfully");
67            exec.with_library(&library);
68        }
69
70        for rodata in package.rodata.iter() {
71            log::debug!(
72                "adding rodata segment for offset {} (size {}) to advice map: {}",
73                rodata.start.as_ptr(),
74                rodata.size_in_bytes(),
75                DisplayHex::new(&rodata.digest.as_bytes())
76            );
77            exec.advice
78                .extend_map([(rodata.digest, rodata.to_elements().map_err(Report::msg)?)]);
79        }
80
81        log::debug!("executor created");
82
83        Ok(exec)
84    }
85
86    /// Set the contents of memory for the shadow stack frame of the entrypoint
87    pub fn with_advice_inputs(&mut self, advice: AdviceInputs) -> &mut Self {
88        self.advice.extend(advice);
89        self
90    }
91
92    /// Add a [CompiledLibrary] to the execution context
93    pub fn with_library(&mut self, lib: &CompiledLibrary) -> &mut Self {
94        self.libraries.push(lib.mast_forest().clone());
95        self
96    }
97
98    /// Convert this [Executor] into a [DebugExecutor], which captures much more information
99    /// about the program being executed, and must be stepped manually.
100    pub fn into_debug(mut self, program: &Program, session: &Session) -> DebugExecutor {
101        log::debug!("creating debug executor");
102
103        let advice_provider = MemAdviceProvider::from(self.advice);
104        let mut host = DebuggerHost::new(advice_provider);
105        for lib in core::mem::take(&mut self.libraries) {
106            host.load_mast_forest(lib);
107        }
108
109        let trace_events: Rc<RefCell<BTreeMap<RowIndex, TraceEvent>>> = Rc::new(Default::default());
110        let frame_start_events = Rc::clone(&trace_events);
111        host.register_trace_handler(TraceEvent::FrameStart, move |clk, event| {
112            frame_start_events.borrow_mut().insert(clk, event);
113        });
114        let frame_end_events = Rc::clone(&trace_events);
115        host.register_trace_handler(TraceEvent::FrameEnd, move |clk, event| {
116            frame_end_events.borrow_mut().insert(clk, event);
117        });
118        let assertion_events = Rc::clone(&trace_events);
119        host.register_assert_failed_tracer(move |clk, event| {
120            assertion_events.borrow_mut().insert(clk, event);
121        });
122
123        let mut process = Process::new_debug(program.kernel().clone(), self.stack, host);
124        let root_context = process.ctx();
125        let result = process.execute(program);
126        let mut iter = VmStateIterator::new(process, result.clone());
127        let mut callstack = CallStack::new(trace_events);
128        DebugExecutor {
129            iter,
130            result,
131            contexts: Default::default(),
132            root_context,
133            current_context: root_context,
134            callstack,
135            recent: VecDeque::with_capacity(5),
136            last: None,
137            cycle: 0,
138            stopped: false,
139        }
140    }
141
142    /// Execute the given program until termination, producing a trace
143    pub fn capture_trace(mut self, program: &Program, session: &Session) -> ExecutionTrace {
144        let mut executor = self.into_debug(program, session);
145        while let Some(step) = executor.next() {
146            if step.is_err() {
147                return executor.into_execution_trace();
148            }
149        }
150        executor.into_execution_trace()
151    }
152
153    /// Execute the given program, producing a trace
154    #[track_caller]
155    pub fn execute(mut self, program: &Program, session: &Session) -> ExecutionTrace {
156        let mut executor = self.into_debug(program, session);
157        while let Some(step) = executor.next() {
158            if let Err(err) = step {
159                render_execution_error(err, &executor, session);
160            }
161
162            /*
163            if let Some(op) = state.op {
164                match op {
165                    miden_core::Operation::MLoad => {
166                        let load_addr = last_state
167                            .as_ref()
168                            .map(|state| state.stack[0].as_int())
169                            .unwrap();
170                        let loaded = match state
171                            .memory
172                            .binary_search_by_key(&load_addr, |&(addr, _)| addr)
173                        {
174                            Ok(index) => state.memory[index].1[0].as_int(),
175                            Err(_) => 0,
176                        };
177                        //dbg!(load_addr, loaded, format!("{loaded:08x}"));
178                    }
179                    miden_core::Operation::MLoadW => {
180                        let load_addr = last_state
181                            .as_ref()
182                            .map(|state| state.stack[0].as_int())
183                            .unwrap();
184                        let loaded = match state
185                            .memory
186                            .binary_search_by_key(&load_addr, |&(addr, _)| addr)
187                        {
188                            Ok(index) => {
189                                let word = state.memory[index].1;
190                                [
191                                    word[0].as_int(),
192                                    word[1].as_int(),
193                                    word[2].as_int(),
194                                    word[3].as_int(),
195                                ]
196                            }
197                            Err(_) => [0; 4],
198                        };
199                        let loaded_bytes = {
200                            let word = loaded;
201                            let a = (word[0] as u32).to_be_bytes();
202                            let b = (word[1] as u32).to_be_bytes();
203                            let c = (word[2] as u32).to_be_bytes();
204                            let d = (word[3] as u32).to_be_bytes();
205                            let bytes = [
206                                a[0], a[1], a[2], a[3], b[0], b[1], b[2], b[3], c[0], c[1],
207                                c[2], c[3], d[0], d[1], d[2], d[3],
208                            ];
209                            u128::from_be_bytes(bytes)
210                        };
211                        //dbg!(load_addr, loaded, format!("{loaded_bytes:032x}"));
212                    }
213                    miden_core::Operation::MStore => {
214                        let store_addr = last_state
215                            .as_ref()
216                            .map(|state| state.stack[0].as_int())
217                            .unwrap();
218                        let stored = match state
219                            .memory
220                            .binary_search_by_key(&store_addr, |&(addr, _)| addr)
221                        {
222                            Ok(index) => state.memory[index].1[0].as_int(),
223                            Err(_) => 0,
224                        };
225                        //dbg!(store_addr, stored, format!("{stored:08x}"));
226                    }
227                    miden_core::Operation::MStoreW => {
228                        let store_addr = last_state
229                            .as_ref()
230                            .map(|state| state.stack[0].as_int())
231                            .unwrap();
232                        let stored = {
233                            let memory = state
234                                .memory
235                                .iter()
236                                .find_map(|(addr, word)| {
237                                    if addr == &store_addr {
238                                        Some(word)
239                                    } else {
240                                        None
241                                    }
242                                })
243                                .unwrap();
244                            let a = memory[0].as_int();
245                            let b = memory[1].as_int();
246                            let c = memory[2].as_int();
247                            let d = memory[3].as_int();
248                            [a, b, c, d]
249                        };
250                        let stored_bytes = {
251                            let word = stored;
252                            let a = (word[0] as u32).to_be_bytes();
253                            let b = (word[1] as u32).to_be_bytes();
254                            let c = (word[2] as u32).to_be_bytes();
255                            let d = (word[3] as u32).to_be_bytes();
256                            let bytes = [
257                                a[0], a[1], a[2], a[3], b[0], b[1], b[2], b[3], c[0], c[1],
258                                c[2], c[3], d[0], d[1], d[2], d[3],
259                            ];
260                            u128::from_be_bytes(bytes)
261                        };
262                        //dbg!(store_addr, stored, format!("{stored_bytes:032x}"));
263                    }
264                    _ => (),
265                }
266            }
267            */
268        }
269
270        executor.into_execution_trace()
271    }
272
273    /// Execute a program, parsing the operand stack outputs as a value of type `T`
274    pub fn execute_into<T>(self, program: &Program, session: &Session) -> T
275    where
276        T: PopFromStack + PartialEq,
277    {
278        let out = self.execute(program, session);
279        out.parse_result().expect("invalid result")
280    }
281}
282
283#[track_caller]
284fn render_execution_error(
285    err: ExecutionError,
286    execution_state: &DebugExecutor,
287    session: &Session,
288) -> ! {
289    use midenc_hir::diagnostics::{miette::miette, reporting::PrintDiagnostic, LabeledSpan};
290
291    let stacktrace = execution_state.callstack.stacktrace(&execution_state.recent, session);
292
293    eprintln!("{stacktrace}");
294
295    if let Some(last_state) = execution_state.last.as_ref() {
296        let stack = last_state.stack.iter().map(|elem| elem.as_int());
297        let stack = midenc_hir::DisplayValues::new(stack);
298        let fmp = last_state.fmp.as_int();
299        eprintln!(
300            "\nLast Known State (at most recent instruction which succeeded):
301 | Frame Pointer: {fmp} (starts at 2^30)
302 | Operand Stack: [{stack}]
303 "
304        );
305
306        let mut labels = vec![];
307        if let Some(span) = stacktrace
308            .current_frame()
309            .and_then(|frame| frame.location.as_ref())
310            .map(|loc| loc.span)
311        {
312            labels.push(LabeledSpan::new_with_span(
313                None,
314                span.start().to_usize()..span.end().to_usize(),
315            ));
316        }
317        let report = miette!(
318            labels = labels,
319            "program execution failed at step {step} (cycle {cycle}): {err}",
320            step = execution_state.cycle,
321            cycle = last_state.clk,
322        );
323        let report = match stacktrace
324            .current_frame()
325            .and_then(|frame| frame.location.as_ref())
326            .map(|loc| loc.source_file.clone())
327        {
328            Some(source) => report.with_source_code(source),
329            None => report,
330        };
331
332        panic!("{}", PrintDiagnostic::new(report));
333    } else {
334        panic!("program execution failed at step {step}: {err}", step = execution_state.cycle);
335    }
336}