Skip to main content

miden_processor/fast/
execution_api.rs

1use alloc::{sync::Arc, vec::Vec};
2use core::ops::ControlFlow;
3
4use miden_core::{
5    Word,
6    mast::{MastForest, MastNodeId},
7    program::{Kernel, MIN_STACK_DEPTH, Program, StackOutputs},
8};
9use tracing::instrument;
10
11use super::{
12    FastProcessor, NoopTracer,
13    external::maybe_use_caller_error_context,
14    step::{BreakReason, NeverStopper, ResumeContext, StepStopper},
15};
16use crate::{
17    ExecutionError, ExecutionOutput, Host, Stopper, SyncHost, TraceBuildInputs,
18    continuation_stack::ContinuationStack,
19    errors::{MapExecErr, MapExecErrNoCtx, OperationError},
20    execution::{
21        InternalBreakReason, execute_impl, finish_emit_op_execution,
22        finish_load_mast_forest_from_dyn_start, finish_load_mast_forest_from_external,
23    },
24    trace::execution_tracer::ExecutionTracer,
25    tracer::Tracer,
26};
27
28impl FastProcessor {
29    // EXECUTE
30    // -------------------------------------------------------------------------------------------
31
32    /// Executes the given program synchronously and returns the execution output.
33    pub fn execute_sync(
34        self,
35        program: &Program,
36        host: &mut impl SyncHost,
37    ) -> Result<ExecutionOutput, ExecutionError> {
38        self.execute_with_tracer_sync(program, host, &mut NoopTracer)
39    }
40
41    /// Async variant of [`Self::execute_sync`] for hosts that need async callbacks.
42    #[inline(always)]
43    pub async fn execute(
44        self,
45        program: &Program,
46        host: &mut impl Host,
47    ) -> Result<ExecutionOutput, ExecutionError> {
48        self.execute_with_tracer(program, host, &mut NoopTracer).await
49    }
50
51    /// Executes the given program synchronously and returns the bundled trace inputs required by
52    /// [`crate::trace::build_trace`].
53    ///
54    /// # Example
55    /// ```
56    /// use miden_assembly::Assembler;
57    /// use miden_processor::{DefaultHost, FastProcessor, StackInputs};
58    ///
59    /// let program = Assembler::default().assemble_program("begin push.1 drop end").unwrap();
60    /// let mut host = DefaultHost::default();
61    ///
62    /// let trace_inputs = FastProcessor::new(StackInputs::default())
63    ///     .execute_trace_inputs_sync(&program, &mut host)
64    ///     .unwrap();
65    /// let trace = miden_processor::trace::build_trace(trace_inputs).unwrap();
66    ///
67    /// assert_eq!(*trace.program_hash(), program.hash());
68    /// ```
69    #[instrument(name = "execute_trace_inputs_sync", skip_all)]
70    pub fn execute_trace_inputs_sync(
71        self,
72        program: &Program,
73        host: &mut impl SyncHost,
74    ) -> Result<TraceBuildInputs, ExecutionError> {
75        let mut tracer = ExecutionTracer::new(
76            self.options.core_trace_fragment_size(),
77            self.options.max_stack_depth(),
78        );
79        let execution_output = self.execute_with_tracer_sync(program, host, &mut tracer)?;
80        Ok(Self::trace_build_inputs_from_parts(program, execution_output, tracer))
81    }
82
83    /// Async variant of [`Self::execute_trace_inputs_sync`] for async hosts.
84    #[inline(always)]
85    #[instrument(name = "execute_trace_inputs", skip_all)]
86    pub async fn execute_trace_inputs(
87        self,
88        program: &Program,
89        host: &mut impl Host,
90    ) -> Result<TraceBuildInputs, ExecutionError> {
91        let mut tracer = ExecutionTracer::new(
92            self.options.core_trace_fragment_size(),
93            self.options.max_stack_depth(),
94        );
95        let execution_output = self.execute_with_tracer(program, host, &mut tracer).await?;
96        Ok(Self::trace_build_inputs_from_parts(program, execution_output, tracer))
97    }
98
99    /// Executes the given program with the provided tracer using an async host.
100    pub async fn execute_with_tracer<T>(
101        mut self,
102        program: &Program,
103        host: &mut impl Host,
104        tracer: &mut T,
105    ) -> Result<ExecutionOutput, ExecutionError>
106    where
107        T: Tracer<Processor = Self>,
108    {
109        let mut continuation_stack = ContinuationStack::new(program);
110        let mut current_forest = program.mast_forest().clone();
111
112        self.advice.extend_map(current_forest.advice_map()).map_exec_err_no_ctx()?;
113        let flow = self
114            .execute_impl_async(
115                &mut continuation_stack,
116                &mut current_forest,
117                program.kernel(),
118                host,
119                tracer,
120                &NeverStopper,
121            )
122            .await;
123        Self::execution_result_from_flow(flow, self)
124    }
125
126    /// Executes the given program with the provided tracer using a sync host.
127    pub fn execute_with_tracer_sync<T>(
128        mut self,
129        program: &Program,
130        host: &mut impl SyncHost,
131        tracer: &mut T,
132    ) -> Result<ExecutionOutput, ExecutionError>
133    where
134        T: Tracer<Processor = Self>,
135    {
136        let mut continuation_stack = ContinuationStack::new(program);
137        let mut current_forest = program.mast_forest().clone();
138
139        self.advice.extend_map(current_forest.advice_map()).map_exec_err_no_ctx()?;
140        let flow = self.execute_impl(
141            &mut continuation_stack,
142            &mut current_forest,
143            program.kernel(),
144            host,
145            tracer,
146            &NeverStopper,
147        );
148        Self::execution_result_from_flow(flow, self)
149    }
150
151    /// Executes a single clock cycle synchronously.
152    pub fn step_sync(
153        &mut self,
154        host: &mut impl SyncHost,
155        resume_ctx: ResumeContext,
156    ) -> Result<Option<ResumeContext>, ExecutionError> {
157        let ResumeContext {
158            mut current_forest,
159            mut continuation_stack,
160            kernel,
161        } = resume_ctx;
162
163        let flow = self.execute_impl(
164            &mut continuation_stack,
165            &mut current_forest,
166            &kernel,
167            host,
168            &mut NoopTracer,
169            &StepStopper,
170        );
171        Self::resume_context_from_flow(flow, continuation_stack, current_forest, kernel)
172    }
173
174    /// Async variant of [`Self::step_sync`].
175    #[inline(always)]
176    pub async fn step(
177        &mut self,
178        host: &mut impl Host,
179        resume_ctx: ResumeContext,
180    ) -> Result<Option<ResumeContext>, ExecutionError> {
181        let ResumeContext {
182            mut current_forest,
183            mut continuation_stack,
184            kernel,
185        } = resume_ctx;
186
187        let flow = self
188            .execute_impl_async(
189                &mut continuation_stack,
190                &mut current_forest,
191                &kernel,
192                host,
193                &mut NoopTracer,
194                &StepStopper,
195            )
196            .await;
197        Self::resume_context_from_flow(flow, continuation_stack, current_forest, kernel)
198    }
199
200    /// Pairs execution output with the trace inputs captured by the tracer.
201    #[inline(always)]
202    fn trace_build_inputs_from_parts(
203        program: &Program,
204        execution_output: ExecutionOutput,
205        tracer: ExecutionTracer,
206    ) -> TraceBuildInputs {
207        TraceBuildInputs::from_execution(
208            program,
209            execution_output,
210            tracer.into_trace_generation_context(),
211        )
212    }
213
214    /// Converts a step-wise execution result into the next resume context, if execution stopped.
215    #[inline(always)]
216    fn resume_context_from_flow(
217        flow: ControlFlow<BreakReason, StackOutputs>,
218        mut continuation_stack: ContinuationStack,
219        current_forest: Arc<MastForest>,
220        kernel: Kernel,
221    ) -> Result<Option<ResumeContext>, ExecutionError> {
222        match flow {
223            ControlFlow::Continue(_) => Ok(None),
224            ControlFlow::Break(break_reason) => match break_reason {
225                BreakReason::Err(err) => Err(err),
226                BreakReason::Stopped(maybe_continuation) => {
227                    if let Some(continuation) = maybe_continuation {
228                        continuation_stack.push_continuation(continuation);
229                    }
230
231                    Ok(Some(ResumeContext {
232                        current_forest,
233                        continuation_stack,
234                        kernel,
235                    }))
236                },
237            },
238        }
239    }
240
241    /// Materializes the current stack as public outputs without consuming the processor.
242    #[inline(always)]
243    fn current_stack_outputs(&self) -> StackOutputs {
244        StackOutputs::new(
245            &self.stack[self.stack_bot_idx..self.stack_top_idx]
246                .iter()
247                .rev()
248                .copied()
249                .collect::<Vec<_>>(),
250        )
251        .unwrap()
252    }
253
254    /// Executes the given program with the provided tracer and returns the stack outputs.
255    ///
256    /// This function takes a `&mut self` (compared to `self` for the public sync execution
257    /// methods) so that the processor state may be accessed after execution. Reusing the same
258    /// processor for a second program is incorrect. This is mainly meant to be used in tests.
259    fn execute_impl<S, T>(
260        &mut self,
261        continuation_stack: &mut ContinuationStack,
262        current_forest: &mut Arc<MastForest>,
263        kernel: &Kernel,
264        host: &mut impl SyncHost,
265        tracer: &mut T,
266        stopper: &S,
267    ) -> ControlFlow<BreakReason, StackOutputs>
268    where
269        S: Stopper<Processor = Self>,
270        T: Tracer<Processor = Self>,
271    {
272        while let ControlFlow::Break(internal_break_reason) =
273            execute_impl(self, continuation_stack, current_forest, kernel, host, tracer, stopper)
274        {
275            match internal_break_reason {
276                InternalBreakReason::User(break_reason) => return ControlFlow::Break(break_reason),
277                InternalBreakReason::Emit {
278                    basic_block_node_id,
279                    op_idx,
280                    continuation,
281                } => {
282                    self.op_emit_sync(host, current_forest, basic_block_node_id, op_idx)?;
283
284                    finish_emit_op_execution(
285                        continuation,
286                        self,
287                        continuation_stack,
288                        current_forest,
289                        tracer,
290                        stopper,
291                    )?;
292                },
293                InternalBreakReason::LoadMastForestFromDyn { dyn_node_id, callee_hash } => {
294                    let (root_id, new_forest) = match self.load_mast_forest_sync(
295                        callee_hash,
296                        host,
297                        current_forest,
298                        dyn_node_id,
299                    ) {
300                        Ok(result) => result,
301                        Err(err) => return ControlFlow::Break(BreakReason::Err(err)),
302                    };
303
304                    finish_load_mast_forest_from_dyn_start(
305                        root_id,
306                        new_forest,
307                        self,
308                        current_forest,
309                        continuation_stack,
310                        tracer,
311                        stopper,
312                    )?;
313                },
314                InternalBreakReason::LoadMastForestFromExternal {
315                    external_node_id,
316                    procedure_hash,
317                } => {
318                    let (root_id, new_forest) = match self.load_mast_forest_sync(
319                        procedure_hash,
320                        host,
321                        current_forest,
322                        external_node_id,
323                    ) {
324                        Ok(result) => result,
325                        Err(err) => {
326                            let maybe_enriched_err = maybe_use_caller_error_context(
327                                err,
328                                current_forest,
329                                continuation_stack,
330                                host,
331                            );
332
333                            return ControlFlow::Break(BreakReason::Err(maybe_enriched_err));
334                        },
335                    };
336
337                    finish_load_mast_forest_from_external(
338                        root_id,
339                        new_forest,
340                        external_node_id,
341                        current_forest,
342                        continuation_stack,
343                        host,
344                        tracer,
345                    )?;
346                },
347            }
348        }
349
350        match StackOutputs::new(
351            &self.stack[self.stack_bot_idx..self.stack_top_idx]
352                .iter()
353                .rev()
354                .copied()
355                .collect::<Vec<_>>(),
356        ) {
357            Ok(stack_outputs) => ControlFlow::Continue(stack_outputs),
358            Err(_) => ControlFlow::Break(BreakReason::Err(ExecutionError::OutputStackOverflow(
359                self.stack_top_idx - self.stack_bot_idx - MIN_STACK_DEPTH,
360            ))),
361        }
362    }
363
364    async fn execute_impl_async<S, T>(
365        &mut self,
366        continuation_stack: &mut ContinuationStack,
367        current_forest: &mut Arc<MastForest>,
368        kernel: &Kernel,
369        host: &mut impl Host,
370        tracer: &mut T,
371        stopper: &S,
372    ) -> ControlFlow<BreakReason, StackOutputs>
373    where
374        S: Stopper<Processor = Self>,
375        T: Tracer<Processor = Self>,
376    {
377        while let ControlFlow::Break(internal_break_reason) =
378            execute_impl(self, continuation_stack, current_forest, kernel, host, tracer, stopper)
379        {
380            match internal_break_reason {
381                InternalBreakReason::User(break_reason) => return ControlFlow::Break(break_reason),
382                InternalBreakReason::Emit {
383                    basic_block_node_id,
384                    op_idx,
385                    continuation,
386                } => {
387                    self.op_emit(host, current_forest, basic_block_node_id, op_idx).await?;
388
389                    finish_emit_op_execution(
390                        continuation,
391                        self,
392                        continuation_stack,
393                        current_forest,
394                        tracer,
395                        stopper,
396                    )?;
397                },
398                InternalBreakReason::LoadMastForestFromDyn { dyn_node_id, callee_hash } => {
399                    let (root_id, new_forest) = match self
400                        .load_mast_forest(callee_hash, host, current_forest, dyn_node_id)
401                        .await
402                    {
403                        Ok(result) => result,
404                        Err(err) => return ControlFlow::Break(BreakReason::Err(err)),
405                    };
406
407                    finish_load_mast_forest_from_dyn_start(
408                        root_id,
409                        new_forest,
410                        self,
411                        current_forest,
412                        continuation_stack,
413                        tracer,
414                        stopper,
415                    )?;
416                },
417                InternalBreakReason::LoadMastForestFromExternal {
418                    external_node_id,
419                    procedure_hash,
420                } => {
421                    let (root_id, new_forest) = match self
422                        .load_mast_forest(procedure_hash, host, current_forest, external_node_id)
423                        .await
424                    {
425                        Ok(result) => result,
426                        Err(err) => {
427                            let maybe_enriched_err = maybe_use_caller_error_context(
428                                err,
429                                current_forest,
430                                continuation_stack,
431                                host,
432                            );
433
434                            return ControlFlow::Break(BreakReason::Err(maybe_enriched_err));
435                        },
436                    };
437
438                    finish_load_mast_forest_from_external(
439                        root_id,
440                        new_forest,
441                        external_node_id,
442                        current_forest,
443                        continuation_stack,
444                        host,
445                        tracer,
446                    )?;
447                },
448            }
449        }
450
451        match StackOutputs::new(
452            &self.stack[self.stack_bot_idx..self.stack_top_idx]
453                .iter()
454                .rev()
455                .copied()
456                .collect::<Vec<_>>(),
457        ) {
458            Ok(stack_outputs) => ControlFlow::Continue(stack_outputs),
459            Err(_) => ControlFlow::Break(BreakReason::Err(ExecutionError::OutputStackOverflow(
460                self.stack_top_idx - self.stack_bot_idx - MIN_STACK_DEPTH,
461            ))),
462        }
463    }
464
465    // HELPERS
466    // ------------------------------------------------------------------------------------------
467
468    fn load_mast_forest_sync(
469        &mut self,
470        node_digest: Word,
471        host: &mut impl SyncHost,
472        current_forest: &MastForest,
473        node_id: MastNodeId,
474    ) -> Result<(MastNodeId, Arc<MastForest>), ExecutionError> {
475        let mast_forest = host.get_mast_forest(&node_digest).ok_or_else(|| {
476            crate::errors::procedure_not_found_with_context(
477                node_digest,
478                current_forest,
479                node_id,
480                host,
481            )
482        })?;
483
484        let root_id = mast_forest.find_procedure_root(node_digest).ok_or_else(|| {
485            Err::<(), _>(OperationError::MalformedMastForestInHost { root_digest: node_digest })
486                .map_exec_err(current_forest, node_id, host)
487                .unwrap_err()
488        })?;
489
490        self.advice.extend_map(mast_forest.advice_map()).map_exec_err(
491            current_forest,
492            node_id,
493            host,
494        )?;
495
496        Ok((root_id, mast_forest))
497    }
498
499    async fn load_mast_forest(
500        &mut self,
501        node_digest: Word,
502        host: &mut impl Host,
503        current_forest: &MastForest,
504        node_id: MastNodeId,
505    ) -> Result<(MastNodeId, Arc<MastForest>), ExecutionError> {
506        let mast_forest = if let Some(mast_forest) = host.get_mast_forest(&node_digest).await {
507            mast_forest
508        } else {
509            return Err(crate::errors::procedure_not_found_with_context(
510                node_digest,
511                current_forest,
512                node_id,
513                host,
514            ));
515        };
516
517        let root_id = mast_forest.find_procedure_root(node_digest).ok_or_else(|| {
518            Err::<(), _>(OperationError::MalformedMastForestInHost { root_digest: node_digest })
519                .map_exec_err(current_forest, node_id, host)
520                .unwrap_err()
521        })?;
522
523        self.advice.extend_map(mast_forest.advice_map()).map_exec_err(
524            current_forest,
525            node_id,
526            host,
527        )?;
528
529        Ok((root_id, mast_forest))
530    }
531
532    /// Executes the given program synchronously one step at a time.
533    pub fn execute_by_step_sync(
534        mut self,
535        program: &Program,
536        host: &mut impl SyncHost,
537    ) -> Result<StackOutputs, ExecutionError> {
538        let mut current_resume_ctx = self.get_initial_resume_context(program)?;
539
540        loop {
541            match self.step_sync(host, current_resume_ctx)? {
542                Some(next_resume_ctx) => {
543                    current_resume_ctx = next_resume_ctx;
544                },
545                None => break Ok(self.current_stack_outputs()),
546            }
547        }
548    }
549
550    /// Async variant of [`Self::execute_by_step_sync`].
551    #[inline(always)]
552    pub async fn execute_by_step(
553        mut self,
554        program: &Program,
555        host: &mut impl Host,
556    ) -> Result<StackOutputs, ExecutionError> {
557        let mut current_resume_ctx = self.get_initial_resume_context(program)?;
558        let mut processor = self;
559
560        loop {
561            match processor.step(host, current_resume_ctx).await? {
562                Some(next_resume_ctx) => {
563                    current_resume_ctx = next_resume_ctx;
564                },
565                None => break Ok(processor.current_stack_outputs()),
566            }
567        }
568    }
569
570    /// Similar to [`Self::execute_sync`], but allows mutable access to the processor.
571    ///
572    /// This is mainly meant to be used in tests.
573    #[cfg(any(test, feature = "testing"))]
574    pub fn execute_mut_sync(
575        &mut self,
576        program: &Program,
577        host: &mut impl SyncHost,
578    ) -> Result<StackOutputs, ExecutionError> {
579        let mut continuation_stack = ContinuationStack::new(program);
580        let mut current_forest = program.mast_forest().clone();
581
582        self.advice.extend_map(current_forest.advice_map()).map_exec_err_no_ctx()?;
583
584        let flow = self.execute_impl(
585            &mut continuation_stack,
586            &mut current_forest,
587            program.kernel(),
588            host,
589            &mut NoopTracer,
590            &NeverStopper,
591        );
592        Self::stack_result_from_flow(flow)
593    }
594
595    /// Async variant of [`Self::execute_mut_sync`].
596    #[cfg(any(test, feature = "testing"))]
597    #[inline(always)]
598    pub async fn execute_mut(
599        &mut self,
600        program: &Program,
601        host: &mut impl Host,
602    ) -> Result<StackOutputs, ExecutionError> {
603        let mut continuation_stack = ContinuationStack::new(program);
604        let mut current_forest = program.mast_forest().clone();
605
606        self.advice.extend_map(current_forest.advice_map()).map_exec_err_no_ctx()?;
607
608        let flow = self
609            .execute_impl_async(
610                &mut continuation_stack,
611                &mut current_forest,
612                program.kernel(),
613                host,
614                &mut NoopTracer,
615                &NeverStopper,
616            )
617            .await;
618        Self::stack_result_from_flow(flow)
619    }
620}