mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 10:19:47 -04:00
1a90c04c27
MFC0 and CFC2 overwrite a GPR, but the recompiled code did not hook them (and neither interpreter hooked MFC0/CFC0), so the register kept the shadow of whatever vertex it held before. Validation by value cannot prove provenance when the new word equals the old one. Emit PGXP_ALU after MFC0 and PGXP_COP2 after CFC2, matching the interpreters' CFC2 hook, and hook MFC0/CFC0 in both interpreters. OpenBIOS regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
95 lines
4.6 KiB
C
95 lines
4.6 KiB
C
#ifndef PGXP_HOOKS_H
|
|
#define PGXP_HOOKS_H
|
|
|
|
/* PGXP dataflow-shadowing hook surface (docs/ENHANCEMENTS.md G1.2/G1.3).
|
|
*
|
|
* The runtime keeps a host-only "shadow" of sub-pixel GTE projection results
|
|
* and follows them BY PROVENANCE as the game moves them through registers and
|
|
* RAM into a GP0 packet (pgxp.cpp). Generated code participates by calling
|
|
* these hooks after the guest operation completes.
|
|
*
|
|
* Gating model (the G1.3 decision): the emitter writes PGXP_*() macro
|
|
* invocations unconditionally; this header expands them to real calls only
|
|
* when the translation unit is compiled with -DPSX_PGXP=1 (the pgxp build
|
|
* variant). In the base variant the macros expand to ((void)0), so base
|
|
* objects compile to exactly the pre-feature code — the feature costs nothing
|
|
* unless a title links the pgxp target.
|
|
*
|
|
* RULES for emit sites:
|
|
* - Macro arguments MUST be side-effect free (they vanish in base builds).
|
|
* - Hooks run AFTER the guest architectural effect (value = what actually
|
|
* loaded/stored/resulted), so the shadow can validate against reality.
|
|
* - `cpu` must be in scope at every emit site (it always is in generated C).
|
|
*
|
|
* The interpreters (dirty_ram_interp.c, psx_interpreter.c) call the
|
|
* psx_pgxp_* functions directly — they are runtime code compiled once, and
|
|
* every hook early-outs on a single global when the feature is off.
|
|
*
|
|
* Overlay DLLs cannot link runtime symbols directly; they forward through the
|
|
* PGXPHooks table below (OverlayCallbacks.pgxp, overlay_api.h). A NULL table
|
|
* no-ops every hook — precision shadowing is a visual enhancement, never
|
|
* load-bearing.
|
|
*
|
|
* LICENSING: this is a clean-room implementation of the publicly documented
|
|
* PGXP technique (psx-spx, public design write-ups). Do NOT port code from
|
|
* the vendored duckstation/ (CC BY-NC-ND) or beetle-psx/ (GPL) trees; they
|
|
* are black-box behavioral oracles only. See docs/ENHANCEMENTS.md G1.
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
struct CPUState;
|
|
|
|
/* Funnel entry points (implemented in runtime/src/pgxp.cpp). The raw
|
|
* instruction word carries the op/rs/rt/rd/imm decode so the emitted call is
|
|
* uniform per class; `value` is the architectural result of the operation.
|
|
*
|
|
* load: rt <- [addr]; value = loaded word/half/byte (post-extension)
|
|
* store: [addr] <- rt; value = the stored word/half/byte
|
|
* alu: rd/rt <- op(s1, s2); result = the written value (also LUI, HILO
|
|
* moves, MOVE idioms and MFC0/CFC0 — the body decodes the class
|
|
* from instr)
|
|
* muldiv: HI/LO <- op(s1, s2)
|
|
* cop2: MFC2/CFC2/MTC2/CTC2 (addr = 0) and LWC2/SWC2 (addr = guest addr);
|
|
* value = the transferred word
|
|
*/
|
|
void psx_pgxp_load (struct CPUState *cpu, uint32_t instr, uint32_t addr, uint32_t value);
|
|
void psx_pgxp_store (struct CPUState *cpu, uint32_t instr, uint32_t addr, uint32_t value);
|
|
void psx_pgxp_alu (struct CPUState *cpu, uint32_t instr, uint32_t result, uint32_t s1, uint32_t s2);
|
|
void psx_pgxp_muldiv(struct CPUState *cpu, uint32_t instr, uint32_t hi, uint32_t lo, uint32_t s1, uint32_t s2);
|
|
void psx_pgxp_cop2 (struct CPUState *cpu, uint32_t instr, uint32_t value, uint32_t addr);
|
|
|
|
/* Forwarder table for overlay DLLs (OverlayCallbacks.pgxp). Appended-last
|
|
* member semantics apply: a NULL pointer (older host) means "no shadowing". */
|
|
typedef struct PGXPHooks {
|
|
void (*load) (struct CPUState *cpu, uint32_t instr, uint32_t addr, uint32_t value);
|
|
void (*store) (struct CPUState *cpu, uint32_t instr, uint32_t addr, uint32_t value);
|
|
void (*alu) (struct CPUState *cpu, uint32_t instr, uint32_t result, uint32_t s1, uint32_t s2);
|
|
void (*muldiv)(struct CPUState *cpu, uint32_t instr, uint32_t hi, uint32_t lo, uint32_t s1, uint32_t s2);
|
|
void (*cop2) (struct CPUState *cpu, uint32_t instr, uint32_t value, uint32_t addr);
|
|
} PGXPHooks;
|
|
|
|
#if defined(PSX_PGXP) && PSX_PGXP
|
|
#define PGXP_LOAD(instr, addr, val) psx_pgxp_load(cpu, (instr), (addr), (val))
|
|
#define PGXP_STORE(instr, addr, val) psx_pgxp_store(cpu, (instr), (addr), (val))
|
|
#define PGXP_ALU(instr, res, s1, s2) psx_pgxp_alu(cpu, (instr), (res), (s1), (s2))
|
|
#define PGXP_MULDIV(instr, hi, lo, s1, s2) psx_pgxp_muldiv(cpu, (instr), (hi), (lo), (s1), (s2))
|
|
#define PGXP_COP2(instr, val, addr) psx_pgxp_cop2(cpu, (instr), (val), (addr))
|
|
#else
|
|
#define PGXP_LOAD(instr, addr, val) ((void)0)
|
|
#define PGXP_STORE(instr, addr, val) ((void)0)
|
|
#define PGXP_ALU(instr, res, s1, s2) ((void)0)
|
|
#define PGXP_MULDIV(instr, hi, lo, s1, s2) ((void)0)
|
|
#define PGXP_COP2(instr, val, addr) ((void)0)
|
|
#endif
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* PGXP_HOOKS_H */
|