Skip to main content

miden_debug_engine/debug/
breakpoint.rs

1use std::{ops::Deref, path::Path, str::FromStr};
2
3use glob::Pattern;
4
5use super::ResolvedLocation;
6use crate::TraceEvent;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct Breakpoint {
10    pub id: u8,
11    pub creation_cycle: usize,
12    pub ty: BreakpointType,
13}
14
15impl Default for Breakpoint {
16    fn default() -> Self {
17        Self {
18            id: 0,
19            creation_cycle: 0,
20            ty: BreakpointType::Step,
21        }
22    }
23}
24
25impl Breakpoint {
26    /// Create a new default `Breakpoint` of the given type
27    pub fn new(ty: BreakpointType) -> Self {
28        Self {
29            ty,
30            ..Default::default()
31        }
32    }
33
34    /// Return the number of cycles this breakpoint indicates we should skip, or `None` if the
35    /// number of cycles is context-specific, or the breakpoint is triggered by something other
36    /// than cycle count.
37    pub fn cycles_to_skip(&self, current_cycle: usize) -> Option<usize> {
38        let cycles_passed = current_cycle - self.creation_cycle;
39        match &self.ty {
40            BreakpointType::Step => Some(1),
41            BreakpointType::StepN(n) => Some(n.saturating_sub(cycles_passed)),
42            BreakpointType::StepTo(to) if to >= &current_cycle => Some(to.abs_diff(current_cycle)),
43            _ => None,
44        }
45    }
46}
47impl Deref for Breakpoint {
48    type Target = BreakpointType;
49
50    #[inline]
51    fn deref(&self) -> &Self::Target {
52        &self.ty
53    }
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum BreakpointType {
58    /// Break at next cycle
59    Step,
60    /// Skip N cycles
61    StepN(usize),
62    /// Break at a given cycle
63    StepTo(usize),
64    /// Break at the first cycle of the next instruction
65    Next,
66    /// Break at the next source line, or the next instruction if no source location is available.
67    NextLine,
68    /// Break when we exit the current call frame
69    Finish,
70    /// Break when any cycle corresponds to a source location whose file matches PATTERN
71    File(Pattern),
72    /// Break when any cycle corresponds to a source location whose file matches PATTERN and occurs
73    /// on LINE
74    Line { pattern: Pattern, line: u32 },
75    /// Break anytime the given operation occurs
76    Opcode(OperationMatcher),
77    /// Break when any cycle causes us to push a frame for PROCEDURE on the call stack
78    Called(Pattern),
79    /// Break when the given trace event occurs
80    Trace(TraceEvent),
81}
82impl BreakpointType {
83    /// Return true if this breakpoint indicates we should break for `current_op`
84    pub fn should_break_for(&self, current_op: &miden_core::operations::Operation) -> bool {
85        match self {
86            Self::Opcode(matcher) => matcher.should_break_for(current_op),
87            _ => false,
88        }
89    }
90
91    /// Return true if this breakpoint indicates we should break on entry to `procedure`
92    pub fn should_break_in(&self, procedure: &str) -> bool {
93        match self {
94            Self::Called(pattern) => pattern.matches(procedure),
95            _ => false,
96        }
97    }
98
99    /// Return true if this breakpoint indicates we should break at `loc`
100    pub fn should_break_at(&self, loc: &ResolvedLocation) -> bool {
101        match self {
102            Self::File(pattern) => {
103                pattern.matches_path(Path::new(loc.source_file.deref().content().uri().as_str()))
104            }
105            Self::Line { pattern, line } if line == &loc.line => {
106                pattern.matches_path(Path::new(loc.source_file.deref().content().uri().as_str()))
107            }
108            _ => false,
109        }
110    }
111
112    /// Returns true if this breakpoint is internal to the debugger (i.e. not creatable via :b)
113    pub fn is_internal(&self) -> bool {
114        matches!(
115            self,
116            BreakpointType::Next
117                | BreakpointType::NextLine
118                | BreakpointType::Step
119                | BreakpointType::Finish
120        )
121    }
122
123    /// Returns true if this breakpoint is removed upon being hit
124    pub fn is_one_shot(&self) -> bool {
125        matches!(
126            self,
127            BreakpointType::Next
128                | BreakpointType::NextLine
129                | BreakpointType::Finish
130                | BreakpointType::Step
131                | BreakpointType::StepN(_)
132                | BreakpointType::StepTo(_)
133        )
134    }
135}
136
137impl FromStr for BreakpointType {
138    type Err = String;
139
140    fn from_str(s: &str) -> Result<Self, Self::Err> {
141        let s = s.trim();
142
143        // b next
144        // b finish
145        // b after {n}
146        // b for {opcode}
147        // b at {cycle}
148        // b in {procedure}
149        // b {file}[:{line}]
150        if s == "next" {
151            return Ok(BreakpointType::Next);
152        }
153        if s == "finish" {
154            return Ok(BreakpointType::Finish);
155        }
156        if let Some(n) = s.strip_prefix("after ") {
157            let n = n.trim().parse::<usize>().map_err(|err| {
158                format!("invalid breakpoint expression: could not parse cycle count: {err}")
159            })?;
160            return Ok(BreakpointType::StepN(n));
161        }
162        if let Some(opcode) = s.strip_prefix("for ") {
163            return Ok(BreakpointType::Opcode(opcode.parse::<OperationMatcher>()?));
164        }
165        if let Some(cycle) = s.strip_prefix("at ") {
166            let cycle = cycle.trim().parse::<usize>().map_err(|err| {
167                format!("invalid breakpoint expression: could not parse cycle value: {err}")
168            })?;
169            return Ok(BreakpointType::StepTo(cycle));
170        }
171        if let Some(procedure) = s.strip_prefix("in ") {
172            let pattern = Pattern::new(procedure.trim())
173                .map_err(|err| format!("invalid breakpoint expression: bad pattern: {err}"))?;
174            return Ok(BreakpointType::Called(pattern));
175        }
176        match s.split_once(':') {
177            Some((file, line)) => {
178                let pattern = Pattern::new(file.trim())
179                    .map_err(|err| format!("invalid breakpoint expression: bad pattern: {err}"))?;
180                let line = line.trim().parse::<u32>().map_err(|err| {
181                    format!("invalid breakpoint expression: could not parse line: {err}")
182                })?;
183                Ok(BreakpointType::Line { pattern, line })
184            }
185            None => {
186                let pattern = Pattern::new(s.trim())
187                    .map_err(|err| format!("invalid breakpoint expression: bad pattern: {err}"))?;
188                Ok(BreakpointType::File(pattern))
189            }
190        }
191    }
192}
193
194#[derive(Debug, Clone, PartialEq, Eq)]
195#[non_exhaustive]
196pub enum OperationMatcher {
197    Asm(String),
198    Exact(miden_core::operations::Operation),
199    Assert,
200    Push,
201    Dup,
202    SwapW,
203    Movup,
204    Movdn,
205}
206
207impl OperationMatcher {
208    pub fn should_break_for(&self, op: &miden_core::operations::Operation) -> bool {
209        use miden_core::operations::Operation;
210        match self {
211            Self::Asm(_) => false,
212            Self::Exact(expected) => op == expected,
213            Self::Assert => matches!(op, Operation::Assert(_)),
214            Self::Push => matches!(op, Operation::Push(_)),
215            Self::Dup => matches!(
216                op,
217                Operation::Dup0
218                    | Operation::Dup1
219                    | Operation::Dup2
220                    | Operation::Dup3
221                    | Operation::Dup4
222                    | Operation::Dup5
223                    | Operation::Dup6
224                    | Operation::Dup7
225                    | Operation::Dup9
226                    | Operation::Dup11
227                    | Operation::Dup13
228                    | Operation::Dup15
229            ),
230            Self::SwapW => matches!(op, Operation::SwapW | Operation::SwapW2 | Operation::SwapW3),
231            Self::Movup => matches!(
232                op,
233                Operation::MovUp2
234                    | Operation::MovUp3
235                    | Operation::MovUp4
236                    | Operation::MovUp5
237                    | Operation::MovUp6
238                    | Operation::MovUp7
239                    | Operation::MovUp8
240            ),
241            Self::Movdn => matches!(
242                op,
243                Operation::MovDn2
244                    | Operation::MovDn3
245                    | Operation::MovDn4
246                    | Operation::MovDn5
247                    | Operation::MovDn6
248                    | Operation::MovDn7
249                    | Operation::MovDn8
250            ),
251        }
252    }
253}
254
255impl core::fmt::Display for OperationMatcher {
256    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
257        match self {
258            Self::Asm(op) => f.write_str(op),
259            Self::Exact(op) => core::fmt::Display::fmt(op, f),
260            Self::Assert => f.write_str("assert.*"),
261            Self::Push => f.write_str("push.*"),
262            Self::Dup => f.write_str("dup"),
263            Self::SwapW => f.write_str("swapw"),
264            Self::Movup => f.write_str("movup"),
265            Self::Movdn => f.write_str("movdn"),
266        }
267    }
268}
269
270impl FromStr for OperationMatcher {
271    type Err = String;
272
273    fn from_str(name: &str) -> Result<Self, Self::Err> {
274        use miden_core::operations::Operation::*;
275        let opcode_parts = name
276            .split_once('.')
277            .map(|(name, rest)| (name, Some(rest)))
278            .unwrap_or((name, None));
279        let opcode = match opcode_parts {
280            ("nop" | "noop", _) => Noop,
281            ("assert", Some("*")) => return Ok(OperationMatcher::Assert),
282            ("assert", Some(code)) => Assert(
283                code.parse::<u32>()
284                    .map(miden_core::Felt::from_u32)
285                    .map_err(|err| err.to_string())?,
286            ),
287            ("assert", None) => Assert(miden_core::Felt::from_u32(0)),
288            ("sdepth", None) => SDepth,
289            ("caller", None) => Caller,
290            ("clk", None) => Clk,
291            ("emit", None) => Emit,
292            ("add", None) => Add,
293            ("neg", None) => Neg,
294            ("mul", None) => Mul,
295            ("inv", None) => Inv,
296            ("incr", None) => Incr,
297            ("and", None) => And,
298            ("or", None) => Or,
299            ("not", None) => Not,
300            ("eq", None) => Eq,
301            ("eqz", None) => Eqz,
302            ("expacc", None) => Expacc,
303            ("ext2mul", None) => Ext2Mul,
304            ("u32split", None) => U32split,
305            ("u32add", None) => U32add,
306            ("u32assert2", Some(code)) => U32assert2(
307                code.parse::<u32>()
308                    .map(miden_core::Felt::from_u32)
309                    .map_err(|err| err.to_string())?,
310            ),
311            ("u32assert2", None) => U32assert2(miden_core::Felt::from_u32(0)),
312            ("u32add3", None) => U32add3,
313            ("u32sub", None) => U32sub,
314            ("u32mul", None) => U32mul,
315            ("u32madd", None) => U32madd,
316            ("u32div", None) => U32div,
317            ("u32and", None) => U32and,
318            ("u32xor", None) => U32xor,
319            ("pad", None) => Pad,
320            ("drop", None) => Drop,
321            ("dup", _) => return Ok(OperationMatcher::Dup),
322            ("swap", _) => Swap,
323            ("swapw", _) => return Ok(OperationMatcher::SwapW),
324            ("swapdw", _) => SwapDW,
325            ("movup", _) => return Ok(OperationMatcher::Movup),
326            ("movdn", _) => return Ok(OperationMatcher::Movdn),
327            ("cswap", _) => CSwap,
328            ("cswapw", _) => CSwapW,
329            ("push", _) => return Ok(OperationMatcher::Push),
330            ("advpop", _) => AdvPop,
331            ("advpopw", _) => AdvPopW,
332            ("mloadw", _) => MLoadW,
333            ("mstorew", _) => MStoreW,
334            ("mload", _) => MLoad,
335            ("mstore", _) => MStore,
336            ("mstream", _) => MStream,
337            ("pipe", _) => Pipe,
338            ("crypto_stream", _) => CryptoStream,
339            ("hperm", _) => HPerm,
340            ("mpverify", Some(code)) => MpVerify(
341                code.parse::<u32>()
342                    .map(miden_core::Felt::from_u32)
343                    .map_err(|err| err.to_string())?,
344            ),
345            ("mpverify", None) => MpVerify(miden_core::Felt::from_u32(0)),
346            ("mrupdate", None) => MrUpdate,
347            ("frie2f4", None) => FriE2F4,
348            ("horner_base", None) => HornerBase,
349            ("horner_ext", None) => HornerExt,
350            ("eval_circuit", None) => EvalCircuit,
351            ("log_precompile", None) => LogPrecompile,
352            _ => return Ok(OperationMatcher::Asm(name.to_string())),
353        };
354
355        Ok(OperationMatcher::Exact(opcode))
356    }
357}