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