miden_processor/
lib.rs

1#![no_std]
2
3#[macro_use]
4extern crate alloc;
5
6#[cfg(feature = "std")]
7extern crate std;
8
9use alloc::vec::Vec;
10
11use miden_air::trace::{
12    CHIPLETS_WIDTH, DECODER_TRACE_WIDTH, MIN_TRACE_LEN, RANGE_CHECK_TRACE_WIDTH, STACK_TRACE_WIDTH,
13    SYS_TRACE_WIDTH,
14};
15pub use miden_air::{ExecutionOptions, ExecutionOptionsError, RowIndex};
16pub use vm_core::{
17    AssemblyOp, EMPTY_WORD, Felt, Kernel, ONE, Operation, Program, ProgramInfo, QuadExtension,
18    StackInputs, StackOutputs, Word, ZERO,
19    chiplets::hasher::Digest,
20    crypto::merkle::SMT_DEPTH,
21    errors::InputError,
22    mast::{MastForest, MastNode, MastNodeId},
23    sys_events::SystemEvent,
24    utils::{DeserializationError, collections::KvMap},
25};
26use vm_core::{
27    Decorator, DecoratorIterator, FieldElement,
28    mast::{
29        BasicBlockNode, CallNode, DynNode, JoinNode, LoopNode, OP_GROUP_SIZE, OpBatch, SplitNode,
30    },
31};
32pub use winter_prover::matrix::ColMatrix;
33
34mod operations;
35
36mod system;
37use system::System;
38pub use system::{ContextId, FMP_MIN, SYSCALL_FMP_MIN};
39
40mod decoder;
41use decoder::Decoder;
42
43mod stack;
44use stack::Stack;
45
46mod range;
47use range::RangeChecker;
48
49mod host;
50pub use host::{
51    DefaultHost, Host, MastForestStore, MemMastForestStore,
52    advice::{AdviceInputs, AdviceProvider, AdviceSource, MemAdviceProvider, RecAdviceProvider},
53};
54
55mod chiplets;
56use chiplets::Chiplets;
57
58mod trace;
59use trace::TraceFragment;
60pub use trace::{ChipletsLengths, ExecutionTrace, NUM_RAND_ROWS, TraceLenSummary};
61
62mod errors;
63pub use errors::{ExecutionError, Ext2InttError};
64
65pub mod utils;
66
67mod debug;
68pub use debug::{AsmOpInfo, VmState, VmStateIterator};
69
70// RE-EXPORTS
71// ================================================================================================
72
73pub mod math {
74    pub use vm_core::{Felt, FieldElement, StarkField};
75    pub use winter_prover::math::fft;
76}
77
78pub mod crypto {
79    pub use vm_core::crypto::{
80        hash::{
81            Blake3_192, Blake3_256, ElementHasher, Hasher, Rpo256, RpoDigest, Rpx256, RpxDigest,
82        },
83        merkle::{
84            MerkleError, MerklePath, MerkleStore, MerkleTree, NodeIndex, PartialMerkleTree,
85            SimpleSmt,
86        },
87        random::{RandomCoin, RpoRandomCoin, RpxRandomCoin, WinterRandomCoin},
88    };
89}
90
91// TYPE ALIASES
92// ================================================================================================
93
94type QuadFelt = QuadExtension<Felt>;
95
96type SysTrace = [Vec<Felt>; SYS_TRACE_WIDTH];
97
98pub struct DecoderTrace {
99    trace: [Vec<Felt>; DECODER_TRACE_WIDTH],
100    aux_builder: decoder::AuxTraceBuilder,
101}
102
103pub struct StackTrace {
104    trace: [Vec<Felt>; STACK_TRACE_WIDTH],
105}
106
107pub struct RangeCheckTrace {
108    trace: [Vec<Felt>; RANGE_CHECK_TRACE_WIDTH],
109    aux_builder: range::AuxTraceBuilder,
110}
111
112pub struct ChipletsTrace {
113    trace: [Vec<Felt>; CHIPLETS_WIDTH],
114    aux_builder: chiplets::AuxTraceBuilder,
115}
116
117// EXECUTORS
118// ================================================================================================
119
120/// Returns an execution trace resulting from executing the provided program against the provided
121/// inputs.
122///
123/// The `host` parameter is used to provide the external environment to the program being executed,
124/// such as access to the advice provider and libraries that the program depends on.
125#[tracing::instrument("execute_program", skip_all)]
126pub fn execute(
127    program: &Program,
128    stack_inputs: StackInputs,
129    host: &mut impl Host,
130    options: ExecutionOptions,
131) -> Result<ExecutionTrace, ExecutionError> {
132    let mut process = Process::new(program.kernel().clone(), stack_inputs, options);
133    let stack_outputs = process.execute(program, host)?;
134    let trace = ExecutionTrace::new(process, stack_outputs);
135    assert_eq!(&program.hash(), trace.program_hash(), "inconsistent program hash");
136    Ok(trace)
137}
138
139/// Returns an iterator which allows callers to step through the execution and inspect VM state at
140/// each execution step.
141pub fn execute_iter(
142    program: &Program,
143    stack_inputs: StackInputs,
144    host: &mut impl Host,
145) -> VmStateIterator {
146    let mut process = Process::new_debug(program.kernel().clone(), stack_inputs);
147    let result = process.execute(program, host);
148    if result.is_ok() {
149        assert_eq!(
150            program.hash(),
151            process.decoder.program_hash().into(),
152            "inconsistent program hash"
153        );
154    }
155    VmStateIterator::new(process, result)
156}
157
158// PROCESS
159// ================================================================================================
160
161/// A [Process] is the underlying execution engine for a Miden [Program].
162///
163/// Typically, you do not need to worry about, or use [Process] directly, instead you should prefer
164/// to use either [execute] or [execute_iter], which also handle setting up the process state,
165/// inputs, as well as compute the [ExecutionTrace] for the program.
166///
167/// However, for situations in which you want finer-grained control over those steps, you will need
168/// to construct an instance of [Process] using [Process::new], invoke [Process::execute], and then
169/// get the execution trace using [ExecutionTrace::new] using the outputs produced by execution.
170#[cfg(not(any(test, feature = "testing")))]
171pub struct Process {
172    system: System,
173    decoder: Decoder,
174    stack: Stack,
175    range: RangeChecker,
176    chiplets: Chiplets,
177    max_cycles: u32,
178    enable_tracing: bool,
179}
180
181#[cfg(any(test, feature = "testing"))]
182pub struct Process {
183    pub system: System,
184    pub decoder: Decoder,
185    pub stack: Stack,
186    pub range: RangeChecker,
187    pub chiplets: Chiplets,
188    pub max_cycles: u32,
189    pub enable_tracing: bool,
190}
191
192impl Process {
193    // CONSTRUCTORS
194    // --------------------------------------------------------------------------------------------
195    /// Creates a new process with the provided inputs.
196    pub fn new(
197        kernel: Kernel,
198        stack_inputs: StackInputs,
199        execution_options: ExecutionOptions,
200    ) -> Self {
201        Self::initialize(kernel, stack_inputs, execution_options)
202    }
203
204    /// Creates a new process with provided inputs and debug options enabled.
205    pub fn new_debug(kernel: Kernel, stack_inputs: StackInputs) -> Self {
206        Self::initialize(
207            kernel,
208            stack_inputs,
209            ExecutionOptions::default().with_tracing().with_debugging(),
210        )
211    }
212
213    fn initialize(kernel: Kernel, stack: StackInputs, execution_options: ExecutionOptions) -> Self {
214        let in_debug_mode = execution_options.enable_debugging();
215        Self {
216            system: System::new(execution_options.expected_cycles() as usize),
217            decoder: Decoder::new(in_debug_mode),
218            stack: Stack::new(&stack, execution_options.expected_cycles() as usize, in_debug_mode),
219            range: RangeChecker::new(),
220            chiplets: Chiplets::new(kernel),
221            max_cycles: execution_options.max_cycles(),
222            enable_tracing: execution_options.enable_tracing(),
223        }
224    }
225
226    // PROGRAM EXECUTOR
227    // --------------------------------------------------------------------------------------------
228
229    /// Executes the provided [`Program`] in this process.
230    pub fn execute(
231        &mut self,
232        program: &Program,
233        host: &mut impl Host,
234    ) -> Result<StackOutputs, ExecutionError> {
235        if self.system.clk() != 0 {
236            return Err(ExecutionError::ProgramAlreadyExecuted);
237        }
238
239        // Load the program's advice data into the advice provider
240        for (digest, values) in program.mast_forest().advice_map().iter() {
241            if let Some(stored_values) = host.advice_provider().get_mapped_values(digest) {
242                if stored_values != values {
243                    return Err(ExecutionError::AdviceMapKeyAlreadyPresent(digest.into()));
244                }
245            } else {
246                host.advice_provider_mut().insert_into_map(digest.into(), values.clone());
247            }
248        }
249
250        self.execute_mast_node(program.entrypoint(), &program.mast_forest().clone(), host)?;
251
252        self.stack.build_stack_outputs()
253    }
254
255    // NODE EXECUTORS
256    // --------------------------------------------------------------------------------------------
257
258    fn execute_mast_node(
259        &mut self,
260        node_id: MastNodeId,
261        program: &MastForest,
262        host: &mut impl Host,
263    ) -> Result<(), ExecutionError> {
264        let node = program
265            .get_node_by_id(node_id)
266            .ok_or(ExecutionError::MastNodeNotFoundInForest { node_id })?;
267
268        for &decorator_id in node.before_enter() {
269            self.execute_decorator(&program[decorator_id], host)?;
270        }
271
272        match node {
273            MastNode::Block(node) => self.execute_basic_block_node(node, program, host)?,
274            MastNode::Join(node) => self.execute_join_node(node, program, host)?,
275            MastNode::Split(node) => self.execute_split_node(node, program, host)?,
276            MastNode::Loop(node) => self.execute_loop_node(node, program, host)?,
277            MastNode::Call(node) => self.execute_call_node(node, program, host)?,
278            MastNode::Dyn(node) => self.execute_dyn_node(node, program, host)?,
279            MastNode::External(external_node) => {
280                let node_digest = external_node.digest();
281                let mast_forest = host.get_mast_forest(&node_digest).ok_or(
282                    ExecutionError::NoMastForestWithProcedure { root_digest: node_digest },
283                )?;
284
285                // We limit the parts of the program that can be called externally to procedure
286                // roots, even though MAST doesn't have that restriction.
287                let root_id = mast_forest.find_procedure_root(node_digest).ok_or(
288                    ExecutionError::MalformedMastForestInHost { root_digest: node_digest },
289                )?;
290
291                // if the node that we got by looking up an external reference is also an External
292                // node, we are about to enter into an infinite loop - so, return an error
293                if mast_forest[root_id].is_external() {
294                    return Err(ExecutionError::CircularExternalNode(node_digest));
295                }
296
297                self.execute_mast_node(root_id, &mast_forest, host)?;
298            },
299        }
300
301        for &decorator_id in node.after_exit() {
302            self.execute_decorator(&program[decorator_id], host)?;
303        }
304
305        Ok(())
306    }
307
308    /// Executes the specified [JoinNode].
309    #[inline(always)]
310    fn execute_join_node(
311        &mut self,
312        node: &JoinNode,
313        program: &MastForest,
314        host: &mut impl Host,
315    ) -> Result<(), ExecutionError> {
316        self.start_join_node(node, program, host)?;
317
318        // execute first and then second child of the join block
319        self.execute_mast_node(node.first(), program, host)?;
320        self.execute_mast_node(node.second(), program, host)?;
321
322        self.end_join_node(node, host)
323    }
324
325    /// Executes the specified [SplitNode].
326    #[inline(always)]
327    fn execute_split_node(
328        &mut self,
329        node: &SplitNode,
330        program: &MastForest,
331        host: &mut impl Host,
332    ) -> Result<(), ExecutionError> {
333        // start the SPLIT block; this also pops the stack and returns the popped element
334        let condition = self.start_split_node(node, program, host)?;
335
336        // execute either the true or the false branch of the split block based on the condition
337        if condition == ONE {
338            self.execute_mast_node(node.on_true(), program, host)?;
339        } else if condition == ZERO {
340            self.execute_mast_node(node.on_false(), program, host)?;
341        } else {
342            return Err(ExecutionError::NotBinaryValue(condition));
343        }
344
345        self.end_split_node(node, host)
346    }
347
348    /// Executes the specified [LoopNode].
349    #[inline(always)]
350    fn execute_loop_node(
351        &mut self,
352        node: &LoopNode,
353        program: &MastForest,
354        host: &mut impl Host,
355    ) -> Result<(), ExecutionError> {
356        // start the LOOP block; this also pops the stack and returns the popped element
357        let condition = self.start_loop_node(node, program, host)?;
358
359        // if the top of the stack is ONE, execute the loop body; otherwise skip the loop body
360        if condition == ONE {
361            // execute the loop body at least once
362            self.execute_mast_node(node.body(), program, host)?;
363
364            // keep executing the loop body until the condition on the top of the stack is no
365            // longer ONE; each iteration of the loop is preceded by executing REPEAT operation
366            // which drops the condition from the stack
367            while self.stack.peek() == ONE {
368                self.decoder.repeat();
369                self.execute_op(Operation::Drop, host)?;
370                self.execute_mast_node(node.body(), program, host)?;
371            }
372
373            if self.stack.peek() != ZERO {
374                return Err(ExecutionError::NotBinaryValue(self.stack.peek()));
375            }
376
377            // end the LOOP block and drop the condition from the stack
378            self.end_loop_node(node, true, host)
379        } else if condition == ZERO {
380            // end the LOOP block, but don't drop the condition from the stack because it was
381            // already dropped when we started the LOOP block
382            self.end_loop_node(node, false, host)
383        } else {
384            Err(ExecutionError::NotBinaryValue(condition))
385        }
386    }
387
388    /// Executes the specified [CallNode].
389    #[inline(always)]
390    fn execute_call_node(
391        &mut self,
392        call_node: &CallNode,
393        program: &MastForest,
394        host: &mut impl Host,
395    ) -> Result<(), ExecutionError> {
396        // call or syscall are not allowed inside a syscall
397        if self.system.in_syscall() {
398            let instruction = if call_node.is_syscall() { "syscall" } else { "call" };
399            return Err(ExecutionError::CallInSyscall(instruction));
400        }
401
402        // if this is a syscall, make sure the call target exists in the kernel
403        if call_node.is_syscall() {
404            let callee = program.get_node_by_id(call_node.callee()).ok_or_else(|| {
405                ExecutionError::MastNodeNotFoundInForest { node_id: call_node.callee() }
406            })?;
407            self.chiplets.kernel_rom.access_proc(callee.digest())?;
408        }
409
410        self.start_call_node(call_node, program, host)?;
411        self.execute_mast_node(call_node.callee(), program, host)?;
412        self.end_call_node(call_node, host)
413    }
414
415    /// Executes the specified [vm_core::mast::DynNode].
416    ///
417    /// The MAST root of the callee is assumed to be at the top of the stack, and the callee is
418    /// expected to be either in the current `program` or in the host.
419    #[inline(always)]
420    fn execute_dyn_node(
421        &mut self,
422        node: &DynNode,
423        program: &MastForest,
424        host: &mut impl Host,
425    ) -> Result<(), ExecutionError> {
426        // dyn calls are not allowed inside a syscall
427        if node.is_dyncall() && self.system.in_syscall() {
428            return Err(ExecutionError::CallInSyscall("dyncall"));
429        }
430
431        let callee_hash = if node.is_dyncall() {
432            self.start_dyncall_node(node)?
433        } else {
434            self.start_dyn_node(node, host)?
435        };
436
437        // if the callee is not in the program's MAST forest, try to find a MAST forest for it in
438        // the host (corresponding to an external library loaded in the host); if none are
439        // found, return an error.
440        match program.find_procedure_root(callee_hash.into()) {
441            Some(callee_id) => self.execute_mast_node(callee_id, program, host)?,
442            None => {
443                let mast_forest = host
444                    .get_mast_forest(&callee_hash.into())
445                    .ok_or_else(|| ExecutionError::DynamicNodeNotFound(callee_hash.into()))?;
446
447                // We limit the parts of the program that can be called externally to procedure
448                // roots, even though MAST doesn't have that restriction.
449                let root_id = mast_forest.find_procedure_root(callee_hash.into()).ok_or(
450                    ExecutionError::MalformedMastForestInHost { root_digest: callee_hash.into() },
451                )?;
452
453                self.execute_mast_node(root_id, &mast_forest, host)?
454            },
455        }
456
457        if node.is_dyncall() {
458            self.end_dyncall_node(node, host)
459        } else {
460            self.end_dyn_node(node, host)
461        }
462    }
463
464    /// Executes the specified [BasicBlockNode].
465    #[inline(always)]
466    fn execute_basic_block_node(
467        &mut self,
468        basic_block: &BasicBlockNode,
469        program: &MastForest,
470        host: &mut impl Host,
471    ) -> Result<(), ExecutionError> {
472        self.start_basic_block_node(basic_block, host)?;
473
474        let mut op_offset = 0;
475        let mut decorator_ids = basic_block.decorator_iter();
476
477        // execute the first operation batch
478        self.execute_op_batch(
479            &basic_block.op_batches()[0],
480            &mut decorator_ids,
481            op_offset,
482            program,
483            host,
484        )?;
485        op_offset += basic_block.op_batches()[0].ops().len();
486
487        // if the span contains more operation batches, execute them. each additional batch is
488        // preceded by a RESPAN operation; executing RESPAN operation does not change the state
489        // of the stack
490        for op_batch in basic_block.op_batches().iter().skip(1) {
491            self.respan(op_batch);
492            self.execute_op(Operation::Noop, host)?;
493            self.execute_op_batch(op_batch, &mut decorator_ids, op_offset, program, host)?;
494            op_offset += op_batch.ops().len();
495        }
496
497        self.end_basic_block_node(basic_block, host)?;
498
499        // execute any decorators which have not been executed during span ops execution; this
500        // can happen for decorators appearing after all operations in a block. these decorators
501        // are executed after SPAN block is closed to make sure the VM clock cycle advances beyond
502        // the last clock cycle of the SPAN block ops.
503        for &decorator_id in decorator_ids {
504            let decorator = program
505                .get_decorator_by_id(decorator_id)
506                .ok_or(ExecutionError::DecoratorNotFoundInForest { decorator_id })?;
507            self.execute_decorator(decorator, host)?;
508        }
509
510        Ok(())
511    }
512
513    /// Executes all operations in an [OpBatch]. This also ensures that all alignment rules are
514    /// satisfied by executing NOOPs as needed. Specifically:
515    /// - If an operation group ends with an operation carrying an immediate value, a NOOP is
516    ///   executed after it.
517    /// - If the number of groups in a batch is not a power of 2, NOOPs are executed (one per group)
518    ///   to bring it up to the next power of two (e.g., 3 -> 4, 5 -> 8).
519    #[inline(always)]
520    fn execute_op_batch(
521        &mut self,
522        batch: &OpBatch,
523        decorators: &mut DecoratorIterator,
524        op_offset: usize,
525        program: &MastForest,
526        host: &mut impl Host,
527    ) -> Result<(), ExecutionError> {
528        let op_counts = batch.op_counts();
529        let mut op_idx = 0;
530        let mut group_idx = 0;
531        let mut next_group_idx = 1;
532
533        // round up the number of groups to be processed to the next power of two; we do this
534        // because the processor requires the number of groups to be either 1, 2, 4, or 8; if
535        // the actual number of groups is smaller, we'll pad the batch with NOOPs at the end
536        let num_batch_groups = batch.num_groups().next_power_of_two();
537
538        // execute operations in the batch one by one
539        for (i, &op) in batch.ops().iter().enumerate() {
540            while let Some(&decorator_id) = decorators.next_filtered(i + op_offset) {
541                let decorator = program
542                    .get_decorator_by_id(decorator_id)
543                    .ok_or(ExecutionError::DecoratorNotFoundInForest { decorator_id })?;
544                self.execute_decorator(decorator, host)?;
545            }
546
547            // decode and execute the operation
548            self.decoder.execute_user_op(op, op_idx);
549            self.execute_op(op, host)?;
550
551            // if the operation carries an immediate value, the value is stored at the next group
552            // pointer; so, we advance the pointer to the following group
553            let has_imm = op.imm_value().is_some();
554            if has_imm {
555                next_group_idx += 1;
556            }
557
558            // determine if we've executed all non-decorator operations in a group
559            if op_idx == op_counts[group_idx] - 1 {
560                // if we are at the end of the group, first check if the operation carries an
561                // immediate value
562                if has_imm {
563                    // an operation with an immediate value cannot be the last operation in a group
564                    // so, we need execute a NOOP after it. the assert also makes sure that there
565                    // is enough room in the group to execute a NOOP (if there isn't, there is a
566                    // bug somewhere in the assembler)
567                    debug_assert!(op_idx < OP_GROUP_SIZE - 1, "invalid op index");
568                    self.decoder.execute_user_op(Operation::Noop, op_idx + 1);
569                    self.execute_op(Operation::Noop, host)?;
570                }
571
572                // then, move to the next group and reset operation index
573                group_idx = next_group_idx;
574                next_group_idx += 1;
575                op_idx = 0;
576
577                // if we haven't reached the end of the batch yet, set up the decoder for
578                // decoding the next operation group
579                if group_idx < num_batch_groups {
580                    self.decoder.start_op_group(batch.groups()[group_idx]);
581                }
582            } else {
583                // if we are not at the end of the group, just increment the operation index
584                op_idx += 1;
585            }
586        }
587
588        // make sure we execute the required number of operation groups; this would happen when
589        // the actual number of operation groups was not a power of two
590        for group_idx in group_idx..num_batch_groups {
591            self.decoder.execute_user_op(Operation::Noop, 0);
592            self.execute_op(Operation::Noop, host)?;
593
594            // if we are not at the last group yet, set up the decoder for decoding the next
595            // operation groups. the groups were are processing are just NOOPs - so, the op group
596            // value is ZERO
597            if group_idx < num_batch_groups - 1 {
598                self.decoder.start_op_group(ZERO);
599            }
600        }
601
602        Ok(())
603    }
604
605    /// Executes the specified decorator
606    fn execute_decorator(
607        &mut self,
608        decorator: &Decorator,
609        host: &mut impl Host,
610    ) -> Result<(), ExecutionError> {
611        match decorator {
612            Decorator::Debug(options) => {
613                if self.decoder.in_debug_mode() {
614                    host.on_debug(self.into(), options)?;
615                }
616            },
617            Decorator::AsmOp(assembly_op) => {
618                if self.decoder.in_debug_mode() {
619                    self.decoder.append_asmop(self.system.clk(), assembly_op.clone());
620                }
621            },
622            Decorator::Trace(id) => {
623                if self.enable_tracing {
624                    host.on_trace(self.into(), *id)?;
625                }
626            },
627        };
628        Ok(())
629    }
630
631    // PUBLIC ACCESSORS
632    // ================================================================================================
633
634    pub const fn kernel(&self) -> &Kernel {
635        self.chiplets.kernel_rom.kernel()
636    }
637
638    pub fn into_parts(self) -> (System, Decoder, Stack, RangeChecker, Chiplets) {
639        (self.system, self.decoder, self.stack, self.range, self.chiplets)
640    }
641}
642
643// PROCESS STATE
644// ================================================================================================
645
646#[derive(Debug, Clone, Copy)]
647pub struct ProcessState<'a> {
648    system: &'a System,
649    stack: &'a Stack,
650    chiplets: &'a Chiplets,
651}
652
653impl ProcessState<'_> {
654    /// Returns the current clock cycle of a process.
655    pub fn clk(&self) -> RowIndex {
656        self.system.clk()
657    }
658
659    /// Returns the current execution context ID.
660    pub fn ctx(&self) -> ContextId {
661        self.system.ctx()
662    }
663
664    /// Returns the current value of the free memory pointer.
665    pub fn fmp(&self) -> u64 {
666        self.system.fmp().as_int()
667    }
668
669    /// Returns the value located at the specified position on the stack at the current clock cycle.
670    pub fn get_stack_item(&self, pos: usize) -> Felt {
671        self.stack.get(pos)
672    }
673
674    /// Returns a word located at the specified word index on the stack.
675    ///
676    /// Specifically, word 0 is defined by the first 4 elements of the stack, word 1 is defined
677    /// by the next 4 elements etc. Since the top of the stack contains 4 word, the highest valid
678    /// word index is 3.
679    ///
680    /// The words are created in reverse order. For example, for word 0 the top element of the
681    /// stack will be at the last position in the word.
682    ///
683    /// Creating a word does not change the state of the stack.
684    pub fn get_stack_word(&self, word_idx: usize) -> Word {
685        self.stack.get_word(word_idx)
686    }
687
688    /// Returns stack state at the current clock cycle. This includes the top 16 items of the
689    /// stack + overflow entries.
690    pub fn get_stack_state(&self) -> Vec<Felt> {
691        self.stack.get_state_at(self.system.clk())
692    }
693
694    /// Returns the element located at the specified context/address, or None if the address hasn't
695    /// been accessed previously.
696    pub fn get_mem_value(&self, ctx: ContextId, addr: u32) -> Option<Felt> {
697        self.chiplets.memory.get_value(ctx, addr)
698    }
699
700    /// Returns the batch of elements starting at the specified context/address.
701    ///
702    /// # Errors
703    /// - If the address is not word aligned.
704    pub fn get_mem_word(&self, ctx: ContextId, addr: u32) -> Result<Option<Word>, ExecutionError> {
705        self.chiplets.memory.get_word(ctx, addr)
706    }
707
708    /// Returns the entire memory state for the specified execution context at the current clock
709    /// cycle.
710    ///
711    /// The state is returned as a vector of (address, value) tuples, and includes addresses which
712    /// have been accessed at least once.
713    pub fn get_mem_state(&self, ctx: ContextId) -> Vec<(u64, Felt)> {
714        self.chiplets.memory.get_state_at(ctx, self.system.clk())
715    }
716}
717
718impl<'a> From<&'a Process> for ProcessState<'a> {
719    fn from(process: &'a Process) -> Self {
720        Self {
721            system: &process.system,
722            stack: &process.stack,
723            chiplets: &process.chiplets,
724        }
725    }
726}
727
728impl<'a> From<&'a mut Process> for ProcessState<'a> {
729    fn from(process: &'a mut Process) -> Self {
730        Self {
731            system: &process.system,
732            stack: &process.stack,
733            chiplets: &process.chiplets,
734        }
735    }
736}