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 match self {
105 Self::Called(pattern) => procedure_matches(pattern, procedure),
106 _ => false,
107 }
108 }
109
110 pub fn should_break_at(&self, loc: &ResolvedLocation) -> bool {
112 let uri = loc.source_file.uri();
113 match self {
114 Self::File(pattern) => pattern.is_match(uri),
115 Self::Line { pattern, line } if line == &loc.line => pattern.is_match(uri),
116 _ => false,
117 }
118 }
119
120 pub fn is_internal(&self) -> bool {
122 matches!(
123 self,
124 BreakpointType::Next
125 | BreakpointType::NextLine
126 | BreakpointType::Step
127 | BreakpointType::Finish
128 )
129 }
130
131 pub fn is_one_shot(&self) -> bool {
133 matches!(
134 self,
135 BreakpointType::Next
136 | BreakpointType::NextLine
137 | BreakpointType::Finish
138 | BreakpointType::Step
139 | BreakpointType::StepN(_)
140 | BreakpointType::StepTo(_)
141 )
142 }
143}
144
145impl FromStr for BreakpointType {
146 type Err = String;
147
148 fn from_str(s: &str) -> Result<Self, Self::Err> {
149 let s = s.trim();
150
151 if s == "next" {
159 return Ok(BreakpointType::Next);
160 }
161 if s == "finish" {
162 return Ok(BreakpointType::Finish);
163 }
164 if let Some(n) = s.strip_prefix("after ") {
165 let n = n.trim().parse::<usize>().map_err(|err| {
166 format!("invalid breakpoint expression: could not parse cycle count: {err}")
167 })?;
168 return Ok(BreakpointType::StepN(n));
169 }
170 if let Some(opcode) = s.strip_prefix("for ") {
171 return Ok(BreakpointType::Opcode(opcode.parse::<OperationMatcher>()?));
172 }
173 if let Some(cycle) = s.strip_prefix("at ") {
174 let cycle = cycle.trim().parse::<usize>().map_err(|err| {
175 format!("invalid breakpoint expression: could not parse cycle value: {err}")
176 })?;
177 return Ok(BreakpointType::StepTo(cycle));
178 }
179 if let Some(procedure) = s.strip_prefix("in ") {
180 return Ok(BreakpointType::Called(procedure_pattern(procedure)?));
181 }
182 match s.split_once(':') {
183 Some((file, line)) => {
184 let pattern = file_pattern(file)?;
185 let line = line.trim().parse::<u32>().map_err(|err| {
186 format!("invalid breakpoint expression: could not parse line: {err}")
187 })?;
188 Ok(BreakpointType::Line { pattern, line })
189 }
190 None => Ok(BreakpointType::File(file_pattern(s)?)),
191 }
192 }
193}
194
195pub(crate) fn procedure_pattern(spec: &str) -> Result<GlobMatcher, String> {
204 let spec = spec.trim();
205 let anchored;
206 let spec = if spec.starts_with("::") || spec.starts_with('*') {
207 spec
208 } else {
209 anchored = format!("*::{spec}");
210 &anchored
211 };
212 crate::glob::GlobBuilder::new(spec)
213 .build()
214 .map(|glob| glob.compile_matcher())
215 .map_err(|err| format!("invalid breakpoint pattern: {err}"))
216}
217
218pub(crate) fn procedure_matches(pattern: &GlobMatcher, procedure: &str) -> bool {
220 let raw = miden_debug_types::Uri::from(format!("file://{procedure}"));
221 if pattern.is_match(&raw) {
222 return true;
223 }
224 let demangled = super::stacktrace::demangle(procedure);
225 demangled != procedure
226 && pattern.is_match(&miden_debug_types::Uri::from(format!("file://{demangled}")))
227}
228
229fn file_pattern(spec: &str) -> Result<GlobMatcher, String> {
235 let spec = spec.trim();
236 let anchored;
237 let spec = if spec.starts_with('/') || spec.starts_with('*') {
238 spec
239 } else {
240 anchored = format!("**/{spec}");
241 &anchored
242 };
243 crate::glob::GlobBuilder::new(spec)
244 .build()
245 .map(|glob| glob.compile_matcher())
246 .map_err(|err| format!("invalid breakpoint pattern: {err}"))
247}
248
249#[derive(Debug, Clone, PartialEq, Eq)]
250#[non_exhaustive]
251pub enum OperationMatcher {
252 Asm(String),
253 Exact(miden_core::operations::Operation),
254 Assert,
255 Push,
256 Dup,
257 SwapW,
258 Movup,
259 Movdn,
260}
261
262impl OperationMatcher {
263 pub fn should_break_for(&self, op: &miden_core::operations::Operation) -> bool {
264 use miden_core::operations::Operation;
265 match self {
266 Self::Asm(_) => false,
267 Self::Exact(expected) => op == expected,
268 Self::Assert => matches!(op, Operation::Assert(_)),
269 Self::Push => matches!(op, Operation::Push(_)),
270 Self::Dup => matches!(
271 op,
272 Operation::Dup0
273 | Operation::Dup1
274 | Operation::Dup2
275 | Operation::Dup3
276 | Operation::Dup4
277 | Operation::Dup5
278 | Operation::Dup6
279 | Operation::Dup7
280 | Operation::Dup9
281 | Operation::Dup11
282 | Operation::Dup13
283 | Operation::Dup15
284 ),
285 Self::SwapW => matches!(op, Operation::SwapW | Operation::SwapW2 | Operation::SwapW3),
286 Self::Movup => matches!(
287 op,
288 Operation::MovUp2
289 | Operation::MovUp3
290 | Operation::MovUp4
291 | Operation::MovUp5
292 | Operation::MovUp6
293 | Operation::MovUp7
294 | Operation::MovUp8
295 ),
296 Self::Movdn => matches!(
297 op,
298 Operation::MovDn2
299 | Operation::MovDn3
300 | Operation::MovDn4
301 | Operation::MovDn5
302 | Operation::MovDn6
303 | Operation::MovDn7
304 | Operation::MovDn8
305 ),
306 }
307 }
308}
309
310impl core::fmt::Display for OperationMatcher {
311 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
312 match self {
313 Self::Asm(op) => f.write_str(op),
314 Self::Exact(op) => core::fmt::Display::fmt(op, f),
315 Self::Assert => f.write_str("assert.*"),
316 Self::Push => f.write_str("push.*"),
317 Self::Dup => f.write_str("dup"),
318 Self::SwapW => f.write_str("swapw"),
319 Self::Movup => f.write_str("movup"),
320 Self::Movdn => f.write_str("movdn"),
321 }
322 }
323}
324
325impl FromStr for OperationMatcher {
326 type Err = String;
327
328 fn from_str(name: &str) -> Result<Self, Self::Err> {
329 use miden_core::operations::Operation::*;
330 let opcode_parts = name
331 .split_once('.')
332 .map(|(name, rest)| (name, Some(rest)))
333 .unwrap_or((name, None));
334 let opcode = match opcode_parts {
335 ("nop" | "noop", _) => Noop,
336 ("assert", Some("*")) => return Ok(OperationMatcher::Assert),
337 ("assert", Some(code)) => Assert(
338 code.parse::<u32>()
339 .map(miden_core::Felt::from_u32)
340 .map_err(|err| err.to_string())?,
341 ),
342 ("assert", None) => Assert(miden_core::Felt::from_u32(0)),
343 ("sdepth", None) => SDepth,
344 ("caller", None) => Caller,
345 ("clk", None) => Clk,
346 ("emit", None) => Emit,
347 ("add", None) => Add,
348 ("neg", None) => Neg,
349 ("mul", None) => Mul,
350 ("inv", None) => Inv,
351 ("incr", None) => Incr,
352 ("and", None) => And,
353 ("or", None) => Or,
354 ("not", None) => Not,
355 ("eq", None) => Eq,
356 ("eqz", None) => Eqz,
357 ("expacc", None) => Expacc,
358 ("ext2mul", None) => Ext2Mul,
359 ("u32split", None) => U32split,
360 ("u32add", None) => U32add,
361 ("u32assert2", Some(code)) => U32assert2(
362 code.parse::<u32>()
363 .map(miden_core::Felt::from_u32)
364 .map_err(|err| err.to_string())?,
365 ),
366 ("u32assert2", None) => U32assert2(miden_core::Felt::from_u32(0)),
367 ("u32add3", None) => U32add3,
368 ("u32sub", None) => U32sub,
369 ("u32mul", None) => U32mul,
370 ("u32madd", None) => U32madd,
371 ("u32div", None) => U32div,
372 ("u32and", None) => U32and,
373 ("u32xor", None) => U32xor,
374 ("pad", None) => Pad,
375 ("drop", None) => Drop,
376 ("dup", _) => return Ok(OperationMatcher::Dup),
377 ("swap", _) => Swap,
378 ("swapw", _) => return Ok(OperationMatcher::SwapW),
379 ("swapdw", _) => SwapDW,
380 ("movup", _) => return Ok(OperationMatcher::Movup),
381 ("movdn", _) => return Ok(OperationMatcher::Movdn),
382 ("cswap", _) => CSwap,
383 ("cswapw", _) => CSwapW,
384 ("push", _) => return Ok(OperationMatcher::Push),
385 ("advpop", _) => AdvPop,
386 ("advpopw", _) => AdvPopW,
387 ("mloadw", _) => MLoadW,
388 ("mstorew", _) => MStoreW,
389 ("mload", _) => MLoad,
390 ("mstore", _) => MStore,
391 ("mstream", _) => MStream,
392 ("pipe", _) => Pipe,
393 ("crypto_stream", _) => CryptoStream,
394 ("hperm", _) => HPerm,
395 ("mpverify", Some(code)) => MpVerify(
396 code.parse::<u32>()
397 .map(miden_core::Felt::from_u32)
398 .map_err(|err| err.to_string())?,
399 ),
400 ("mpverify", None) => MpVerify(miden_core::Felt::from_u32(0)),
401 ("mrupdate", None) => MrUpdate,
402 ("frie2f4", None) => FriE2F4,
403 ("horner_base", None) => HornerBase,
404 ("horner_ext", None) => HornerExt,
405 ("eval_circuit", None) => EvalCircuit,
406 ("log_deferred" | "log_precompile", None) => LogDeferred,
407 _ => return Ok(OperationMatcher::Asm(name.to_string())),
408 };
409
410 Ok(OperationMatcher::Exact(opcode))
411 }
412}
413
414#[cfg(test)]
415mod tests;