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::{KernelDescriptor, MIN_STACK_DEPTH, Program, StackOutputs},
8};
9use miden_mast_package::debug_info::{
10    DebugSourceGraphLookupError, DebugSourceNodeId, PackageDebugInfo,
11};
12use tracing::instrument;
13
14use super::{
15    FastProcessor, NoopTracer,
16    external::maybe_use_caller_error_context,
17    step::{BreakReason, NeverStopper, ResumeContext, StepStopper},
18};
19use crate::{
20    ExecutionError, ExecutionOutput, Host, LoadedMastForest, Stopper, SyncHost, TraceBuildInputs,
21    continuation_stack::ContinuationStack,
22    errors::{
23        MapExecErr, MapExecErrNoCtx, PackageSourceDebugContext, malformed_mast_forest_with_context,
24    },
25    execution::{
26        InternalBreakReason, execute_impl, finish_emit_op_execution,
27        finish_load_mast_forest_from_dyn_start, finish_load_mast_forest_from_external,
28    },
29    trace::execution_tracer::ExecutionTracer,
30    tracer::Tracer,
31};
32
33impl FastProcessor {
34    // EXECUTE
35    // -------------------------------------------------------------------------------------------
36
37    /// Executes the given program synchronously and returns the execution output.
38    pub fn execute_sync(
39        self,
40        program: &Program,
41        host: &mut impl SyncHost,
42    ) -> Result<ExecutionOutput, ExecutionError> {
43        self.execute_with_tracer_sync(program, host, &mut NoopTracer)
44    }
45
46    /// Executes the given program synchronously with package-owned source/debug context.
47    ///
48    /// This derives the entrypoint source occurrence from [`PackageDebugInfo`], so the source graph
49    /// must contain at most one root for the executable entrypoint. When the package manifest names
50    /// the exact entrypoint source occurrence, use
51    /// [`Self::execute_with_package_debug_info_at_source_node_sync`] instead.
52    pub fn execute_with_package_debug_info_sync(
53        self,
54        program: &Program,
55        package_debug_info: &PackageDebugInfo,
56        host: &mut impl SyncHost,
57    ) -> Result<ExecutionOutput, ExecutionError> {
58        self.execute_with_package_debug_info_and_tracer_sync(
59            program,
60            package_debug_info,
61            None,
62            host,
63            &mut NoopTracer,
64        )
65    }
66
67    /// Executes the given program synchronously with package-owned source/debug context rooted at
68    /// `entrypoint_source_node_id`.
69    ///
70    /// Use this when the package manifest names the exact source/debug occurrence for the
71    /// executable entrypoint. This preserves source disambiguation when multiple source roots map
72    /// to the same executable MAST node.
73    pub fn execute_with_package_debug_info_at_source_node_sync(
74        self,
75        program: &Program,
76        package_debug_info: &PackageDebugInfo,
77        entrypoint_source_node_id: DebugSourceNodeId,
78        host: &mut impl SyncHost,
79    ) -> Result<ExecutionOutput, ExecutionError> {
80        self.execute_with_package_debug_info_and_tracer_sync(
81            program,
82            package_debug_info,
83            Some(entrypoint_source_node_id),
84            host,
85            &mut NoopTracer,
86        )
87    }
88
89    /// Async variant of [`Self::execute_sync`] for hosts that need async callbacks.
90    #[inline(always)]
91    pub async fn execute(
92        self,
93        program: &Program,
94        host: &mut impl Host,
95    ) -> Result<ExecutionOutput, ExecutionError> {
96        self.execute_with_tracer(program, host, &mut NoopTracer).await
97    }
98
99    /// Async variant of [`Self::execute_with_package_debug_info_sync`].
100    ///
101    /// When the package manifest names the exact entrypoint source occurrence, use
102    /// [`Self::execute_with_package_debug_info_at_source_node`] instead.
103    #[inline(always)]
104    pub async fn execute_with_package_debug_info(
105        self,
106        program: &Program,
107        package_debug_info: &PackageDebugInfo,
108        host: &mut impl Host,
109    ) -> Result<ExecutionOutput, ExecutionError> {
110        self.execute_with_package_debug_info_and_tracer(
111            program,
112            package_debug_info,
113            None,
114            host,
115            &mut NoopTracer,
116        )
117        .await
118    }
119
120    /// Async variant of [`Self::execute_with_package_debug_info_at_source_node_sync`].
121    #[inline(always)]
122    pub async fn execute_with_package_debug_info_at_source_node(
123        self,
124        program: &Program,
125        package_debug_info: &PackageDebugInfo,
126        entrypoint_source_node_id: DebugSourceNodeId,
127        host: &mut impl Host,
128    ) -> Result<ExecutionOutput, ExecutionError> {
129        self.execute_with_package_debug_info_and_tracer(
130            program,
131            package_debug_info,
132            Some(entrypoint_source_node_id),
133            host,
134            &mut NoopTracer,
135        )
136        .await
137    }
138
139    /// Executes the program and builds its execution trace, overlapping the two: the hasher
140    /// chiplet — the dominant serial part of trace building — is built on a second thread from a
141    /// live stream of requests while execution is still running, hiding its cost behind the
142    /// (inherently sequential) execution itself.
143    ///
144    /// Produces the same trace as `execute_trace_inputs_sync` followed by
145    /// [`crate::trace::build_trace`].
146    #[cfg(feature = "std")]
147    #[instrument(name = "execute_and_build_trace_sync", skip_all)]
148    pub fn execute_and_build_trace_sync(
149        self,
150        program: &Program,
151        host: &mut impl SyncHost,
152    ) -> Result<crate::trace::ExecutionTrace, ExecutionError> {
153        use crate::trace::{MAX_TRACE_LEN, build_hasher_chiplet, build_trace_with_prebuilt_hasher};
154
155        let (sender, receiver) = std::sync::mpsc::channel();
156        let mut tracer = ExecutionTracer::new_with_streamed_hasher(
157            self.options.core_trace_fragment_size(),
158            self.options.max_stack_depth(),
159            sender,
160        );
161
162        std::thread::scope(|scope| {
163            // Only the receiver crosses threads; execution (and the host) stay on this one.
164            // Spans are thread-local, so the builder thread re-enters this function's span
165            // to keep its work attributed under it in profiling traces.
166            let span = tracing::Span::current();
167            let hasher = scope.spawn(move || {
168                let _span = span.entered();
169                build_hasher_chiplet(receiver.into_iter().map(Ok), MAX_TRACE_LEN)
170            });
171
172            // Liveness invariant: both match arms consume `tracer` by value, so the scope
173            // closure captures it by move and any unwind (including a panic in execution)
174            // drops the stream's sender, unblocking the builder before the scope's join.
175            let execution_output = self.execute_with_tracer_sync(program, host, &mut tracer);
176
177            let mut inputs = match execution_output {
178                Ok(output) => Self::trace_build_inputs_from_parts(program, output, tracer),
179                Err(err) => {
180                    // Dropping the tracer drops the stream's sender; the builder then sees
181                    // end-of-input and finishes, letting the scope join it cleanly. The
182                    // execution error is the root cause, so the builder's outcome is only
183                    // logged, not propagated.
184                    drop(tracer);
185                    match hasher.join() {
186                        Ok(Err(builder_err)) => {
187                            tracing::debug!(%builder_err, "hasher builder also failed");
188                        },
189                        Ok(Ok(_)) => (),
190                        Err(panic) => std::panic::resume_unwind(panic),
191                    }
192                    return Err(err);
193                },
194            };
195            // End the stream before joining the builder.
196            drop(inputs.take_hasher_replay());
197            let hasher = match hasher.join() {
198                Ok(result) => result?,
199                Err(panic) => std::panic::resume_unwind(panic),
200            };
201
202            build_trace_with_prebuilt_hasher(inputs, hasher)
203        })
204    }
205
206    /// Executes the given program synchronously and returns the bundled trace inputs required by
207    /// [`crate::trace::build_trace`].
208    ///
209    /// # Example
210    /// ```
211    /// use miden_assembly::Assembler;
212    /// use miden_processor::{DefaultHost, FastProcessor, StackInputs};
213    ///
214    /// let program = Assembler::default()
215    ///     .assemble_program("prg", "begin push.1 drop end")
216    ///     .unwrap()
217    ///     .unwrap_program();
218    /// let mut host = DefaultHost::default();
219    ///
220    /// let trace_inputs = FastProcessor::new(StackInputs::default())
221    ///     .execute_trace_inputs_sync(&program, &mut host)
222    ///     .unwrap();
223    /// let trace = miden_processor::trace::build_trace(trace_inputs).unwrap();
224    ///
225    /// assert_eq!(*trace.program_hash(), program.hash());
226    /// ```
227    #[instrument(name = "execute_trace_inputs_sync", skip_all)]
228    pub fn execute_trace_inputs_sync(
229        self,
230        program: &Program,
231        host: &mut impl SyncHost,
232    ) -> Result<TraceBuildInputs, ExecutionError> {
233        let mut tracer = ExecutionTracer::new(
234            self.options.core_trace_fragment_size(),
235            self.options.max_stack_depth(),
236        );
237        let execution_output = self.execute_with_tracer_sync(program, host, &mut tracer)?;
238        Ok(Self::trace_build_inputs_from_parts(program, execution_output, tracer))
239    }
240
241    /// Executes the given program synchronously with package-owned source/debug context and returns
242    /// the bundled trace inputs required by [`crate::trace::build_trace`].
243    #[instrument(name = "execute_trace_inputs_with_package_debug_info_sync", skip_all)]
244    pub fn execute_trace_inputs_with_package_debug_info_sync(
245        self,
246        program: &Program,
247        package_debug_info: &PackageDebugInfo,
248        host: &mut impl SyncHost,
249    ) -> Result<TraceBuildInputs, ExecutionError> {
250        let mut tracer = ExecutionTracer::new(
251            self.options.core_trace_fragment_size(),
252            self.options.max_stack_depth(),
253        );
254        let execution_output = self.execute_with_package_debug_info_and_tracer_sync(
255            program,
256            package_debug_info,
257            None,
258            host,
259            &mut tracer,
260        )?;
261        Ok(Self::trace_build_inputs_from_parts(program, execution_output, tracer))
262    }
263
264    /// Executes the given program synchronously with package-owned source/debug context rooted at
265    /// `entrypoint_source_node_id` and returns the bundled trace inputs required by
266    /// [`crate::trace::build_trace`].
267    #[instrument(
268        name = "execute_trace_inputs_with_package_debug_info_at_source_node_sync",
269        skip_all
270    )]
271    pub fn execute_trace_inputs_with_package_debug_info_at_source_node_sync(
272        self,
273        program: &Program,
274        package_debug_info: &PackageDebugInfo,
275        entrypoint_source_node_id: DebugSourceNodeId,
276        host: &mut impl SyncHost,
277    ) -> Result<TraceBuildInputs, ExecutionError> {
278        let mut tracer = ExecutionTracer::new(
279            self.options.core_trace_fragment_size(),
280            self.options.max_stack_depth(),
281        );
282        let execution_output = self.execute_with_package_debug_info_and_tracer_sync(
283            program,
284            package_debug_info,
285            Some(entrypoint_source_node_id),
286            host,
287            &mut tracer,
288        )?;
289        Ok(Self::trace_build_inputs_from_parts(program, execution_output, tracer))
290    }
291
292    /// Async variant of [`Self::execute_trace_inputs_sync`] for async hosts.
293    #[inline(always)]
294    #[instrument(name = "execute_trace_inputs", skip_all)]
295    pub async fn execute_trace_inputs(
296        self,
297        program: &Program,
298        host: &mut impl Host,
299    ) -> Result<TraceBuildInputs, ExecutionError> {
300        let mut tracer = ExecutionTracer::new(
301            self.options.core_trace_fragment_size(),
302            self.options.max_stack_depth(),
303        );
304        let execution_output = self.execute_with_tracer(program, host, &mut tracer).await?;
305        Ok(Self::trace_build_inputs_from_parts(program, execution_output, tracer))
306    }
307
308    /// Async variant of [`Self::execute_trace_inputs_with_package_debug_info_sync`].
309    #[cfg(any(test, feature = "testing"))]
310    #[inline(always)]
311    #[instrument(name = "execute_trace_inputs_with_package_debug_info", skip_all)]
312    pub async fn execute_trace_inputs_with_package_debug_info(
313        self,
314        program: &Program,
315        package_debug_info: &PackageDebugInfo,
316        host: &mut impl Host,
317    ) -> Result<TraceBuildInputs, ExecutionError> {
318        let mut tracer = ExecutionTracer::new(
319            self.options.core_trace_fragment_size(),
320            self.options.max_stack_depth(),
321        );
322        let execution_output = self
323            .execute_with_package_debug_info_and_tracer(
324                program,
325                package_debug_info,
326                None,
327                host,
328                &mut tracer,
329            )
330            .await?;
331        Ok(Self::trace_build_inputs_from_parts(program, execution_output, tracer))
332    }
333
334    /// Async variant of
335    /// [`Self::execute_trace_inputs_with_package_debug_info_at_source_node_sync`].
336    #[cfg(any(test, feature = "testing"))]
337    #[inline(always)]
338    #[instrument(name = "execute_trace_inputs_with_package_debug_info_at_source_node", skip_all)]
339    pub async fn execute_trace_inputs_with_package_debug_info_at_source_node(
340        self,
341        program: &Program,
342        package_debug_info: &PackageDebugInfo,
343        entrypoint_source_node_id: DebugSourceNodeId,
344        host: &mut impl Host,
345    ) -> Result<TraceBuildInputs, ExecutionError> {
346        let mut tracer = ExecutionTracer::new(
347            self.options.core_trace_fragment_size(),
348            self.options.max_stack_depth(),
349        );
350        let execution_output = self
351            .execute_with_package_debug_info_and_tracer(
352                program,
353                package_debug_info,
354                Some(entrypoint_source_node_id),
355                host,
356                &mut tracer,
357            )
358            .await?;
359        Ok(Self::trace_build_inputs_from_parts(program, execution_output, tracer))
360    }
361
362    /// Executes the given program with the provided tracer using an async host.
363    pub async fn execute_with_tracer<T>(
364        mut self,
365        program: &Program,
366        host: &mut impl Host,
367        tracer: &mut T,
368    ) -> Result<ExecutionOutput, ExecutionError>
369    where
370        T: Tracer<Processor = Self, Forest = Arc<MastForest>>,
371    {
372        let mut continuation_stack = ContinuationStack::new(program);
373        let mut current_forest = program.mast_forest().clone();
374        let mut package_debug_info = None;
375
376        self.advice.extend_map(current_forest.advice_map()).map_exec_err_no_ctx()?;
377        let flow = self
378            .execute_impl_async(
379                &mut continuation_stack,
380                &mut current_forest,
381                program.kernel(),
382                host,
383                tracer,
384                &NeverStopper,
385                &mut package_debug_info,
386            )
387            .await;
388        Self::execution_result_from_flow(flow, self)
389    }
390
391    /// Executes the given program with package-owned source/debug context and the provided tracer
392    /// using an async host.
393    async fn execute_with_package_debug_info_and_tracer<T>(
394        mut self,
395        program: &Program,
396        package_debug_info: &PackageDebugInfo,
397        entrypoint_source_node_id: Option<DebugSourceNodeId>,
398        host: &mut impl Host,
399        tracer: &mut T,
400    ) -> Result<ExecutionOutput, ExecutionError>
401    where
402        T: Tracer<Processor = Self, Forest = Arc<MastForest>>,
403    {
404        let mut continuation_stack = Self::source_aware_continuation_stack(
405            program,
406            package_debug_info,
407            entrypoint_source_node_id,
408        )?;
409        let mut current_forest = program.mast_forest().clone();
410        let mut package_debug_info = Some(Arc::new(package_debug_info.clone()));
411
412        self.advice.extend_map(current_forest.advice_map()).map_exec_err_no_ctx()?;
413        let flow = self
414            .execute_impl_async(
415                &mut continuation_stack,
416                &mut current_forest,
417                program.kernel(),
418                host,
419                tracer,
420                &NeverStopper,
421                &mut package_debug_info,
422            )
423            .await;
424        Self::execution_result_from_flow(flow, self)
425    }
426
427    /// Executes the given program with the provided tracer using a sync host.
428    pub fn execute_with_tracer_sync<T>(
429        mut self,
430        program: &Program,
431        host: &mut impl SyncHost,
432        tracer: &mut T,
433    ) -> Result<ExecutionOutput, ExecutionError>
434    where
435        T: Tracer<Processor = Self, Forest = Arc<MastForest>>,
436    {
437        let mut continuation_stack = ContinuationStack::new(program);
438        let mut current_forest = program.mast_forest().clone();
439        let mut package_debug_info = None;
440
441        self.advice.extend_map(current_forest.advice_map()).map_exec_err_no_ctx()?;
442        let flow = self.execute_impl(
443            &mut continuation_stack,
444            &mut current_forest,
445            program.kernel(),
446            host,
447            tracer,
448            &NeverStopper,
449            &mut package_debug_info,
450        );
451        Self::execution_result_from_flow(flow, self)
452    }
453
454    /// Executes the given program with package-owned source/debug context and the provided tracer
455    /// using a sync host.
456    fn execute_with_package_debug_info_and_tracer_sync<T>(
457        mut self,
458        program: &Program,
459        package_debug_info: &PackageDebugInfo,
460        entrypoint_source_node_id: Option<DebugSourceNodeId>,
461        host: &mut impl SyncHost,
462        tracer: &mut T,
463    ) -> Result<ExecutionOutput, ExecutionError>
464    where
465        T: Tracer<Processor = Self, Forest = Arc<MastForest>>,
466    {
467        let mut continuation_stack = Self::source_aware_continuation_stack(
468            program,
469            package_debug_info,
470            entrypoint_source_node_id,
471        )?;
472        let mut current_forest = program.mast_forest().clone();
473        let mut package_debug_info = Some(Arc::new(package_debug_info.clone()));
474
475        self.advice.extend_map(current_forest.advice_map()).map_exec_err_no_ctx()?;
476        let flow = self.execute_impl(
477            &mut continuation_stack,
478            &mut current_forest,
479            program.kernel(),
480            host,
481            tracer,
482            &NeverStopper,
483            &mut package_debug_info,
484        );
485        Self::execution_result_from_flow(flow, self)
486    }
487
488    /// Executes a single clock cycle synchronously.
489    pub fn step_sync(
490        &mut self,
491        host: &mut impl SyncHost,
492        resume_ctx: ResumeContext,
493    ) -> Result<Option<ResumeContext>, ExecutionError> {
494        let ResumeContext {
495            mut current_forest,
496            mut continuation_stack,
497            kernel,
498            mut package_debug_info,
499        } = resume_ctx;
500
501        let flow = self.execute_impl(
502            &mut continuation_stack,
503            &mut current_forest,
504            &kernel,
505            host,
506            &mut NoopTracer,
507            &StepStopper,
508            &mut package_debug_info,
509        );
510        Self::resume_context_from_flow(
511            flow,
512            continuation_stack,
513            current_forest,
514            kernel,
515            package_debug_info,
516        )
517    }
518
519    /// Executes a single clock cycle synchronously with package-owned source/debug context.
520    pub fn step_with_package_debug_info_sync(
521        &mut self,
522        host: &mut impl SyncHost,
523        resume_ctx: ResumeContext,
524        package_debug_info: &PackageDebugInfo,
525    ) -> Result<Option<ResumeContext>, ExecutionError> {
526        let ResumeContext {
527            mut current_forest,
528            mut continuation_stack,
529            kernel,
530            package_debug_info: mut active_package_debug_info,
531        } = resume_ctx;
532        Self::ensure_source_aware_step_context(
533            &mut continuation_stack,
534            &mut active_package_debug_info,
535            package_debug_info,
536        )?;
537
538        let flow = self.execute_impl(
539            &mut continuation_stack,
540            &mut current_forest,
541            &kernel,
542            host,
543            &mut NoopTracer,
544            &StepStopper,
545            &mut active_package_debug_info,
546        );
547        Self::resume_context_from_flow(
548            flow,
549            continuation_stack,
550            current_forest,
551            kernel,
552            active_package_debug_info,
553        )
554    }
555
556    /// Async variant of [`Self::step_sync`].
557    #[inline(always)]
558    pub async fn step(
559        &mut self,
560        host: &mut impl Host,
561        resume_ctx: ResumeContext,
562    ) -> Result<Option<ResumeContext>, ExecutionError> {
563        let ResumeContext {
564            mut current_forest,
565            mut continuation_stack,
566            kernel,
567            mut package_debug_info,
568        } = resume_ctx;
569
570        let flow = self
571            .execute_impl_async(
572                &mut continuation_stack,
573                &mut current_forest,
574                &kernel,
575                host,
576                &mut NoopTracer,
577                &StepStopper,
578                &mut package_debug_info,
579            )
580            .await;
581        Self::resume_context_from_flow(
582            flow,
583            continuation_stack,
584            current_forest,
585            kernel,
586            package_debug_info,
587        )
588    }
589
590    /// Async variant of [`Self::step_with_package_debug_info_sync`].
591    #[inline(always)]
592    pub async fn step_with_package_debug_info(
593        &mut self,
594        host: &mut impl Host,
595        resume_ctx: ResumeContext,
596        package_debug_info: &PackageDebugInfo,
597    ) -> Result<Option<ResumeContext>, ExecutionError> {
598        let ResumeContext {
599            mut current_forest,
600            mut continuation_stack,
601            kernel,
602            package_debug_info: mut active_package_debug_info,
603        } = resume_ctx;
604        Self::ensure_source_aware_step_context(
605            &mut continuation_stack,
606            &mut active_package_debug_info,
607            package_debug_info,
608        )?;
609
610        let flow = self
611            .execute_impl_async(
612                &mut continuation_stack,
613                &mut current_forest,
614                &kernel,
615                host,
616                &mut NoopTracer,
617                &StepStopper,
618                &mut active_package_debug_info,
619            )
620            .await;
621        Self::resume_context_from_flow(
622            flow,
623            continuation_stack,
624            current_forest,
625            kernel,
626            active_package_debug_info,
627        )
628    }
629
630    /// Pairs execution output with the trace inputs captured by the tracer.
631    #[inline(always)]
632    fn trace_build_inputs_from_parts(
633        program: &Program,
634        execution_output: ExecutionOutput,
635        tracer: ExecutionTracer,
636    ) -> TraceBuildInputs {
637        TraceBuildInputs::from_execution(
638            program,
639            execution_output,
640            tracer.into_trace_generation_context(),
641        )
642    }
643
644    pub(super) fn source_aware_continuation_stack(
645        program: &Program,
646        package_debug_info: &PackageDebugInfo,
647        entrypoint_source_node_id: Option<DebugSourceNodeId>,
648    ) -> Result<ContinuationStack<Arc<MastForest>>, ExecutionError> {
649        if let Some(source_node_id) = entrypoint_source_node_id {
650            let Some(source_node) = package_debug_info.source_node(source_node_id) else {
651                return Err(ExecutionError::Internal(
652                    "package debug source graph is missing the entrypoint source node",
653                ));
654            };
655            if source_node.exec_node != program.entrypoint() {
656                return Err(ExecutionError::Internal(
657                    "package debug entrypoint source node does not match the program entrypoint",
658                ));
659            }
660
661            return Ok(ContinuationStack::new_with_source_node_id(program, source_node_id));
662        }
663
664        let Some(source_node_id) = package_debug_info
665            .unique_source_root_for_exec_node(program.entrypoint())
666            .map_err(|_| {
667                ExecutionError::Internal(
668                    "package debug source graph has ambiguous or malformed entrypoint roots",
669                )
670            })?
671        else {
672            return Ok(ContinuationStack::new_with_optional_source_node_id(program, None));
673        };
674
675        Ok(ContinuationStack::new_with_source_node_id(program, source_node_id))
676    }
677
678    #[cfg(any(test, feature = "testing"))]
679    fn source_aware_resume_context(
680        &mut self,
681        program: &Program,
682        package_debug_info: &PackageDebugInfo,
683        entrypoint_source_node_id: Option<DebugSourceNodeId>,
684    ) -> Result<ResumeContext, ExecutionError> {
685        self.advice
686            .extend_map(program.mast_forest().advice_map())
687            .map_exec_err_no_ctx()?;
688
689        Ok(ResumeContext {
690            current_forest: program.mast_forest().clone(),
691            continuation_stack: Self::source_aware_continuation_stack(
692                program,
693                package_debug_info,
694                entrypoint_source_node_id,
695            )?,
696            kernel: program.kernel().clone(),
697            package_debug_info: Some(Arc::new(package_debug_info.clone())),
698        })
699    }
700
701    fn ensure_source_aware_step_context(
702        continuation_stack: &mut ContinuationStack<Arc<MastForest>>,
703        package_debug_info: &mut Option<Arc<PackageDebugInfo>>,
704        supplied_package_debug_info: &PackageDebugInfo,
705    ) -> Result<(), ExecutionError> {
706        if package_debug_info.is_none() {
707            *package_debug_info = Some(Arc::new(supplied_package_debug_info.clone()));
708        }
709
710        if !continuation_stack.tracks_source_nodes() {
711            let source_node_id = Self::source_root_for_next_continuation(
712                continuation_stack,
713                package_debug_info.as_deref().expect("package debug info was just initialized"),
714            )?;
715            continuation_stack.start_tracking_source_nodes(source_node_id);
716        }
717
718        Ok(())
719    }
720
721    fn source_root_for_next_continuation(
722        continuation_stack: &ContinuationStack<Arc<MastForest>>,
723        package_debug_info: &PackageDebugInfo,
724    ) -> Result<Option<DebugSourceNodeId>, ExecutionError> {
725        let Some((continuation, _)) = continuation_stack.peek_continuation_with_source_node_id()
726        else {
727            return Ok(None);
728        };
729
730        let Some(exec_node) = continuation.exec_node() else {
731            return Ok(None);
732        };
733
734        package_debug_info.unique_source_root_for_exec_node(exec_node).map_err(|_| {
735            ExecutionError::Internal(
736                "package debug source graph has ambiguous or malformed continuation roots",
737            )
738        })
739    }
740
741    /// Converts a step-wise execution result into the next resume context, if execution stopped.
742    #[inline(always)]
743    fn resume_context_from_flow(
744        flow: ControlFlow<BreakReason<Arc<MastForest>>, StackOutputs>,
745        mut continuation_stack: ContinuationStack<Arc<MastForest>>,
746        current_forest: Arc<MastForest>,
747        kernel: KernelDescriptor,
748        package_debug_info: Option<Arc<PackageDebugInfo>>,
749    ) -> Result<Option<ResumeContext>, ExecutionError> {
750        match flow {
751            ControlFlow::Continue(_) => Ok(None),
752            ControlFlow::Break(break_reason) => match break_reason {
753                BreakReason::Err(err) => Err(err),
754                BreakReason::Stopped(maybe_continuation) => {
755                    if let Some((continuation, source_node_id)) = maybe_continuation {
756                        continuation_stack.push_with_source_node_id(continuation, source_node_id);
757                    }
758
759                    Ok(Some(ResumeContext {
760                        current_forest,
761                        continuation_stack,
762                        kernel,
763                        package_debug_info,
764                    }))
765                },
766            },
767        }
768    }
769
770    /// Materializes the current stack as public outputs without consuming the processor.
771    #[inline(always)]
772    fn current_stack_outputs(&self) -> StackOutputs {
773        StackOutputs::new(
774            &self.stack[self.stack_bot_idx..self.stack_top_idx]
775                .iter()
776                .rev()
777                .copied()
778                .collect::<Vec<_>>(),
779        )
780        .unwrap()
781    }
782
783    /// Executes the given program with the provided tracer and returns the stack outputs.
784    ///
785    /// This function takes a `&mut self` (compared to `self` for the public sync execution
786    /// methods) so that the processor state may be accessed after execution. Reusing the same
787    /// processor for a second program is incorrect. This is mainly meant to be used in tests.
788    fn execute_impl<S, T>(
789        &mut self,
790        continuation_stack: &mut ContinuationStack<Arc<MastForest>>,
791        current_forest: &mut Arc<MastForest>,
792        kernel: &KernelDescriptor,
793        host: &mut impl SyncHost,
794        tracer: &mut T,
795        stopper: &S,
796        package_debug_info: &mut Option<Arc<PackageDebugInfo>>,
797    ) -> ControlFlow<BreakReason<Arc<MastForest>>, StackOutputs>
798    where
799        S: Stopper<Processor = Self, Forest = Arc<MastForest>>,
800        T: Tracer<Processor = Self, Forest = Arc<MastForest>>,
801    {
802        while let ControlFlow::Break(internal_break_reason) = execute_impl(
803            self,
804            continuation_stack,
805            current_forest,
806            kernel,
807            host,
808            tracer,
809            stopper,
810            package_debug_info,
811        ) {
812            let current_package_debug_info = package_debug_info.as_deref();
813            let source_aware_execution =
814                current_package_debug_info.is_some() || continuation_stack.tracks_source_nodes();
815            match internal_break_reason {
816                InternalBreakReason::User(break_reason) => return ControlFlow::Break(break_reason),
817                InternalBreakReason::Emit { op_idx, continuation, source_node_id } => {
818                    self.op_emit_sync(host, op_idx, current_package_debug_info, source_node_id)?;
819
820                    finish_emit_op_execution(
821                        continuation,
822                        source_node_id,
823                        self,
824                        continuation_stack,
825                        current_forest,
826                        tracer,
827                        stopper,
828                    )?;
829                },
830                InternalBreakReason::LoadMastForestFromDyn { callee_hash, source_node_id } => {
831                    let (root_id, new_forest, new_package_debug_info, new_source_node_id) =
832                        match self.load_mast_forest_sync(
833                            callee_hash,
834                            host,
835                            current_package_debug_info,
836                            source_node_id,
837                            source_aware_execution,
838                        ) {
839                            Ok(result) => result,
840                            Err(err) => return ControlFlow::Break(BreakReason::Err(err)),
841                        };
842
843                    finish_load_mast_forest_from_dyn_start(
844                        root_id,
845                        new_forest,
846                        new_package_debug_info,
847                        new_source_node_id,
848                        self,
849                        current_forest,
850                        package_debug_info,
851                        continuation_stack,
852                        tracer,
853                        stopper,
854                    )?;
855                },
856                InternalBreakReason::LoadMastForestFromExternal {
857                    external_node_id,
858                    procedure_hash,
859                    source_node_id,
860                } => {
861                    let (root_id, new_forest, new_package_debug_info, new_source_node_id) =
862                        match self.load_mast_forest_sync(
863                            procedure_hash,
864                            host,
865                            current_package_debug_info,
866                            source_node_id,
867                            source_aware_execution,
868                        ) {
869                            Ok(result) => result,
870                            Err(err) => {
871                                let maybe_enriched_err = maybe_use_caller_error_context(
872                                    err,
873                                    continuation_stack,
874                                    current_package_debug_info,
875                                    host,
876                                );
877                                return ControlFlow::Break(BreakReason::Err(maybe_enriched_err));
878                            },
879                        };
880
881                    finish_load_mast_forest_from_external(
882                        root_id,
883                        new_forest,
884                        new_package_debug_info,
885                        new_source_node_id,
886                        external_node_id,
887                        current_forest,
888                        package_debug_info,
889                        continuation_stack,
890                        tracer,
891                    )?;
892                },
893            }
894        }
895
896        match StackOutputs::new(
897            &self.stack[self.stack_bot_idx..self.stack_top_idx]
898                .iter()
899                .rev()
900                .copied()
901                .collect::<Vec<_>>(),
902        ) {
903            Ok(stack_outputs) => ControlFlow::Continue(stack_outputs),
904            Err(_) => ControlFlow::Break(BreakReason::Err(ExecutionError::OutputStackOverflow(
905                self.stack_top_idx - self.stack_bot_idx - MIN_STACK_DEPTH,
906            ))),
907        }
908    }
909
910    async fn execute_impl_async<S, T>(
911        &mut self,
912        continuation_stack: &mut ContinuationStack<Arc<MastForest>>,
913        current_forest: &mut Arc<MastForest>,
914        kernel: &KernelDescriptor,
915        host: &mut impl Host,
916        tracer: &mut T,
917        stopper: &S,
918        package_debug_info: &mut Option<Arc<PackageDebugInfo>>,
919    ) -> ControlFlow<BreakReason<Arc<MastForest>>, StackOutputs>
920    where
921        S: Stopper<Processor = Self, Forest = Arc<MastForest>>,
922        T: Tracer<Processor = Self, Forest = Arc<MastForest>>,
923    {
924        while let ControlFlow::Break(internal_break_reason) = execute_impl(
925            self,
926            continuation_stack,
927            current_forest,
928            kernel,
929            host,
930            tracer,
931            stopper,
932            package_debug_info,
933        ) {
934            let current_package_debug_info = package_debug_info.as_deref();
935            let source_aware_execution =
936                current_package_debug_info.is_some() || continuation_stack.tracks_source_nodes();
937            match internal_break_reason {
938                InternalBreakReason::User(break_reason) => return ControlFlow::Break(break_reason),
939                InternalBreakReason::Emit { op_idx, continuation, source_node_id } => {
940                    self.op_emit(host, op_idx, current_package_debug_info, source_node_id).await?;
941
942                    finish_emit_op_execution(
943                        continuation,
944                        source_node_id,
945                        self,
946                        continuation_stack,
947                        current_forest,
948                        tracer,
949                        stopper,
950                    )?;
951                },
952                InternalBreakReason::LoadMastForestFromDyn { callee_hash, source_node_id } => {
953                    let (root_id, new_forest, new_package_debug_info, new_source_node_id) =
954                        match self
955                            .load_mast_forest(
956                                callee_hash,
957                                host,
958                                current_package_debug_info,
959                                source_node_id,
960                                source_aware_execution,
961                            )
962                            .await
963                        {
964                            Ok(result) => result,
965                            Err(err) => return ControlFlow::Break(BreakReason::Err(err)),
966                        };
967
968                    finish_load_mast_forest_from_dyn_start(
969                        root_id,
970                        new_forest,
971                        new_package_debug_info,
972                        new_source_node_id,
973                        self,
974                        current_forest,
975                        package_debug_info,
976                        continuation_stack,
977                        tracer,
978                        stopper,
979                    )?;
980                },
981                InternalBreakReason::LoadMastForestFromExternal {
982                    external_node_id,
983                    procedure_hash,
984                    source_node_id,
985                } => {
986                    let (root_id, new_forest, new_package_debug_info, new_source_node_id) =
987                        match self
988                            .load_mast_forest(
989                                procedure_hash,
990                                host,
991                                current_package_debug_info,
992                                source_node_id,
993                                source_aware_execution,
994                            )
995                            .await
996                        {
997                            Ok(result) => result,
998                            Err(err) => {
999                                let maybe_enriched_err = maybe_use_caller_error_context(
1000                                    err,
1001                                    continuation_stack,
1002                                    current_package_debug_info,
1003                                    host,
1004                                );
1005                                return ControlFlow::Break(BreakReason::Err(maybe_enriched_err));
1006                            },
1007                        };
1008
1009                    finish_load_mast_forest_from_external(
1010                        root_id,
1011                        new_forest,
1012                        new_package_debug_info,
1013                        new_source_node_id,
1014                        external_node_id,
1015                        current_forest,
1016                        package_debug_info,
1017                        continuation_stack,
1018                        tracer,
1019                    )?;
1020                },
1021            }
1022        }
1023
1024        match StackOutputs::new(
1025            &self.stack[self.stack_bot_idx..self.stack_top_idx]
1026                .iter()
1027                .rev()
1028                .copied()
1029                .collect::<Vec<_>>(),
1030        ) {
1031            Ok(stack_outputs) => ControlFlow::Continue(stack_outputs),
1032            Err(_) => ControlFlow::Break(BreakReason::Err(ExecutionError::OutputStackOverflow(
1033                self.stack_top_idx - self.stack_bot_idx - MIN_STACK_DEPTH,
1034            ))),
1035        }
1036    }
1037
1038    // HELPERS
1039    // ------------------------------------------------------------------------------------------
1040
1041    fn load_mast_forest_sync(
1042        &mut self,
1043        node_digest: Word,
1044        host: &mut impl SyncHost,
1045        package_debug_info: Option<&PackageDebugInfo>,
1046        source_node_id: Option<DebugSourceNodeId>,
1047        source_aware_execution: bool,
1048    ) -> Result<
1049        (
1050            MastNodeId,
1051            Arc<MastForest>,
1052            Option<Arc<PackageDebugInfo>>,
1053            Option<DebugSourceNodeId>,
1054        ),
1055        ExecutionError,
1056    > {
1057        let loaded_mast_forest = host.get_mast_forest(&node_digest).ok_or_else(|| {
1058            match (package_debug_info, source_node_id) {
1059                (Some(debug_info), Some(source_node_id)) => {
1060                    crate::errors::procedure_not_found_with_package_source_context(
1061                        node_digest,
1062                        PackageSourceDebugContext::new(debug_info, source_node_id),
1063                        host,
1064                    )
1065                },
1066                _ => crate::errors::procedure_not_found_with_context(node_digest),
1067            }
1068        })?;
1069        let mast_forest = loaded_mast_forest.mast_forest().clone();
1070
1071        let root_id = mast_forest.find_procedure_root(node_digest).ok_or_else(|| {
1072            let context = match (package_debug_info, source_node_id) {
1073                (Some(debug_info), Some(source_node_id)) => {
1074                    Some(PackageSourceDebugContext::new(debug_info, source_node_id))
1075                },
1076                _ => None,
1077            };
1078            malformed_mast_forest_with_context(node_digest, context, host)
1079        })?;
1080
1081        self.advice.extend_map(mast_forest.advice_map()).map_exec_err()?;
1082        let (loaded_package_debug_info, loaded_source_node_id) =
1083            Self::loaded_package_source_context(
1084                &loaded_mast_forest,
1085                root_id,
1086                source_aware_execution,
1087            )?;
1088
1089        Ok((root_id, mast_forest, loaded_package_debug_info, loaded_source_node_id))
1090    }
1091
1092    async fn load_mast_forest(
1093        &mut self,
1094        node_digest: Word,
1095        host: &mut impl Host,
1096        package_debug_info: Option<&PackageDebugInfo>,
1097        source_node_id: Option<DebugSourceNodeId>,
1098        source_aware_execution: bool,
1099    ) -> Result<
1100        (
1101            MastNodeId,
1102            Arc<MastForest>,
1103            Option<Arc<PackageDebugInfo>>,
1104            Option<DebugSourceNodeId>,
1105        ),
1106        ExecutionError,
1107    > {
1108        let loaded_mast_forest = if let Some(mast_forest) = host.get_mast_forest(&node_digest).await
1109        {
1110            mast_forest
1111        } else {
1112            return Err(match (package_debug_info, source_node_id) {
1113                (Some(debug_info), Some(source_node_id)) => {
1114                    crate::errors::procedure_not_found_with_package_source_context(
1115                        node_digest,
1116                        PackageSourceDebugContext::new(debug_info, source_node_id),
1117                        host,
1118                    )
1119                },
1120                _ => crate::errors::procedure_not_found_with_context(node_digest),
1121            });
1122        };
1123        let mast_forest = loaded_mast_forest.mast_forest().clone();
1124
1125        let root_id = mast_forest.find_procedure_root(node_digest).ok_or_else(|| {
1126            let context = match (package_debug_info, source_node_id) {
1127                (Some(debug_info), Some(source_node_id)) => {
1128                    Some(PackageSourceDebugContext::new(debug_info, source_node_id))
1129                },
1130                _ => None,
1131            };
1132            malformed_mast_forest_with_context(node_digest, context, host)
1133        })?;
1134
1135        self.advice.extend_map(mast_forest.advice_map()).map_exec_err()?;
1136        let (loaded_package_debug_info, loaded_source_node_id) =
1137            Self::loaded_package_source_context(
1138                &loaded_mast_forest,
1139                root_id,
1140                source_aware_execution,
1141            )?;
1142
1143        Ok((root_id, mast_forest, loaded_package_debug_info, loaded_source_node_id))
1144    }
1145
1146    fn loaded_package_source_context(
1147        loaded_mast_forest: &LoadedMastForest,
1148        root_id: MastNodeId,
1149        source_aware_execution: bool,
1150    ) -> Result<(Option<Arc<PackageDebugInfo>>, Option<DebugSourceNodeId>), ExecutionError> {
1151        if !source_aware_execution {
1152            return Ok((None, None));
1153        }
1154
1155        let Some(package_debug_info) = loaded_mast_forest
1156            .package_debug_info()
1157            .map_err(|_| ExecutionError::Internal("loaded package debug info is malformed"))?
1158        else {
1159            return Ok((None, None));
1160        };
1161
1162        let source_node_id = match package_debug_info.unique_source_root_for_exec_node(root_id) {
1163            Ok(source_node_id) => source_node_id,
1164            Err(DebugSourceGraphLookupError::AmbiguousRoot { .. }) => None,
1165            Err(_) => {
1166                return Err(ExecutionError::Internal(
1167                    "loaded package debug source graph has malformed entrypoint roots",
1168                ));
1169            },
1170        };
1171
1172        Ok((Some(package_debug_info), source_node_id))
1173    }
1174
1175    /// Executes the given program synchronously one step at a time.
1176    pub fn execute_by_step_sync(
1177        mut self,
1178        program: &Program,
1179        host: &mut impl SyncHost,
1180    ) -> Result<StackOutputs, ExecutionError> {
1181        let mut current_resume_ctx = self.get_initial_resume_context(program)?;
1182
1183        loop {
1184            match self.step_sync(host, current_resume_ctx)? {
1185                Some(next_resume_ctx) => {
1186                    current_resume_ctx = next_resume_ctx;
1187                },
1188                None => break Ok(self.current_stack_outputs()),
1189            }
1190        }
1191    }
1192
1193    /// Executes the given program synchronously one step at a time with package-owned source/debug
1194    /// context.
1195    #[cfg(any(test, feature = "testing"))]
1196    pub fn execute_by_step_with_package_debug_info_sync(
1197        mut self,
1198        program: &Program,
1199        package_debug_info: &PackageDebugInfo,
1200        host: &mut impl SyncHost,
1201    ) -> Result<StackOutputs, ExecutionError> {
1202        let mut current_resume_ctx =
1203            self.source_aware_resume_context(program, package_debug_info, None)?;
1204
1205        loop {
1206            match self.step_with_package_debug_info_sync(
1207                host,
1208                current_resume_ctx,
1209                package_debug_info,
1210            )? {
1211                Some(next_resume_ctx) => {
1212                    current_resume_ctx = next_resume_ctx;
1213                },
1214                None => break Ok(self.current_stack_outputs()),
1215            }
1216        }
1217    }
1218
1219    /// Executes the given program synchronously one step at a time with package-owned source/debug
1220    /// context rooted at `entrypoint_source_node_id`.
1221    #[cfg(any(test, feature = "testing"))]
1222    pub fn execute_by_step_with_package_debug_info_at_source_node_sync(
1223        mut self,
1224        program: &Program,
1225        package_debug_info: &PackageDebugInfo,
1226        entrypoint_source_node_id: DebugSourceNodeId,
1227        host: &mut impl SyncHost,
1228    ) -> Result<StackOutputs, ExecutionError> {
1229        let mut current_resume_ctx = self.source_aware_resume_context(
1230            program,
1231            package_debug_info,
1232            Some(entrypoint_source_node_id),
1233        )?;
1234
1235        loop {
1236            match self.step_with_package_debug_info_sync(
1237                host,
1238                current_resume_ctx,
1239                package_debug_info,
1240            )? {
1241                Some(next_resume_ctx) => {
1242                    current_resume_ctx = next_resume_ctx;
1243                },
1244                None => break Ok(self.current_stack_outputs()),
1245            }
1246        }
1247    }
1248
1249    /// Async variant of [`Self::execute_by_step_sync`].
1250    #[inline(always)]
1251    pub async fn execute_by_step(
1252        mut self,
1253        program: &Program,
1254        host: &mut impl Host,
1255    ) -> Result<StackOutputs, ExecutionError> {
1256        let mut current_resume_ctx = self.get_initial_resume_context(program)?;
1257        let mut processor = self;
1258
1259        loop {
1260            match processor.step(host, current_resume_ctx).await? {
1261                Some(next_resume_ctx) => {
1262                    current_resume_ctx = next_resume_ctx;
1263                },
1264                None => break Ok(processor.current_stack_outputs()),
1265            }
1266        }
1267    }
1268
1269    /// Async variant of [`Self::execute_by_step_with_package_debug_info_sync`].
1270    #[cfg(any(test, feature = "testing"))]
1271    #[inline(always)]
1272    pub async fn execute_by_step_with_package_debug_info(
1273        mut self,
1274        program: &Program,
1275        package_debug_info: &PackageDebugInfo,
1276        host: &mut impl Host,
1277    ) -> Result<StackOutputs, ExecutionError> {
1278        let mut current_resume_ctx =
1279            self.source_aware_resume_context(program, package_debug_info, None)?;
1280        let mut processor = self;
1281
1282        loop {
1283            match processor
1284                .step_with_package_debug_info(host, current_resume_ctx, package_debug_info)
1285                .await?
1286            {
1287                Some(next_resume_ctx) => {
1288                    current_resume_ctx = next_resume_ctx;
1289                },
1290                None => break Ok(processor.current_stack_outputs()),
1291            }
1292        }
1293    }
1294
1295    /// Async variant of
1296    /// [`Self::execute_by_step_with_package_debug_info_at_source_node_sync`].
1297    #[cfg(any(test, feature = "testing"))]
1298    #[inline(always)]
1299    pub async fn execute_by_step_with_package_debug_info_at_source_node(
1300        mut self,
1301        program: &Program,
1302        package_debug_info: &PackageDebugInfo,
1303        entrypoint_source_node_id: DebugSourceNodeId,
1304        host: &mut impl Host,
1305    ) -> Result<StackOutputs, ExecutionError> {
1306        let mut current_resume_ctx = self.source_aware_resume_context(
1307            program,
1308            package_debug_info,
1309            Some(entrypoint_source_node_id),
1310        )?;
1311        let mut processor = self;
1312
1313        loop {
1314            match processor
1315                .step_with_package_debug_info(host, current_resume_ctx, package_debug_info)
1316                .await?
1317            {
1318                Some(next_resume_ctx) => {
1319                    current_resume_ctx = next_resume_ctx;
1320                },
1321                None => break Ok(processor.current_stack_outputs()),
1322            }
1323        }
1324    }
1325
1326    /// Similar to [`Self::execute_sync`], but allows mutable access to the processor.
1327    ///
1328    /// This is mainly meant to be used in tests.
1329    #[cfg(any(test, feature = "testing"))]
1330    pub fn execute_mut_sync(
1331        &mut self,
1332        program: &Program,
1333        host: &mut impl SyncHost,
1334    ) -> Result<StackOutputs, ExecutionError> {
1335        let mut continuation_stack = ContinuationStack::new(program);
1336        let mut current_forest = program.mast_forest().clone();
1337        let mut package_debug_info = None;
1338
1339        self.advice.extend_map(current_forest.advice_map()).map_exec_err_no_ctx()?;
1340
1341        let flow = self.execute_impl(
1342            &mut continuation_stack,
1343            &mut current_forest,
1344            program.kernel(),
1345            host,
1346            &mut NoopTracer,
1347            &NeverStopper,
1348            &mut package_debug_info,
1349        );
1350        Self::stack_result_from_flow(flow)
1351    }
1352
1353    /// Async variant of [`Self::execute_mut_sync`].
1354    #[cfg(any(test, feature = "testing"))]
1355    #[inline(always)]
1356    pub async fn execute_mut(
1357        &mut self,
1358        program: &Program,
1359        host: &mut impl Host,
1360    ) -> Result<StackOutputs, ExecutionError> {
1361        let mut continuation_stack = ContinuationStack::new(program);
1362        let mut current_forest = program.mast_forest().clone();
1363        let mut package_debug_info = None;
1364
1365        self.advice.extend_map(current_forest.advice_map()).map_exec_err_no_ctx()?;
1366
1367        let flow = self
1368            .execute_impl_async(
1369                &mut continuation_stack,
1370                &mut current_forest,
1371                program.kernel(),
1372                host,
1373                &mut NoopTracer,
1374                &NeverStopper,
1375                &mut package_debug_info,
1376            )
1377            .await;
1378        Self::stack_result_from_flow(flow)
1379    }
1380}