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