eerunner: fix stepdiff self-loop skip and add entry-state probe

The self-loop phase-skip only consulted BlockBackwardBranchTarget on
ar.pc (the fall-through), so a self-loop whose divergence is observed
at its exit was never skipped and stopped the walk as a false positive
(this is what pinned Carbon's 0x174c30 byte-fill loop). Also check
ar.prev_pc, the offending self-loop itself.

The zoom report now dumps both streams' GPRs and FPRs at the offending
block head, so identical-entry false positives are visible directly in
the report instead of needing a hand-built harness.
This commit is contained in:
Brian Degenhardt
2026-07-24 15:30:32 -07:00
parent 365f69c641
commit b8e7ad46ad
+55 -1
View File
@@ -2063,7 +2063,15 @@ static bool ZoomFromCheckpoint(const std::string& ckpt)
// full of short scan loops doesn't exhaust the tight timer budget.
{
u32 loop_branch_addr = 0;
const u32 loop_target = BlockBackwardBranchTarget(ar.pc, &loop_branch_addr);
// The self-loop can be the block being ENTERED (a re-entry sample), OR
// the offending block just executed (ar.prev_pc): the interpreter
// samples the loop's terminating-branch delay slot at branch+8, which
// aliases the loop's fall-through EXIT pc every iteration — so the
// divergence is reported "entering <exit>" with the loop sitting in
// prev_pc. Check the entered block first, then the offending block.
u32 loop_target = BlockBackwardBranchTarget(ar.pc, &loop_branch_addr);
if (loop_target == 0 && ar.prev_pc)
loop_target = BlockBackwardBranchTarget(ar.prev_pc, &loop_branch_addr);
if (loop_target != 0 && selfloop_skipped < kSelfLoopCap)
{
const u32 loop_lo = loop_target;
@@ -2235,6 +2243,52 @@ static bool ZoomFromCheckpoint(const std::string& ckpt)
if (ar.prev_pc)
DisasmBlock(ar.prev_pc);
// Offending-block ENTRY probe: dump BOTH streams' GPR file at prev_pc
// (the offending block's head). Tells apart "entered already-diverged =
// upstream culprit" from "entered identical, exits diverged = this block
// is the bug", and hands a single-block fixture the real seed values.
if (ar.prev_pc)
{
auto entry_snap = [&](const std::vector<ee_divtrace::Sample>& stream, uint32_t upto,
bool jit_side, ee_divtrace::FullSnap& out) -> bool {
for (uint32_t pi = upto; pi-- > 0;)
{
if (stream[pi].pc == ar.prev_pc)
{
if (!reload(jit_side))
return false;
out = RunFineSnapAtFromHere(pi);
return true;
}
}
return false;
};
ee_divtrace::FullSnap ientry{}, jentry{};
const bool hi = entry_snap(interp_fine, ar.interp_idx, false, ientry);
const bool hj = entry_snap(jit_fine, ar.jit_idx, true, jentry);
if (hi && hj)
{
Console.WriteLn(fmt::format("STEPDIFF zoom: offending-block ENTRY GPRs @ {:#010x} (interp | jit):", ar.prev_pc));
for (int r = 0; r < 32; ++r)
{
const u64 iv = ientry.cpu.GPR.r[r].UD[0];
const u64 jv = jentry.cpu.GPR.r[r].UD[0];
if (iv != jv || (r >= 4 && r <= 7))
Console.WriteLn(fmt::format(" r{:<2} {:#018x} | {:#018x}{}", r, iv, jv,
iv != jv ? " <-- DIFFERS" : ""));
}
Console.WriteLn("STEPDIFF zoom: offending-block ENTRY FPRs (interp | jit; f0-f3,f12 + diffs):");
for (int f = 0; f < 32; ++f)
{
const u32 iv = ientry.fpu.fpr[f].UL;
const u32 jv = jentry.fpu.fpr[f].UL;
if (iv != jv || f <= 3 || f == 12)
Console.WriteLn(fmt::format(" f{:<2} {:#010x} | {:#010x}{}", f, iv, jv,
iv != jv ? " <-- DIFFERS" : ""));
}
}
}
const auto diffs = DiffFullSnaps(jsnap, isnap);
if (diffs.empty())
{