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:
Matthew Stanley
2026-10-02 12:21:52 -07:00
parent 534dcd5df3
commit 72e6d7e01d
5 changed files with 310 additions and 126 deletions
+11 -7
View File
@@ -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); }}",
+29 -10
View File
@@ -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
View File
@@ -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. */
+18 -9
View File
@@ -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 */
+90
View File
@@ -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) ==