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