use alloc::{collections::BTreeMap, sync::Arc, vec::Vec};
use miden_air::trace::chiplets::hasher::{
CONTROLLER_ROWS_PER_PERM_FELT, CONTROLLER_ROWS_PER_PERMUTATION, STATE_WIDTH,
};
use miden_core::{
FMP_ADDR, FMP_INIT_VALUE,
operations::Operation,
serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
};
use super::{
block_stack::{BlockInfo, BlockStack, ExecutionContextInfo},
stack::OverflowTable,
trace_state::{
AceReplay, AdviceReplay, BitwiseReplay, BlockAddressReplay, BlockStackReplay,
CoreTraceFragmentContext, CoreTraceState, DecoderState, ExecutionContextReplay,
ExecutionContextSystemInfo, ExecutionReplay, HasherRequestReplay, HasherResponseReplay,
KernelReplay, MastForestResolutionReplay, MemoryReadsReplay, MemoryWritesReplay,
RangeCheckerReplay, StackOverflowReplay, StackState, SystemState,
},
utils::split_u32_into_u16,
};
use crate::{
ContextId, EMPTY_WORD, ExecutionError, FastProcessor, Felt, MIN_STACK_DEPTH, ONE, RowIndex,
Word, ZERO,
continuation_stack::{Continuation, ContinuationStack},
crypto::merkle::MerklePath,
mast::{
BasicBlockNode, JoinNode, LoopNode, MastForest, MastForestId, MastNode, MastNodeExt,
MastNodeId, SparseMastForest, SparseMastForestBuilder, SplitNode, VisitKind,
},
processor::{Processor, StackInterface, SystemInterface},
trace::chiplets::{CircuitEvaluation, PTR_OFFSET_ELEM, PTR_OFFSET_WORD},
tracer::{OperationHelperRegisters, Tracer},
utils::Idx,
};
#[derive(Debug)]
struct StateSnapshot {
state: CoreTraceState,
continuation_stack: ContinuationStack<MastForestId>,
initial_mast_forest_id: MastForestId,
}
#[derive(Debug)]
pub(crate) struct TraceReplay {
pub(crate) core_trace_contexts: Vec<CoreTraceFragmentContext>,
pub(crate) mast_forest_store: Vec<Arc<SparseMastForest>>,
pub(crate) range_checker_replay: RangeCheckerReplay,
pub(crate) memory_writes: MemoryWritesReplay,
pub(crate) bitwise_replay: BitwiseReplay,
pub(crate) hasher_for_chiplet: HasherRequestReplay,
pub(crate) kernel_replay: KernelReplay,
pub(crate) ace_replay: AceReplay,
pub(crate) fragment_size: usize,
pub(crate) max_stack_depth: usize,
}
impl Serializable for TraceReplay {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
target.write_usize(self.mast_forest_store.len());
for forest in &self.mast_forest_store {
forest.write_into(target);
}
self.core_trace_contexts.write_into(target);
self.range_checker_replay.write_into(target);
self.memory_writes.write_into(target);
self.bitwise_replay.write_into(target);
self.hasher_for_chiplet.write_into(target);
self.kernel_replay.write_into(target);
self.ace_replay.write_into(target);
self.fragment_size.write_into(target);
self.max_stack_depth.write_into(target);
}
}
impl Deserializable for TraceReplay {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let store_len = source.read_usize()?;
let max_store_len = source.max_alloc(SparseMastForest::min_serialized_size());
if store_len > max_store_len {
return Err(DeserializationError::InvalidValue(format!(
"MAST forest store length {store_len} exceeds reader allocation bound {max_store_len}"
)));
}
let mut mast_forest_store = Vec::with_capacity(store_len);
for _ in 0..store_len {
mast_forest_store.push(Arc::new(SparseMastForest::read_from(source)?));
}
let context = Self {
mast_forest_store,
core_trace_contexts: Vec::<CoreTraceFragmentContext>::read_from(source)?,
range_checker_replay: RangeCheckerReplay::read_from(source)?,
memory_writes: MemoryWritesReplay::read_from(source)?,
bitwise_replay: BitwiseReplay::read_from(source)?,
hasher_for_chiplet: HasherRequestReplay::read_from(source)?,
kernel_replay: KernelReplay::read_from(source)?,
ace_replay: AceReplay::read_from(source)?,
fragment_size: usize::read_from(source)?,
max_stack_depth: usize::read_from(source)?,
};
validate_trace_generation_context_invariants(&context)?;
validate_trace_generation_context_forest_ids(&context)?;
Ok(context)
}
}
fn validate_trace_generation_context_invariants(
context: &TraceReplay,
) -> Result<(), DeserializationError> {
if context.fragment_size == 0 {
return Err(DeserializationError::InvalidValue(
"trace generation fragment_size must be non-zero".into(),
));
}
if context.max_stack_depth < MIN_STACK_DEPTH {
return Err(DeserializationError::InvalidValue(format!(
"trace generation max_stack_depth {} is below minimum {MIN_STACK_DEPTH}",
context.max_stack_depth
)));
}
for (fragment_index, fragment) in context.core_trace_contexts.iter().enumerate() {
let stack_depth = fragment.state.stack.stack_depth();
if stack_depth > context.max_stack_depth {
return Err(DeserializationError::InvalidValue(format!(
"fragment {fragment_index}: stack depth {stack_depth} exceeds max_stack_depth {}",
context.max_stack_depth
)));
}
}
Ok(())
}
fn validate_trace_generation_context_forest_ids(
context: &TraceReplay,
) -> Result<(), DeserializationError> {
let store_len = context.mast_forest_store.len();
for (fragment_index, fragment) in context.core_trace_contexts.iter().enumerate() {
validate_mast_forest_id(
fragment.initial_mast_forest_id,
store_len,
"core trace fragment initial_mast_forest_id",
)?;
for forest_id in fragment.continuation.iter_enter_forest_ids() {
validate_mast_forest_id(
forest_id,
store_len,
"core trace fragment continuation EnterForest",
)
.map_err(|err| {
DeserializationError::InvalidValue(format!("fragment {fragment_index}: {err}"))
})?;
}
for forest_id in fragment.replay.mast_forest_resolution.iter_forest_ids() {
validate_mast_forest_id(
forest_id,
store_len,
"core trace fragment MastForestResolutionReplay",
)
.map_err(|err| {
DeserializationError::InvalidValue(format!("fragment {fragment_index}: {err}"))
})?;
}
}
for forest_id in context.hasher_for_chiplet.iter_hash_basic_block_forest_ids() {
validate_mast_forest_id(forest_id, store_len, "hasher HashBasicBlock replay")?;
}
Ok(())
}
fn validate_mast_forest_id(
forest_id: MastForestId,
store_len: usize,
label: &str,
) -> Result<(), DeserializationError> {
if forest_id.to_usize() >= store_len {
return Err(DeserializationError::InvalidValue(format!(
"{label} id {} is out of range for mast_forest_store length {store_len}",
u32::from(forest_id)
)));
}
Ok(())
}
#[derive(Debug)]
pub struct ExecutionTracer {
state_snapshot: Option<StateSnapshot>,
overflow_table: OverflowTable,
overflow_replay: StackOverflowReplay,
block_stack: BlockStack,
block_stack_replay: BlockStackReplay,
execution_context_replay: ExecutionContextReplay,
hasher_chiplet_shim: HasherChipletShim,
memory_reads: MemoryReadsReplay,
advice: AdviceReplay,
external: MastForestResolutionReplay,
range_checker: RangeCheckerReplay,
memory_writes: MemoryWritesReplay,
bitwise: BitwiseReplay,
kernel: KernelReplay,
hasher_for_chiplet: HasherRequestReplay,
ace: AceReplay,
fragment_contexts: Vec<CoreTraceFragmentContext>,
mast_forest_builders: Vec<SparseMastForestBuilder>,
mast_forest_ids: BTreeMap<*const MastForest, MastForestId>,
fragment_size: usize,
max_stack_depth: usize,
pending_restore_context: bool,
is_eval_circuit_op: bool,
}
impl ExecutionTracer {
#[cfg(feature = "std")]
pub(crate) fn new_with_streamed_hasher(
fragment_size: usize,
max_stack_depth: usize,
hasher_sender: std::sync::mpsc::Sender<crate::trace::ResolvedHasherOp<'static>>,
) -> Self {
let mut tracer = Self::new(fragment_size, max_stack_depth);
tracer.hasher_for_chiplet = HasherRequestReplay::streamed(hasher_sender);
tracer
}
#[inline(always)]
pub fn new(fragment_size: usize, max_stack_depth: usize) -> Self {
Self {
state_snapshot: None,
overflow_table: OverflowTable::default(),
overflow_replay: StackOverflowReplay::default(),
block_stack: BlockStack::default(),
block_stack_replay: BlockStackReplay::default(),
execution_context_replay: ExecutionContextReplay::default(),
hasher_chiplet_shim: HasherChipletShim::default(),
memory_reads: MemoryReadsReplay::default(),
range_checker: RangeCheckerReplay::default(),
memory_writes: MemoryWritesReplay::default(),
advice: AdviceReplay::default(),
bitwise: BitwiseReplay::default(),
kernel: KernelReplay::default(),
hasher_for_chiplet: HasherRequestReplay::default(),
ace: AceReplay::default(),
external: MastForestResolutionReplay::default(),
fragment_contexts: Vec::new(),
mast_forest_builders: Vec::new(),
mast_forest_ids: BTreeMap::new(),
fragment_size,
max_stack_depth,
pending_restore_context: false,
is_eval_circuit_op: false,
}
}
#[inline]
fn forest_id(&mut self, forest: &Arc<MastForest>) -> MastForestId {
let key = Arc::as_ptr(forest);
if let Some(&id) = self.mast_forest_ids.get(&key) {
return id;
}
let id = MastForestId::from(self.mast_forest_builders.len() as u32);
self.mast_forest_builders.push(SparseMastForestBuilder::new(forest.clone()));
self.mast_forest_ids.insert(key, id);
id
}
#[inline]
fn record_visit(&mut self, forest: &Arc<MastForest>, node_id: MastNodeId) {
let id = self.forest_id(forest);
self.mast_forest_builders[id.to_usize()].record_visit(node_id, VisitKind::FullVisit);
if let Some(node) = forest.get_node_by_id(node_id) {
node.for_each_child(|child_id| {
self.mast_forest_builders[id.to_usize()]
.record_visit(child_id, VisitKind::DigestOnly);
});
}
}
fn translate_continuation_stack(
&mut self,
live: ContinuationStack<Arc<MastForest>>,
) -> ContinuationStack<MastForestId> {
let mut translated: ContinuationStack<MastForestId> = ContinuationStack::default();
for cont in live.into_inner() {
let translated_cont = match cont {
Continuation::EnterForest {
forest,
package_debug_info,
inline_context_depth,
} => Continuation::EnterForest {
forest: self.forest_id(&forest),
package_debug_info,
inline_context_depth,
},
Continuation::StartNode(id) => Continuation::StartNode(id),
Continuation::FinishJoin(id) => Continuation::FinishJoin(id),
Continuation::FinishSplit(id) => Continuation::FinishSplit(id),
Continuation::FinishLoop(node_id) => Continuation::FinishLoop(node_id),
Continuation::FinishCall(id) => Continuation::FinishCall(id),
Continuation::FinishDyn(id) => Continuation::FinishDyn(id),
Continuation::ResumeBasicBlock { node_id, batch_index, op_idx_in_batch } => {
Continuation::ResumeBasicBlock { node_id, batch_index, op_idx_in_batch }
},
Continuation::Respan { node_id, batch_index } => {
Continuation::Respan { node_id, batch_index }
},
Continuation::FinishBasicBlock(id) => Continuation::FinishBasicBlock(id),
};
translated.push_continuation(translated_cont);
}
translated
}
#[inline(always)]
pub(crate) fn into_trace_replay(mut self) -> TraceReplay {
self.finish_current_fragment_context();
let mast_forest_store = self
.mast_forest_builders
.into_iter()
.map(|builder| Arc::new(builder.finalize()))
.collect();
TraceReplay {
core_trace_contexts: self.fragment_contexts,
mast_forest_store,
range_checker_replay: self.range_checker,
memory_writes: self.memory_writes,
bitwise_replay: self.bitwise,
kernel_replay: self.kernel,
hasher_for_chiplet: self.hasher_for_chiplet,
ace_replay: self.ace,
fragment_size: self.fragment_size,
max_stack_depth: self.max_stack_depth,
}
}
#[inline(always)]
fn start_new_fragment_context(
&mut self,
system_state: SystemState,
stack_top: [Felt; MIN_STACK_DEPTH],
mut continuation_stack: ContinuationStack<Arc<MastForest>>,
continuation: Continuation<Arc<MastForest>>,
current_forest: Arc<MastForest>,
) {
self.finish_current_fragment_context();
let decoder_state = {
if self.block_stack.is_empty() {
DecoderState { current_addr: ZERO, parent_addr: ZERO }
} else {
let block_info = self.block_stack.peek();
DecoderState {
current_addr: block_info.addr,
parent_addr: block_info.parent_addr,
}
}
};
let stack = {
let stack_depth = MIN_STACK_DEPTH + self.overflow_table.num_elements_in_current_ctx();
let last_overflow_addr = self.overflow_table.last_update_clk_in_current_ctx();
StackState::new(stack_top, stack_depth, last_overflow_addr)
};
continuation_stack.push_continuation(continuation);
let initial_mast_forest_id = self.forest_id(¤t_forest);
let translated_stack = self.translate_continuation_stack(continuation_stack);
self.state_snapshot = Some(StateSnapshot {
state: CoreTraceState {
system: system_state,
decoder: decoder_state,
stack,
},
continuation_stack: translated_stack,
initial_mast_forest_id,
});
}
#[inline(always)]
fn record_control_node_start<P: Processor>(
&mut self,
node: &MastNode,
processor: &P,
current_forest: &MastForest,
) {
let ctx_info = match node {
MastNode::Join(node) => {
let child1_hash = current_forest
.get_node_by_id(node.first())
.expect("join node's first child expected to be in the forest")
.digest();
let child2_hash = current_forest
.get_node_by_id(node.second())
.expect("join node's second child expected to be in the forest")
.digest();
self.hasher_for_chiplet.record_hash_control_block(
child1_hash,
child2_hash,
JoinNode::DOMAIN,
node.digest(),
);
None
},
MastNode::Split(node) => {
let child1_hash = current_forest
.get_node_by_id(node.on_true())
.expect("split node's true child expected to be in the forest")
.digest();
let child2_hash = current_forest
.get_node_by_id(node.on_false())
.expect("split node's false child expected to be in the forest")
.digest();
self.hasher_for_chiplet.record_hash_control_block(
child1_hash,
child2_hash,
SplitNode::DOMAIN,
node.digest(),
);
None
},
MastNode::Loop(node) => {
let body_hash = current_forest
.get_node_by_id(node.body())
.expect("loop node's body expected to be in the forest")
.digest();
self.hasher_for_chiplet.record_hash_control_block(
body_hash,
EMPTY_WORD,
LoopNode::DOMAIN,
node.digest(),
);
None
},
MastNode::Call(node) => {
let callee_hash = current_forest
.get_node_by_id(node.callee())
.expect("call node's callee expected to be in the forest")
.digest();
self.hasher_for_chiplet.record_hash_control_block(
callee_hash,
EMPTY_WORD,
node.domain(),
node.digest(),
);
let overflow_addr = self.overflow_table.last_update_clk_in_current_ctx();
Some(ExecutionContextInfo::new(
processor.system().ctx(),
processor.system().caller_hash(),
processor.stack().depth(),
overflow_addr,
))
},
MastNode::Dyn(dyn_node) => {
self.hasher_for_chiplet.record_hash_control_block(
EMPTY_WORD,
EMPTY_WORD,
dyn_node.domain(),
dyn_node.digest(),
);
if dyn_node.is_dyncall() {
let (stack_depth_after_drop, overflow_addr) =
if processor.stack().depth() > MIN_STACK_DEPTH as u32 {
(
processor.stack().depth() - 1,
self.overflow_table.clk_after_pop_in_current_ctx(),
)
} else {
(processor.stack().depth(), ZERO)
};
Some(ExecutionContextInfo::new(
processor.system().ctx(),
processor.system().caller_hash(),
stack_depth_after_drop,
overflow_addr,
))
} else {
None
}
},
MastNode::Block(_) => panic!(
"`ExecutionTracer::record_basic_block_start()` must be called instead for basic blocks"
),
MastNode::External(_) => panic!(
"External nodes are guaranteed to be resolved before record_control_node_start is called"
),
};
let block_addr = self.hasher_chiplet_shim.record_hash_control_block();
let parent_addr = self.block_stack.push(block_addr, ctx_info);
self.block_stack_replay.record_node_start_parent_addr(parent_addr);
}
#[inline(always)]
fn record_node_end(&mut self, block_info: &BlockInfo) {
let (prev_addr, prev_parent_addr) = if self.block_stack.is_empty() {
(ZERO, ZERO)
} else {
let prev_block = self.block_stack.peek();
(prev_block.addr, prev_block.parent_addr)
};
self.block_stack_replay
.record_node_end(block_info.addr, prev_addr, prev_parent_addr);
}
#[inline(always)]
fn record_execution_context(&mut self, ctx_info: ExecutionContextSystemInfo) {
self.execution_context_replay.record_execution_context(ctx_info);
}
#[inline(always)]
fn finish_current_fragment_context(&mut self) {
if let Some(snapshot) = self.state_snapshot.take() {
let (hasher_replay, block_addr_replay) = self.hasher_chiplet_shim.extract_replay();
let memory_reads_replay = core::mem::take(&mut self.memory_reads);
let advice_replay = core::mem::take(&mut self.advice);
let external_replay = core::mem::take(&mut self.external);
let stack_overflow_replay = core::mem::take(&mut self.overflow_replay);
let block_stack_replay = core::mem::take(&mut self.block_stack_replay);
let execution_context_replay = core::mem::take(&mut self.execution_context_replay);
let trace_state = CoreTraceFragmentContext {
state: snapshot.state,
replay: ExecutionReplay {
hasher: hasher_replay,
block_address: block_addr_replay,
memory_reads: memory_reads_replay,
advice: advice_replay,
mast_forest_resolution: external_replay,
stack_overflow: stack_overflow_replay,
block_stack: block_stack_replay,
execution_context: execution_context_replay,
},
continuation: snapshot.continuation_stack,
initial_mast_forest_id: snapshot.initial_mast_forest_id,
};
self.fragment_contexts.push(trace_state);
}
}
#[inline(always)]
fn increment_stack_size(&mut self, processor: &FastProcessor) {
let new_overflow_value = processor.stack_get(15);
self.overflow_table.push(new_overflow_value, processor.system().clock());
}
#[inline(always)]
fn decrement_stack_size(&mut self) {
if let Some(popped_value) = self.overflow_table.pop() {
let new_overflow_addr = self.overflow_table.last_update_clk_in_current_ctx();
self.overflow_replay.record_pop_overflow(popped_value, new_overflow_addr);
}
}
}
impl Tracer for ExecutionTracer {
type Processor = FastProcessor;
type Forest = Arc<MastForest>;
#[inline(always)]
fn start_clock_cycle(
&mut self,
processor: &FastProcessor,
continuation: Continuation<Arc<MastForest>>,
continuation_stack: &ContinuationStack<Arc<MastForest>>,
current_forest: &Arc<MastForest>,
) {
if processor.system().clock().as_usize().is_multiple_of(self.fragment_size) {
self.start_new_fragment_context(
SystemState::from_processor(processor),
processor
.stack_top()
.try_into()
.expect("stack_top expected to be MIN_STACK_DEPTH elements"),
continuation_stack.clone(),
continuation.clone(),
current_forest.clone(),
);
}
if let Some(visited_node_id) = node_id_for_visit(&continuation) {
self.record_visit(current_forest, visited_node_id);
}
match continuation {
Continuation::ResumeBasicBlock { node_id, batch_index, op_idx_in_batch } => {
let basic_block = current_forest[node_id].unwrap_basic_block();
let op = &basic_block.op_batches()[batch_index].ops()[op_idx_in_batch];
if op.increments_stack_size() {
self.increment_stack_size(processor);
} else if op.decrements_stack_size() {
self.decrement_stack_size();
}
if matches!(op, Operation::EvalCircuit) {
self.is_eval_circuit_op = true;
}
},
Continuation::StartNode(mast_node_id) => match ¤t_forest[mast_node_id] {
MastNode::Join(_) | MastNode::Loop(_) => {
self.record_control_node_start(
¤t_forest[mast_node_id],
processor,
current_forest,
);
},
MastNode::Split(_) => {
self.record_control_node_start(
¤t_forest[mast_node_id],
processor,
current_forest,
);
self.decrement_stack_size();
},
MastNode::Call(_) => {
self.record_control_node_start(
¤t_forest[mast_node_id],
processor,
current_forest,
);
self.overflow_table.start_context();
},
MastNode::Dyn(dyn_node) => {
self.record_control_node_start(
¤t_forest[mast_node_id],
processor,
current_forest,
);
self.decrement_stack_size();
if dyn_node.is_dyncall() {
self.overflow_table.start_context();
}
},
MastNode::Block(basic_block_node) => {
let forest_id = self.forest_id(current_forest);
self.hasher_for_chiplet.record_hash_basic_block(
forest_id,
mast_node_id,
basic_block_node,
);
let block_addr =
self.hasher_chiplet_shim.record_hash_basic_block(basic_block_node);
let parent_addr = self.block_stack.push(block_addr, None);
self.block_stack_replay.record_node_start_parent_addr(parent_addr);
},
MastNode::External(_) => unreachable!(
"start_clock_cycle is guaranteed not to be called on external nodes"
),
},
Continuation::Respan { node_id: _, batch_index: _ } => {
self.block_stack.peek_mut().addr += CONTROLLER_ROWS_PER_PERM_FELT;
},
Continuation::FinishLoop(_) if processor.stack_get(0) == ONE => {
self.decrement_stack_size();
},
Continuation::FinishJoin(_)
| Continuation::FinishSplit(_)
| Continuation::FinishCall(_)
| Continuation::FinishDyn(_)
| Continuation::FinishLoop(_) | Continuation::FinishBasicBlock(_) => {
if matches!(
&continuation,
Continuation::FinishLoop(_)
) {
self.decrement_stack_size();
}
let block_info = self.block_stack.pop();
self.record_node_end(&block_info);
if let Some(ctx_info) = block_info.ctx_info {
self.record_execution_context(ExecutionContextSystemInfo {
parent_ctx: ctx_info.parent_ctx,
parent_fn_hash: ctx_info.parent_fn_hash,
});
self.pending_restore_context = true;
}
},
Continuation::EnterForest { .. } => {
panic!("EnterForest continuations are guaranteed not to be passed here")
},
}
}
#[inline(always)]
fn record_mast_forest_resolution(&mut self, node_id: MastNodeId, forest: &Arc<MastForest>) {
let forest_id = self.forest_id(forest);
self.external.record_resolution(node_id, forest_id);
}
#[inline(always)]
fn record_external_node_entered(
&mut self,
external_node_id: MastNodeId,
forest: &Arc<MastForest>,
) {
self.record_visit(forest, external_node_id);
}
#[inline(always)]
fn record_hasher_permute(
&mut self,
input_state: [Felt; STATE_WIDTH],
output_state: [Felt; STATE_WIDTH],
) {
self.hasher_for_chiplet.record_permute_input(input_state);
self.hasher_chiplet_shim.record_permute_output(output_state);
}
#[inline(always)]
fn record_hasher_build_merkle_root(
&mut self,
node: Word,
path: Option<&MerklePath>,
depth: Felt,
index: Felt,
output_root: Word,
) {
let path = path.expect("execution tracer expects a valid Merkle path");
self.hasher_chiplet_shim.record_build_merkle_root(path, output_root);
self.hasher_for_chiplet.record_build_merkle_root(node, path.clone(), index);
self.range_checker.record_merkle_depth(depth);
}
#[inline(always)]
fn record_hasher_update_merkle_root(
&mut self,
old_value: Word,
new_value: Word,
path: Option<&MerklePath>,
depth: Felt,
index: Felt,
old_root: Word,
new_root: Word,
) {
let path = path.expect("execution tracer expects a valid Merkle path");
self.hasher_chiplet_shim.record_update_merkle_root(path, old_root, new_root);
self.hasher_for_chiplet.record_update_merkle_root(
old_value,
new_value,
path.clone(),
index,
);
self.range_checker.record_merkle_depth(depth);
}
#[inline(always)]
fn record_memory_read_element(
&mut self,
element: Felt,
addr: Felt,
ctx: ContextId,
clk: RowIndex,
) {
self.memory_reads.record_read_element(element, addr, ctx, clk);
}
#[inline(always)]
fn record_memory_read_word(&mut self, word: Word, addr: Felt, ctx: ContextId, clk: RowIndex) {
self.memory_reads.record_read_word(word, addr, ctx, clk);
}
#[inline(always)]
fn record_memory_write_element(
&mut self,
element: Felt,
addr: Felt,
ctx: ContextId,
clk: RowIndex,
) {
self.memory_writes.record_write_element(element, addr, ctx, clk);
}
#[inline(always)]
fn record_memory_write_word(&mut self, word: Word, addr: Felt, ctx: ContextId, clk: RowIndex) {
self.memory_writes.record_write_word(word, addr, ctx, clk);
}
#[inline(always)]
fn record_memory_read_dword(
&mut self,
words: [Word; 2],
addr: Felt,
ctx: ContextId,
clk: RowIndex,
) {
self.memory_reads.record_read_word(words[0], addr, ctx, clk);
self.memory_reads.record_read_word(words[1], addr + PTR_OFFSET_WORD, ctx, clk);
}
#[inline(always)]
fn record_dyncall_memory(
&mut self,
callee_hash: Word,
read_addr: Felt,
read_ctx: ContextId,
fmp_ctx: ContextId,
clk: RowIndex,
) {
self.memory_reads.record_read_word(callee_hash, read_addr, read_ctx, clk);
self.memory_writes.record_write_element(FMP_INIT_VALUE, FMP_ADDR, fmp_ctx, clk);
}
#[inline(always)]
fn record_crypto_stream(
&mut self,
plaintext: [Word; 2],
src_addr: Felt,
ciphertext: [Word; 2],
dst_addr: Felt,
ctx: ContextId,
clk: RowIndex,
) {
self.memory_reads.record_read_word(plaintext[0], src_addr, ctx, clk);
self.memory_reads
.record_read_word(plaintext[1], src_addr + PTR_OFFSET_WORD, ctx, clk);
self.memory_writes.record_write_word(ciphertext[0], dst_addr, ctx, clk);
self.memory_writes
.record_write_word(ciphertext[1], dst_addr + PTR_OFFSET_WORD, ctx, clk);
}
#[inline(always)]
fn record_pipe(&mut self, words: [Word; 2], addr: Felt, ctx: ContextId, clk: RowIndex) {
self.advice.record_pop_stack_dword(words);
self.memory_writes.record_write_word(words[0], addr, ctx, clk);
self.memory_writes.record_write_word(words[1], addr + PTR_OFFSET_WORD, ctx, clk);
}
#[inline(always)]
fn record_advice_pop_stack(&mut self, value: Felt) {
self.advice.record_pop_stack(value);
}
#[inline(always)]
fn record_advice_pop_stack_word(&mut self, word: Word) {
self.advice.record_pop_stack_word(word);
}
#[inline(always)]
fn record_u32and(&mut self, a: Felt, b: Felt) {
self.bitwise.record_u32and(a, b);
}
#[inline(always)]
fn record_u32xor(&mut self, a: Felt, b: Felt) {
self.bitwise.record_u32xor(a, b);
}
#[inline(always)]
fn record_u32_range_checks(&mut self, u32_lo: Felt, u32_hi: Felt) {
let (t1, t0) = split_u32_into_u16(u32_lo.as_canonical_u64());
let (t3, t2) = split_u32_into_u16(u32_hi.as_canonical_u64());
self.range_checker.record_range_check_u32([t0, t1, t2, t3]);
}
#[inline(always)]
fn record_u32div_range_checks(
&mut self,
quotient: Felt,
remainder: Felt,
remainder_diff: Felt,
) {
self.record_u32_range_checks(quotient, remainder);
let (d1, d0) = split_u32_into_u16(remainder_diff.as_canonical_u64());
self.range_checker.record_u32div_remainder_diff([d0, d1]);
}
#[inline(always)]
fn record_kernel_proc_access(&mut self, proc_hash: Word) {
self.kernel.record_kernel_proc_access(proc_hash);
}
#[inline(always)]
fn record_circuit_evaluation(&mut self, circuit_evaluation: CircuitEvaluation) {
self.ace.record_circuit_evaluation(circuit_evaluation);
}
#[inline(always)]
fn finalize_clock_cycle(
&mut self,
processor: &FastProcessor,
_op_helper_registers: OperationHelperRegisters,
_current_forest: &Arc<MastForest>,
) -> Result<(), ExecutionError> {
if self.pending_restore_context {
self.overflow_table.restore_context().map_err(|_| {
ExecutionError::Internal(
"overflow table restore_context failed during trace finalization",
)
})?;
self.overflow_replay.record_restore_context_overflow_addr(
MIN_STACK_DEPTH + self.overflow_table.num_elements_in_current_ctx(),
self.overflow_table.last_update_clk_in_current_ctx(),
);
self.pending_restore_context = false;
}
if self.is_eval_circuit_op {
let ptr = processor.stack_get(0);
let num_read = processor.stack_get(1).as_canonical_u64();
let num_eval = processor.stack_get(2).as_canonical_u64();
let ctx = processor.ctx();
let clk = processor.clock();
let num_read_rows = num_read / 2;
let mut addr = ptr;
for _ in 0..num_read_rows {
let word = processor
.memory()
.read_word(ctx, addr, clk)
.expect("EvalCircuit memory read should not fail after successful execution");
self.memory_reads.record_read_word(word, addr, ctx, clk);
addr += PTR_OFFSET_WORD;
}
for _ in 0..num_eval {
let element = processor
.memory()
.read_element(ctx, addr)
.expect("EvalCircuit memory read should not fail after successful execution");
self.memory_reads.record_read_element(element, addr, ctx, clk);
addr += PTR_OFFSET_ELEM;
}
self.is_eval_circuit_op = false;
}
Ok(())
}
}
#[inline]
fn node_id_for_visit<F>(continuation: &Continuation<F>) -> Option<MastNodeId> {
match *continuation {
Continuation::StartNode(id)
| Continuation::FinishJoin(id)
| Continuation::FinishSplit(id)
| Continuation::FinishCall(id)
| Continuation::FinishDyn(id)
| Continuation::FinishBasicBlock(id) => Some(id),
Continuation::FinishLoop(id) => Some(id),
Continuation::ResumeBasicBlock { node_id, .. } | Continuation::Respan { node_id, .. } => {
Some(node_id)
},
Continuation::EnterForest { .. } => None,
}
}
const NUM_HASHER_ROWS_PER_PERMUTATION: u32 = CONTROLLER_ROWS_PER_PERMUTATION as u32;
#[derive(Debug)]
pub struct HasherChipletShim {
addr: u32,
hasher_replay: HasherResponseReplay,
block_addr_replay: BlockAddressReplay,
}
impl HasherChipletShim {
pub fn new() -> Self {
Self {
addr: 1,
hasher_replay: HasherResponseReplay::default(),
block_addr_replay: BlockAddressReplay::default(),
}
}
pub fn record_hash_control_block(&mut self) -> Felt {
let block_addr = Felt::from_u32(self.addr);
self.block_addr_replay.record_block_address(block_addr);
self.addr += NUM_HASHER_ROWS_PER_PERMUTATION;
block_addr
}
pub fn record_hash_basic_block(&mut self, basic_block_node: &BasicBlockNode) -> Felt {
let block_addr = Felt::from_u32(self.addr);
self.block_addr_replay.record_block_address(block_addr);
self.addr += NUM_HASHER_ROWS_PER_PERMUTATION * basic_block_node.num_op_batches() as u32;
block_addr
}
pub fn record_permute_output(&mut self, hashed_state: [Felt; 12]) {
self.hasher_replay.record_permute(Felt::from_u32(self.addr), hashed_state);
self.addr += NUM_HASHER_ROWS_PER_PERMUTATION;
}
pub fn record_build_merkle_root(&mut self, path: &MerklePath, computed_root: Word) {
self.hasher_replay
.record_build_merkle_root(Felt::from_u32(self.addr), computed_root);
self.addr += NUM_HASHER_ROWS_PER_PERMUTATION * path.depth() as u32;
}
pub fn record_update_merkle_root(&mut self, path: &MerklePath, old_root: Word, new_root: Word) {
self.hasher_replay
.record_update_merkle_root(Felt::from_u32(self.addr), old_root, new_root);
self.addr += 2 * NUM_HASHER_ROWS_PER_PERMUTATION * path.depth() as u32;
}
pub fn extract_replay(&mut self) -> (HasherResponseReplay, BlockAddressReplay) {
(
core::mem::take(&mut self.hasher_replay),
core::mem::take(&mut self.block_addr_replay),
)
}
}
impl Default for HasherChipletShim {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod serialization_tests {
use super::*;
use crate::mast::BasicBlockNodeBuilder;
fn empty_trace_generation_context(fragment_size: usize, max_stack_depth: usize) -> TraceReplay {
TraceReplay {
mast_forest_store: Vec::new(),
core_trace_contexts: Vec::new(),
range_checker_replay: RangeCheckerReplay::default(),
memory_writes: MemoryWritesReplay::default(),
bitwise_replay: BitwiseReplay::default(),
hasher_for_chiplet: HasherRequestReplay::default(),
kernel_replay: KernelReplay::default(),
ace_replay: AceReplay::default(),
fragment_size,
max_stack_depth,
}
}
fn one_node_sparse_forest() -> Arc<SparseMastForest> {
let mut forest = MastForest::new();
let root = BasicBlockNodeBuilder::new(vec![Operation::Noop])
.add_to_forest(&mut forest)
.unwrap();
forest.make_root(root);
let forest = Arc::new(forest);
let mut builder = SparseMastForestBuilder::new(Arc::clone(&forest));
builder.record_visit(root, VisitKind::FullVisit);
Arc::new(builder.finalize())
}
fn core_trace_state() -> CoreTraceState {
CoreTraceState {
system: SystemState {
clk: RowIndex::from(0u32),
ctx: ContextId::root(),
fn_hash: Word::default(),
deferred_root: Word::default(),
},
decoder: DecoderState { current_addr: ZERO, parent_addr: ZERO },
stack: StackState::new([ZERO; MIN_STACK_DEPTH], MIN_STACK_DEPTH, ZERO),
}
}
fn valid_trace_generation_context() -> TraceReplay {
TraceReplay {
mast_forest_store: vec![one_node_sparse_forest()],
core_trace_contexts: vec![CoreTraceFragmentContext {
state: core_trace_state(),
replay: ExecutionReplay::default(),
continuation: ContinuationStack::default(),
initial_mast_forest_id: MastForestId::from(0u32),
}],
range_checker_replay: RangeCheckerReplay::default(),
memory_writes: MemoryWritesReplay::default(),
bitwise_replay: BitwiseReplay::default(),
hasher_for_chiplet: HasherRequestReplay::default(),
kernel_replay: KernelReplay::default(),
ace_replay: AceReplay::default(),
fragment_size: 1,
max_stack_depth: MIN_STACK_DEPTH,
}
}
fn assert_context_read_rejects_bad_forest_id(context: TraceReplay, expected_label: &str) {
let err = TraceReplay::read_from_bytes(&context.to_bytes()).unwrap_err();
let DeserializationError::InvalidValue(message) = err else {
panic!("expected invalid forest id error");
};
assert!(message.contains(expected_label), "{message}");
assert!(message.contains("out of range for mast_forest_store length 1"), "{message}");
}
#[test]
fn trace_generation_context_read_rejects_zero_fragment_size() {
let context = empty_trace_generation_context(0, MIN_STACK_DEPTH);
let err = TraceReplay::read_from_bytes(&context.to_bytes()).unwrap_err();
let DeserializationError::InvalidValue(message) = err else {
panic!("expected invalid fragment size error");
};
assert!(message.contains("fragment_size must be non-zero"));
}
#[test]
fn trace_generation_context_read_rejects_max_stack_depth_below_minimum() {
let context = empty_trace_generation_context(1, MIN_STACK_DEPTH - 1);
let err = TraceReplay::read_from_bytes(&context.to_bytes()).unwrap_err();
let DeserializationError::InvalidValue(message) = err else {
panic!("expected invalid max stack depth error");
};
assert!(message.contains("max_stack_depth"));
}
#[test]
fn trace_generation_context_read_rejects_bad_initial_forest_id() {
let mut context = valid_trace_generation_context();
context.core_trace_contexts[0].initial_mast_forest_id = MastForestId::from(1u32);
assert_context_read_rejects_bad_forest_id(
context,
"core trace fragment initial_mast_forest_id",
);
}
#[test]
fn trace_generation_context_read_rejects_bad_continuation_forest_id() {
let mut context = valid_trace_generation_context();
context.core_trace_contexts[0]
.continuation
.push_enter_forest(MastForestId::from(1u32));
assert_context_read_rejects_bad_forest_id(
context,
"core trace fragment continuation EnterForest",
);
}
#[test]
fn trace_generation_context_read_rejects_bad_resolution_replay_forest_id() {
let mut context = valid_trace_generation_context();
context.core_trace_contexts[0]
.replay
.mast_forest_resolution
.record_resolution(MastNodeId::from(0), MastForestId::from(1u32));
assert_context_read_rejects_bad_forest_id(
context,
"core trace fragment MastForestResolutionReplay",
);
}
#[test]
fn trace_generation_context_read_rejects_bad_hasher_replay_forest_id() {
let mut context = valid_trace_generation_context();
let basic_block = BasicBlockNodeBuilder::new(vec![Operation::Noop]).build().unwrap();
context.hasher_for_chiplet.record_hash_basic_block(
MastForestId::from(1u32),
MastNodeId::from(0),
&basic_block,
);
assert_context_read_rejects_bad_forest_id(context, "hasher HashBasicBlock replay");
}
}