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    chiplets::hasher::Digest,
18    crypto::merkle::SMT_DEPTH,
19    errors::InputError,
20    mast::{MastForest, MastNode, MastNodeId},
21    sys_events::SystemEvent,
22    utils::{collections::KvMap, DeserializationError},
23    AssemblyOp, Felt, Kernel, Operation, Program, ProgramInfo, QuadExtension, StackInputs,
24    StackOutputs, Word, EMPTY_WORD, ONE, ZERO,
25};
26use vm_core::{
27    mast::{
28        BasicBlockNode, CallNode, DynNode, JoinNode, LoopNode, OpBatch, SplitNode, OP_GROUP_SIZE,
29    },
30    Decorator, DecoratorIterator, FieldElement,
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    advice::{AdviceInputs, AdviceProvider, AdviceSource, MemAdviceProvider, RecAdviceProvider},
52    DefaultHost, Host, MastForestStore, MemMastForestStore,
53};
54
55mod chiplets;
56use chiplets::Chiplets;
57
58mod trace;
59use trace::TraceFragment;
60pub use trace::{ChipletsLengths, ExecutionTrace, TraceLenSummary, NUM_RAND_ROWS};
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            // end the LOOP block and drop the condition from the stack
374            self.end_loop_node(node, true, host)
375        } else if condition == ZERO {
376            // end the LOOP block, but don't drop the condition from the stack because it was
377            // already dropped when we started the LOOP block
378            self.end_loop_node(node, false, host)
379        } else {
380            Err(ExecutionError::NotBinaryValue(condition))
381        }
382    }
383
384    /// Executes the specified [CallNode].
385    #[inline(always)]
386    fn execute_call_node(
387        &mut self,
388        call_node: &CallNode,
389        program: &MastForest,
390        host: &mut impl Host,
391    ) -> Result<(), ExecutionError> {
392        // if this is a syscall, make sure the call target exists in the kernel
393        if call_node.is_syscall() {
394            let callee = program.get_node_by_id(call_node.callee()).ok_or_else(|| {
395                ExecutionError::MastNodeNotFoundInForest { node_id: call_node.callee() }
396            })?;
397            self.chiplets.access_kernel_proc(callee.digest())?;
398        }
399
400        self.start_call_node(call_node, program, host)?;
401        self.execute_mast_node(call_node.callee(), program, host)?;
402        self.end_call_node(call_node, host)
403    }
404
405    /// Executes the specified [vm_core::mast::DynNode].
406    ///
407    /// The MAST root of the callee is assumed to be at the top of the stack, and the callee is
408    /// expected to be either in the current `program` or in the host.
409    #[inline(always)]
410    fn execute_dyn_node(
411        &mut self,
412        node: &DynNode,
413        program: &MastForest,
414        host: &mut impl Host,
415    ) -> Result<(), ExecutionError> {
416        let callee_hash = if node.is_dyncall() {
417            self.start_dyncall_node(node)?
418        } else {
419            self.start_dyn_node(node, host)?
420        };
421
422        // if the callee is not in the program's MAST forest, try to find a MAST forest for it in
423        // the host (corresponding to an external library loaded in the host); if none are
424        // found, return an error.
425        match program.find_procedure_root(callee_hash.into()) {
426            Some(callee_id) => self.execute_mast_node(callee_id, program, host)?,
427            None => {
428                let mast_forest = host
429                    .get_mast_forest(&callee_hash.into())
430                    .ok_or_else(|| ExecutionError::DynamicNodeNotFound(callee_hash.into()))?;
431
432                // We limit the parts of the program that can be called externally to procedure
433                // roots, even though MAST doesn't have that restriction.
434                let root_id = mast_forest.find_procedure_root(callee_hash.into()).ok_or(
435                    ExecutionError::MalformedMastForestInHost { root_digest: callee_hash.into() },
436                )?;
437
438                self.execute_mast_node(root_id, &mast_forest, host)?
439            },
440        }
441
442        if node.is_dyncall() {
443            self.end_dyncall_node(node, host)
444        } else {
445            self.end_dyn_node(node, host)
446        }
447    }
448
449    /// Executes the specified [BasicBlockNode].
450    #[inline(always)]
451    fn execute_basic_block_node(
452        &mut self,
453        basic_block: &BasicBlockNode,
454        program: &MastForest,
455        host: &mut impl Host,
456    ) -> Result<(), ExecutionError> {
457        self.start_basic_block_node(basic_block, host)?;
458
459        let mut op_offset = 0;
460        let mut decorator_ids = basic_block.decorator_iter();
461
462        // execute the first operation batch
463        self.execute_op_batch(
464            &basic_block.op_batches()[0],
465            &mut decorator_ids,
466            op_offset,
467            program,
468            host,
469        )?;
470        op_offset += basic_block.op_batches()[0].ops().len();
471
472        // if the span contains more operation batches, execute them. each additional batch is
473        // preceded by a RESPAN operation; executing RESPAN operation does not change the state
474        // of the stack
475        for op_batch in basic_block.op_batches().iter().skip(1) {
476            self.respan(op_batch);
477            self.execute_op(Operation::Noop, host)?;
478            self.execute_op_batch(op_batch, &mut decorator_ids, op_offset, program, host)?;
479            op_offset += op_batch.ops().len();
480        }
481
482        self.end_basic_block_node(basic_block, host)?;
483
484        // execute any decorators which have not been executed during span ops execution; this
485        // can happen for decorators appearing after all operations in a block. these decorators
486        // are executed after SPAN block is closed to make sure the VM clock cycle advances beyond
487        // the last clock cycle of the SPAN block ops.
488        for &decorator_id in decorator_ids {
489            let decorator = program
490                .get_decorator_by_id(decorator_id)
491                .ok_or(ExecutionError::DecoratorNotFoundInForest { decorator_id })?;
492            self.execute_decorator(decorator, host)?;
493        }
494
495        Ok(())
496    }
497
498    /// Executes all operations in an [OpBatch]. This also ensures that all alignment rules are
499    /// satisfied by executing NOOPs as needed. Specifically:
500    /// - If an operation group ends with an operation carrying an immediate value, a NOOP is
501    ///   executed after it.
502    /// - If the number of groups in a batch is not a power of 2, NOOPs are executed (one per group)
503    ///   to bring it up to the next power of two (e.g., 3 -> 4, 5 -> 8).
504    #[inline(always)]
505    fn execute_op_batch(
506        &mut self,
507        batch: &OpBatch,
508        decorators: &mut DecoratorIterator,
509        op_offset: usize,
510        program: &MastForest,
511        host: &mut impl Host,
512    ) -> Result<(), ExecutionError> {
513        let op_counts = batch.op_counts();
514        let mut op_idx = 0;
515        let mut group_idx = 0;
516        let mut next_group_idx = 1;
517
518        // round up the number of groups to be processed to the next power of two; we do this
519        // because the processor requires the number of groups to be either 1, 2, 4, or 8; if
520        // the actual number of groups is smaller, we'll pad the batch with NOOPs at the end
521        let num_batch_groups = batch.num_groups().next_power_of_two();
522
523        // execute operations in the batch one by one
524        for (i, &op) in batch.ops().iter().enumerate() {
525            while let Some(&decorator_id) = decorators.next_filtered(i + op_offset) {
526                let decorator = program
527                    .get_decorator_by_id(decorator_id)
528                    .ok_or(ExecutionError::DecoratorNotFoundInForest { decorator_id })?;
529                self.execute_decorator(decorator, host)?;
530            }
531
532            // decode and execute the operation
533            self.decoder.execute_user_op(op, op_idx);
534            self.execute_op(op, host)?;
535
536            // if the operation carries an immediate value, the value is stored at the next group
537            // pointer; so, we advance the pointer to the following group
538            let has_imm = op.imm_value().is_some();
539            if has_imm {
540                next_group_idx += 1;
541            }
542
543            // determine if we've executed all non-decorator operations in a group
544            if op_idx == op_counts[group_idx] - 1 {
545                // if we are at the end of the group, first check if the operation carries an
546                // immediate value
547                if has_imm {
548                    // an operation with an immediate value cannot be the last operation in a group
549                    // so, we need execute a NOOP after it. the assert also makes sure that there
550                    // is enough room in the group to execute a NOOP (if there isn't, there is a
551                    // bug somewhere in the assembler)
552                    debug_assert!(op_idx < OP_GROUP_SIZE - 1, "invalid op index");
553                    self.decoder.execute_user_op(Operation::Noop, op_idx + 1);
554                    self.execute_op(Operation::Noop, host)?;
555                }
556
557                // then, move to the next group and reset operation index
558                group_idx = next_group_idx;
559                next_group_idx += 1;
560                op_idx = 0;
561
562                // if we haven't reached the end of the batch yet, set up the decoder for
563                // decoding the next operation group
564                if group_idx < num_batch_groups {
565                    self.decoder.start_op_group(batch.groups()[group_idx]);
566                }
567            } else {
568                // if we are not at the end of the group, just increment the operation index
569                op_idx += 1;
570            }
571        }
572
573        // make sure we execute the required number of operation groups; this would happen when
574        // the actual number of operation groups was not a power of two
575        for group_idx in group_idx..num_batch_groups {
576            self.decoder.execute_user_op(Operation::Noop, 0);
577            self.execute_op(Operation::Noop, host)?;
578
579            // if we are not at the last group yet, set up the decoder for decoding the next
580            // operation groups. the groups were are processing are just NOOPs - so, the op group
581            // value is ZERO
582            if group_idx < num_batch_groups - 1 {
583                self.decoder.start_op_group(ZERO);
584            }
585        }
586
587        Ok(())
588    }
589
590    /// Executes the specified decorator
591    fn execute_decorator(
592        &mut self,
593        decorator: &Decorator,
594        host: &mut impl Host,
595    ) -> Result<(), ExecutionError> {
596        match decorator {
597            Decorator::Debug(options) => {
598                if self.decoder.in_debug_mode() {
599                    host.on_debug(self.into(), options)?;
600                }
601            },
602            Decorator::AsmOp(assembly_op) => {
603                if self.decoder.in_debug_mode() {
604                    self.decoder.append_asmop(self.system.clk(), assembly_op.clone());
605                }
606            },
607            Decorator::Trace(id) => {
608                if self.enable_tracing {
609                    host.on_trace(self.into(), *id)?;
610                }
611            },
612        };
613        Ok(())
614    }
615
616    // PUBLIC ACCESSORS
617    // ================================================================================================
618
619    pub const fn kernel(&self) -> &Kernel {
620        self.chiplets.kernel()
621    }
622
623    pub fn into_parts(self) -> (System, Decoder, Stack, RangeChecker, Chiplets) {
624        (self.system, self.decoder, self.stack, self.range, self.chiplets)
625    }
626}
627
628// PROCESS STATE
629// ================================================================================================
630
631#[derive(Debug, Clone, Copy)]
632pub struct ProcessState<'a> {
633    system: &'a System,
634    stack: &'a Stack,
635    chiplets: &'a Chiplets,
636}
637
638impl ProcessState<'_> {
639    /// Returns the current clock cycle of a process.
640    pub fn clk(&self) -> RowIndex {
641        self.system.clk()
642    }
643
644    /// Returns the current execution context ID.
645    pub fn ctx(&self) -> ContextId {
646        self.system.ctx()
647    }
648
649    /// Returns the current value of the free memory pointer.
650    pub fn fmp(&self) -> u64 {
651        self.system.fmp().as_int()
652    }
653
654    /// Returns the value located at the specified position on the stack at the current clock cycle.
655    pub fn get_stack_item(&self, pos: usize) -> Felt {
656        self.stack.get(pos)
657    }
658
659    /// Returns a word located at the specified word index on the stack.
660    ///
661    /// Specifically, word 0 is defined by the first 4 elements of the stack, word 1 is defined
662    /// by the next 4 elements etc. Since the top of the stack contains 4 word, the highest valid
663    /// word index is 3.
664    ///
665    /// The words are created in reverse order. For example, for word 0 the top element of the
666    /// stack will be at the last position in the word.
667    ///
668    /// Creating a word does not change the state of the stack.
669    pub fn get_stack_word(&self, word_idx: usize) -> Word {
670        self.stack.get_word(word_idx)
671    }
672
673    /// Returns stack state at the current clock cycle. This includes the top 16 items of the
674    /// stack + overflow entries.
675    pub fn get_stack_state(&self) -> Vec<Felt> {
676        self.stack.get_state_at(self.system.clk())
677    }
678
679    /// Returns the element located at the specified context/address, or None if the address hasn't
680    /// been accessed previously.
681    pub fn get_mem_value(&self, ctx: ContextId, addr: u32) -> Option<Felt> {
682        self.chiplets.memory().get_value(ctx, addr)
683    }
684
685    /// Returns the batch of elements starting at the specified context/address.
686    ///
687    /// # Errors
688    /// - If the address is not word aligned.
689    pub fn get_mem_word(&self, ctx: ContextId, addr: u32) -> Result<Option<Word>, ExecutionError> {
690        self.chiplets.memory().get_word(ctx, addr)
691    }
692
693    /// Returns the entire memory state for the specified execution context at the current clock
694    /// cycle.
695    ///
696    /// The state is returned as a vector of (address, value) tuples, and includes addresses which
697    /// have been accessed at least once.
698    pub fn get_mem_state(&self, ctx: ContextId) -> Vec<(u64, Felt)> {
699        self.chiplets.memory().get_state_at(ctx, self.system.clk())
700    }
701}
702
703impl<'a> From<&'a Process> for ProcessState<'a> {
704    fn from(process: &'a Process) -> Self {
705        Self {
706            system: &process.system,
707            stack: &process.stack,
708            chiplets: &process.chiplets,
709        }
710    }
711}
712
713impl<'a> From<&'a mut Process> for ProcessState<'a> {
714    fn from(process: &'a mut Process) -> Self {
715        Self {
716            system: &process.system,
717            stack: &process.stack,
718            chiplets: &process.chiplets,
719        }
720    }
721}