mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
CTR graphical diagnostic
This commit is contained in:
+512
-2
@@ -2267,6 +2267,368 @@ static inline void cyc_watch_observe(uint32_t block_leader_phys)
|
||||
if (s_cyc_watch_hits >= s_cyc_watch_max_hits) s_cyc_watch_armed = 0;
|
||||
}
|
||||
|
||||
/* ---- pc_probe: multi-PC block-leader counters + reg samples (default-off) ----
|
||||
* Arm via TCP `pc_probe_arm` or env PSX_PC_PROBE / PSX_ND_INTRO_PROBE.
|
||||
* Samples $t0/$fp/$v0/frame at matched leaders. Disarmed = one branch.
|
||||
* nd_intro=2 also fills mode/depth/ot_base/ot_index from GP + game struct. */
|
||||
#define PC_PROBE_MAX_PCS 16
|
||||
#define PC_PROBE_SAMPLE_CAP 64
|
||||
typedef struct {
|
||||
uint32_t pc;
|
||||
uint64_t count;
|
||||
uint32_t last_t0;
|
||||
uint32_t last_fp;
|
||||
uint32_t last_v0;
|
||||
uint32_t last_mode;
|
||||
uint32_t last_depth;
|
||||
uint32_t last_ot_base;
|
||||
uint32_t last_ot_index;
|
||||
uint32_t last_frame;
|
||||
uint32_t t0_zero;
|
||||
uint32_t t0_nonzero;
|
||||
} PcProbeSlot;
|
||||
typedef struct {
|
||||
uint32_t pc;
|
||||
uint32_t frame;
|
||||
uint32_t t0;
|
||||
uint32_t fp;
|
||||
uint32_t v0;
|
||||
uint32_t mode;
|
||||
uint32_t depth;
|
||||
uint32_t ot_base;
|
||||
uint32_t ot_index;
|
||||
} PcProbeSample;
|
||||
static volatile int s_pc_probe_armed = 0;
|
||||
static int s_pc_probe_n = 0;
|
||||
static PcProbeSlot s_pc_probe[PC_PROBE_MAX_PCS];
|
||||
static PcProbeSample s_pc_probe_samples[PC_PROBE_SAMPLE_CAP];
|
||||
static uint32_t s_pc_probe_sample_n = 0;
|
||||
static uint32_t s_pc_probe_sample_max = 32;
|
||||
|
||||
static void pc_probe_clear_state(void)
|
||||
{
|
||||
s_pc_probe_armed = 0;
|
||||
s_pc_probe_n = 0;
|
||||
s_pc_probe_sample_n = 0;
|
||||
memset(s_pc_probe, 0, sizeof(s_pc_probe));
|
||||
memset(s_pc_probe_samples, 0, sizeof(s_pc_probe_samples));
|
||||
}
|
||||
|
||||
static int pc_probe_add_pc(uint32_t raw)
|
||||
{
|
||||
uint32_t phys = raw & 0x1FFFFFFFu;
|
||||
if (phys == 0) return 0;
|
||||
for (int i = 0; i < s_pc_probe_n; i++) {
|
||||
if (s_pc_probe[i].pc == phys) return 1;
|
||||
}
|
||||
if (s_pc_probe_n >= PC_PROBE_MAX_PCS) return 0;
|
||||
s_pc_probe[s_pc_probe_n].pc = phys;
|
||||
s_pc_probe_n++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Comma/space-separated hex list, e.g. "0x80044D10,0x80044E58". */
|
||||
static int pc_probe_parse_list(const char *list)
|
||||
{
|
||||
if (!list || !*list) return 0;
|
||||
char tmp[512];
|
||||
snprintf(tmp, sizeof(tmp), "%s", list);
|
||||
int added = 0;
|
||||
for (char *p = tmp, *tok; (tok = strtok(p, ", \t\n")) != NULL; p = NULL) {
|
||||
uint32_t raw = (uint32_t)strtoul(tok, NULL, 0);
|
||||
if (pc_probe_add_pc(raw)) added++;
|
||||
}
|
||||
return added;
|
||||
}
|
||||
|
||||
static void pc_probe_arm_nd_intro_defaults(void)
|
||||
{
|
||||
/* NdIntroMeshDraw post-RTPT funnel (block leaders with cyc_observe). */
|
||||
static const uint32_t k[] = {
|
||||
0x80044580u, /* after dispatch: $fp = v0 OT ptr */
|
||||
0x80044C80u, /* face clip start */
|
||||
0x80044CB0u, /* passed neg outcode */
|
||||
0x80044CDCu, /* passed hi outcode → alloc */
|
||||
0x80044D10u, /* $t0 == 0 ? skip : emit */
|
||||
0x80044D18u, /* emit continue */
|
||||
0x80044DA4u, /* PolyG4 build */
|
||||
0x80044E58u, /* clip skip */
|
||||
0x80044EC8u, /* $t0==0 / OOM skip */
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(k) / sizeof(k[0]); i++)
|
||||
pc_probe_add_pc(k[i]);
|
||||
}
|
||||
|
||||
/* OT/depth leafs of NdIntroDrawDispatch — which path returns the OT slot. */
|
||||
static void pc_probe_arm_nd_intro_ot_defaults(void)
|
||||
{
|
||||
static const uint32_t k[] = {
|
||||
0x80044580u, /* after jal: v0 → $fp OT slot */
|
||||
0x800440A0u, /* dispatch entry (mode half @ gp+0x4DE) */
|
||||
0x80044120u, /* mode0 depth scale using *(game+0x1D04) */
|
||||
0x80044154u, /* return OT base via *(game+buf*4+0x18C8) */
|
||||
0x800441A8u, /* return *(game+0x25C)+0xFFC (near-ish) */
|
||||
0x800441C0u, /* mode2 entry */
|
||||
0x80044268u, /* mode1/ shared OT-base return */
|
||||
0x80044DA4u, /* PolyG4 emit: confirm $fp sticky */
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(k) / sizeof(k[0]); i++)
|
||||
pc_probe_add_pc(k[i]);
|
||||
}
|
||||
|
||||
/* Textured wood funnel — live path is func_8006A52C (not dead twin 0x8006A6B8). */
|
||||
static void pc_probe_arm_nd_intro_wood_defaults(void)
|
||||
{
|
||||
static const uint32_t k[] = {
|
||||
0x8006A52Cu, /* NdIntroWoodEmit entry */
|
||||
0x8006A564u, /* RTPT GTE caller_ra (sticky) */
|
||||
0x8006A57Cu, /* post-RTPT face loop */
|
||||
0x8006A590u, /* FLAG read / face setup */
|
||||
0x8006A5B4u, /* NCLIP */
|
||||
0x8006A610u, /* next-face hub (skip + post-emit) */
|
||||
0x8006A69Cu, /* RTPS single-vert return (bgez $0 → loop) */
|
||||
0x8006A6B8u, /* dead twin entry — expect count=0 */
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(k) / sizeof(k[0]); i++)
|
||||
pc_probe_add_pc(k[i]);
|
||||
}
|
||||
|
||||
/* Depth compare: wood OTZ vs digit/glow OT link ptrs vs MeshDraw OtFar.
|
||||
* Only block-leader PCs (cyc_observe); mid-block AddPrim sites never fire. */
|
||||
static void pc_probe_arm_nd_intro_depth_defaults(void)
|
||||
{
|
||||
static const uint32_t k[] = {
|
||||
0x8006A608u, /* wood jalr emit — GTE OTZ */
|
||||
0x80023094u, /* DigitGt4 entry — $t7 already OT slot */
|
||||
0x80053E10u, /* glow color ori — $a2=prim; OT via game+0x147C later */
|
||||
0x800440A0u, /* DrawDispatch entry — GP mode/depth */
|
||||
0x80044580u, /* MeshDraw after dispatch */
|
||||
0x800441A8u, /* OtFar leaf */
|
||||
0x80052F98u, /* DigitFx entry */
|
||||
0x800444ECu, /* MeshDraw entry — sample $ra */
|
||||
0x80044DA4u, /* PolyG4 emit hub — lighting bytes on stack */
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(k) / sizeof(k[0]); i++)
|
||||
pc_probe_add_pc(k[i]);
|
||||
}
|
||||
|
||||
/* Wood DL / helper selection: batch setup + which emit helper is bound. */
|
||||
static void pc_probe_arm_nd_intro_wood_dl_defaults(void)
|
||||
{
|
||||
static const uint32_t k[] = {
|
||||
0x8006AAF0u, /* wood batch setup: a0=stream, ra=material desc */
|
||||
0x8006AB58u, /* fallthrough past flag gates (accepted batch) */
|
||||
0x8006ACE0u, /* after lw s5,96(ra) — helper bound */
|
||||
0x8006A52Cu, /* wood emit entry */
|
||||
0x8006A608u, /* wood jalr emit — also GTE OTZ/SXY via wood_ot */
|
||||
0x8006AE90u, /* alt helper — expect 0 in ND */
|
||||
0x8006AD20u, /* wood batch epilogue / next-opcode hub */
|
||||
0x80044580u, /* MeshDraw post-RTPT — compare SX band */
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(k) / sizeof(k[0]); i++)
|
||||
pc_probe_add_pc(k[i]);
|
||||
}
|
||||
|
||||
/* Resolve double-buffered OT base: *(*(0x8008D2AC) + 0x18C8 + (*(+0xC)<<2)). */
|
||||
static void pc_probe_ot_context(CPUState *cpu, uint32_t v0_ot,
|
||||
uint32_t *mode_out, uint32_t *depth_out,
|
||||
uint32_t *ot_base_out, uint32_t *ot_index_out)
|
||||
{
|
||||
uint32_t mode = 0, depth = 0, ot_base = 0, ot_index = 0xFFFFFFFFu;
|
||||
if (cpu) {
|
||||
uint32_t gp = cpu->gpr[28];
|
||||
mode = psx_read_word(gp + 0x4D4u);
|
||||
/* Depth countdown half lives at gp+0x4D8 (signed). */
|
||||
depth = (uint32_t)(int32_t)(int16_t)(psx_read_word(gp + 0x4D8u) & 0xFFFFu);
|
||||
uint32_t game = psx_read_word(0x8008D2ACu);
|
||||
if (game) {
|
||||
uint32_t buf = psx_read_word(game + 0xCu);
|
||||
ot_base = psx_read_word(game + (buf << 2) + 0x18C8u);
|
||||
if (ot_base && v0_ot) {
|
||||
int32_t delta = (int32_t)(v0_ot - ot_base);
|
||||
if ((delta & 3) == 0 && delta >= 0 && delta < (1 << 20))
|
||||
ot_index = (uint32_t)(delta >> 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mode_out) *mode_out = mode;
|
||||
if (depth_out) *depth_out = depth;
|
||||
if (ot_base_out) *ot_base_out = ot_base;
|
||||
if (ot_index_out) *ot_index_out = ot_index;
|
||||
}
|
||||
|
||||
static inline void pc_probe_observe(uint32_t block_leader_phys)
|
||||
{
|
||||
if (!s_pc_probe_armed || s_pc_probe_n <= 0) return;
|
||||
extern CPUState *debug_cpu_ptr;
|
||||
CPUState *cpu = debug_cpu_ptr;
|
||||
for (int i = 0; i < s_pc_probe_n; i++) {
|
||||
if (s_pc_probe[i].pc != block_leader_phys) continue;
|
||||
PcProbeSlot *s = &s_pc_probe[i];
|
||||
s->count++;
|
||||
if (cpu) {
|
||||
uint32_t t0 = cpu->gpr[8];
|
||||
uint32_t fp = cpu->gpr[30];
|
||||
uint32_t v0 = cpu->gpr[2];
|
||||
uint32_t a1 = cpu->gpr[5];
|
||||
uint32_t a2 = cpu->gpr[6];
|
||||
uint32_t t7 = cpu->gpr[15];
|
||||
uint32_t otz = cpu->gte_data[7] & 0xFFFFu;
|
||||
uint32_t mode = 0, depth = 0, ot_base = 0, ot_index = 0xFFFFFFFFu;
|
||||
/* Wood: a2 is stack-table value (often prim-ish); OT slot ≈ ot_base+OTZ.
|
||||
* Digit GT4 AddPrim @23180: $t7 = OT slot. Glow @53EB8: $a1 = OT head. */
|
||||
int wood_ot = (block_leader_phys == 0x0006A608u ||
|
||||
block_leader_phys == 0x0006A600u);
|
||||
int digit_ot = (block_leader_phys == 0x00023094u);
|
||||
int glow_ot = (block_leader_phys == 0x00053E10u);
|
||||
int dispatch = (block_leader_phys == 0x000440A0u);
|
||||
uint32_t ot_ptr = 0;
|
||||
if (digit_ot)
|
||||
ot_ptr = t7; /* caller-supplied OT slot */
|
||||
else if (glow_ot) {
|
||||
/* OT head at game+0x147C (same as AddPrim a few insns later). */
|
||||
uint32_t game = psx_read_word(0x8008D2ACu);
|
||||
ot_ptr = game ? psx_read_word(game + 0x147Cu) : 0;
|
||||
} else if (block_leader_phys == 0x00044580u ||
|
||||
block_leader_phys == 0x000441A8u)
|
||||
ot_ptr = v0; /* dispatch result / OtFar v0 */
|
||||
else if (wood_ot)
|
||||
ot_ptr = 0; /* resolve via OTZ after ot_base known */
|
||||
else
|
||||
ot_ptr = fp ? fp : v0;
|
||||
pc_probe_ot_context(cpu, ot_ptr, &mode, &depth, &ot_base, &ot_index);
|
||||
if (wood_ot) {
|
||||
/* Prefer OTZ as OT index (PsyQ AddPrim(ot+OTZ) style). */
|
||||
if (ot_base && otz < (1u << 20)) {
|
||||
ot_ptr = ot_base + (otz << 2);
|
||||
ot_index = otz;
|
||||
}
|
||||
t0 = a2;
|
||||
depth = otz;
|
||||
v0 = otz;
|
||||
/* Pack max screen-X of SXY0/1/2 into mode for extent compares. */
|
||||
{
|
||||
int32_t sx_max = -0x8000;
|
||||
for (int si = 12; si <= 14; si++) {
|
||||
int32_t sx = (int32_t)(int16_t)(cpu->gte_data[si] & 0xFFFFu);
|
||||
if (sx > sx_max) sx_max = sx;
|
||||
}
|
||||
mode = (uint32_t)sx_max;
|
||||
}
|
||||
} else if (digit_ot) {
|
||||
t0 = t7;
|
||||
v0 = ot_index;
|
||||
} else if (glow_ot) {
|
||||
t0 = ot_ptr;
|
||||
v0 = ot_index;
|
||||
depth = a2; /* prim ptr */
|
||||
} else if (dispatch) {
|
||||
/* Fresh GP mode/depth before leaf runs; v0 not OT yet. */
|
||||
t0 = mode;
|
||||
v0 = depth;
|
||||
} else if (block_leader_phys == 0x000444ECu) {
|
||||
/* MeshDraw entry: expose caller $ra (no jal-site in EXE). */
|
||||
t0 = cpu->gpr[31];
|
||||
v0 = cpu->gpr[2]; /* entry gate */
|
||||
} else if (block_leader_phys == 0x00044DA4u) {
|
||||
/* PolyG4 emit: sp+0x40/0x41 hold computed shade bytes. */
|
||||
uint32_t sp = cpu->gpr[29];
|
||||
t0 = psx_read_word(sp + 0x40u) & 0xFFFFu;
|
||||
v0 = fp; /* OT link ptr */
|
||||
} else if (block_leader_phys == 0x0006A52Cu) {
|
||||
/* Wood emit entry: $t9=face list, $at=ctx, $s5=emit helper. */
|
||||
t0 = cpu->gpr[25]; /* t9 */
|
||||
v0 = cpu->gpr[1]; /* at */
|
||||
depth = cpu->gpr[21]; /* s5 */
|
||||
} else if (block_leader_phys == 0x0006ACE0u) {
|
||||
/* Post helper-select: $s5 set, $a2=model.
|
||||
* ra may be clobbered by pre-emit jalr; trust s5 + model. */
|
||||
t0 = cpu->gpr[21]; /* s5 helper */
|
||||
v0 = cpu->gpr[6]; /* a2 model */
|
||||
if (v0) {
|
||||
mode = psx_read_word(v0 + 184u); /* flags */
|
||||
/* face-list ptr @a2+200; depth = first face word */
|
||||
uint32_t faces = psx_read_word(v0 + 200u);
|
||||
depth = faces ? psx_read_word(faces) : 0;
|
||||
ot_base = faces;
|
||||
ot_index = faces ? psx_read_word(faces + 4u) : 0;
|
||||
}
|
||||
} else if (block_leader_phys == 0x0006AAF0u) {
|
||||
/* Wood batch setup: $a0 stream, $ra = model (helpers at +92).
|
||||
* mode=flags@+184, depth=helper@+96, ot_base=name word@+8. */
|
||||
t0 = cpu->gpr[4]; /* a0 */
|
||||
v0 = cpu->gpr[31]; /* model */
|
||||
if (v0) {
|
||||
mode = psx_read_word(v0 + 184u); /* draw flags */
|
||||
depth = psx_read_word(v0 + 96u); /* s5 helper */
|
||||
ot_base = psx_read_word(v0 + 8u); /* name/tag */
|
||||
ot_index = psx_read_word(v0 + 92u); /* pre-emit */
|
||||
}
|
||||
} else if (block_leader_phys == 0x0006AB58u) {
|
||||
/* Fallthrough past flag bne — batch accepted. */
|
||||
t0 = cpu->gpr[6]; /* a2 */
|
||||
v0 = t0 ? psx_read_word(t0 + 184u) : 0;
|
||||
depth = cpu->gpr[31]; /* desc */
|
||||
} else if (block_leader_phys == 0x00044580u) {
|
||||
/* MeshDraw post-dispatch: also pack GTE max SX into mode. */
|
||||
{
|
||||
int32_t sx_max = -0x8000;
|
||||
for (int si = 12; si <= 14; si++) {
|
||||
int32_t sx = (int32_t)(int16_t)(cpu->gte_data[si] & 0xFFFFu);
|
||||
if (sx > sx_max) sx_max = sx;
|
||||
}
|
||||
mode = (uint32_t)sx_max;
|
||||
}
|
||||
} else if (block_leader_phys == 0x000444E8u) {
|
||||
/* True MeshDraw entry: lh gp+0x4DC → v0 gate (observe pre-lh). */
|
||||
t0 = cpu->gpr[28]; /* gp */
|
||||
v0 = cpu->gpr[2];
|
||||
} else if (block_leader_phys == 0x00069BB0u) {
|
||||
/* Sibling matrix/OT setup entry: a1=ctx (s4+360), a0=mesh. */
|
||||
t0 = cpu->gpr[5]; /* a1 */
|
||||
v0 = cpu->gpr[4]; /* a0 */
|
||||
if (t0) {
|
||||
mode = psx_read_word(t0 + 244u); /* OT base field before +4092 */
|
||||
depth = mode ? (mode + 4092u) : 0;
|
||||
ot_base = mode;
|
||||
}
|
||||
} else if (block_leader_phys == 0x00069C34u ||
|
||||
block_leader_phys == 0x00069CC4u) {
|
||||
/* Face-loop jal / entry: a3 = OT origin (last slot). */
|
||||
t0 = cpu->gpr[7]; /* a3 */
|
||||
v0 = cpu->gpr[5]; /* a1 */
|
||||
mode = cpu->gpr[4]; /* a0 mesh */
|
||||
depth = t0;
|
||||
ot_base = (t0 >= 4092u) ? (t0 - 4092u) : 0;
|
||||
}
|
||||
s->last_t0 = t0;
|
||||
s->last_fp = fp;
|
||||
s->last_v0 = v0;
|
||||
s->last_mode = mode;
|
||||
s->last_depth = depth;
|
||||
s->last_ot_base = ot_base;
|
||||
s->last_ot_index = ot_index;
|
||||
s->last_frame = (uint32_t)s_frame_count;
|
||||
if (t0 == 0) s->t0_zero++;
|
||||
else s->t0_nonzero++;
|
||||
if (s_pc_probe_sample_n < s_pc_probe_sample_max &&
|
||||
s_pc_probe_sample_n < PC_PROBE_SAMPLE_CAP) {
|
||||
PcProbeSample *sm = &s_pc_probe_samples[s_pc_probe_sample_n++];
|
||||
sm->pc = block_leader_phys;
|
||||
sm->frame = (uint32_t)s_frame_count;
|
||||
sm->t0 = t0;
|
||||
sm->fp = fp;
|
||||
sm->v0 = v0;
|
||||
sm->mode = mode;
|
||||
sm->depth = depth;
|
||||
sm->ot_base = ot_base;
|
||||
sm->ot_index = ot_index;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Exported per-basic-block-leader cycle observer. Emitted by the recompiler at
|
||||
* EVERY compiled block leader (under #ifndef PSX_NO_DEBUG_TOOLS, so prod builds
|
||||
* emit nothing — zero overhead) so native's cycle observation matches Beetle's
|
||||
@@ -2280,11 +2642,58 @@ void debug_server_cyc_observe(uint32_t block_leader_phys) {
|
||||
return;
|
||||
#else
|
||||
if (s_fmv_quiet) return;
|
||||
cyc_watch_observe(block_leader_phys & 0x1FFFFFFFu);
|
||||
uint32_t phys = block_leader_phys & 0x1FFFFFFFu;
|
||||
/* ND debug: PSX_ND_WOOD_CLEAR80=1 clears model+184 bit0x80 at the flag-load
|
||||
* leader so 0x5CF/'cras' models enter NdIntroWoodBatchSetup's textured path.
|
||||
* Fires before lw v1,184(a2) @ AB3C. Default-off. */
|
||||
if (phys == 0x0006AB3Cu) {
|
||||
static int s_clear80 = -1;
|
||||
if (s_clear80 < 0) {
|
||||
const char *e = getenv("PSX_ND_WOOD_CLEAR80");
|
||||
s_clear80 = (e && *e && *e != '0') ? 1 : 0;
|
||||
if (s_clear80)
|
||||
fprintf(stdout, "psxrecomp: PSX_ND_WOOD_CLEAR80 enabled\n");
|
||||
}
|
||||
if (s_clear80 && debug_cpu_ptr) {
|
||||
uint32_t a2 = debug_cpu_ptr->gpr[6];
|
||||
if (a2) {
|
||||
uint32_t fl = psx_read_word(a2 + 184u);
|
||||
if (fl & 0x80u)
|
||||
psx_write_word(a2 + 184u, fl & ~0x80u);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* ND debug: PSX_ND_SIB_OT_LIFT=<n> adds n*4 to $a3 at sibling face-loop
|
||||
* entry 0x80069CC4 (a3 = *(a1+244)+4092 = last OT slot). Sibling PolyFT3
|
||||
* right-flap faces use face_hi≈0 → farthest bucket (drawn before additive
|
||||
* 0x36 glow). Negative n (e.g. -800) moves them nearer in the 1024-entry OT.
|
||||
* Applied once per a3 value. */
|
||||
if (phys == 0x00069CC4u && debug_cpu_ptr) {
|
||||
static int s_sib_lift = -2; /* -2 unset, 0 disabled, else delta */
|
||||
static uint32_t s_sib_lifted_a3 = 0;
|
||||
if (s_sib_lift == -2) {
|
||||
const char *e = getenv("PSX_ND_SIB_OT_LIFT");
|
||||
if (e && *e) {
|
||||
s_sib_lift = atoi(e);
|
||||
fprintf(stdout, "psxrecomp: PSX_ND_SIB_OT_LIFT=%d\n", s_sib_lift);
|
||||
} else {
|
||||
s_sib_lift = 0;
|
||||
}
|
||||
}
|
||||
if (s_sib_lift != 0) {
|
||||
uint32_t a3 = debug_cpu_ptr->gpr[7];
|
||||
if (a3 && a3 != s_sib_lifted_a3) {
|
||||
debug_cpu_ptr->gpr[7] = a3 + (uint32_t)(int32_t)(s_sib_lift * 4);
|
||||
s_sib_lifted_a3 = debug_cpu_ptr->gpr[7];
|
||||
}
|
||||
}
|
||||
}
|
||||
cyc_watch_observe(phys);
|
||||
pc_probe_observe(phys);
|
||||
/* #2 lockstep comparator: per-basic-block compiled-vs-interp check. Self-gates
|
||||
* on the armed frame window; ~free (one branch) when disarmed. */
|
||||
{ extern void ls_at_leader(uint32_t, CPUState*); extern CPUState *debug_cpu_ptr;
|
||||
ls_at_leader(block_leader_phys & 0x1FFFFFFFu, debug_cpu_ptr); }
|
||||
ls_at_leader(phys, debug_cpu_ptr); }
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -9034,6 +9443,81 @@ static void handle_cyc_watch_clear(int id, const char *json)
|
||||
send_ok(id);
|
||||
}
|
||||
|
||||
/* pc_probe_arm — {"pcs":"0xA,0xB", "n":32, "nd_intro":1..5} or empty pcs+nd_intro.
|
||||
* 1=PolyG4 clip 2=OT leaves 3=wood 4=depth 5=wood DL/helper bind. */
|
||||
static void handle_pc_probe_arm(int id, const char *json)
|
||||
{
|
||||
pc_probe_clear_state();
|
||||
int n = json_get_int(json, "n", 32);
|
||||
if (n < 1) n = 1;
|
||||
if (n > PC_PROBE_SAMPLE_CAP) n = PC_PROBE_SAMPLE_CAP;
|
||||
s_pc_probe_sample_max = (uint32_t)n;
|
||||
|
||||
int nd = json_get_int(json, "nd_intro", 0);
|
||||
char pcs[512];
|
||||
const char *have_pcs = json_get_str(json, "pcs", pcs, sizeof(pcs));
|
||||
if (nd == 5) pc_probe_arm_nd_intro_wood_dl_defaults();
|
||||
else if (nd == 4) pc_probe_arm_nd_intro_depth_defaults();
|
||||
else if (nd == 3) pc_probe_arm_nd_intro_wood_defaults();
|
||||
else if (nd == 2) pc_probe_arm_nd_intro_ot_defaults();
|
||||
else if (nd) pc_probe_arm_nd_intro_defaults();
|
||||
if (have_pcs) pc_probe_parse_list(pcs);
|
||||
if (s_pc_probe_n <= 0) {
|
||||
send_err(id, "pc_probe_arm needs pcs=... and/or nd_intro=1..5");
|
||||
return;
|
||||
}
|
||||
s_pc_probe_armed = 1;
|
||||
send_fmt("{\"id\":%d,\"ok\":true,\"armed\":1,\"n_pcs\":%d,\"sample_max\":%u,\"nd_intro\":%d}",
|
||||
id, s_pc_probe_n, s_pc_probe_sample_max, nd);
|
||||
}
|
||||
|
||||
static void handle_pc_probe_dump(int id, const char *json)
|
||||
{
|
||||
(void)json;
|
||||
char buf[512];
|
||||
snprintf(buf, sizeof(buf),
|
||||
"{\"id\":%d,\"ok\":true,\"armed\":%d,\"n_pcs\":%d,\"sample_max\":%u,"
|
||||
"\"samples_n\":%u,\"slots\":[",
|
||||
id, s_pc_probe_armed ? 1 : 0, s_pc_probe_n, s_pc_probe_sample_max,
|
||||
s_pc_probe_sample_n);
|
||||
send_line(buf);
|
||||
for (int i = 0; i < s_pc_probe_n; i++) {
|
||||
const PcProbeSlot *s = &s_pc_probe[i];
|
||||
snprintf(buf, sizeof(buf),
|
||||
"%s{\"pc\":\"0x%08X\",\"count\":%llu,\"t0_zero\":%u,\"t0_nonzero\":%u,"
|
||||
"\"last_t0\":\"0x%08X\",\"last_fp\":\"0x%08X\",\"last_v0\":\"0x%08X\","
|
||||
"\"last_mode\":\"0x%08X\",\"last_depth\":\"0x%08X\","
|
||||
"\"last_ot_base\":\"0x%08X\",\"last_ot_index\":\"0x%08X\","
|
||||
"\"last_frame\":%u}",
|
||||
(i == 0) ? "" : ",",
|
||||
s->pc, (unsigned long long)s->count, s->t0_zero, s->t0_nonzero,
|
||||
s->last_t0, s->last_fp, s->last_v0,
|
||||
s->last_mode, s->last_depth, s->last_ot_base, s->last_ot_index,
|
||||
s->last_frame);
|
||||
send_line(buf);
|
||||
}
|
||||
send_line("],\"samples\":[");
|
||||
for (uint32_t i = 0; i < s_pc_probe_sample_n; i++) {
|
||||
const PcProbeSample *sm = &s_pc_probe_samples[i];
|
||||
snprintf(buf, sizeof(buf),
|
||||
"%s{\"pc\":\"0x%08X\",\"frame\":%u,\"t0\":\"0x%08X\","
|
||||
"\"fp\":\"0x%08X\",\"v0\":\"0x%08X\",\"mode\":\"0x%08X\","
|
||||
"\"depth\":\"0x%08X\",\"ot_base\":\"0x%08X\",\"ot_index\":\"0x%08X\"}",
|
||||
(i == 0) ? "" : ",",
|
||||
sm->pc, sm->frame, sm->t0, sm->fp, sm->v0,
|
||||
sm->mode, sm->depth, sm->ot_base, sm->ot_index);
|
||||
send_line(buf);
|
||||
}
|
||||
send_line("]}");
|
||||
}
|
||||
|
||||
static void handle_pc_probe_clear(int id, const char *json)
|
||||
{
|
||||
(void)json;
|
||||
pc_probe_clear_state();
|
||||
send_ok(id);
|
||||
}
|
||||
|
||||
/* Liveness/freeze diagnostic: returns a single snapshot of every counter
|
||||
* that distinguishes "stuck in a tight handler loop" from "just slow" or
|
||||
* "starved on TCP poll". Pass {"window":N} (default 256) to also include
|
||||
@@ -12734,6 +13218,9 @@ static const CmdEntry s_commands[] = {
|
||||
{ "cyc_watch", handle_cyc_watch },
|
||||
{ "cyc_watch_dump", handle_cyc_watch_dump },
|
||||
{ "cyc_watch_clear", handle_cyc_watch_clear },
|
||||
{ "pc_probe_arm", handle_pc_probe_arm },
|
||||
{ "pc_probe_dump", handle_pc_probe_dump },
|
||||
{ "pc_probe_clear", handle_pc_probe_clear },
|
||||
{ "mmio_dump", handle_mmio_dump },
|
||||
{ "mmio_clear", handle_mmio_clear },
|
||||
{ "capture_freeze", handle_capture_freeze },
|
||||
@@ -12933,6 +13420,29 @@ void debug_server_init(int port)
|
||||
if (s_rwatch_hi > s_rwatch_lo) g_ram_read_watch_active = 1;
|
||||
}
|
||||
}
|
||||
/* PSX_ND_INTRO_PROBE=1 PolyG4; =2 OT; =3 wood; =4 depth; =5 wood DL.
|
||||
* PSX_PC_PROBE="0xA,0xB" arms an explicit list (can combine). */
|
||||
{
|
||||
const char *nd = getenv("PSX_ND_INTRO_PROBE");
|
||||
const char *pcs = getenv("PSX_PC_PROBE");
|
||||
if ((nd && *nd && *nd != '0') || (pcs && *pcs)) {
|
||||
pc_probe_clear_state();
|
||||
s_pc_probe_sample_max = 48;
|
||||
if (nd && *nd && *nd != '0') {
|
||||
int ndv = (int)strtol(nd, NULL, 0);
|
||||
if (ndv == 5) pc_probe_arm_nd_intro_wood_dl_defaults();
|
||||
else if (ndv == 4) pc_probe_arm_nd_intro_depth_defaults();
|
||||
else if (ndv == 3) pc_probe_arm_nd_intro_wood_defaults();
|
||||
else if (ndv == 2) pc_probe_arm_nd_intro_ot_defaults();
|
||||
else pc_probe_arm_nd_intro_defaults();
|
||||
}
|
||||
if (pcs && *pcs) pc_probe_parse_list(pcs);
|
||||
if (s_pc_probe_n > 0) {
|
||||
s_pc_probe_armed = 1;
|
||||
fprintf(stdout, "psxrecomp: pc_probe armed (%d pcs)\n", s_pc_probe_n);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
@@ -437,6 +437,29 @@ def build_cmd(args):
|
||||
if len(args) > 2:
|
||||
d["nth"] = int(args[2])
|
||||
return d, pretty_json
|
||||
elif cmd == "pc_probe_arm":
|
||||
# pc_probe_arm [nd_intro=1|2|3|nd|nd_ot|nd_wood] [pcs=0xA,0xB] [n=32]
|
||||
d = {"cmd": "pc_probe_arm"}
|
||||
for a in args[1:]:
|
||||
if "=" in a:
|
||||
k, v = a.split("=", 1)
|
||||
if k in ("n", "nd_intro") and v.lstrip("-").isdigit():
|
||||
d[k] = int(v)
|
||||
else:
|
||||
d[k] = v
|
||||
elif a in ("nd", "nd_intro"):
|
||||
d["nd_intro"] = 1
|
||||
elif a in ("nd_ot", "ot"):
|
||||
d["nd_intro"] = 2
|
||||
elif a in ("nd_wood", "wood"):
|
||||
d["nd_intro"] = 3
|
||||
elif a in ("nd_depth", "depth"):
|
||||
d["nd_intro"] = 4
|
||||
return d, pretty_json
|
||||
elif cmd == "pc_probe_dump":
|
||||
return {"cmd": "pc_probe_dump"}, pretty_json
|
||||
elif cmd == "pc_probe_clear":
|
||||
return {"cmd": "pc_probe_clear"}, pretty_json
|
||||
elif cmd == "fntrace_arm":
|
||||
# <target_hex|0xFFFFFFFF=all|0=clear>
|
||||
return {"cmd": "fntrace_arm", "target": args[1]}, pretty_json
|
||||
|
||||
Reference in New Issue
Block a user