use alloc::sync::Arc;
use miden_air::trace::{STACK_TRACE_WIDTH, SYS_TRACE_WIDTH};
use miden_core::program::MIN_STACK_DEPTH;
use super::{
super::{
decoder::block_stack::ExecutionContextInfo,
trace_state::{
BlockAddressReplay, BlockStackReplay, DecoderState, StackState, SystemState,
},
},
core_trace_fragment::{BasicBlockContext, CoreTraceFragment},
processor::ReplayProcessor,
};
use crate::{
ExecutionError, Felt, ONE, Word, ZERO,
continuation_stack::{Continuation, ContinuationStack},
errors::MapExecErrNoCtx,
mast::{MastForest, MastNode, MastNodeExt, MastNodeId},
tracer::{OperationHelperRegisters, Tracer},
};
mod trace_row;
pub(crate) struct TracerFinalState {
pub last_stack_cols: [Felt; STACK_TRACE_WIDTH],
pub last_system_cols: [Felt; SYS_TRACE_WIDTH],
pub num_rows_written: usize,
}
#[derive(Debug)]
pub(crate) struct CoreTraceGenerationTracer<'a> {
fragment: &'a mut CoreTraceFragment<'a>,
row_write_index: usize,
decoder_state: DecoderState,
block_address_replay: BlockAddressReplay,
block_stack_replay: BlockStackReplay,
stack_cols: Option<[Felt; STACK_TRACE_WIDTH]>,
system_cols: Option<[Felt; SYS_TRACE_WIDTH]>,
ctx_info: Option<ExecutionContextInfo>,
finish_loop_condition: Option<bool>,
dyn_callee_hash: Option<Word>,
continuation: Option<Continuation>,
error_encountered: Option<ExecutionError>,
}
impl<'a> CoreTraceGenerationTracer<'a> {
pub fn new(
fragment: &'a mut CoreTraceFragment<'a>,
decoder_state: DecoderState,
block_address_replay: BlockAddressReplay,
block_stack_replay: BlockStackReplay,
) -> Self {
Self {
fragment,
row_write_index: 0,
decoder_state,
block_address_replay,
block_stack_replay,
stack_cols: None,
system_cols: None,
ctx_info: None,
finish_loop_condition: None,
dyn_callee_hash: None,
continuation: None,
error_encountered: None,
}
}
pub fn into_final_state(self) -> Result<TracerFinalState, ExecutionError> {
if let Some(err) = self.error_encountered {
return Err(err);
}
Ok(TracerFinalState {
last_stack_cols: self.stack_cols.unwrap_or([ZERO; STACK_TRACE_WIDTH]),
last_system_cols: self.system_cols.unwrap_or([ZERO; SYS_TRACE_WIDTH]),
num_rows_written: self.row_write_index,
})
}
fn fill_start_row(
&mut self,
node_id: MastNodeId,
processor: &ReplayProcessor,
current_forest: &Arc<MastForest>,
) -> Result<(), ExecutionError> {
let node = get_node_in_forest(current_forest, node_id)?;
match node {
MastNode::Block(basic_block_node) => {
self.fill_basic_block_start_trace_row(
&processor.system,
&processor.stack,
basic_block_node,
)?;
},
MastNode::Join(join_node) => {
self.fill_join_start_trace_row(
&processor.system,
&processor.stack,
join_node,
current_forest,
)?;
},
MastNode::Split(split_node) => {
self.fill_split_start_trace_row(
&processor.system,
&processor.stack,
split_node,
current_forest,
)?;
},
MastNode::Loop(loop_node) => {
self.fill_loop_start_trace_row(
&processor.system,
&processor.stack,
loop_node,
current_forest,
)?;
},
MastNode::Call(call_node) => {
self.fill_call_start_trace_row(
&processor.system,
&processor.stack,
call_node,
current_forest,
)?;
},
MastNode::Dyn(dyn_node) => {
let callee_hash = self.dyn_callee_hash.take().ok_or(ExecutionError::Internal(
"dyn callee hash not stored at start of clock cycle",
))?;
if dyn_node.is_dyncall() {
let ctx_info = self.ctx_info.take().ok_or(ExecutionError::Internal(
"execution context info not stored at start of clock cycle for DYNCALL node",
))?;
self.fill_dyncall_start_trace_row(
&processor.system,
&processor.stack,
callee_hash,
ctx_info,
);
} else {
self.fill_dyn_start_trace_row(&processor.system, &processor.stack, callee_hash);
}
},
MastNode::External(_) => {
unreachable!("The Tracer contract guarantees that external nodes do not occur here")
},
}
Ok(())
}
fn get_execution_context_for_dyncall(
&self,
current_forest: &MastForest,
continuation: &Continuation,
processor: &ReplayProcessor,
) -> Result<Option<ExecutionContextInfo>, ExecutionError> {
let Continuation::StartNode(node_id) = &continuation else {
return Ok(None);
};
let Some(node) = current_forest.get_node_by_id(*node_id) else {
return Err(ExecutionError::Internal("invalid node ID stored in continuation"));
};
let MastNode::Dyn(dyn_node) = node else {
return Ok(None);
};
if !dyn_node.is_dyncall() {
return Ok(None);
}
let (stack_depth_after_drop, overflow_addr_after_drop) =
if processor.stack.stack_depth() > MIN_STACK_DEPTH {
let (_, overflow_addr_after_drop) = processor
.stack_overflow_replay
.peek_replay_pop_overflow()
.ok_or(ExecutionError::Internal(
"stack depth is above minimum, but no corresponding overflow pop in the replay",
))?;
let stack_depth_after_drop = processor.stack.stack_depth() - 1;
(stack_depth_after_drop as u32, *overflow_addr_after_drop)
} else {
(processor.stack.stack_depth() as u32, ZERO)
};
Ok(Some(ExecutionContextInfo::new(
processor.system.ctx,
processor.system.fn_hash,
stack_depth_after_drop,
overflow_addr_after_drop,
)))
}
fn get_finish_loop_condition(
&self,
continuation: &Continuation,
processor: &ReplayProcessor,
) -> Option<bool> {
if let Continuation::FinishLoop { .. } = &continuation {
let condition = processor.stack.get(0);
return Some(condition == ONE);
}
None
}
fn get_dyn_callee_hash(
&self,
continuation: &Continuation,
processor: &ReplayProcessor,
current_forest: &MastForest,
) -> Result<Option<Word>, ExecutionError> {
if let Continuation::StartNode(node_id) = continuation {
let Some(node) = current_forest.get_node_by_id(*node_id) else {
return Err(ExecutionError::Internal("invalid node ID stored in continuation"));
};
if let MastNode::Dyn(_) = node {
let (word_read, _addr, _ctx, _clk) =
processor.memory_reads_replay.iter_read_words().next().ok_or(
ExecutionError::Internal(
"dyn node reads the procedure hash (word) from memory",
),
)?;
return Ok(Some(word_read));
}
}
Ok(None)
}
}
impl Tracer for CoreTraceGenerationTracer<'_> {
type Processor = ReplayProcessor;
fn start_clock_cycle(
&mut self,
processor: &ReplayProcessor,
continuation: Continuation,
_continuation_stack: &ContinuationStack,
current_forest: &Arc<MastForest>,
) {
if self.error_encountered.is_some() {
return;
}
let result = (|| -> Result<(), ExecutionError> {
self.ctx_info =
self.get_execution_context_for_dyncall(current_forest, &continuation, processor)?;
self.finish_loop_condition = self.get_finish_loop_condition(&continuation, processor);
self.dyn_callee_hash =
self.get_dyn_callee_hash(&continuation, processor, current_forest)?;
self.continuation = Some(continuation);
Ok(())
})();
if let Err(e) = result {
self.error_encountered = Some(e);
}
}
fn finalize_clock_cycle(
&mut self,
processor: &ReplayProcessor,
op_helper_registers: OperationHelperRegisters,
current_forest: &Arc<MastForest>,
) {
if self.error_encountered.is_some() {
return;
}
use Continuation::*;
let result = (|| -> Result<(), ExecutionError> {
let continuation = self.continuation.as_ref().ok_or(ExecutionError::Internal(
"continuation not stored at start of clock cycle",
))?;
match continuation {
StartNode(node_id) => {
self.decoder_state.replay_node_start(
&mut self.block_address_replay,
&mut self.block_stack_replay,
)?;
self.fill_start_row(*node_id, processor, current_forest)?;
},
FinishJoin(node_id) => {
let node = get_node_in_forest(current_forest, *node_id)?;
self.fill_end_trace_row(&processor.system, &processor.stack, node.digest())?;
},
FinishSplit(node_id) => {
let node = get_node_in_forest(current_forest, *node_id)?;
self.fill_end_trace_row(&processor.system, &processor.stack, node.digest())?;
},
FinishLoop { node_id, was_entered: _ } => {
let loop_condition = self.finish_loop_condition.take().ok_or(
ExecutionError::Internal(
"loop condition not stored at start of clock cycle for FinishLoop continuation",
),
)?;
if loop_condition {
let MastNode::Loop(loop_node) =
get_node_in_forest(current_forest, *node_id)?
else {
return Err(ExecutionError::Internal(
"expected loop node in FinishLoop continuation",
));
};
let current_addr = self.decoder_state.current_addr;
self.fill_loop_repeat_trace_row(
&processor.system,
&processor.stack,
loop_node,
current_forest,
current_addr,
)?;
} else {
let node = get_node_in_forest(current_forest, *node_id)?;
self.fill_end_trace_row(
&processor.system,
&processor.stack,
node.digest(),
)?;
}
},
FinishCall(node_id) => {
let node = get_node_in_forest(current_forest, *node_id)?;
self.fill_end_trace_row(&processor.system, &processor.stack, node.digest())?;
},
FinishDyn(node_id) => {
let node = get_node_in_forest(current_forest, *node_id)?;
self.fill_end_trace_row(&processor.system, &processor.stack, node.digest())?;
},
ResumeBasicBlock { node_id, batch_index, op_idx_in_batch } => {
let MastNode::Block(basic_block_node) =
get_node_in_forest(current_forest, *node_id)?
else {
return Err(ExecutionError::Internal(
"expected basic block node in ResumeBasicBlock continuation",
));
};
let mut basic_block_context = BasicBlockContext::new_at_op(
basic_block_node,
*batch_index,
*op_idx_in_batch,
)
.map_exec_err_no_ctx()?;
let current_batch = basic_block_node
.op_batches()
.get(*batch_index)
.ok_or(ExecutionError::Internal("batch index out of bounds"))?;
let operation = *current_batch
.ops()
.get(*op_idx_in_batch)
.ok_or(ExecutionError::Internal("op index in batch out of bounds"))?;
let (_, op_idx_in_group) = current_batch
.op_idx_in_batch_to_group(*op_idx_in_batch)
.ok_or(ExecutionError::Internal("invalid op index in batch"))?;
self.fill_operation_trace_row(
&processor.system,
&processor.stack,
operation,
op_idx_in_group,
op_helper_registers.to_user_op_helpers(),
&mut basic_block_context,
);
},
Respan { node_id, batch_index } => {
let MastNode::Block(basic_block_node) =
get_node_in_forest(current_forest, *node_id)?
else {
return Err(ExecutionError::Internal(
"expected basic block node in Respan continuation",
));
};
let mut basic_block_context =
BasicBlockContext::new_at_batch_start(basic_block_node, *batch_index)
.map_exec_err_no_ctx()?;
let current_batch = basic_block_node
.op_batches()
.get(*batch_index)
.ok_or(ExecutionError::Internal("batch index out of bounds"))?;
self.fill_respan_trace_row(
&processor.system,
&processor.stack,
current_batch,
&mut basic_block_context,
)?;
},
FinishBasicBlock(node_id) => {
let MastNode::Block(basic_block_node) =
get_node_in_forest(current_forest, *node_id)?
else {
return Err(ExecutionError::Internal(
"expected basic block node in FinishBasicBlock continuation",
));
};
self.fill_basic_block_end_trace_row(
&processor.system,
&processor.stack,
basic_block_node,
)?;
},
FinishExternal(_)
| EnterForest(_)
| AfterExitDecorators(_)
| AfterExitDecoratorsBasicBlock(_) => {
unreachable!(
"Tracer contract guarantees that these continuations do not occur here"
)
},
}
Ok(())
})();
if let Err(e) = result {
self.error_encountered = Some(e);
}
}
}
fn get_node_in_forest(
forest: &MastForest,
node_id: MastNodeId,
) -> Result<&MastNode, ExecutionError> {
forest
.get_node_by_id(node_id)
.ok_or(ExecutionError::Internal("invalid node ID stored in continuation"))
}
#[cfg(test)]
mod tests {
use alloc::{vec, vec::Vec};
use miden_core::mast::{DynNodeBuilder, MastForestContributor};
use super::*;
use crate::{
ContextId,
trace::{
parallel::CORE_TRACE_WIDTH,
trace_state::{
AdviceReplay, ExecutionContextReplay, HasherResponseReplay,
MastForestResolutionReplay, MemoryReadsReplay, StackOverflowReplay, StackState,
SystemState,
},
},
};
#[test]
fn get_execution_context_for_dyncall_at_min_stack_depth_with_overflow_entries() {
let mut forest = MastForest::new();
let dyncall_node_id = DynNodeBuilder::new_dyncall().add_to_forest(&mut forest).unwrap();
let continuation = Continuation::StartNode(dyncall_node_id);
let stack = StackState::new([ZERO; MIN_STACK_DEPTH], MIN_STACK_DEPTH, ZERO);
let overflow_addr_of_future_pop = Felt::new(42);
let mut stack_overflow_replay = StackOverflowReplay::new();
stack_overflow_replay.record_pop_overflow(Felt::new(99), overflow_addr_of_future_pop);
let system = SystemState {
clk: 0u32.into(),
ctx: ContextId::root(),
fn_hash: Word::default(),
pc_transcript_state: Word::default(),
};
let processor = ReplayProcessor::new(
system,
stack,
stack_overflow_replay,
ExecutionContextReplay::default(),
AdviceReplay::default(),
MemoryReadsReplay::default(),
HasherResponseReplay::default(),
MastForestResolutionReplay::default(),
1u32.into(),
);
let mut columns_data: Vec<Vec<Felt>> =
(0..CORE_TRACE_WIDTH).map(|_| vec![ZERO; 1]).collect();
let column_slices: Vec<&mut [Felt]> =
columns_data.iter_mut().map(|v| v.as_mut_slice()).collect();
let mut fragment = CoreTraceFragment {
columns: column_slices.try_into().expect("CORE_TRACE_WIDTH columns"),
};
let tracer = CoreTraceGenerationTracer::new(
&mut fragment,
DecoderState { current_addr: ZERO, parent_addr: ZERO },
BlockAddressReplay::default(),
BlockStackReplay::default(),
);
let ctx_info = tracer
.get_execution_context_for_dyncall(&forest, &continuation, &processor)
.expect("replay should not fail")
.expect("should return Some for a DYNCALL StartNode continuation");
assert_eq!(
ctx_info.parent_next_overflow_addr, ZERO,
"parent_next_overflow_addr should be ZERO, reflecting the current overflow state of the stack, \
and should not reflect the non-zero overflow address from the future pop in the replay queue"
);
}
}