Skip to main content

miden_debug_engine/exec/
advice.rs

1//! Support utilities for working with [AdviceMutation]s, in particular cloning, recording, and
2//! (de)serializing the mutations produced by event handlers so they can be replayed later.
3
4use alloc::{sync::Arc, vec::Vec};
5
6use miden_core::{
7    Felt, Word,
8    crypto::merkle::InnerNodeInfo,
9    serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
10};
11use miden_processor::advice::{AdviceMap, AdviceMutation, AdviceStack};
12use miden_utils_sync::RwLock;
13
14/// Clone a single [AdviceMutation].
15///
16/// [AdviceMutation] does not implement [Clone] upstream, so recording and replaying event
17/// mutations requires reconstructing each variant by hand.
18pub fn clone_advice_mutation(mutation: &AdviceMutation) -> AdviceMutation {
19    match mutation {
20        AdviceMutation::ExtendStack { stack } => AdviceMutation::ExtendStack {
21            stack: stack.clone(),
22        },
23        AdviceMutation::ExtendMap { map: other } => {
24            AdviceMutation::ExtendMap { map: other.clone() }
25        }
26        AdviceMutation::ExtendMerkleStore { inner_nodes } => AdviceMutation::ExtendMerkleStore {
27            inner_nodes: inner_nodes.clone(),
28        },
29    }
30}
31
32/// Clone a batch of [AdviceMutation]s. See [clone_advice_mutation].
33pub fn clone_advice_mutations(mutations: &[AdviceMutation]) -> Vec<AdviceMutation> {
34    mutations.iter().map(clone_advice_mutation).collect()
35}
36
37// SERIALIZATION
38// ================================================================================================
39
40// Variant tags for the manual [AdviceMutation] encoding. [AdviceMutation] does not implement the
41// serialization traits upstream, so — as with cloning — each variant is (de)serialized by hand.
42const TAG_EXTEND_STACK: u8 = 0;
43const TAG_EXTEND_MAP: u8 = 1;
44const TAG_EXTEND_MERKLE_STORE: u8 = 2;
45
46/// Serialize a single [AdviceMutation] into `target`.
47pub fn write_advice_mutation<W: ByteWriter>(mutation: &AdviceMutation, target: &mut W) {
48    match mutation {
49        AdviceMutation::ExtendStack { stack } => {
50            target.write_u8(TAG_EXTEND_STACK);
51            stack.iter().copied().collect::<Vec<_>>().write_into(target);
52        }
53        AdviceMutation::ExtendMap { map } => {
54            target.write_u8(TAG_EXTEND_MAP);
55            map.write_into(target);
56        }
57        AdviceMutation::ExtendMerkleStore { inner_nodes } => {
58            target.write_u8(TAG_EXTEND_MERKLE_STORE);
59            target.write_usize(inner_nodes.len());
60            for info in inner_nodes {
61                info.value.write_into(target);
62                info.left.write_into(target);
63                info.right.write_into(target);
64            }
65        }
66    }
67}
68
69/// Deserialize a single [AdviceMutation] from `source`.
70pub fn read_advice_mutation<R: ByteReader>(
71    source: &mut R,
72) -> Result<AdviceMutation, DeserializationError> {
73    match source.read_u8()? {
74        TAG_EXTEND_STACK => Ok(AdviceMutation::ExtendStack {
75            stack: AdviceStack::from(Vec::<Felt>::read_from(source)?),
76        }),
77        TAG_EXTEND_MAP => Ok(AdviceMutation::ExtendMap {
78            map: AdviceMap::read_from(source)?,
79        }),
80        TAG_EXTEND_MERKLE_STORE => {
81            let len = source.read_usize()?;
82            let mut inner_nodes = Vec::with_capacity(len);
83            for _ in 0..len {
84                let value = Word::read_from(source)?;
85                let left = Word::read_from(source)?;
86                let right = Word::read_from(source)?;
87                inner_nodes.push(InnerNodeInfo { value, left, right });
88            }
89            Ok(AdviceMutation::ExtendMerkleStore { inner_nodes })
90        }
91        other => Err(DeserializationError::InvalidValue(format!(
92            "unknown AdviceMutation variant tag: {other}"
93        ))),
94    }
95}
96
97/// Serialize a recorded event log (one entry per `on_event` invocation) into `target`.
98///
99/// This raw encoding is not independently versioned. Use [`super::ReplaySnapshot`] for persistent
100/// storage with an explicit compatibility boundary.
101pub fn write_event_log<W: ByteWriter>(log: &[Vec<AdviceMutation>], target: &mut W) {
102    target.write_usize(log.len());
103    for batch in log {
104        target.write_usize(batch.len());
105        for mutation in batch {
106            write_advice_mutation(mutation, target);
107        }
108    }
109}
110
111/// Deserialize a recorded event log from `source`.
112///
113/// Legacy logs containing the removed precompile-request mutation tag are rejected. Use
114/// [`super::ReplaySnapshot`] for versioned persistent storage.
115pub fn read_event_log<R: ByteReader>(
116    source: &mut R,
117) -> Result<Vec<Vec<AdviceMutation>>, DeserializationError> {
118    let batches = source.read_usize()?;
119    let mut log = Vec::with_capacity(batches);
120    for _ in 0..batches {
121        let len = source.read_usize()?;
122        let mut batch = Vec::with_capacity(len);
123        for _ in 0..len {
124            batch.push(read_advice_mutation(source)?);
125        }
126        log.push(batch);
127    }
128    Ok(log)
129}
130
131/// A shared, cloneable log of the advice mutations produced by event handlers.
132///
133/// One entry is recorded per `on_event` invocation, in execution order, **including empty
134/// mutation sets**: event replay pops exactly one entry per event, so the log must stay aligned
135/// with the event stream of the recorded execution.
136///
137/// This handle exists for executors that are consumed by execution and whose return type cannot
138/// carry the log (see `DapExecutor::record_event_mutations`): obtain it before running, read it
139/// once execution completes, and feed the recorded log into `DebuggerHost::set_event_replay`
140/// (or `Executor::into_debug_with_replay`) to debug the same execution later without access to
141/// the original host's event handlers. Hosts owned by the caller record internally instead; see
142/// `DebuggerHost::with_event_advice_mutations_recording`.
143#[derive(Clone, Default)]
144pub struct EventMutationRecorder {
145    log: Arc<RwLock<Vec<Vec<AdviceMutation>>>>,
146}
147
148impl core::fmt::Debug for EventMutationRecorder {
149    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
150        f.debug_struct("EventMutationRecorder").field("events", &self.len()).finish()
151    }
152}
153
154impl EventMutationRecorder {
155    /// Create a new, empty recorder.
156    pub fn new() -> Self {
157        Self::default()
158    }
159
160    /// Returns the recorded mutation batches, leaving the recorder empty.
161    pub fn take(&self) -> Vec<Vec<AdviceMutation>> {
162        core::mem::take(&mut *self.log.write())
163    }
164
165    /// Returns a copy of the recorded mutation batches, leaving the recorder intact.
166    pub fn snapshot(&self) -> Vec<Vec<AdviceMutation>> {
167        self.log.read().iter().map(|batch| clone_advice_mutations(batch)).collect()
168    }
169
170    /// The number of `on_event` invocations recorded so far.
171    pub fn len(&self) -> usize {
172        self.log.read().len()
173    }
174
175    /// Returns true if no `on_event` invocations have been recorded.
176    pub fn is_empty(&self) -> bool {
177        self.len() == 0
178    }
179
180    /// Record the mutations produced by one `on_event` invocation.
181    #[cfg(feature = "dap")]
182    pub(crate) fn record(&self, mutations: Vec<AdviceMutation>) {
183        self.log.write().push(mutations);
184    }
185
186    /// Discard everything recorded so far, e.g. when execution restarts from the beginning.
187    #[cfg(feature = "dap")]
188    pub(crate) fn clear(&self) {
189        self.log.write().clear();
190    }
191}