Files
Henrik RydgårdandClaude Opus 5 d385c86a98 Fix breakpoints being swallowed when you step onto them
Set two breakpoints four bytes apart, both logging, run into the first, then
press Next: the second one never logs, however many times you step. Reproduced
on both the interpreter and the JIT.

The skip-first mechanism was doing two different jobs with one marker. Every
resume and every step recorded the address it started from, and any breakpoint
check at that address was suppressed outright. That's right for the breakpoint
you're parked on - you have to be able to get off it - but stepping *onto* an
address is not the same as having reported the breakpoint there, and the next
step suppressed it before it ever logged.

Split into the two things that were being conflated:

- resumedFrom_ is where the current run or step started. It only drops the
  pause, not the log or the hit count. It still covers the temporary breakpoint,
  which is what makes "run to here" work when you're already on that address.
- reported_ is the breakpoint we already logged and counted. Reporting stops the
  CPU before the instruction runs, so the resume that follows arrives at the
  same pending execution and must not report it twice.

Both are (address, tick count) pairs, which identify one pending execution of
one instruction: ticks only move when the CPU retires an instruction, so the
marker stops matching as soon as it runs, and a breakpoint in a loop still fires
every iteration.

reported_ can't be armed where the report happens, though. Under a JIT that's
inside a compiled block whose cycles are already accounted for, so the tick
count there isn't the settled one we see on the way back in - arming it there
double-logged the breakpoint under -j. So the report just records the address,
and NotifyResumingFrom() turns it into a real marker once the CPU has stopped.
That also has to be idempotent: a step-over arms its temporary breakpoint and
then calls Core_Resume(), which notifies a second time.

MemCheck::Action() no longer pauses by itself - the caller decides, the same way
ExecBreakPoint() already did, so all three breakpoint kinds share the handling.

Verified on both backends: two adjacent breakpoints now log once each while
stepping (was one log total), stepping off a breakpoint still doesn't re-log it
(was two under -j), step-over still skips the call and logs a breakpoint at the
address it lands on, and a breakpoint in a loop reports once per iteration.
pspautotests 314/314.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-17 23:25:56 +02:00
..
2024-11-06 11:59:34 +01:00