use alloc::sync::Arc;
use miden_air::{StackCols, SystemCols};
use miden_core::program::MIN_STACK_DEPTH;
use super::{
super::{
block_stack::ExecutionContextInfo,
trace_state::{
BlockAddressReplay, BlockStackReplay, DecoderState, StackState, SystemState,
},
utils::RowMajorTraceWriter,
},
core_trace_fragment::BasicBlockContext,
processor::ReplayProcessor,
};
use crate::{
ExecutionError, Felt, ONE, Word, ZERO,
continuation_stack::{Continuation, ContinuationStack},
errors::MapExecErrNoCtx,
mast::{ExecutableMastForest, MastNode, MastNodeExt, MastNodeId, SparseMastForest},
trace::trace_state::NodeFlags,
tracer::{OperationHelperRegisters, Tracer},
};
mod trace_row;
pub(crate) struct TracerFinalState {
pub last_stack_cols: StackCols<Felt>,
pub last_system_cols: SystemCols<Felt>,
pub num_rows_written: usize,
}
#[derive(Debug)]
pub(crate) struct CoreTraceGenerationTracer<'a> {
writer: RowMajorTraceWriter<'a, Felt>,
row_write_index: usize,
decoder_state: DecoderState,
block_address_replay: BlockAddressReplay,
block_stack_replay: BlockStackReplay,
stack_cols: Option<StackCols<Felt>>,
system_cols: Option<SystemCols<Felt>>,
ctx_info: Option<ExecutionContextInfo>,
finish_loop_condition: Option<bool>,
dyn_callee_hash: Option<Word>,
continuation: Option<Continuation<Arc<SparseMastForest>>>,
is_loop_body: bool,
error_encountered: Option<ExecutionError>,
}
impl<'a> CoreTraceGenerationTracer<'a> {
pub fn new(
writer: RowMajorTraceWriter<'a, Felt>,
decoder_state: DecoderState,
block_address_replay: BlockAddressReplay,
block_stack_replay: BlockStackReplay,
) -> Self {
Self {
writer,
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,
is_loop_body: false,
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(StackCols {
top: [ZERO; MIN_STACK_DEPTH],
b0: ZERO,
b1: ZERO,
h0: ZERO,
}),
last_system_cols: self.system_cols.unwrap_or(SystemCols {
clk: ZERO,
ctx: ZERO,
fn_hash: [ZERO; miden_core::WORD_SIZE],
}),
num_rows_written: self.row_write_index,
})
}
fn fill_start_row(
&mut self,
node_id: MastNodeId,
processor: &ReplayProcessor,
current_forest: &Arc<SparseMastForest>,
) -> 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: &Arc<SparseMastForest>,
continuation: &Continuation<Arc<SparseMastForest>>,
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<Arc<SparseMastForest>>,
processor: &ReplayProcessor,
) -> Option<bool> {
if let Continuation::FinishLoop(_) = &continuation {
let condition = processor.stack.get(0);
return Some(condition == ONE);
}
None
}
fn compute_node_flags(
&self,
continuation: &Continuation<Arc<SparseMastForest>>,
current_forest: &Arc<SparseMastForest>,
) -> Result<NodeFlags, ExecutionError> {
let is_loop_body = self.is_loop_body;
match continuation {
Continuation::FinishLoop(_) => {
Ok(NodeFlags::new(is_loop_body, true, false, false))
},
Continuation::FinishCall(node_id) => {
let node = get_node_in_forest(current_forest, *node_id)?;
let MastNode::Call(call_node) = node else {
return Err(ExecutionError::Internal(
"expected call node in FinishCall continuation",
));
};
let is_syscall = call_node.is_syscall();
let is_call = !is_syscall;
Ok(NodeFlags::new(is_loop_body, false, is_call, is_syscall))
},
Continuation::FinishDyn(node_id) => {
let node = get_node_in_forest(current_forest, *node_id)?;
let MastNode::Dyn(dyn_node) = node else {
return Err(ExecutionError::Internal(
"expected dyn node in FinishDyn continuation",
));
};
let is_call = dyn_node.is_dyncall();
Ok(NodeFlags::new(is_loop_body, false, is_call, false))
},
Continuation::FinishJoin(_)
| Continuation::FinishSplit(_)
| Continuation::FinishBasicBlock(_) => {
Ok(NodeFlags::new(is_loop_body, false, false, false))
},
_ => {
Err(ExecutionError::Internal("compute_node_flags called for non-END continuation"))
},
}
}
fn get_dyn_callee_hash(
&self,
continuation: &Continuation<Arc<SparseMastForest>>,
processor: &ReplayProcessor,
current_forest: &Arc<SparseMastForest>,
) -> 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;
type Forest = Arc<SparseMastForest>;
fn start_clock_cycle(
&mut self,
processor: &ReplayProcessor,
continuation: Continuation<Arc<SparseMastForest>>,
continuation_stack: &ContinuationStack<Arc<SparseMastForest>>,
current_forest: &Arc<SparseMastForest>,
) {
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.is_loop_body = matches!(
continuation_stack.iter_continuations_for_next_clock().last(),
Some(Continuation::FinishLoop(_))
);
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<SparseMastForest>,
) {
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)?;
let flags = self.compute_node_flags(continuation, current_forest)?;
self.fill_end_trace_row(
&processor.system,
&processor.stack,
node.digest(),
flags.to_hasher_state_second_word(),
)?;
},
FinishSplit(node_id) => {
let node = get_node_in_forest(current_forest, *node_id)?;
let flags = self.compute_node_flags(continuation, current_forest)?;
self.fill_end_trace_row(
&processor.system,
&processor.stack,
node.digest(),
flags.to_hasher_state_second_word(),
)?;
},
FinishLoop(node_id) => {
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)?;
let flags = self.compute_node_flags(continuation, current_forest)?;
self.fill_end_trace_row(
&processor.system,
&processor.stack,
node.digest(),
flags.to_hasher_state_second_word(),
)?;
}
},
FinishCall(node_id) => {
let node = get_node_in_forest(current_forest, *node_id)?;
let flags = self.compute_node_flags(continuation, current_forest)?;
self.fill_end_trace_row(
&processor.system,
&processor.stack,
node.digest(),
flags.to_hasher_state_second_word(),
)?;
},
FinishDyn(node_id) => {
let node = get_node_in_forest(current_forest, *node_id)?;
let flags = self.compute_node_flags(continuation, current_forest)?;
self.fill_end_trace_row(
&processor.system,
&processor.stack,
node.digest(),
flags.to_hasher_state_second_word(),
)?;
},
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",
));
};
let flags = self.compute_node_flags(continuation, current_forest)?;
self.fill_basic_block_end_trace_row(
&processor.system,
&processor.stack,
basic_block_node,
flags.to_hasher_state_second_word(),
)?;
},
EnterForest { .. } => {
unreachable!(
"Tracer contract guarantees that EnterForest continuations do not occur here"
)
},
}
Ok(())
})();
if let Err(e) = result {
self.error_encountered = Some(e);
}
}
}
fn get_node_in_forest<F>(forest: &F, node_id: MastNodeId) -> Result<&MastNode, ExecutionError>
where
F: ExecutableMastForest + ?Sized,
{
forest
.get_node_by_id(node_id)
.ok_or(ExecutionError::Internal("invalid node ID stored in continuation"))
}
fn get_digest_in_forest<F>(forest: &F, node_id: MastNodeId) -> Result<Word, ExecutionError>
where
F: ExecutableMastForest + ?Sized,
{
forest
.get_digest_by_id(node_id)
.ok_or(ExecutionError::Internal("invalid node ID stored in continuation"))
}
#[cfg(test)]
mod tests {
use alloc::vec;
use miden_core::{
mast::{DynNodeBuilder, MastForestContributor},
precompile::PrecompileTranscriptState,
};
use super::*;
use crate::{
ContextId,
trace::{
parallel::CORE_TRACE_WIDTH,
trace_state::{
AdviceReplay, ExecutionContextReplay, HasherResponseReplay,
MastForestResolutionReplay, MemoryReadsReplay, StackOverflowReplay, StackState,
SystemState,
},
utils::RowMajorTraceWriter,
},
};
#[test]
fn get_execution_context_for_dyncall_at_min_stack_depth_with_overflow_entries() {
let mut forest = miden_core::mast::MastForest::new();
let dyncall_node_id = DynNodeBuilder::new_dyncall().add_to_forest(&mut forest).unwrap();
let mut sparse_builder = miden_core::mast::SparseMastForestBuilder::new(Arc::new(forest));
sparse_builder.record_visit(dyncall_node_id, miden_core::mast::VisitKind::FullVisit);
let forest = Arc::new(sparse_builder.finalize());
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_unchecked(42);
let mut stack_overflow_replay = StackOverflowReplay::new();
stack_overflow_replay
.record_pop_overflow(Felt::new_unchecked(99), overflow_addr_of_future_pop);
let system = SystemState {
clk: 0u32.into(),
ctx: ContextId::root(),
fn_hash: Word::default(),
pc_transcript_state: PrecompileTranscriptState::default(),
};
let processor = ReplayProcessor::new(
system,
stack,
stack_overflow_replay,
ExecutionContextReplay::default(),
AdviceReplay::default(),
MemoryReadsReplay::default(),
HasherResponseReplay::default(),
MastForestResolutionReplay::default(),
vec![forest.clone()],
crate::ExecutionOptions::DEFAULT_MAX_STACK_DEPTH,
1u32.into(),
);
let mut buffer = vec![ZERO; CORE_TRACE_WIDTH];
let writer = RowMajorTraceWriter::new(&mut buffer, CORE_TRACE_WIDTH);
let tracer = CoreTraceGenerationTracer::new(
writer,
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"
);
}
}