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 procedure = miden_debug_types::Uri::from(format!("file://{procedure}"));
221 pattern.is_match(&procedure)
222}
223
224fn file_pattern(spec: &str) -> Result<GlobMatcher, String> {
230 let spec = spec.trim();
231 let anchored;
232 let spec = if spec.starts_with('/') || spec.starts_with('*') {
233 spec
234 } else {
235 anchored = format!("**/{spec}");
236 &anchored
237 };
238 crate::glob::GlobBuilder::new(spec)
239 .build()
240 .map(|glob| glob.compile_matcher())
241 .map_err(|err| format!("invalid breakpoint pattern: {err}"))
242}
243
244#[derive(Debug, Clone, PartialEq, Eq)]
245#[non_exhaustive]
246pub enum OperationMatcher {
247 Asm(String),
248 Exact(miden_core::operations::Operation),
249 Assert,
250 Push,
251 Dup,
252 SwapW,
253 Movup,
254 Movdn,
255}
256
257impl OperationMatcher {
258 pub fn should_break_for(&self, op: &miden_core::operations::Operation) -> bool {
259 use miden_core::operations::Operation;
260 match self {
261 Self::Asm(_) => false,
262 Self::Exact(expected) => op == expected,
263 Self::Assert => matches!(op, Operation::Assert(_)),
264 Self::Push => matches!(op, Operation::Push(_)),
265 Self::Dup => matches!(
266 op,
267 Operation::Dup0
268 | Operation::Dup1
269 | Operation::Dup2
270 | Operation::Dup3
271 | Operation::Dup4
272 | Operation::Dup5
273 | Operation::Dup6
274 | Operation::Dup7
275 | Operation::Dup9
276 | Operation::Dup11
277 | Operation::Dup13
278 | Operation::Dup15
279 ),
280 Self::SwapW => matches!(op, Operation::SwapW | Operation::SwapW2 | Operation::SwapW3),
281 Self::Movup => matches!(
282 op,
283 Operation::MovUp2
284 | Operation::MovUp3
285 | Operation::MovUp4
286 | Operation::MovUp5
287 | Operation::MovUp6
288 | Operation::MovUp7
289 | Operation::MovUp8
290 ),
291 Self::Movdn => matches!(
292 op,
293 Operation::MovDn2
294 | Operation::MovDn3
295 | Operation::MovDn4
296 | Operation::MovDn5
297 | Operation::MovDn6
298 | Operation::MovDn7
299 | Operation::MovDn8
300 ),
301 }
302 }
303}
304
305impl core::fmt::Display for OperationMatcher {
306 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
307 match self {
308 Self::Asm(op) => f.write_str(op),
309 Self::Exact(op) => core::fmt::Display::fmt(op, f),
310 Self::Assert => f.write_str("assert.*"),
311 Self::Push => f.write_str("push.*"),
312 Self::Dup => f.write_str("dup"),
313 Self::SwapW => f.write_str("swapw"),
314 Self::Movup => f.write_str("movup"),
315 Self::Movdn => f.write_str("movdn"),
316 }
317 }
318}
319
320impl FromStr for OperationMatcher {
321 type Err = String;
322
323 fn from_str(name: &str) -> Result<Self, Self::Err> {
324 use miden_core::operations::Operation::*;
325 let opcode_parts = name
326 .split_once('.')
327 .map(|(name, rest)| (name, Some(rest)))
328 .unwrap_or((name, None));
329 let opcode = match opcode_parts {
330 ("nop" | "noop", _) => Noop,
331 ("assert", Some("*")) => return Ok(OperationMatcher::Assert),
332 ("assert", Some(code)) => Assert(
333 code.parse::<u32>()
334 .map(miden_core::Felt::from_u32)
335 .map_err(|err| err.to_string())?,
336 ),
337 ("assert", None) => Assert(miden_core::Felt::from_u32(0)),
338 ("sdepth", None) => SDepth,
339 ("caller", None) => Caller,
340 ("clk", None) => Clk,
341 ("emit", None) => Emit,
342 ("add", None) => Add,
343 ("neg", None) => Neg,
344 ("mul", None) => Mul,
345 ("inv", None) => Inv,
346 ("incr", None) => Incr,
347 ("and", None) => And,
348 ("or", None) => Or,
349 ("not", None) => Not,
350 ("eq", None) => Eq,
351 ("eqz", None) => Eqz,
352 ("expacc", None) => Expacc,
353 ("ext2mul", None) => Ext2Mul,
354 ("u32split", None) => U32split,
355 ("u32add", None) => U32add,
356 ("u32assert2", Some(code)) => U32assert2(
357 code.parse::<u32>()
358 .map(miden_core::Felt::from_u32)
359 .map_err(|err| err.to_string())?,
360 ),
361 ("u32assert2", None) => U32assert2(miden_core::Felt::from_u32(0)),
362 ("u32add3", None) => U32add3,
363 ("u32sub", None) => U32sub,
364 ("u32mul", None) => U32mul,
365 ("u32madd", None) => U32madd,
366 ("u32div", None) => U32div,
367 ("u32and", None) => U32and,
368 ("u32xor", None) => U32xor,
369 ("pad", None) => Pad,
370 ("drop", None) => Drop,
371 ("dup", _) => return Ok(OperationMatcher::Dup),
372 ("swap", _) => Swap,
373 ("swapw", _) => return Ok(OperationMatcher::SwapW),
374 ("swapdw", _) => SwapDW,
375 ("movup", _) => return Ok(OperationMatcher::Movup),
376 ("movdn", _) => return Ok(OperationMatcher::Movdn),
377 ("cswap", _) => CSwap,
378 ("cswapw", _) => CSwapW,
379 ("push", _) => return Ok(OperationMatcher::Push),
380 ("advpop", _) => AdvPop,
381 ("advpopw", _) => AdvPopW,
382 ("mloadw", _) => MLoadW,
383 ("mstorew", _) => MStoreW,
384 ("mload", _) => MLoad,
385 ("mstore", _) => MStore,
386 ("mstream", _) => MStream,
387 ("pipe", _) => Pipe,
388 ("crypto_stream", _) => CryptoStream,
389 ("hperm", _) => HPerm,
390 ("mpverify", Some(code)) => MpVerify(
391 code.parse::<u32>()
392 .map(miden_core::Felt::from_u32)
393 .map_err(|err| err.to_string())?,
394 ),
395 ("mpverify", None) => MpVerify(miden_core::Felt::from_u32(0)),
396 ("mrupdate", None) => MrUpdate,
397 ("frie2f4", None) => FriE2F4,
398 ("horner_base", None) => HornerBase,
399 ("horner_ext", None) => HornerExt,
400 ("eval_circuit", None) => EvalCircuit,
401 ("log_deferred" | "log_precompile", None) => LogDeferred,
402 _ => return Ok(OperationMatcher::Asm(name.to_string())),
403 };
404
405 Ok(OperationMatcher::Exact(opcode))
406 }
407}
408
409#[cfg(test)]
410mod tests;