mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
PGXP: carry vertex fields through bitwise ops, keep depth, fix in-place aliasing
Hook every GPR-writing ALU op; rebase onto the 11-bit GP0 field. Spider-Man ORs clip flags into SXY2 before storing it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,11 +24,13 @@ inline int16_t get_imm16(uint32_t i) { return (int16_t)(i & 0xFFFF); }
|
||||
* the macros preprocess to ((void)0) and the optimizer erases the dead
|
||||
* capture locals, so base objects are unchanged; only a -DPSX_PGXP=1 TU pays.
|
||||
*
|
||||
* Deliberately unhooked: AND/XOR/NOR/SLT-family and the exotic immediates —
|
||||
* they only ever DESTROY precision, and the engine's validate-on-read drops
|
||||
* their stale shadows without help. The configured widescreen special sites
|
||||
* return early above translate_instruction's main dispatch and are likewise
|
||||
* unhooked (they are cull compares, not vertex moves; validation covers). */
|
||||
* Every GPR-writing ALU op is hooked. Bitwise ops (AND/OR/XOR/NOR, ANDI/ORI/
|
||||
* XORI) carry a vertex half whose 11-bit GPU field survives them - engines
|
||||
* pack clip flags above the field after projecting - and SLT-family results
|
||||
* are reset, so no destination keeps a stale shadow. The configured
|
||||
* widescreen special sites return early above translate_instruction's main
|
||||
* dispatch and are unhooked (they are cull compares, not vertex moves;
|
||||
* validation covers). */
|
||||
bool emission_ends_on_preprocessor_directive(const std::string& code) {
|
||||
/* Scan back to the start of the final line, then find its first
|
||||
* non-blank character. A directive is legal with leading whitespace, so
|
||||
@@ -89,7 +91,8 @@ void append_pgxp_hooks(uint32_t instr, std::string& code) {
|
||||
code, instr, reg_name(rs), reg_name(rt));
|
||||
return;
|
||||
case 0x20: case 0x21: case 0x22: case 0x23: /* ADD(U)/SUB(U) */
|
||||
case 0x25: /* OR */
|
||||
case 0x24: case 0x25: case 0x26: case 0x27: /* AND/OR/XOR/NOR */
|
||||
case 0x2A: case 0x2B: /* SLT/SLTU */
|
||||
if (rd == 0) return;
|
||||
code = fmt::format(
|
||||
"{{ uint32_t _pgx1 = {}; uint32_t _pgx2 = {}; {}\n "
|
||||
@@ -107,7 +110,8 @@ void append_pgxp_hooks(uint32_t instr, std::string& code) {
|
||||
reg_name(rs), code, instr, reg_name(rt),
|
||||
(uint32_t)(int32_t)offset);
|
||||
return;
|
||||
case 0x0D: /* ORI */
|
||||
case 0x0A: case 0x0B: /* SLTI / SLTIU */
|
||||
case 0x0C: case 0x0D: case 0x0E: /* ANDI / ORI / XORI */
|
||||
if (rt == 0) return;
|
||||
code = fmt::format(
|
||||
"{{ uint32_t _pgx1 = {}; {}\n PGXP_ALU(0x{:08X}u, {}, _pgx1, 0x{:04X}u); }}",
|
||||
|
||||
@@ -1877,10 +1877,13 @@ static int exec_one_fetched_inner(CPUState *cpu, uint32_t pc, uint32_t insn,
|
||||
cpu->gpr[0] = 0;
|
||||
return 0;
|
||||
}
|
||||
case 0x24: /* AND */
|
||||
cpu->gpr[rd] = cpu->gpr[rs] & cpu->gpr[rt];
|
||||
case 0x24: { /* AND */
|
||||
uint32_t a = cpu->gpr[rs], b = cpu->gpr[rt];
|
||||
cpu->gpr[rd] = a & b;
|
||||
psx_pgxp_alu(cpu, insn, cpu->gpr[rd], a, b);
|
||||
cpu->gpr[0] = 0;
|
||||
return 0;
|
||||
}
|
||||
case 0x25: { /* OR */
|
||||
uint32_t a = cpu->gpr[rs], b = cpu->gpr[rt];
|
||||
cpu->gpr[rd] = a | b;
|
||||
@@ -1888,14 +1891,20 @@ static int exec_one_fetched_inner(CPUState *cpu, uint32_t pc, uint32_t insn,
|
||||
cpu->gpr[0] = 0;
|
||||
return 0;
|
||||
}
|
||||
case 0x26: /* XOR */
|
||||
cpu->gpr[rd] = cpu->gpr[rs] ^ cpu->gpr[rt];
|
||||
case 0x26: { /* XOR */
|
||||
uint32_t a = cpu->gpr[rs], b = cpu->gpr[rt];
|
||||
cpu->gpr[rd] = a ^ b;
|
||||
psx_pgxp_alu(cpu, insn, cpu->gpr[rd], a, b);
|
||||
cpu->gpr[0] = 0;
|
||||
return 0;
|
||||
case 0x27: /* NOR */
|
||||
cpu->gpr[rd] = ~(cpu->gpr[rs] | cpu->gpr[rt]);
|
||||
}
|
||||
case 0x27: { /* NOR */
|
||||
uint32_t a = cpu->gpr[rs], b = cpu->gpr[rt];
|
||||
cpu->gpr[rd] = ~(a | b);
|
||||
psx_pgxp_alu(cpu, insn, cpu->gpr[rd], a, b);
|
||||
cpu->gpr[0] = 0;
|
||||
return 0;
|
||||
}
|
||||
case 0x2A: /* SLT */
|
||||
{
|
||||
uint32_t vanilla =
|
||||
@@ -1905,6 +1914,7 @@ static int exec_one_fetched_inner(CPUState *cpu, uint32_t pc, uint32_t insn,
|
||||
!psx_ws_aspect_cone_site(cpu, pc, insn, vanilla, &kept))
|
||||
(void)psx_ws_cull_keep_site(pc, insn, vanilla, &kept);
|
||||
cpu->gpr[rd] = kept;
|
||||
psx_pgxp_alu(cpu, insn, kept, 0, 0);
|
||||
cpu->gpr[0] = 0;
|
||||
return 0;
|
||||
}
|
||||
@@ -1915,6 +1925,7 @@ static int exec_one_fetched_inner(CPUState *cpu, uint32_t pc, uint32_t insn,
|
||||
if (!psx_ws_cull_scale_site(pc, insn, cpu->gpr[rs], cpu->gpr[rt], &kept))
|
||||
(void)psx_ws_cull_keep_site(pc, insn, vanilla, &kept);
|
||||
cpu->gpr[rd] = kept;
|
||||
psx_pgxp_alu(cpu, insn, kept, 0, 0);
|
||||
cpu->gpr[0] = 0;
|
||||
return 0;
|
||||
}
|
||||
@@ -2106,6 +2117,7 @@ static int exec_one_fetched_inner(CPUState *cpu, uint32_t pc, uint32_t insn,
|
||||
cpu->gpr[rt] = (uint32_t)psx_ws_cull_slti(cpu->gpr[rs], imm);
|
||||
else
|
||||
cpu->gpr[rt] = ((int32_t)cpu->gpr[rs] < simm) ? 1u : 0u;
|
||||
psx_pgxp_alu(cpu, insn, cpu->gpr[rt], 0, 0);
|
||||
cpu->gpr[0] = 0;
|
||||
return 0;
|
||||
}
|
||||
@@ -2135,13 +2147,17 @@ static int exec_one_fetched_inner(CPUState *cpu, uint32_t pc, uint32_t insn,
|
||||
cpu->gpr[rt] = (uint32_t)psx_ws_cull_sltiu(cpu->gpr[rs], imm);
|
||||
else
|
||||
cpu->gpr[rt] = (cpu->gpr[rs] < (uint32_t)simm) ? 1u : 0u;
|
||||
psx_pgxp_alu(cpu, insn, cpu->gpr[rt], 0, 0);
|
||||
cpu->gpr[0] = 0;
|
||||
return 0;
|
||||
}
|
||||
case 0x0C: /* ANDI */
|
||||
cpu->gpr[rt] = cpu->gpr[rs] & imm;
|
||||
case 0x0C: { /* ANDI */
|
||||
uint32_t a = cpu->gpr[rs];
|
||||
cpu->gpr[rt] = a & imm;
|
||||
psx_pgxp_alu(cpu, insn, cpu->gpr[rt], a, imm);
|
||||
cpu->gpr[0] = 0;
|
||||
return 0;
|
||||
}
|
||||
case 0x0D: { /* ORI */
|
||||
uint32_t a = cpu->gpr[rs];
|
||||
if (rs == 0 && rt != 0 && psx_ws_is_signed_x_bound_site(pc, insn))
|
||||
@@ -2152,10 +2168,13 @@ static int exec_one_fetched_inner(CPUState *cpu, uint32_t pc, uint32_t insn,
|
||||
cpu->gpr[0] = 0;
|
||||
return 0;
|
||||
}
|
||||
case 0x0E: /* XORI */
|
||||
cpu->gpr[rt] = cpu->gpr[rs] ^ imm;
|
||||
case 0x0E: { /* XORI */
|
||||
uint32_t a = cpu->gpr[rs];
|
||||
cpu->gpr[rt] = a ^ imm;
|
||||
psx_pgxp_alu(cpu, insn, cpu->gpr[rt], a, imm);
|
||||
cpu->gpr[0] = 0;
|
||||
return 0;
|
||||
}
|
||||
case 0x0F: /* LUI rt, imm */
|
||||
if (psx_ws_is_signed_x_bound_site(pc, insn))
|
||||
cpu->gpr[rt] = (uint32_t)psx_ws_player_x_bound((int32_t)(imm << 16));
|
||||
|
||||
+162
-100
@@ -419,17 +419,95 @@ static inline int halves_independent(uint32_t a, uint32_t b, uint32_t r, int sub
|
||||
((lo >> 16) == 0); /* no carry/borrow out of low */
|
||||
}
|
||||
|
||||
/* Bitwise ops (AND/OR/XOR/NOR and ANDI/ORI/XORI) move bits; they do not
|
||||
* compute coordinates. GP0 decodes only the 11-bit field of each vertex half
|
||||
* (bits 0..10 of X, 16..26 of Y), and engines pack clip / outcode bits into
|
||||
* the rest of the word after projecting (Spider-Man ORs off-screen flags into
|
||||
* bits 14/15 of each half of SXY2 before storing it). A result half whose GPU
|
||||
* field equals the field of a tracked operand half therefore still describes
|
||||
* that vertex. Carrying it is exact - no arithmetic, like a move - so it runs
|
||||
* in both tiers. The carried value keeps the shadow invariant (integer part
|
||||
* == the half read as int16): the fraction rides along, the integer becomes
|
||||
* the result half. The GPU consumer rebases onto the field it decodes. */
|
||||
static inline int half_exact(const PGXPValue *pv, uint32_t value, int hi_half) {
|
||||
if (!comp_tracked(pv, value, hi_half)) return 0;
|
||||
int32_t v16 = hi_half ? pv->y16 : pv->x16;
|
||||
/* A saturated projection shadow sits beyond the clamped word; it is not
|
||||
* the value of that half and must not be carried as one. */
|
||||
return (v16 >> 16) == (int32_t)(int16_t)(hi_half ? (value >> 16) : value);
|
||||
}
|
||||
|
||||
static inline int carry_field(PGXPValue *dst, const PGXPValue *src,
|
||||
uint32_t sval, uint32_t result, int hi_half) {
|
||||
if (!half_exact(src, sval, hi_half)) return 0;
|
||||
uint32_t sh = hi_half ? 16u : 0u;
|
||||
if ((((sval ^ result) >> sh) & 0x7FFu) != 0) return 0;
|
||||
int32_t frac = (hi_half ? src->y16 : src->x16) & 0xFFFF;
|
||||
int32_t v = (int32_t)((uint32_t)(int32_t)(int16_t)(result >> sh) << 16) | frac;
|
||||
if (hi_half) { dst->y16 = v; dst->flags |= PGXP_F_VY; }
|
||||
else { dst->x16 = v; dst->flags |= PGXP_F_VX; }
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline int has_depth(const PGXPValue *pv) {
|
||||
return pv && pv->gen == s_gen && (pv->flags & PGXP_F_VZ) && pv->z != 0;
|
||||
}
|
||||
|
||||
/* Depth belongs to a whole vertex: it survives an op only when both halves of
|
||||
* the result came from one operand that carried it, and the other operand is
|
||||
* not itself a vertex (a constant, a mask, an offset). */
|
||||
static inline void keep_depth(PGXPValue *dst, const PGXPValue *p,
|
||||
const PGXPValue *other) {
|
||||
if ((dst->flags & PGXP_F_VXY) == PGXP_F_VXY && has_depth(p) &&
|
||||
!has_depth(other)) {
|
||||
dst->z = p->z;
|
||||
dst->flags |= PGXP_F_VZ;
|
||||
}
|
||||
}
|
||||
|
||||
static void alu_bitwise(PGXPValue *dst, const PGXPValue *a, uint32_t s1,
|
||||
const PGXPValue *b, uint32_t s2, uint32_t result) {
|
||||
pv_reset(dst, result);
|
||||
/* Prefer the operand that is a whole vertex, so a vertex ORed with flags
|
||||
* keeps its depth even when the flag word happens to share a field. */
|
||||
const PGXPValue *p = a; uint32_t pval = s1;
|
||||
const PGXPValue *q = b; uint32_t qval = s2;
|
||||
if (!has_depth(a) && has_depth(b)) { p = b; pval = s2; q = a; qval = s1; }
|
||||
const PGXPValue *from[2] = { nullptr, nullptr };
|
||||
for (int hi = 0; hi < 2; hi++) {
|
||||
if (carry_field(dst, p, pval, result, hi)) from[hi] = p;
|
||||
else if (carry_field(dst, q, qval, result, hi)) from[hi] = q;
|
||||
else if (((hi ? result >> 16 : result) & 0xFFFFu) == 0) {
|
||||
/* An exactly-zero half is an exact coordinate (the repack
|
||||
* pattern ORs a shifted half into a zero half). */
|
||||
if (hi) { dst->y16 = 0; dst->flags |= PGXP_F_VY; }
|
||||
else { dst->x16 = 0; dst->flags |= PGXP_F_VX; }
|
||||
}
|
||||
}
|
||||
if (from[0] && from[0] == from[1])
|
||||
keep_depth(dst, from[0], from[0] == p ? q : p);
|
||||
}
|
||||
|
||||
static const PGXPValue kNoShadow = { 0, 0, 0, 0, 0, 0 };
|
||||
|
||||
/* Snapshot a source GPR shadow: the destination may alias a source (in-place
|
||||
* ops such as `or t2, t2, t9` or `addiu t0, t0, 4`), and it is reset before
|
||||
* the sources are read. */
|
||||
static inline const PGXPValue *gpr_shadow(uint32_t r, PGXPValue *copy) {
|
||||
*copy = (r != 0) ? s_gpr[r] : kNoShadow;
|
||||
return copy;
|
||||
}
|
||||
|
||||
extern "C" void psx_pgxp_alu(struct CPUState *cpu, uint32_t instr,
|
||||
uint32_t result, uint32_t s1, uint32_t s2) {
|
||||
(void)cpu;
|
||||
if (!g_pgxp_active) return;
|
||||
|
||||
uint32_t op = f_op(instr);
|
||||
uint32_t dst_reg;
|
||||
PGXPValue *a = nullptr, *b = nullptr; /* source shadows (may be 0) */
|
||||
PGXPValue ca, cb;
|
||||
|
||||
if (op == 0) { /* SPECIAL */
|
||||
dst_reg = f_rd(instr);
|
||||
uint32_t dst_reg = f_rd(instr);
|
||||
uint32_t rs = f_rs(instr), rt = f_rt(instr);
|
||||
uint32_t funct = f_funct(instr);
|
||||
if (funct == 0x11 || funct == 0x13) { /* MTHI / MTLO */
|
||||
@@ -445,38 +523,31 @@ extern "C" void psx_pgxp_alu(struct CPUState *cpu, uint32_t instr,
|
||||
|
||||
switch (funct) {
|
||||
case 0x10: /* MFHI */
|
||||
if (s_cpu_mode && s_gpr[PGXP_REG_HI].gen == s_gen) {
|
||||
pv_validate(&s_gpr[PGXP_REG_HI], result);
|
||||
*dst = s_gpr[PGXP_REG_HI];
|
||||
} else pv_reset(dst, result);
|
||||
return;
|
||||
case 0x12: /* MFLO */
|
||||
if (s_cpu_mode && s_gpr[PGXP_REG_LO].gen == s_gen) {
|
||||
pv_validate(&s_gpr[PGXP_REG_LO], result);
|
||||
*dst = s_gpr[PGXP_REG_LO];
|
||||
case 0x12: { /* MFLO */
|
||||
PGXPValue *hl = &s_gpr[funct == 0x10 ? PGXP_REG_HI : PGXP_REG_LO];
|
||||
if (s_cpu_mode && hl->gen == s_gen) {
|
||||
pv_validate(hl, result);
|
||||
*dst = *hl;
|
||||
} else pv_reset(dst, result);
|
||||
return;
|
||||
}
|
||||
case 0x00: case 0x02: case 0x03: /* SLL / SRL / SRA */
|
||||
case 0x04: case 0x06: case 0x07: { /* SLLV / SRLV / SRAV */
|
||||
uint32_t sh = (funct < 4) ? f_shamt(instr) : (s2 & 31u);
|
||||
uint32_t src_reg = rt;
|
||||
PGXPValue *src = (src_reg != 0) ? &s_gpr[src_reg] : nullptr;
|
||||
const PGXPValue *src = gpr_shadow(rt, &ca);
|
||||
int right = (funct & 3u) != 0;
|
||||
if (!s_cpu_mode || sh != 16 || !src || src->gen != s_gen) {
|
||||
pv_reset(dst, result);
|
||||
return;
|
||||
}
|
||||
pv_validate(src, s1);
|
||||
pv_reset(dst, result);
|
||||
if (!s_cpu_mode || sh != 16 || src->gen != s_gen)
|
||||
return;
|
||||
if (!right) { /* << 16: low comp -> Y */
|
||||
if (src->flags & PGXP_F_VX) {
|
||||
if (comp_tracked(src, s1, 0)) {
|
||||
dst->y16 = src->x16;
|
||||
dst->flags |= PGXP_F_VY;
|
||||
}
|
||||
dst->x16 = 0;
|
||||
dst->flags |= PGXP_F_VX; /* low half exactly zero */
|
||||
} else { /* >> 16: high comp -> X */
|
||||
if (src->flags & PGXP_F_VY) {
|
||||
if (comp_tracked(src, s1, 1)) {
|
||||
dst->x16 = src->y16;
|
||||
dst->flags |= PGXP_F_VX;
|
||||
}
|
||||
@@ -485,52 +556,36 @@ extern "C" void psx_pgxp_alu(struct CPUState *cpu, uint32_t instr,
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x20: case 0x21: /* ADD / ADDU */
|
||||
case 0x22: case 0x23: /* SUB / SUBU */
|
||||
case 0x25: { /* OR */
|
||||
/* MOVE idioms first — they matter even without full cpu-mode
|
||||
* arithmetic, and they are exact. */
|
||||
int is_or = (funct == 0x25);
|
||||
int is_sub = (funct == 0x22 || funct == 0x23);
|
||||
if ((is_or || !is_sub) && rt == 0) { /* op rd, rs, $zero */
|
||||
if (rs != 0 && s_gpr[rs].gen == s_gen) {
|
||||
pv_validate(&s_gpr[rs], result);
|
||||
*dst = s_gpr[rs];
|
||||
case 0x24: case 0x25: case 0x26: case 0x27: { /* AND/OR/XOR/NOR */
|
||||
const PGXPValue *a = gpr_shadow(rs, &ca);
|
||||
const PGXPValue *b = gpr_shadow(rt, &cb);
|
||||
/* OR with $zero is the MOVE idiom: the whole shadow, depth too. */
|
||||
if (funct == 0x25 && (rs == 0 || rt == 0)) {
|
||||
const PGXPValue *m = (rt == 0) ? a : b;
|
||||
if (m->gen == s_gen) {
|
||||
*dst = *m;
|
||||
pv_validate(dst, result);
|
||||
} else pv_reset(dst, result);
|
||||
return;
|
||||
}
|
||||
if ((is_or || !is_sub) && rs == 0) { /* op rd, $zero, rt */
|
||||
if (rt != 0 && s_gpr[rt].gen == s_gen) {
|
||||
pv_validate(&s_gpr[rt], result);
|
||||
*dst = s_gpr[rt];
|
||||
alu_bitwise(dst, a, s1, b, s2, result);
|
||||
return;
|
||||
}
|
||||
case 0x20: case 0x21: /* ADD / ADDU */
|
||||
case 0x22: case 0x23: { /* SUB / SUBU */
|
||||
const PGXPValue *a = gpr_shadow(rs, &ca);
|
||||
const PGXPValue *b = gpr_shadow(rt, &cb);
|
||||
int is_sub = (funct == 0x22 || funct == 0x23);
|
||||
/* MOVE idioms first - exact, so they run in both tiers. */
|
||||
if (rt == 0 || (!is_sub && rs == 0)) {
|
||||
const PGXPValue *m = (rt == 0) ? a : b;
|
||||
if (m->gen == s_gen) {
|
||||
*dst = *m;
|
||||
pv_validate(dst, result);
|
||||
} else pv_reset(dst, result);
|
||||
return;
|
||||
}
|
||||
if (!s_cpu_mode) { pv_reset(dst, result); return; }
|
||||
a = (rs != 0) ? &s_gpr[rs] : nullptr;
|
||||
b = (rt != 0) ? &s_gpr[rt] : nullptr;
|
||||
if (is_or) {
|
||||
/* Merge disjoint halves (the lhu/sll/or repack pattern). */
|
||||
if ((s1 & s2) != 0) { pv_reset(dst, result); return; }
|
||||
pv_reset(dst, result);
|
||||
const PGXPValue *xsrc; uint32_t xval;
|
||||
if ((s1 & 0xFFFFu) != 0) { xsrc = a; xval = s1; }
|
||||
else { xsrc = b; xval = s2; }
|
||||
const PGXPValue *ysrc; uint32_t yval;
|
||||
if ((s1 & 0xFFFF0000u) != 0) { ysrc = a; yval = s1; }
|
||||
else { ysrc = b; yval = s2; }
|
||||
if (comp_tracked(xsrc, xval, 0)) {
|
||||
dst->x16 = xsrc->x16; dst->flags |= PGXP_F_VX;
|
||||
} else if ((result & 0xFFFFu) == 0) {
|
||||
dst->x16 = 0; dst->flags |= PGXP_F_VX;
|
||||
}
|
||||
if (comp_tracked(ysrc, yval, 1)) {
|
||||
dst->y16 = ysrc->y16; dst->flags |= PGXP_F_VY;
|
||||
} else if ((result & 0xFFFF0000u) == 0) {
|
||||
dst->y16 = 0; dst->flags |= PGXP_F_VY;
|
||||
}
|
||||
return;
|
||||
}
|
||||
/* add/sub: require at least one tracked component and halves
|
||||
* that combined independently (no cross-half carry). */
|
||||
if (!halves_independent(s1, s2, result, is_sub) ||
|
||||
@@ -545,19 +600,24 @@ extern "C" void psx_pgxp_alu(struct CPUState *cpu, uint32_t instr,
|
||||
dst->y16 = is_sub ? comp16(a, s1, 1) - comp16(b, s2, 1)
|
||||
: comp16(a, s1, 1) + comp16(b, s2, 1);
|
||||
dst->flags = PGXP_F_VXY;
|
||||
/* A vertex plus / minus an offset is still that vertex. */
|
||||
if (comp_tracked(a, s1, 0) && comp_tracked(a, s1, 1))
|
||||
keep_depth(dst, a, b);
|
||||
else if (!is_sub && comp_tracked(b, s2, 0) && comp_tracked(b, s2, 1))
|
||||
keep_depth(dst, b, a);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
default: /* SLT/SLTU/...: not a vertex */
|
||||
pv_reset(dst, result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* immediates */
|
||||
dst_reg = f_rt(instr);
|
||||
uint32_t dst_reg = f_rt(instr);
|
||||
if (dst_reg == 0) return;
|
||||
PGXPValue *dst = &s_gpr[dst_reg];
|
||||
uint32_t rs = f_rs(instr);
|
||||
const PGXPValue *a = gpr_shadow(f_rs(instr), &ca);
|
||||
|
||||
switch (op) {
|
||||
case 0x0F: { /* LUI: both halves exact */
|
||||
@@ -570,54 +630,43 @@ extern "C" void psx_pgxp_alu(struct CPUState *cpu, uint32_t instr,
|
||||
case 0x08: case 0x09: { /* ADDI / ADDIU */
|
||||
int32_t imm = f_simm(instr);
|
||||
if (imm == 0) { /* MOVE idiom */
|
||||
if (rs != 0 && s_gpr[rs].gen == s_gen) {
|
||||
pv_validate(&s_gpr[rs], result);
|
||||
*dst = s_gpr[rs];
|
||||
if (a->gen == s_gen) {
|
||||
*dst = *a;
|
||||
pv_validate(dst, result);
|
||||
} else pv_reset(dst, result);
|
||||
return;
|
||||
}
|
||||
a = (rs != 0) ? &s_gpr[rs] : nullptr;
|
||||
/* A negative immediate is a subtraction of its magnitude: checking
|
||||
* carry on the sign-extended form would reject nearly every -N. */
|
||||
{
|
||||
int neg = imm < 0;
|
||||
uint32_t mag = (uint32_t)(neg ? -imm : imm);
|
||||
if (!s_cpu_mode ||
|
||||
!halves_independent(s1, mag, result, neg) ||
|
||||
(!comp_tracked(a, s1, 0) && !comp_tracked(a, s1, 1))) {
|
||||
pv_reset(dst, result);
|
||||
return;
|
||||
}
|
||||
int32_t dx = (int32_t)(mag & 0xFFFFu) << 16;
|
||||
int neg = imm < 0;
|
||||
uint32_t mag = (uint32_t)(neg ? -imm : imm);
|
||||
if (!s_cpu_mode ||
|
||||
!halves_independent(s1, mag, result, neg) ||
|
||||
(!comp_tracked(a, s1, 0) && !comp_tracked(a, s1, 1))) {
|
||||
pv_reset(dst, result);
|
||||
dst->x16 = comp16(a, s1, 0) + (neg ? -dx : dx);
|
||||
dst->y16 = comp16(a, s1, 1); /* no carry crossed: Y untouched */
|
||||
dst->flags = PGXP_F_VXY;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 0x0D: { /* ORI */
|
||||
uint32_t imm = instr & 0xFFFFu;
|
||||
if (imm == 0 || (s_cpu_mode && (s1 & 0xFFFFu) == 0)) {
|
||||
a = (rs != 0) ? &s_gpr[rs] : nullptr;
|
||||
pv_reset(dst, result);
|
||||
if (comp_tracked(a, s1, 1)) {
|
||||
dst->y16 = a->y16; dst->flags |= PGXP_F_VY;
|
||||
}
|
||||
if (imm == 0) {
|
||||
if (comp_tracked(a, s1, 0)) {
|
||||
dst->x16 = a->x16; dst->flags |= PGXP_F_VX;
|
||||
}
|
||||
} else { /* exact constant low half */
|
||||
dst->x16 = ((int32_t)(int16_t)imm) << 16;
|
||||
dst->flags |= PGXP_F_VX;
|
||||
}
|
||||
return;
|
||||
}
|
||||
int32_t dx = (int32_t)(mag & 0xFFFFu) << 16;
|
||||
pv_reset(dst, result);
|
||||
dst->x16 = comp16(a, s1, 0) + (neg ? -dx : dx);
|
||||
dst->y16 = comp16(a, s1, 1); /* no carry crossed: Y untouched */
|
||||
dst->flags = PGXP_F_VXY;
|
||||
if (comp_tracked(a, s1, 0) && comp_tracked(a, s1, 1))
|
||||
keep_depth(dst, a, nullptr);
|
||||
return;
|
||||
}
|
||||
default: /* ANDI/XORI/SLTI/... */
|
||||
case 0x0C: case 0x0D: case 0x0E: { /* ANDI / ORI / XORI */
|
||||
uint32_t imm = instr & 0xFFFFu;
|
||||
alu_bitwise(dst, a, s1, nullptr, imm, result);
|
||||
/* ORI into a zero low half writes an exact constant there (the
|
||||
* lui/ori and repack idioms). */
|
||||
if (op == 0x0D && !(dst->flags & PGXP_F_VX) && (s1 & 0xFFFFu) == 0) {
|
||||
dst->x16 = ((int32_t)(int16_t)imm) << 16;
|
||||
dst->flags |= PGXP_F_VX;
|
||||
}
|
||||
return;
|
||||
}
|
||||
default: /* SLTI/SLTIU: not a vertex */
|
||||
pv_reset(dst, result);
|
||||
return;
|
||||
}
|
||||
@@ -686,6 +735,13 @@ extern "C" void pgxp_gte_reg_written(int reg, uint32_t value) {
|
||||
/* GPU consumer */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
static inline int32_t field_rebase(int32_t v16, uint32_t half, int32_t parsed) {
|
||||
int32_t as16 = (int16_t)half;
|
||||
int32_t as11 = ((int32_t)(half << 21)) >> 21;
|
||||
if (parsed != as11 || parsed == as16) return v16;
|
||||
return (int32_t)((int64_t)v16 + ((int64_t)as11 - as16) * 65536);
|
||||
}
|
||||
|
||||
extern "C" int pgxp_get_precise_vertex(uint32_t addr, uint32_t packet_word,
|
||||
int32_t int_x, int32_t int_y,
|
||||
int32_t *x16, int32_t *y16,
|
||||
@@ -717,6 +773,12 @@ extern "C" int pgxp_get_precise_vertex(uint32_t addr, uint32_t packet_word,
|
||||
}
|
||||
|
||||
if (have) {
|
||||
/* A shadow describes each half read as int16; GP0 decodes only its
|
||||
* 11-bit field (engines keep clip / outcode bits above it). When the
|
||||
* caller parsed the field, rebase the precise value onto it - the
|
||||
* fraction is unchanged. Any other integer is caught below. */
|
||||
px = field_rebase(px, packet_word & 0xFFFFu, int_x);
|
||||
py = field_rebase(py, packet_word >> 16, int_y);
|
||||
/* Truncation agreement: the GPU parsed 11-bit integers out of the
|
||||
* packet; a precise position whose integer part disagrees (a
|
||||
* wrapped/CPU-modified coordinate) must not be believed. */
|
||||
|
||||
@@ -283,13 +283,18 @@ static void exec_one(CPUState* cpu) {
|
||||
}
|
||||
case 0x23: set_reg(cpu, rd, rs_val - rt_val);
|
||||
psx_pgxp_alu(cpu, insn, cpu->gpr[rd], rs_val, rt_val); break; /* SUBU */
|
||||
case 0x24: set_reg(cpu, rd, rs_val & rt_val); break; /* AND */
|
||||
case 0x24: set_reg(cpu, rd, rs_val & rt_val);
|
||||
psx_pgxp_alu(cpu, insn, cpu->gpr[rd], rs_val, rt_val); break; /* AND */
|
||||
case 0x25: set_reg(cpu, rd, rs_val | rt_val);
|
||||
psx_pgxp_alu(cpu, insn, cpu->gpr[rd], rs_val, rt_val); break; /* OR */
|
||||
case 0x26: set_reg(cpu, rd, rs_val ^ rt_val); break; /* XOR */
|
||||
case 0x27: set_reg(cpu, rd, ~(rs_val | rt_val)); break; /* NOR */
|
||||
case 0x2A: set_reg(cpu, rd, (int32_t)rs_val < (int32_t)rt_val ? 1 : 0); break; /* SLT */
|
||||
case 0x2B: set_reg(cpu, rd, rs_val < rt_val ? 1 : 0); break; /* SLTU */
|
||||
case 0x26: set_reg(cpu, rd, rs_val ^ rt_val);
|
||||
psx_pgxp_alu(cpu, insn, cpu->gpr[rd], rs_val, rt_val); break; /* XOR */
|
||||
case 0x27: set_reg(cpu, rd, ~(rs_val | rt_val));
|
||||
psx_pgxp_alu(cpu, insn, cpu->gpr[rd], rs_val, rt_val); break; /* NOR */
|
||||
case 0x2A: set_reg(cpu, rd, (int32_t)rs_val < (int32_t)rt_val ? 1 : 0);
|
||||
psx_pgxp_alu(cpu, insn, cpu->gpr[rd], rs_val, rt_val); break; /* SLT */
|
||||
case 0x2B: set_reg(cpu, rd, rs_val < rt_val ? 1 : 0);
|
||||
psx_pgxp_alu(cpu, insn, cpu->gpr[rd], rs_val, rt_val); break; /* SLTU */
|
||||
default:
|
||||
/* R3000A ignores undefined SPECIAL encodings (no trap). */
|
||||
break;
|
||||
@@ -363,12 +368,16 @@ static void exec_one(CPUState* cpu) {
|
||||
}
|
||||
case 0x09: set_reg(cpu, RT(insn), rs_val + (uint32_t)SIMM(insn));
|
||||
psx_pgxp_alu(cpu, insn, cpu->gpr[RT(insn)], rs_val, (uint32_t)SIMM(insn)); break; /* ADDIU */
|
||||
case 0x0A: set_reg(cpu, RT(insn), (int32_t)rs_val < SIMM(insn) ? 1 : 0); break; /* SLTI */
|
||||
case 0x0B: set_reg(cpu, RT(insn), rs_val < (uint32_t)SIMM(insn) ? 1 : 0); break; /* SLTIU */
|
||||
case 0x0C: set_reg(cpu, RT(insn), rs_val & IMM16(insn)); break; /* ANDI */
|
||||
case 0x0A: set_reg(cpu, RT(insn), (int32_t)rs_val < SIMM(insn) ? 1 : 0);
|
||||
psx_pgxp_alu(cpu, insn, cpu->gpr[RT(insn)], rs_val, (uint32_t)SIMM(insn)); break; /* SLTI */
|
||||
case 0x0B: set_reg(cpu, RT(insn), rs_val < (uint32_t)SIMM(insn) ? 1 : 0);
|
||||
psx_pgxp_alu(cpu, insn, cpu->gpr[RT(insn)], rs_val, (uint32_t)SIMM(insn)); break; /* SLTIU */
|
||||
case 0x0C: set_reg(cpu, RT(insn), rs_val & IMM16(insn));
|
||||
psx_pgxp_alu(cpu, insn, cpu->gpr[RT(insn)], rs_val, IMM16(insn)); break; /* ANDI */
|
||||
case 0x0D: set_reg(cpu, RT(insn), rs_val | IMM16(insn));
|
||||
psx_pgxp_alu(cpu, insn, cpu->gpr[RT(insn)], rs_val, IMM16(insn)); break; /* ORI */
|
||||
case 0x0E: set_reg(cpu, RT(insn), rs_val ^ IMM16(insn)); break; /* XORI */
|
||||
case 0x0E: set_reg(cpu, RT(insn), rs_val ^ IMM16(insn));
|
||||
psx_pgxp_alu(cpu, insn, cpu->gpr[RT(insn)], rs_val, IMM16(insn)); break; /* XORI */
|
||||
case 0x0F: set_reg(cpu, RT(insn), (uint32_t)IMM16(insn) << 16);
|
||||
psx_pgxp_alu(cpu, insn, cpu->gpr[RT(insn)], 0, 0); break; /* LUI */
|
||||
|
||||
|
||||
@@ -63,6 +63,12 @@ static uint32_t enc_cop2(uint32_t sub, uint32_t rt, uint32_t rd) {
|
||||
#define SRA(rt, rd, sh) enc_r(0, rt, rd, sh, 0x03)
|
||||
#define OR(rs, rt, rd) enc_r(rs, rt, rd, 0, 0x25)
|
||||
#define ADDU(rs, rt, rd) enc_r(rs, rt, rd, 0, 0x21)
|
||||
#define SUBU(rs, rt, rd) enc_r(rs, rt, rd, 0, 0x23)
|
||||
#define AND(rs, rt, rd) enc_r(rs, rt, rd, 0, 0x24)
|
||||
#define NOR(rs, rt, rd) enc_r(rs, rt, rd, 0, 0x27)
|
||||
#define SLT(rs, rt, rd) enc_r(rs, rt, rd, 0, 0x2A)
|
||||
#define ANDI(rs, rt, imm) enc_i(0x0C, rs, rt, (uint16_t)(imm))
|
||||
#define ORI(rs, rt, imm) enc_i(0x0D, rs, rt, (uint16_t)(imm))
|
||||
|
||||
/* One projected vertex: x = 160.5, y = 80.25 -> packed integer word. */
|
||||
static const uint32_t PACKED = (80u << 16) | 160u;
|
||||
@@ -213,6 +219,90 @@ int main(void) {
|
||||
CHECK(lookup(ADDR_B, (PACKED >> 16) << 16, 0, 80, nullptr, nullptr,
|
||||
nullptr) == PGXP_SRC_NATIVE);
|
||||
|
||||
/* --- clip flags packed above the GPU field (Spider-Man 0x8007F31C):
|
||||
* mfc2 t2,SXY2; subu t9,t2,t4; nor t9,t9,t5; and t2,t2,t5; or t2,t2,t9
|
||||
* ... and t2,t2,a3; or t2,t2,t9'; sw t2
|
||||
* The flag ops rewrite bits 14/15 and 30/31 of the word, never the 11-bit
|
||||
* fields GP0 decodes, so the vertex (and its depth) must survive - in
|
||||
* BOTH tiers, since bitwise carries are exact. --- */
|
||||
for (int mode = 0; mode < 2; mode++) {
|
||||
pgxp_set_cpu_mode(mode);
|
||||
const uint32_t T2 = 10, T4 = 12, T5 = 13, T9 = 25, A3 = 7;
|
||||
const uint32_t bounds = (240u << 16) | 128u; /* x >= 128 -> flag */
|
||||
const uint32_t m14 = 0xBFFFBFFFu, m15 = 0x7FFF7FFFu;
|
||||
pgxp_gte_push_sxy(X16, Y16, SZ3, PACKED);
|
||||
psx_pgxp_cop2(nullptr, MFC2(T2, 14), PACKED, 0);
|
||||
psx_pgxp_load(nullptr, LW(1, T4), 0x80180000u, bounds); /* untracked */
|
||||
psx_pgxp_alu(nullptr, LUI(T5, 0xBFFF), 0xBFFF0000u, 0, 0);
|
||||
psx_pgxp_alu(nullptr, ORI(T5, T5, 0xBFFF), m14, 0xBFFF0000u, 0xBFFFu);
|
||||
uint32_t d = PACKED - bounds;
|
||||
psx_pgxp_alu(nullptr, SUBU(T2, T4, T9), d, PACKED, bounds);
|
||||
uint32_t f = ~(d | m14);
|
||||
psx_pgxp_alu(nullptr, NOR(T9, T5, T9), f, d, m14);
|
||||
uint32_t w = PACKED & m14;
|
||||
psx_pgxp_alu(nullptr, AND(T2, T5, T2), w, PACKED, m14);
|
||||
psx_pgxp_alu(nullptr, OR(T2, T9, T2), w | f, w, f);
|
||||
w |= f;
|
||||
CHECK(w == (PACKED | 0x4000u)); /* x flagged */
|
||||
psx_pgxp_alu(nullptr, AND(T2, A3, T2), w & m15, w, m15);
|
||||
w &= m15;
|
||||
psx_pgxp_load(nullptr, LW(1, T9), 0x80180004u, 0x80000000u);
|
||||
psx_pgxp_alu(nullptr, OR(T2, T9, T2), w | 0x80000000u, w, 0x80000000u);
|
||||
w |= 0x80000000u;
|
||||
psx_pgxp_store(nullptr, SW(1, T2), ADDR_B, w);
|
||||
int32_t x, y; uint16_t z;
|
||||
CHECK(lookup(ADDR_B, w, 160, 80, &x, &y, &z) == PGXP_SRC_DATAFLOW);
|
||||
CHECK(x == X16 && y == Y16 && z == SZ3);
|
||||
uint16_t wz = 0;
|
||||
CHECK(pgxp_load_precise_word(ADDR_B, w, nullptr, nullptr, &wz) == 1);
|
||||
CHECK(wz == SZ3);
|
||||
}
|
||||
pgxp_set_cpu_mode(0);
|
||||
|
||||
/* --- in-place ops read their source before the destination is reset --- */
|
||||
pgxp_set_cpu_mode(1);
|
||||
produce_at(ADDR_A);
|
||||
psx_pgxp_load(nullptr, LW(1, 8), ADDR_A, PACKED);
|
||||
psx_pgxp_alu(nullptr, ADDIU(8, 8, 4), PACKED + 4u, PACKED, 4u);
|
||||
psx_pgxp_store(nullptr, SW(1, 8), ADDR_B, PACKED + 4u);
|
||||
{
|
||||
int32_t x, y; uint16_t z;
|
||||
CHECK(lookup(ADDR_B, PACKED + 4u, 164, 80, &x, &y, &z) ==
|
||||
PGXP_SRC_DATAFLOW);
|
||||
CHECK(x == X16 + (4 << 16) && y == Y16);
|
||||
CHECK(z == SZ3); /* vertex + offset keeps depth */
|
||||
}
|
||||
pgxp_set_cpu_mode(0);
|
||||
|
||||
/* --- a mask that changes the GPU field does not carry that half --- */
|
||||
produce_at(ADDR_A);
|
||||
psx_pgxp_load(nullptr, LW(1, 8), ADDR_A, PACKED);
|
||||
psx_pgxp_alu(nullptr, ANDI(8, 8, 0x0080), PACKED & 0x80u, PACKED, 0x80u);
|
||||
psx_pgxp_store(nullptr, SW(1, 8), ADDR_B, PACKED & 0x80u);
|
||||
CHECK(lookup(ADDR_B, PACKED & 0x80u, 128, 0, nullptr, nullptr, nullptr) !=
|
||||
PGXP_SRC_DATAFLOW);
|
||||
CHECK(pgxp_load_precise_word(ADDR_B, PACKED & 0x80u, nullptr, nullptr,
|
||||
nullptr) == 0);
|
||||
|
||||
/* --- a saturated projection is not that half's value: never carried --- */
|
||||
{
|
||||
const uint32_t sat = (80u << 16) | 1023u; /* x clamped */
|
||||
pgxp_gte_push_sxy(2000 << 16, Y16, SZ3, sat);
|
||||
psx_pgxp_cop2(nullptr, MFC2(10, 14), sat, 0);
|
||||
psx_pgxp_load(nullptr, LW(1, 25), 0x80180008u, 0); /* untracked 0 */
|
||||
psx_pgxp_alu(nullptr, OR(10, 25, 10), sat, sat, 0);
|
||||
psx_pgxp_store(nullptr, SW(1, 10), ADDR_B, sat);
|
||||
CHECK(pgxp_load_precise_word(ADDR_B, sat, nullptr, nullptr, nullptr) == 0);
|
||||
}
|
||||
|
||||
/* --- SLT-family results are not vertices: the destination resets --- */
|
||||
pgxp_gte_push_sxy(X16, Y16, SZ3, PACKED);
|
||||
psx_pgxp_cop2(nullptr, MFC2(10, 14), PACKED, 0);
|
||||
psx_pgxp_alu(nullptr, SLT(0, 10, 10), 1u, 0, PACKED);
|
||||
psx_pgxp_store(nullptr, SW(1, 10), ADDR_B, 1u);
|
||||
CHECK(lookup(ADDR_B, 1u, 1, 0, nullptr, nullptr, nullptr) ==
|
||||
PGXP_SRC_NATIVE);
|
||||
|
||||
/* --- truncation agreement: integer part must match the native parse --- */
|
||||
produce_at(ADDR_A);
|
||||
CHECK(lookup(ADDR_A, PACKED, 161, 80, nullptr, nullptr, nullptr) ==
|
||||
|
||||
Reference in New Issue
Block a user