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}