midenc_debug/exec/
executor.rs

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