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}