diff --git a/recompiler/src/pgxp_hook_emitter.cpp b/recompiler/src/pgxp_hook_emitter.cpp index a998b12c..5f251f3e 100644 --- a/recompiler/src/pgxp_hook_emitter.cpp +++ b/recompiler/src/pgxp_hook_emitter.cpp @@ -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); }}", diff --git a/runtime/src/dirty_ram_interp.c b/runtime/src/dirty_ram_interp.c index f95ebde8..2d4ada9a 100644 --- a/runtime/src/dirty_ram_interp.c +++ b/runtime/src/dirty_ram_interp.c @@ -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)); diff --git a/runtime/src/pgxp.cpp b/runtime/src/pgxp.cpp index 50a3f15b..cee86da0 100644 --- a/runtime/src/pgxp.cpp +++ b/runtime/src/pgxp.cpp @@ -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. */ diff --git a/runtime/src/psx_interpreter.c b/runtime/src/psx_interpreter.c index 340305f0..4f94d87e 100644 --- a/runtime/src/psx_interpreter.c +++ b/runtime/src/psx_interpreter.c @@ -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 */ diff --git a/runtime/tests/test_pgxp.cpp b/runtime/tests/test_pgxp.cpp index 2b022f24..8de86d08 100644 --- a/runtime/tests/test_pgxp.cpp +++ b/runtime/tests/test_pgxp.cpp @@ -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) ==