pgxp: emit hook macros + dual build targets + flavor-namespaced caches (phase 2)

Emitter side of the value-propagation engine (ENHANCEMENTS.md G1.10):

- recompiler/src/pgxp_hook_emitter.{h,cpp} (new, in the codegen-hash
  surface): ONE hook grammar shared by BOTH emitters, wrapping loads/
  stores/COP2 transfers and precision-carrying ALU ops (moves, add/sub,
  or-merge, shifts, LUI, HI/LO, muldiv) with PGXP_*() macros. Clobberable
  sources are captured into locals before the statement. Precision-
  destroying ops (AND/XOR/SLT/...) are deliberately unhooked - validate-
  on-read drops their stale shadows for free.
- code_generator.cpp: central append in translate_instruction; LWC2/SWC2
  emissions capture the RAW loaded/stored word (a masked GTE register
  write must validate against the word as loaded).
- strict_translator.cpp: translate() wraps translate_impl()'s c_code with
  the shared grammar; deferred load-delay variants hook the psx_ldd temp.
- BIOS regenerated, fingerprints verified CHANGED (SCPH1001 + OpenBIOS;
  42.6k/12.2k macro sites). Base runtime + 33/33 tests green against it.
- Gating: macros expand only under -DPSX_PGXP=1. runtime.cmake grows a
  PGXP option on psxrecomp_add_runtime_target (defines PSX_PGXP=1 +
  PSX_OVERLAY_FLAVOR=2, _pgxp exe suffix) and PSX_PGXP_VARIANT to build
  the sibling target from the same generated tree (framework runtime +
  game wrapper). No ABI version bump needed: the appended PGXPHooks
  table is NULL-tolerant and the flavor half of the ABI tag rejects any
  host/DLL flavor mix.
- Shard caches: flavor folded into the cache tag (cg<V>_<hash>_gc<hash>
  _f<flavor>) in overlay_loader.c + compile_overlays.py so base and pgxp
  caches never share a directory; compile_overlays.py passes
  -DPSX_PGXP=1 when flavor bit 2 is set; overlay preamble forwards
  psx_pgxp_* through the table. PSX_OVERLAY_CODEGEN_VER 9 -> 10.
- Env overrides PSX_GEOMETRY_CORRECTION / PSX_PERSPECTIVE_TEXTURING /
  PSX_PGXP_CPU_MODE (validation path: free-running headless boots arm
  from the first vertex); census grew produced/swc2_stores and the pgxp
  verb reports suppress/active gate state.

Validated: PSXRecomp_pgxp.exe boots the recompiled BIOS with hooks armed
(engine active, per-vertex lookups flowing; the BIOS shell has no RTPS
content so produced=0 there is correct - coverage measurement moves to a
3D title in phase 3).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-08-15 19:03:36 -07:00
parent 2ba74f3b4b
commit 38bdeeaade
17 changed files with 941 additions and 587 deletions
+18 -2
View File
@@ -86,6 +86,13 @@
* model participation with queue-safe guard/hysteresis policy. */
/* v21: ws_angle_widen forwarder for exact, aspect-scaled 12-bit terrain
* frustum half-angle constants. */
/* (PGXP, no version bump): OverlayCallbacks grew an appended-last PGXPHooks
* table pointer (pgxp_hooks.h). Only pgxp-flavour shards (compiled with
* -DPSX_PGXP=1, PSX_OVERLAY_FLAVOR_PGXP set) reference the psx_pgxp_*
* forwarders, and the flavor half of the ABI tag already rejects any
* host/DLL flavor mix — base-flavour DLLs and hosts are untouched, so
* the version stays. The emit-content change (PGXP_*() macros in all
* generated C) is covered by the codegen hash + CODEGEN_VER below. */
#define PSX_OVERLAY_ABI_VERSION 21
/* Process-lifetime overlay candidate capacity. Every accepted manifest F
@@ -113,6 +120,12 @@
#define PSX_OVERLAY_FLAVOR 0
#endif
/* Flavor bits (compose bitwise; base = 0):
* 1 — widescreen variant codegen
* 2 — PGXP precision shadowing (-DPSX_PGXP=1 objects; pgxp_hooks.h) */
#define PSX_OVERLAY_FLAVOR_WIDESCREEN 1
#define PSX_OVERLAY_FLAVOR_PGXP 2
/* Combined tag exported by overlay_abi() and checked by the loader. */
#define PSX_OVERLAY_ABI_TAG \
((int)((PSX_OVERLAY_ABI_VERSION & 0xFFFF) | ((PSX_OVERLAY_FLAVOR & 0xFFFF) << 16)))
@@ -139,8 +152,11 @@
* masked-16-bit screen-X, and depth cull emits.
* 7: + configured side-plane normal-X (plane_nx) cull emit.
* 8: + configured per-primitive bound-load (xclip) cull emit.
* 9: + full-word-guarded object/model participation compare emit. */
#define PSX_OVERLAY_CODEGEN_VER 9
* 9: + full-word-guarded object/model participation compare emit.
* 10: + PGXP dataflow-shadowing PGXP_*() macro emission at loads/stores/
* COP2 transfers/precision-carrying ALU ops (pgxp_hooks.h; no-ops in
* base-flavour objects, real hooks under -DPSX_PGXP=1). */
#define PSX_OVERLAY_CODEGEN_VER 10
/* Auto codegen hash (generated by hash_codegen.cmake from the recompiler codegen
* sources). Folded into the cache PATH next to cg<N> (gcc/<arch-abi>/cg<N>_<hash>/)
@@ -265,4 +265,27 @@ uint32_t psx_ws_angle_widen(uint32_t vanilla) {
void gte_precision_store_word(uint32_t addr, uint8_t reg) {
if (g_cbs.gte_precision_store_word) g_cbs.gte_precision_store_word(addr, reg);
}
#if defined(PSX_PGXP) && PSX_PGXP
/* PGXP dataflow-shadowing hooks (pgxp_hooks.h; ENHANCEMENTS.md G1.10). Only a
* pgxp-flavour shard (compiled -DPSX_PGXP=1, flavor bit set) references these
* — base-flavour shards preprocess the PGXP_*() macros away, so this block is
* compiled out of them entirely. Forwards to the host engine through the
* appended-last table; every hook no-ops on a host that predates it
* (precision shadowing is visual-only, never load-bearing). */
void psx_pgxp_load(struct CPUState *cpu, uint32_t instr, uint32_t addr, uint32_t value) {
if (g_cbs.pgxp && g_cbs.pgxp->load) g_cbs.pgxp->load(cpu, instr, addr, value);
}
void psx_pgxp_store(struct CPUState *cpu, uint32_t instr, uint32_t addr, uint32_t value) {
if (g_cbs.pgxp && g_cbs.pgxp->store) g_cbs.pgxp->store(cpu, instr, addr, value);
}
void psx_pgxp_alu(struct CPUState *cpu, uint32_t instr, uint32_t result, uint32_t s1, uint32_t s2) {
if (g_cbs.pgxp && g_cbs.pgxp->alu) g_cbs.pgxp->alu(cpu, instr, result, s1, s2);
}
void psx_pgxp_muldiv(struct CPUState *cpu, uint32_t instr, uint32_t hi, uint32_t lo, uint32_t s1, uint32_t s2) {
if (g_cbs.pgxp && g_cbs.pgxp->muldiv) g_cbs.pgxp->muldiv(cpu, instr, hi, lo, s1, s2);
}
void psx_pgxp_cop2(struct CPUState *cpu, uint32_t instr, uint32_t value, uint32_t addr) {
if (g_cbs.pgxp && g_cbs.pgxp->cop2) g_cbs.pgxp->cop2(cpu, instr, value, addr);
}
#endif /* PSX_PGXP */
/* ----------------------------------------------------------------------- */
+4
View File
@@ -92,6 +92,8 @@ typedef struct PGXPStats {
uint64_t trunc_reject; /* integer part disagreed with native parse */
uint64_t tolerance_reject;
uint64_t w_valid; /* lookups that also carried a usable depth */
uint64_t produced; /* RTPS/RTPT projections pushed into shadows */
uint64_t swc2_stores; /* GTE reg shadows copied to RAM shadows */
} PGXPStats;
void pgxp_get_stats(PGXPStats *out);
@@ -115,6 +117,8 @@ void pgxp_test_get_gte_sxy(uint32_t index, uint32_t *packed,
int32_t *x16, int32_t *y16, uint16_t *z,
uint8_t *valid);
uint32_t pgxp_test_generation(void);
uint32_t pgxp_test_suppress_depth(void);
int pgxp_test_active(void);
void pgxp_test_set_generation(uint32_t gen);
#ifdef __cplusplus