1use alloc::string::{String, ToString};
2use core::{ops::Deref, str::FromStr};
3
4use miden_processor::ProcessorState;
5
6use super::ResolvedLocation;
7use crate::{Event, glob::GlobMatcher};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct Breakpoint {
11 pub id: u8,
12 pub creation_cycle: usize,
13 pub ty: BreakpointType,
14}
15
16impl Default for Breakpoint {
17 fn default() -> Self {
18 Self {
19 id: 0,
20 creation_cycle: 0,
21 ty: BreakpointType::Step,
22 }
23 }
24}
25
26impl Breakpoint {
27 pub fn new(ty: BreakpointType) -> Self {
29 Self {
30 ty,
31 ..Default::default()
32 }
33 }
34
35 pub fn cycles_to_skip(&self, current_cycle: usize) -> Option<usize> {
39 let cycles_passed = current_cycle - self.creation_cycle;
40 match &self.ty {
41 BreakpointType::Step => Some(1usize.saturating_sub(cycles_passed)),
42 BreakpointType::StepN(n) => Some(n.saturating_sub(cycles_passed)),
43 BreakpointType::StepTo(to) if to >= ¤t_cycle => Some(to - current_cycle),
44 _ => None,
45 }
46 }
47}
48impl Deref for Breakpoint {
49 type Target = BreakpointType;
50
51 #[inline]
52 fn deref(&self) -> &Self::Target {
53 &self.ty
54 }
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub enum BreakpointType {
59 Step,
61 StepN(usize),
63 StepTo(usize),
65 Next,
67 NextLine,
69 Finish,
71 File(GlobMatcher),
73 Line { pattern: GlobMatcher, line: u32 },
76 Opcode(OperationMatcher),
78 Called(GlobMatcher),
80 Event(Event),
82}
83
84impl BreakpointType {
85 pub fn should_break_for(
87 &self,
88 current_op: &miden_core::operations::Operation,
89 state: &ProcessorState<'_>,
90 ) -> bool {
91 use miden_core::operations::Operation;
92
93 match self {
94 Self::Opcode(matcher) => matcher.should_break_for(current_op),
95 Self::Event(event) if matches!(current_op, Operation::Emit) => {
96 state.get_stack_item(0) == event.as_event_id().as_felt()
97 }
98 _ => false,
99 }
100 }
101
102 pub fn should_break_in(&self, procedure: &str) -> bool {
104 let procedure = miden_debug_types::Uri::from(format!("file://{procedure}"));
105 match self {
106 Self::Called(pattern) => pattern.is_match(&procedure),
107 _ => false,
108 }
109 }
110
111 pub fn should_break_at(&self, loc: &ResolvedLocation) -> bool {
113 let uri = loc.source_file.uri();
114 match self {
115 Self::File(pattern) => pattern.is_match(uri),
116 Self::Line { pattern, line } if line == &loc.line => pattern.is_match(uri),
117 _ => false,
118 }
119 }
120
121 pub fn is_internal(&self) -> bool {
123 matches!(
124 self,
125 BreakpointType::Next
126 | BreakpointType::NextLine
127 | BreakpointType::Step
128 | BreakpointType::Finish
129 )
130 }
131
132 pub fn is_one_shot(&self) -> bool {
134 matches!(
135 self,
136 BreakpointType::Next
137 | BreakpointType::NextLine
138 | BreakpointType::Finish
139 | BreakpointType::Step
140 | BreakpointType::StepN(_)
141 | BreakpointType::StepTo(_)
142 )
143 }
144}
145
146impl FromStr for BreakpointType {
147 type Err = String;
148
149 fn from_str(s: &str) -> Result<Self, Self::Err> {
150 let s = s.trim();
151
152 if s == "next" {
160 return Ok(BreakpointType::Next);
161 }
162 if s == "finish" {
163 return Ok(BreakpointType::Finish);
164 }
165 if let Some(n) = s.strip_prefix("after ") {
166 let n = n.trim().parse::<usize>().map_err(|err| {
167 format!("invalid breakpoint expression: could not parse cycle count: {err}")
168 })?;
169 return Ok(BreakpointType::StepN(n));
170 }
171 if let Some(opcode) = s.strip_prefix("for ") {
172 return Ok(BreakpointType::Opcode(opcode.parse::<OperationMatcher>()?));
173 }
174 if let Some(cycle) = s.strip_prefix("at ") {
175 let cycle = cycle.trim().parse::<usize>().map_err(|err| {
176 format!("invalid breakpoint expression: could not parse cycle value: {err}")
177 })?;
178 return Ok(BreakpointType::StepTo(cycle));
179 }
180 if let Some(procedure) = s.strip_prefix("in ") {
181 return Ok(BreakpointType::Called(procedure_pattern(procedure)?));
182 }
183 match s.split_once(':') {
184 Some((file, line)) => {
185 let pattern = file_pattern(file)?;
186 let line = line.trim().parse::<u32>().map_err(|err| {
187 format!("invalid breakpoint expression: could not parse line: {err}")
188 })?;
189 Ok(BreakpointType::Line { pattern, line })
190 }
191 None => Ok(BreakpointType::File(file_pattern(s)?)),
192 }
193 }
194}
195
196fn procedure_pattern(spec: &str) -> Result<GlobMatcher, String> {
205 let spec = spec.trim();
206 let anchored;
207 let spec = if spec.starts_with("::") || spec.starts_with('*') {
208 spec
209 } else {
210 anchored = format!("*::{spec}");
211 &anchored
212 };
213 crate::glob::GlobBuilder::new(spec)
214 .build()
215 .map(|glob| glob.compile_matcher())
216 .map_err(|err| format!("invalid breakpoint pattern: {err}"))
217}
218
219fn file_pattern(spec: &str) -> Result<GlobMatcher, String> {
225 let spec = spec.trim();
226 let anchored;
227 let spec = if spec.starts_with('/') || spec.starts_with('*') {
228 spec
229 } else {
230 anchored = format!("**/{spec}");
231 &anchored
232 };
233 crate::glob::GlobBuilder::new(spec)
234 .build()
235 .map(|glob| glob.compile_matcher())
236 .map_err(|err| format!("invalid breakpoint pattern: {err}"))
237}
238
239#[derive(Debug, Clone, PartialEq, Eq)]
240#[non_exhaustive]
241pub enum OperationMatcher {
242 Asm(String),
243 Exact(miden_core::operations::Operation),
244 Assert,
245 Push,
246 Dup,
247 SwapW,
248 Movup,
249 Movdn,
250}
251
252impl OperationMatcher {
253 pub fn should_break_for(&self, op: &miden_core::operations::Operation) -> bool {
254 use miden_core::operations::Operation;
255 match self {
256 Self::Asm(_) => false,
257 Self::Exact(expected) => op == expected,
258 Self::Assert => matches!(op, Operation::Assert(_)),
259 Self::Push => matches!(op, Operation::Push(_)),
260 Self::Dup => matches!(
261 op,
262 Operation::Dup0
263 | Operation::Dup1
264 | Operation::Dup2
265 | Operation::Dup3
266 | Operation::Dup4
267 | Operation::Dup5
268 | Operation::Dup6
269 | Operation::Dup7
270 | Operation::Dup9
271 | Operation::Dup11
272 | Operation::Dup13
273 | Operation::Dup15
274 ),
275 Self::SwapW => matches!(op, Operation::SwapW | Operation::SwapW2 | Operation::SwapW3),
276 Self::Movup => matches!(
277 op,
278 Operation::MovUp2
279 | Operation::MovUp3
280 | Operation::MovUp4
281 | Operation::MovUp5
282 | Operation::MovUp6
283 | Operation::MovUp7
284 | Operation::MovUp8
285 ),
286 Self::Movdn => matches!(
287 op,
288 Operation::MovDn2
289 | Operation::MovDn3
290 | Operation::MovDn4
291 | Operation::MovDn5
292 | Operation::MovDn6
293 | Operation::MovDn7
294 | Operation::MovDn8
295 ),
296 }
297 }
298}
299
300impl core::fmt::Display for OperationMatcher {
301 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
302 match self {
303 Self::Asm(op) => f.write_str(op),
304 Self::Exact(op) => core::fmt::Display::fmt(op, f),
305 Self::Assert => f.write_str("assert.*"),
306 Self::Push => f.write_str("push.*"),
307 Self::Dup => f.write_str("dup"),
308 Self::SwapW => f.write_str("swapw"),
309 Self::Movup => f.write_str("movup"),
310 Self::Movdn => f.write_str("movdn"),
311 }
312 }
313}
314
315impl FromStr for OperationMatcher {
316 type Err = String;
317
318 fn from_str(name: &str) -> Result<Self, Self::Err> {
319 use miden_core::operations::Operation::*;
320 let opcode_parts = name
321 .split_once('.')
322 .map(|(name, rest)| (name, Some(rest)))
323 .unwrap_or((name, None));
324 let opcode = match opcode_parts {
325 ("nop" | "noop", _) => Noop,
326 ("assert", Some("*")) => return Ok(OperationMatcher::Assert),
327 ("assert", Some(code)) => Assert(
328 code.parse::<u32>()
329 .map(miden_core::Felt::from_u32)
330 .map_err(|err| err.to_string())?,
331 ),
332 ("assert", None) => Assert(miden_core::Felt::from_u32(0)),
333 ("sdepth", None) => SDepth,
334 ("caller", None) => Caller,
335 ("clk", None) => Clk,
336 ("emit", None) => Emit,
337 ("add", None) => Add,
338 ("neg", None) => Neg,
339 ("mul", None) => Mul,
340 ("inv", None) => Inv,
341 ("incr", None) => Incr,
342 ("and", None) => And,
343 ("or", None) => Or,
344 ("not", None) => Not,
345 ("eq", None) => Eq,
346 ("eqz", None) => Eqz,
347 ("expacc", None) => Expacc,
348 ("ext2mul", None) => Ext2Mul,
349 ("u32split", None) => U32split,
350 ("u32add", None) => U32add,
351 ("u32assert2", Some(code)) => U32assert2(
352 code.parse::<u32>()
353 .map(miden_core::Felt::from_u32)
354 .map_err(|err| err.to_string())?,
355 ),
356 ("u32assert2", None) => U32assert2(miden_core::Felt::from_u32(0)),
357 ("u32add3", None) => U32add3,
358 ("u32sub", None) => U32sub,
359 ("u32mul", None) => U32mul,
360 ("u32madd", None) => U32madd,
361 ("u32div", None) => U32div,
362 ("u32and", None) => U32and,
363 ("u32xor", None) => U32xor,
364 ("pad", None) => Pad,
365 ("drop", None) => Drop,
366 ("dup", _) => return Ok(OperationMatcher::Dup),
367 ("swap", _) => Swap,
368 ("swapw", _) => return Ok(OperationMatcher::SwapW),
369 ("swapdw", _) => SwapDW,
370 ("movup", _) => return Ok(OperationMatcher::Movup),
371 ("movdn", _) => return Ok(OperationMatcher::Movdn),
372 ("cswap", _) => CSwap,
373 ("cswapw", _) => CSwapW,
374 ("push", _) => return Ok(OperationMatcher::Push),
375 ("advpop", _) => AdvPop,
376 ("advpopw", _) => AdvPopW,
377 ("mloadw", _) => MLoadW,
378 ("mstorew", _) => MStoreW,
379 ("mload", _) => MLoad,
380 ("mstore", _) => MStore,
381 ("mstream", _) => MStream,
382 ("pipe", _) => Pipe,
383 ("crypto_stream", _) => CryptoStream,
384 ("hperm", _) => HPerm,
385 ("mpverify", Some(code)) => MpVerify(
386 code.parse::<u32>()
387 .map(miden_core::Felt::from_u32)
388 .map_err(|err| err.to_string())?,
389 ),
390 ("mpverify", None) => MpVerify(miden_core::Felt::from_u32(0)),
391 ("mrupdate", None) => MrUpdate,
392 ("frie2f4", None) => FriE2F4,
393 ("horner_base", None) => HornerBase,
394 ("horner_ext", None) => HornerExt,
395 ("eval_circuit", None) => EvalCircuit,
396 ("log_deferred" | "log_precompile", None) => LogDeferred,
397 _ => return Ok(OperationMatcher::Asm(name.to_string())),
398 };
399
400 Ok(OperationMatcher::Exact(opcode))
401 }
402}
403
404#[cfg(test)]
405mod tests {
406 use alloc::string::ToString;
407
408 use super::OperationMatcher;
409
410 #[test]
411 fn log_deferred_breakpoint_accepts_legacy_name_and_displays_canonically() {
412 let canonical = "log_deferred".parse::<OperationMatcher>().unwrap();
413 let legacy = "log_precompile".parse::<OperationMatcher>().unwrap();
414
415 assert_eq!(canonical, legacy);
416 assert_eq!(canonical.to_string(), "log_deferred");
417 }
418}