Skip to main content

miden_processor/trace/
execution_tracer.rs

1use alloc::{sync::Arc, vec::Vec};
2
3use miden_air::trace::chiplets::hasher::{HASH_CYCLE_LEN, HASH_CYCLE_LEN_FELT, STATE_WIDTH};
4use miden_core::{FMP_ADDR, FMP_INIT_VALUE, operations::Operation};
5
6use super::{
7    decoder::block_stack::{BlockInfo, BlockStack, BlockType, ExecutionContextInfo},
8    stack::OverflowTable,
9    trace_state::{
10        AceReplay, AdviceReplay, BitwiseReplay, BlockAddressReplay, BlockStackReplay,
11        CoreTraceFragmentContext, CoreTraceState, DecoderState, ExecutionContextReplay,
12        ExecutionContextSystemInfo, ExecutionReplay, HasherRequestReplay, HasherResponseReplay,
13        KernelReplay, MastForestResolutionReplay, MemoryReadsReplay, MemoryWritesReplay, NodeFlags,
14        RangeCheckerReplay, StackOverflowReplay, StackState, SystemState,
15    },
16    utils::split_u32_into_u16,
17};
18use crate::{
19    ContextId, EMPTY_WORD, FastProcessor, Felt, MIN_STACK_DEPTH, ONE, RowIndex, Word, ZERO,
20    continuation_stack::{Continuation, ContinuationStack},
21    crypto::merkle::MerklePath,
22    mast::{
23        BasicBlockNode, JoinNode, LoopNode, MastForest, MastNode, MastNodeExt, MastNodeId,
24        SplitNode,
25    },
26    processor::{Processor, StackInterface, SystemInterface},
27    trace::chiplets::{CircuitEvaluation, PTR_OFFSET_ELEM, PTR_OFFSET_WORD},
28    tracer::{OperationHelperRegisters, Tracer},
29};
30
31// STATE SNAPSHOT
32// ================================================================================================
33
34/// Execution state snapshot, used to record the state at the start of a trace fragment.
35#[derive(Debug)]
36struct StateSnapshot {
37    state: CoreTraceState,
38    continuation_stack: ContinuationStack,
39    initial_mast_forest: Arc<MastForest>,
40}
41
42// TRACE GENERATION CONTEXT
43// ================================================================================================
44
45pub struct TraceGenerationContext {
46    /// The list of trace fragment contexts built during execution.
47    pub core_trace_contexts: Vec<CoreTraceFragmentContext>,
48
49    // Replays that contain additional data needed to generate the range checker and chiplets
50    // columns.
51    pub range_checker_replay: RangeCheckerReplay,
52    pub memory_writes: MemoryWritesReplay,
53    pub bitwise_replay: BitwiseReplay,
54    pub hasher_for_chiplet: HasherRequestReplay,
55    pub kernel_replay: KernelReplay,
56    pub ace_replay: AceReplay,
57
58    /// The number of rows per core trace fragment, except for the last fragment which may be
59    /// shorter.
60    pub fragment_size: usize,
61}
62
63/// Builder for recording the context to generate trace fragments during execution.
64///
65/// Specifically, this records the information necessary to be able to generate the trace in
66/// fragments of configurable length. This requires storing state at the very beginning of the
67/// fragment before any operations are executed, as well as recording the various values read during
68/// execution in the corresponding "replays" (e.g. values read from memory are recorded in
69/// `MemoryReadsReplay`, values read from the advice provider are recorded in `AdviceReplay``, etc).
70///
71/// Then, to generate a trace fragment, we initialize the state of the processor using the stored
72/// snapshot from the beginning of the fragment, and replay the recorded values as they are
73/// encountered during execution (e.g. when encountering a memory read operation, we will replay the
74/// value rather than querying the memory chiplet).
75#[derive(Debug)]
76pub struct ExecutionTracer {
77    // State stored at the start of a core trace fragment.
78    //
79    // This field is only set to `None` at initialization, and is populated when starting a new
80    // trace fragment with `Self::start_new_fragment_context()`. Hence, on the first call to
81    // `Self::start_new_fragment_context()`, we don't extract a new `TraceFragmentContext`, but in
82    // every other call, we do.
83    state_snapshot: Option<StateSnapshot>,
84
85    // Replay data aggregated throughout the execution of a core trace fragment
86    overflow_table: OverflowTable,
87    overflow_replay: StackOverflowReplay,
88
89    block_stack: BlockStack,
90    block_stack_replay: BlockStackReplay,
91    execution_context_replay: ExecutionContextReplay,
92
93    hasher_chiplet_shim: HasherChipletShim,
94    memory_reads: MemoryReadsReplay,
95    advice: AdviceReplay,
96    external: MastForestResolutionReplay,
97
98    // Replays that contain additional data needed to generate the range checker and chiplets
99    // columns.
100    range_checker: RangeCheckerReplay,
101    memory_writes: MemoryWritesReplay,
102    bitwise: BitwiseReplay,
103    kernel: KernelReplay,
104    hasher_for_chiplet: HasherRequestReplay,
105    ace: AceReplay,
106
107    // Output
108    fragment_contexts: Vec<CoreTraceFragmentContext>,
109
110    /// The number of rows per core trace fragment.
111    fragment_size: usize,
112
113    /// Flag set in `start_clock_cycle` when a Call/Syscall/Dyncall END is encountered, consumed
114    /// in `finalize_clock_cycle` to call `overflow_table.restore_context()`. This is deferred to
115    /// `finalize_clock_cycle` because `finalize_clock_cycle` is only called when the operation
116    /// succeeds (i.e., the stack depth check passes).
117    pending_restore_context: bool,
118
119    /// Flag set in `start_clock_cycle` when an `EvalCircuit` operation is encountered, consumed
120    /// in `finalize_clock_cycle` to record the memory reads performed by the operation.
121    is_eval_circuit_op: bool,
122}
123
124impl ExecutionTracer {
125    /// Creates a new `ExecutionTracer` with the given fragment size.
126    #[inline(always)]
127    pub fn new(fragment_size: usize) -> Self {
128        Self {
129            state_snapshot: None,
130            overflow_table: OverflowTable::default(),
131            overflow_replay: StackOverflowReplay::default(),
132            block_stack: BlockStack::default(),
133            block_stack_replay: BlockStackReplay::default(),
134            execution_context_replay: ExecutionContextReplay::default(),
135            hasher_chiplet_shim: HasherChipletShim::default(),
136            memory_reads: MemoryReadsReplay::default(),
137            range_checker: RangeCheckerReplay::default(),
138            memory_writes: MemoryWritesReplay::default(),
139            advice: AdviceReplay::default(),
140            bitwise: BitwiseReplay::default(),
141            kernel: KernelReplay::default(),
142            hasher_for_chiplet: HasherRequestReplay::default(),
143            ace: AceReplay::default(),
144            external: MastForestResolutionReplay::default(),
145            fragment_contexts: Vec::new(),
146            fragment_size,
147            pending_restore_context: false,
148            is_eval_circuit_op: false,
149        }
150    }
151
152    /// Convert the `ExecutionTracer` into a [TraceGenerationContext] using the data accumulated
153    /// during execution.
154    #[inline(always)]
155    pub fn into_trace_generation_context(mut self) -> TraceGenerationContext {
156        // If there is an ongoing trace state being built, finish it
157        self.finish_current_fragment_context();
158
159        TraceGenerationContext {
160            core_trace_contexts: self.fragment_contexts,
161            range_checker_replay: self.range_checker,
162            memory_writes: self.memory_writes,
163            bitwise_replay: self.bitwise,
164            kernel_replay: self.kernel,
165            hasher_for_chiplet: self.hasher_for_chiplet,
166            ace_replay: self.ace,
167            fragment_size: self.fragment_size,
168        }
169    }
170
171    // HELPERS
172    // -------------------------------------------------------------------------------------------
173
174    /// Captures the internal state into a new [TraceFragmentContext] (stored internally), resets
175    /// the internal replay state of the builder, and records a new state snapshot, marking the
176    /// beginning of the next trace state.
177    ///
178    /// This must be called at the beginning of a new trace fragment, before executing the first
179    /// operation. Internal replay fields are expected to be accessed during execution of this new
180    /// fragment to record data to be replayed by the trace fragment generators.
181    #[inline(always)]
182    fn start_new_fragment_context(
183        &mut self,
184        system_state: SystemState,
185        stack_top: [Felt; MIN_STACK_DEPTH],
186        mut continuation_stack: ContinuationStack,
187        continuation: Continuation,
188        current_forest: Arc<MastForest>,
189    ) {
190        // If there is an ongoing snapshot, finish it
191        self.finish_current_fragment_context();
192
193        // Start a new snapshot
194        self.state_snapshot = {
195            let decoder_state = {
196                if self.block_stack.is_empty() {
197                    DecoderState { current_addr: ZERO, parent_addr: ZERO }
198                } else {
199                    let block_info = self.block_stack.peek();
200
201                    DecoderState {
202                        current_addr: block_info.addr,
203                        parent_addr: block_info.parent_addr,
204                    }
205                }
206            };
207            let stack = {
208                let stack_depth =
209                    MIN_STACK_DEPTH + self.overflow_table.num_elements_in_current_ctx();
210                let last_overflow_addr = self.overflow_table.last_update_clk_in_current_ctx();
211                StackState::new(stack_top, stack_depth, last_overflow_addr)
212            };
213
214            // Push new continuation corresponding to the current execution state
215            continuation_stack.push_continuation(continuation);
216
217            Some(StateSnapshot {
218                state: CoreTraceState {
219                    system: system_state,
220                    decoder: decoder_state,
221                    stack,
222                },
223                continuation_stack,
224                initial_mast_forest: current_forest,
225            })
226        };
227    }
228
229    #[inline(always)]
230    fn record_control_node_start<P: Processor>(
231        &mut self,
232        node: &MastNode,
233        processor: &P,
234        current_forest: &MastForest,
235    ) {
236        let (ctx_info, block_type) = match node {
237            MastNode::Join(node) => {
238                let child1_hash = current_forest
239                    .get_node_by_id(node.first())
240                    .expect("join node's first child expected to be in the forest")
241                    .digest();
242                let child2_hash = current_forest
243                    .get_node_by_id(node.second())
244                    .expect("join node's second child expected to be in the forest")
245                    .digest();
246                self.hasher_for_chiplet.record_hash_control_block(
247                    child1_hash,
248                    child2_hash,
249                    JoinNode::DOMAIN,
250                    node.digest(),
251                );
252
253                (None, BlockType::Join(false))
254            },
255            MastNode::Split(node) => {
256                let child1_hash = current_forest
257                    .get_node_by_id(node.on_true())
258                    .expect("split node's true child expected to be in the forest")
259                    .digest();
260                let child2_hash = current_forest
261                    .get_node_by_id(node.on_false())
262                    .expect("split node's false child expected to be in the forest")
263                    .digest();
264                self.hasher_for_chiplet.record_hash_control_block(
265                    child1_hash,
266                    child2_hash,
267                    SplitNode::DOMAIN,
268                    node.digest(),
269                );
270
271                (None, BlockType::Split)
272            },
273            MastNode::Loop(node) => {
274                let body_hash = current_forest
275                    .get_node_by_id(node.body())
276                    .expect("loop node's body expected to be in the forest")
277                    .digest();
278
279                self.hasher_for_chiplet.record_hash_control_block(
280                    body_hash,
281                    EMPTY_WORD,
282                    LoopNode::DOMAIN,
283                    node.digest(),
284                );
285
286                let loop_entered = {
287                    let condition = processor.stack().get(0);
288                    condition == ONE
289                };
290
291                (None, BlockType::Loop(loop_entered))
292            },
293            MastNode::Call(node) => {
294                let callee_hash = current_forest
295                    .get_node_by_id(node.callee())
296                    .expect("call node's callee expected to be in the forest")
297                    .digest();
298
299                self.hasher_for_chiplet.record_hash_control_block(
300                    callee_hash,
301                    EMPTY_WORD,
302                    node.domain(),
303                    node.digest(),
304                );
305
306                let exec_ctx = {
307                    let overflow_addr = self.overflow_table.last_update_clk_in_current_ctx();
308                    ExecutionContextInfo::new(
309                        processor.system().ctx(),
310                        processor.system().caller_hash(),
311                        processor.stack().depth(),
312                        overflow_addr,
313                    )
314                };
315                let block_type = if node.is_syscall() {
316                    BlockType::SysCall
317                } else {
318                    BlockType::Call
319                };
320
321                (Some(exec_ctx), block_type)
322            },
323            MastNode::Dyn(dyn_node) => {
324                self.hasher_for_chiplet.record_hash_control_block(
325                    EMPTY_WORD,
326                    EMPTY_WORD,
327                    dyn_node.domain(),
328                    dyn_node.digest(),
329                );
330
331                if dyn_node.is_dyncall() {
332                    let exec_ctx = {
333                        let overflow_addr = self.overflow_table.last_update_clk_in_current_ctx();
334                        // Note: the stack depth to record is the `current_stack_depth - 1` due to
335                        // the semantics of DYNCALL. That is, the top of the
336                        // stack contains the memory address to where the
337                        // address to dynamically call is located. Then, the
338                        // DYNCALL operation performs a drop, and
339                        // records the stack depth after the drop as the beginning of
340                        // the new context. For more information, look at the docs for how the
341                        // constraints are designed; it's a bit tricky but it works.
342                        let stack_depth_after_drop = processor.stack().depth() - 1;
343                        ExecutionContextInfo::new(
344                            processor.system().ctx(),
345                            processor.system().caller_hash(),
346                            stack_depth_after_drop,
347                            overflow_addr,
348                        )
349                    };
350                    (Some(exec_ctx), BlockType::Dyncall)
351                } else {
352                    (None, BlockType::Dyn)
353                }
354            },
355            MastNode::Block(_) => panic!(
356                "`ExecutionTracer::record_basic_block_start()` must be called instead for basic blocks"
357            ),
358            MastNode::External(_) => panic!(
359                "External nodes are guaranteed to be resolved before record_control_node_start is called"
360            ),
361        };
362
363        let block_addr = self.hasher_chiplet_shim.record_hash_control_block();
364        let parent_addr = self.block_stack.push(block_addr, block_type, ctx_info);
365        self.block_stack_replay.record_node_start_parent_addr(parent_addr);
366    }
367
368    /// Records the block address and flags for an END operation based on the block being popped.
369    #[inline(always)]
370    fn record_node_end(&mut self, block_info: &BlockInfo) {
371        let flags = NodeFlags::new(
372            block_info.is_loop_body() == ONE,
373            block_info.is_entered_loop() == ONE,
374            block_info.is_call() == ONE,
375            block_info.is_syscall() == ONE,
376        );
377        let (prev_addr, prev_parent_addr) = if self.block_stack.is_empty() {
378            (ZERO, ZERO)
379        } else {
380            let prev_block = self.block_stack.peek();
381            (prev_block.addr, prev_block.parent_addr)
382        };
383        self.block_stack_replay.record_node_end(
384            block_info.addr,
385            flags,
386            prev_addr,
387            prev_parent_addr,
388        );
389    }
390
391    /// Records the execution context system info for CALL/SYSCALL/DYNCALL operations.
392    #[inline(always)]
393    fn record_execution_context(&mut self, ctx_info: ExecutionContextSystemInfo) {
394        self.execution_context_replay.record_execution_context(ctx_info);
395    }
396
397    /// Records the current core trace state, if any.
398    ///
399    /// Specifically, extracts the stored [SnapshotStart] as well as all the replay data recorded
400    /// from the various components (e.g. memory, advice, etc) since the last call to this method.
401    /// Resets the internal state to default values to prepare for the next trace fragment.
402    ///
403    /// Note that the very first time that this is called (at clock cycle 0), the snapshot will not
404    /// contain any replay data, and so no core trace state will be recorded.
405    #[inline(always)]
406    fn finish_current_fragment_context(&mut self) {
407        if let Some(snapshot) = self.state_snapshot.take() {
408            // Extract the replays
409            let (hasher_replay, block_addr_replay) = self.hasher_chiplet_shim.extract_replay();
410            let memory_reads_replay = core::mem::take(&mut self.memory_reads);
411            let advice_replay = core::mem::take(&mut self.advice);
412            let external_replay = core::mem::take(&mut self.external);
413            let stack_overflow_replay = core::mem::take(&mut self.overflow_replay);
414            let block_stack_replay = core::mem::take(&mut self.block_stack_replay);
415            let execution_context_replay = core::mem::take(&mut self.execution_context_replay);
416
417            let trace_state = CoreTraceFragmentContext {
418                state: snapshot.state,
419                replay: ExecutionReplay {
420                    hasher: hasher_replay,
421                    block_address: block_addr_replay,
422                    memory_reads: memory_reads_replay,
423                    advice: advice_replay,
424                    mast_forest_resolution: external_replay,
425                    stack_overflow: stack_overflow_replay,
426                    block_stack: block_stack_replay,
427                    execution_context: execution_context_replay,
428                },
429                continuation: snapshot.continuation_stack,
430                initial_mast_forest: snapshot.initial_mast_forest,
431            };
432
433            self.fragment_contexts.push(trace_state);
434        }
435    }
436
437    /// Pushes the value at stack position 15 onto the overflow table. This must be called in
438    /// `Tracer::start_clock_cycle()` *before* the processor increments the stack size, where stack
439    /// position 15 at the start of the clock cycle corresponds to the element that overflows.
440    #[inline(always)]
441    fn increment_stack_size(&mut self, processor: &FastProcessor) {
442        let new_overflow_value = processor.stack_get(15);
443        self.overflow_table.push(new_overflow_value, processor.system().clock());
444    }
445
446    /// Pops a value from the overflow table and records it for replay.
447    #[inline(always)]
448    fn decrement_stack_size(&mut self) {
449        if let Some(popped_value) = self.overflow_table.pop() {
450            let new_overflow_addr = self.overflow_table.last_update_clk_in_current_ctx();
451            self.overflow_replay.record_pop_overflow(popped_value, new_overflow_addr);
452        }
453    }
454}
455
456impl Tracer for ExecutionTracer {
457    type Processor = FastProcessor;
458
459    /// When sufficiently many clock cycles have elapsed, starts a new trace state. Also updates the
460    /// internal block stack.
461    #[inline(always)]
462    fn start_clock_cycle(
463        &mut self,
464        processor: &FastProcessor,
465        continuation: Continuation,
466        continuation_stack: &ContinuationStack,
467        current_forest: &Arc<MastForest>,
468    ) {
469        // check if we need to start a new trace state
470        if processor.system().clock().as_usize().is_multiple_of(self.fragment_size) {
471            self.start_new_fragment_context(
472                SystemState::from_processor(processor),
473                processor
474                    .stack_top()
475                    .try_into()
476                    .expect("stack_top expected to be MIN_STACK_DEPTH elements"),
477                continuation_stack.clone(),
478                continuation.clone(),
479                current_forest.clone(),
480            );
481        }
482
483        match continuation {
484            Continuation::ResumeBasicBlock { node_id, batch_index, op_idx_in_batch } => {
485                // Update overflow table based on whether the operation increments or decrements
486                // the stack size.
487                let basic_block = current_forest[node_id].unwrap_basic_block();
488                let op = &basic_block.op_batches()[batch_index].ops()[op_idx_in_batch];
489
490                if op.increments_stack_size() {
491                    self.increment_stack_size(processor);
492                } else if op.decrements_stack_size() {
493                    self.decrement_stack_size();
494                }
495
496                if matches!(op, Operation::EvalCircuit) {
497                    self.is_eval_circuit_op = true;
498                }
499            },
500            Continuation::StartNode(mast_node_id) => match &current_forest[mast_node_id] {
501                MastNode::Join(_) => {
502                    self.record_control_node_start(
503                        &current_forest[mast_node_id],
504                        processor,
505                        current_forest,
506                    );
507                },
508                MastNode::Split(_) | MastNode::Loop(_) => {
509                    self.record_control_node_start(
510                        &current_forest[mast_node_id],
511                        processor,
512                        current_forest,
513                    );
514                    self.decrement_stack_size();
515                },
516                MastNode::Call(_) => {
517                    self.record_control_node_start(
518                        &current_forest[mast_node_id],
519                        processor,
520                        current_forest,
521                    );
522                    self.overflow_table.start_context();
523                },
524                MastNode::Dyn(dyn_node) => {
525                    self.record_control_node_start(
526                        &current_forest[mast_node_id],
527                        processor,
528                        current_forest,
529                    );
530                    // DYN and DYNCALL both drop the memory address from the stack.
531                    self.decrement_stack_size();
532
533                    if dyn_node.is_dyncall() {
534                        // Note: the overflow pop (stack size decrement above) must happen before
535                        // starting the new context so that it operates on the old context's
536                        // overflow table, per the semantics of dyncall.
537                        self.overflow_table.start_context();
538                    }
539                },
540                MastNode::Block(basic_block_node) => {
541                    self.hasher_for_chiplet.record_hash_basic_block(
542                        current_forest.clone(),
543                        mast_node_id,
544                        basic_block_node.digest(),
545                    );
546                    let block_addr =
547                        self.hasher_chiplet_shim.record_hash_basic_block(basic_block_node);
548                    let parent_addr =
549                        self.block_stack.push(block_addr, BlockType::BasicBlock, None);
550                    self.block_stack_replay.record_node_start_parent_addr(parent_addr);
551                },
552                MastNode::External(_) => unreachable!(
553                    "start_clock_cycle is guaranteed not to be called on external nodes"
554                ),
555            },
556            Continuation::Respan { node_id: _, batch_index: _ } => {
557                self.block_stack.peek_mut().addr += HASH_CYCLE_LEN_FELT;
558            },
559            Continuation::FinishLoop { node_id: _, was_entered }
560                if was_entered && processor.stack_get(0) == ONE =>
561            {
562                // This is a REPEAT operation, which drops the condition (top element) off the stack
563                self.decrement_stack_size();
564            },
565            Continuation::FinishJoin(_)
566            | Continuation::FinishSplit(_)
567            | Continuation::FinishCall(_)
568            | Continuation::FinishDyn(_)
569            | Continuation::FinishLoop { .. } // not a REPEAT, which is handled separately above
570            | Continuation::FinishBasicBlock(_) => {
571                // The END of a loop that was entered drops the condition from the stack.
572                if matches!(
573                    &continuation,
574                    Continuation::FinishLoop { was_entered, .. } if *was_entered
575                ) {
576                    self.decrement_stack_size();
577                }
578
579                // This is an END operation; pop the block stack and record the node end
580                let block_info = self.block_stack.pop();
581                self.record_node_end(&block_info);
582
583                if let Some(ctx_info) = block_info.ctx_info {
584                    self.record_execution_context(ExecutionContextSystemInfo {
585                        parent_ctx: ctx_info.parent_ctx,
586                        parent_fn_hash: ctx_info.parent_fn_hash,
587                    });
588
589                    self.pending_restore_context = true;
590                }
591            },
592            Continuation::FinishExternal(_)
593            | Continuation::EnterForest(_)
594            | Continuation::AfterExitDecorators(_)
595            | Continuation::AfterExitDecoratorsBasicBlock(_) => {
596                panic!(
597                    "FinishExternal, EnterForest, AfterExitDecorators and AfterExitDecoratorsBasicBlock continuations are guaranteed not to be passed here"
598                )
599            },
600        }
601    }
602
603    #[inline(always)]
604    fn record_mast_forest_resolution(&mut self, node_id: MastNodeId, forest: &Arc<MastForest>) {
605        self.external.record_resolution(node_id, forest.clone());
606    }
607
608    #[inline(always)]
609    fn record_hasher_permute(
610        &mut self,
611        input_state: [Felt; STATE_WIDTH],
612        output_state: [Felt; STATE_WIDTH],
613    ) {
614        self.hasher_for_chiplet.record_permute_input(input_state);
615        self.hasher_chiplet_shim.record_permute_output(output_state);
616    }
617
618    #[inline(always)]
619    fn record_hasher_build_merkle_root(
620        &mut self,
621        node: Word,
622        path: Option<&MerklePath>,
623        index: Felt,
624        output_root: Word,
625    ) {
626        let path = path.expect("execution tracer expects a valid Merkle path");
627        self.hasher_chiplet_shim.record_build_merkle_root(path, output_root);
628        self.hasher_for_chiplet.record_build_merkle_root(node, path.clone(), index);
629    }
630
631    #[inline(always)]
632    fn record_hasher_update_merkle_root(
633        &mut self,
634        old_value: Word,
635        new_value: Word,
636        path: Option<&MerklePath>,
637        index: Felt,
638        old_root: Word,
639        new_root: Word,
640    ) {
641        let path = path.expect("execution tracer expects a valid Merkle path");
642        self.hasher_chiplet_shim.record_update_merkle_root(path, old_root, new_root);
643        self.hasher_for_chiplet.record_update_merkle_root(
644            old_value,
645            new_value,
646            path.clone(),
647            index,
648        );
649    }
650
651    #[inline(always)]
652    fn record_memory_read_element(
653        &mut self,
654        element: Felt,
655        addr: Felt,
656        ctx: ContextId,
657        clk: RowIndex,
658    ) {
659        self.memory_reads.record_read_element(element, addr, ctx, clk);
660    }
661
662    #[inline(always)]
663    fn record_memory_read_word(&mut self, word: Word, addr: Felt, ctx: ContextId, clk: RowIndex) {
664        self.memory_reads.record_read_word(word, addr, ctx, clk);
665    }
666
667    #[inline(always)]
668    fn record_memory_write_element(
669        &mut self,
670        element: Felt,
671        addr: Felt,
672        ctx: ContextId,
673        clk: RowIndex,
674    ) {
675        self.memory_writes.record_write_element(element, addr, ctx, clk);
676    }
677
678    #[inline(always)]
679    fn record_memory_write_word(&mut self, word: Word, addr: Felt, ctx: ContextId, clk: RowIndex) {
680        self.memory_writes.record_write_word(word, addr, ctx, clk);
681    }
682
683    #[inline(always)]
684    fn record_memory_read_element_pair(
685        &mut self,
686        element_0: Felt,
687        addr_0: Felt,
688        element_1: Felt,
689        addr_1: Felt,
690        ctx: ContextId,
691        clk: RowIndex,
692    ) {
693        self.memory_reads.record_read_element(element_0, addr_0, ctx, clk);
694        self.memory_reads.record_read_element(element_1, addr_1, ctx, clk);
695    }
696
697    #[inline(always)]
698    fn record_memory_read_dword(
699        &mut self,
700        words: [Word; 2],
701        addr: Felt,
702        ctx: ContextId,
703        clk: RowIndex,
704    ) {
705        self.memory_reads.record_read_word(words[0], addr, ctx, clk);
706        self.memory_reads.record_read_word(words[1], addr + Felt::new(4), ctx, clk);
707    }
708
709    #[inline(always)]
710    fn record_dyncall_memory(
711        &mut self,
712        callee_hash: Word,
713        read_addr: Felt,
714        read_ctx: ContextId,
715        fmp_ctx: ContextId,
716        clk: RowIndex,
717    ) {
718        self.memory_reads.record_read_word(callee_hash, read_addr, read_ctx, clk);
719        self.memory_writes.record_write_element(FMP_INIT_VALUE, FMP_ADDR, fmp_ctx, clk);
720    }
721
722    #[inline(always)]
723    fn record_crypto_stream(
724        &mut self,
725        plaintext: [Word; 2],
726        src_addr: Felt,
727        ciphertext: [Word; 2],
728        dst_addr: Felt,
729        ctx: ContextId,
730        clk: RowIndex,
731    ) {
732        self.memory_reads.record_read_word(plaintext[0], src_addr, ctx, clk);
733        self.memory_reads
734            .record_read_word(plaintext[1], src_addr + Felt::new(4), ctx, clk);
735        self.memory_writes.record_write_word(ciphertext[0], dst_addr, ctx, clk);
736        self.memory_writes
737            .record_write_word(ciphertext[1], dst_addr + Felt::new(4), ctx, clk);
738    }
739
740    #[inline(always)]
741    fn record_pipe(&mut self, words: [Word; 2], addr: Felt, ctx: ContextId, clk: RowIndex) {
742        self.advice.record_pop_stack_dword(words);
743        self.memory_writes.record_write_word(words[0], addr, ctx, clk);
744        self.memory_writes.record_write_word(words[1], addr + Felt::new(4), ctx, clk);
745    }
746
747    #[inline(always)]
748    fn record_advice_pop_stack(&mut self, value: Felt) {
749        self.advice.record_pop_stack(value);
750    }
751
752    #[inline(always)]
753    fn record_advice_pop_stack_word(&mut self, word: Word) {
754        self.advice.record_pop_stack_word(word);
755    }
756
757    #[inline(always)]
758    fn record_u32and(&mut self, a: Felt, b: Felt) {
759        self.bitwise.record_u32and(a, b);
760    }
761
762    #[inline(always)]
763    fn record_u32xor(&mut self, a: Felt, b: Felt) {
764        self.bitwise.record_u32xor(a, b);
765    }
766
767    #[inline(always)]
768    fn record_u32_range_checks(&mut self, clk: RowIndex, u32_lo: Felt, u32_hi: Felt) {
769        let (t1, t0) = split_u32_into_u16(u32_lo.as_canonical_u64());
770        let (t3, t2) = split_u32_into_u16(u32_hi.as_canonical_u64());
771
772        self.range_checker.record_range_check_u32(clk, [t0, t1, t2, t3]);
773    }
774
775    #[inline(always)]
776    fn record_kernel_proc_access(&mut self, proc_hash: Word) {
777        self.kernel.record_kernel_proc_access(proc_hash);
778    }
779
780    #[inline(always)]
781    fn record_circuit_evaluation(&mut self, circuit_evaluation: CircuitEvaluation) {
782        self.ace.record_circuit_evaluation(circuit_evaluation);
783    }
784
785    #[inline(always)]
786    fn finalize_clock_cycle(
787        &mut self,
788        processor: &FastProcessor,
789        _op_helper_registers: OperationHelperRegisters,
790        _current_forest: &Arc<MastForest>,
791    ) {
792        // Restore the overflow table context for Call/Syscall/Dyncall END. This is deferred
793        // from start_clock_cycle because finalize_clock_cycle is only called when the operation
794        // succeeds (i.e., the stack depth check in processor.restore_context() passes).
795        if self.pending_restore_context {
796            // Restore context for call/syscall/dyncall: pop the current context's
797            // (empty) overflow stack and restore the previous context's overflow state.
798            self.overflow_table.restore_context();
799            self.overflow_replay.record_restore_context_overflow_addr(
800                MIN_STACK_DEPTH + self.overflow_table.num_elements_in_current_ctx(),
801                self.overflow_table.last_update_clk_in_current_ctx(),
802            );
803
804            self.pending_restore_context = false;
805        }
806
807        // Record all memory reads performed during EvalCircuit operations. We run this in
808        // `finalize_clock_cycle` to ensure that the memory reads are only recorded if the operation
809        // succeeds (and hence the values read from the stack can be assumed to be valid).
810        if self.is_eval_circuit_op {
811            let ptr = processor.stack_get(0);
812            let num_read = processor.stack_get(1).as_canonical_u64();
813            let num_eval = processor.stack_get(2).as_canonical_u64();
814            let ctx = processor.ctx();
815            let clk = processor.clock();
816
817            let num_read_rows = num_read / 2;
818
819            let mut addr = ptr;
820            for _ in 0..num_read_rows {
821                let word = processor
822                    .memory()
823                    .read_word(ctx, addr, clk)
824                    .expect("EvalCircuit memory read should not fail after successful execution");
825                self.memory_reads.record_read_word(word, addr, ctx, clk);
826                addr += PTR_OFFSET_WORD;
827            }
828            for _ in 0..num_eval {
829                let element = processor
830                    .memory()
831                    .read_element(ctx, addr)
832                    .expect("EvalCircuit memory read should not fail after successful execution");
833                self.memory_reads.record_read_element(element, addr, ctx, clk);
834                addr += PTR_OFFSET_ELEM;
835            }
836
837            self.is_eval_circuit_op = false;
838        }
839    }
840}
841
842// HASHER CHIPLET SHIM
843// ================================================================================================
844
845/// The number of hasher rows per permutation operation. This is used to compute the address for
846/// the next operation in the hasher chiplet.
847const NUM_HASHER_ROWS_PER_PERMUTATION: u32 = HASH_CYCLE_LEN as u32;
848
849/// Implements a shim for the hasher chiplet, where the responses of the hasher chiplet are emulated
850/// and recorded for later replay.
851///
852/// This is used to simulate hasher operations in parallel trace generation without needing to
853/// actually generate the hasher trace. All hasher operations are recorded during fast execution and
854/// then replayed during core trace generation.
855#[derive(Debug)]
856pub struct HasherChipletShim {
857    /// The address of the next MAST node encountered during execution. This field is used to keep
858    /// track of the number of rows in the hasher chiplet, from which the address of the next MAST
859    /// node is derived.
860    addr: u32,
861    /// Replay for the hasher chiplet responses, recording only the hasher chiplet responses.
862    hasher_replay: HasherResponseReplay,
863    block_addr_replay: BlockAddressReplay,
864}
865
866impl HasherChipletShim {
867    /// Creates a new [HasherChipletShim].
868    pub fn new() -> Self {
869        Self {
870            addr: 1,
871            hasher_replay: HasherResponseReplay::default(),
872            block_addr_replay: BlockAddressReplay::default(),
873        }
874    }
875
876    /// Records the address returned from a call to `Hasher::hash_control_block()`.
877    pub fn record_hash_control_block(&mut self) -> Felt {
878        let block_addr = Felt::from_u32(self.addr);
879
880        self.block_addr_replay.record_block_address(block_addr);
881        self.addr += NUM_HASHER_ROWS_PER_PERMUTATION;
882
883        block_addr
884    }
885
886    /// Records the address returned from a call to `Hasher::hash_basic_block()`.
887    pub fn record_hash_basic_block(&mut self, basic_block_node: &BasicBlockNode) -> Felt {
888        let block_addr = Felt::from_u32(self.addr);
889
890        self.block_addr_replay.record_block_address(block_addr);
891        self.addr += NUM_HASHER_ROWS_PER_PERMUTATION * basic_block_node.num_op_batches() as u32;
892
893        block_addr
894    }
895    /// Records the result of a call to `Hasher::permute()`.
896    pub fn record_permute_output(&mut self, hashed_state: [Felt; 12]) {
897        self.hasher_replay.record_permute(Felt::from_u32(self.addr), hashed_state);
898        self.addr += NUM_HASHER_ROWS_PER_PERMUTATION;
899    }
900
901    /// Records the result of a call to `Hasher::build_merkle_root()`.
902    pub fn record_build_merkle_root(&mut self, path: &MerklePath, computed_root: Word) {
903        self.hasher_replay
904            .record_build_merkle_root(Felt::from_u32(self.addr), computed_root);
905        self.addr += NUM_HASHER_ROWS_PER_PERMUTATION * path.depth() as u32;
906    }
907
908    /// Records the result of a call to `Hasher::update_merkle_root()`.
909    pub fn record_update_merkle_root(&mut self, path: &MerklePath, old_root: Word, new_root: Word) {
910        self.hasher_replay
911            .record_update_merkle_root(Felt::from_u32(self.addr), old_root, new_root);
912
913        // The Merkle path is verified twice: once for the old root and once for the new root.
914        self.addr += 2 * NUM_HASHER_ROWS_PER_PERMUTATION * path.depth() as u32;
915    }
916
917    pub fn extract_replay(&mut self) -> (HasherResponseReplay, BlockAddressReplay) {
918        (
919            core::mem::take(&mut self.hasher_replay),
920            core::mem::take(&mut self.block_addr_replay),
921        )
922    }
923}
924
925impl Default for HasherChipletShim {
926    fn default() -> Self {
927        Self::new()
928    }
929}