mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
126 lines
6.5 KiB
C++
126 lines
6.5 KiB
C++
/*
|
|
* A preprocessor directive owns its whole line. Anything appended after it on
|
|
* that line is swallowed as "extra tokens" and silently DISCARDED -- no build
|
|
* failure, just a -Wendif-labels warning nobody reads. PR #171 was exactly
|
|
* this: `append_pgxp_hooks` space-joined its PGXP_*() call onto emissions that
|
|
* end on a bare `#endif` (the PSX_ENABLE_BLOCK_CYCLES muldiv stall/latency
|
|
* blocks), so 3,187 hooks per title were thrown away.
|
|
*
|
|
* This pins the predicate that now guards the remaining same-line appenders,
|
|
* so the hazard cannot come back by construction.
|
|
*
|
|
* Build/run: ctest -R emitter_directive_line_test
|
|
*/
|
|
|
|
#include "pgxp_hook_emitter.h"
|
|
|
|
#include <cstdio>
|
|
#include <string>
|
|
|
|
static int failures;
|
|
|
|
#define CHECK(cond, msg) \
|
|
do { \
|
|
if (!(cond)) { std::fprintf(stderr, "FAIL: %s\n", (msg)); failures++; } \
|
|
else { std::printf("ok: %s\n", (msg)); } \
|
|
} while (0)
|
|
|
|
using PSXRecomp::emission_ends_on_preprocessor_directive;
|
|
|
|
int main() {
|
|
/* The shape that caused PR #171: translate_mult's real emission. */
|
|
const std::string muldiv =
|
|
"{ int64_t result = (int64_t)(int32_t)cpu->gpr[4] * (int64_t)(int32_t)cpu->gpr[5];"
|
|
" cpu->lo = (uint32_t)result; cpu->hi = (uint32_t)(result >> 32); }"
|
|
"\n#ifdef PSX_ENABLE_BLOCK_CYCLES\n psx_muldiv_set(cpu, 13u);\n#endif";
|
|
CHECK(emission_ends_on_preprocessor_directive(muldiv),
|
|
"a muldiv emission ending on a bare #endif is detected");
|
|
|
|
/* translate_mfhi with rd==$zero: the stall block with no statement. */
|
|
CHECK(emission_ends_on_preprocessor_directive(
|
|
"/* nop: write to $zero */"
|
|
"\n#ifdef PSX_ENABLE_BLOCK_CYCLES\n psx_muldiv_stall(cpu);\n#endif"),
|
|
"a zero-reg mfhi/mflo emission ending on #endif is detected");
|
|
|
|
/* Leading whitespace is legal before a directive. */
|
|
CHECK(emission_ends_on_preprocessor_directive("foo();\n #endif"),
|
|
"an indented directive still owns its line");
|
|
CHECK(emission_ends_on_preprocessor_directive("foo();\n\t#else"),
|
|
"a tab-indented directive still owns its line");
|
|
CHECK(emission_ends_on_preprocessor_directive("#endif"),
|
|
"a single-line emission that is only a directive is detected");
|
|
|
|
/* Ordinary statements must NOT be flagged -- a false positive here would
|
|
* inject stray newlines into every instruction's output. */
|
|
CHECK(!emission_ends_on_preprocessor_directive("cpu->gpr[4] = cpu->hi;"),
|
|
"a plain statement is not a directive line");
|
|
CHECK(!emission_ends_on_preprocessor_directive(
|
|
"\n#ifdef PSX_ENABLE_BLOCK_CYCLES\n psx_gte_stall(cpu);\n#endif\n "),
|
|
"an emission whose directive block is followed by a fresh indented "
|
|
"line is safe to append to");
|
|
CHECK(!emission_ends_on_preprocessor_directive(
|
|
"{ uint32_t _pgxa = cpu->gpr[4]; foo();\n PGXP_LOAD(0u, _pgxa, x); }"),
|
|
"an already-fixed hook emission is not flagged");
|
|
CHECK(!emission_ends_on_preprocessor_directive(""),
|
|
"an empty emission is not flagged");
|
|
CHECK(!emission_ends_on_preprocessor_directive("x = 1; /* #endif in a comment */"),
|
|
"a '#' that is not the line's first token is not a directive");
|
|
|
|
/* COP2 register transfers all carry their PGXP_COP2 hook - the control
|
|
* forms too, so the engine can follow vertex halves packed into a matrix
|
|
* row (CTC2) through MVMVA and back (CFC2). A transfer to $zero writes no
|
|
* register and stays unhooked. */
|
|
{
|
|
const auto hooked = [](uint32_t instr) {
|
|
std::string code = "stmt;";
|
|
PSXRecomp::append_pgxp_hooks(instr, code);
|
|
return code;
|
|
};
|
|
const uint32_t mfc2 = 0x4805C800u; /* mfc2 a1, $25 */
|
|
const uint32_t cfc2 = 0x4845C800u; /* cfc2 a1, $25 */
|
|
const uint32_t mtc2 = 0x48994800u; /* mtc2 t9, $9 */
|
|
const uint32_t ctc2 = 0x48C34000u; /* ctc2 v1, $8 */
|
|
const uint32_t cfc2_zero = 0x4840C800u; /* cfc2 zero, $25 */
|
|
CHECK(hooked(mfc2).find("PGXP_COP2(0x4805C800u, cpu->gpr[5], 0u);") != std::string::npos,
|
|
"MFC2 carries PGXP_COP2 with its destination value");
|
|
CHECK(hooked(cfc2).find("PGXP_COP2(0x4845C800u, cpu->gpr[5], 0u);") != std::string::npos,
|
|
"CFC2 carries PGXP_COP2 with its destination value");
|
|
CHECK(hooked(mtc2).find("PGXP_COP2(0x48994800u, cpu->gpr[25], 0u);") != std::string::npos,
|
|
"MTC2 carries PGXP_COP2 with its source value");
|
|
CHECK(hooked(ctc2).find("PGXP_COP2(0x48C34000u, cpu->gpr[3], 0u);") != std::string::npos,
|
|
"CTC2 carries PGXP_COP2 with its source value");
|
|
CHECK(hooked(cfc2_zero) == "stmt;",
|
|
"a CFC2 to $zero writes no register and is not hooked");
|
|
}
|
|
|
|
/* Coprocessor reads into a GPR replace its value like any ALU op. The
|
|
* interpreters hook them; the compiled code must too, or the register
|
|
* keeps the shadow of the vertex it held before. MFC0's emission ends on
|
|
* the block-cycles #endif, so its hook also has to land on a new line. */
|
|
{
|
|
const uint32_t mfc0 = (0x10u << 26) | (0x00u << 21) | (3u << 16) | (12u << 11);
|
|
std::string code =
|
|
"cpu->gpr[3] = cpu->cop0[12];"
|
|
"\n#ifdef PSX_ENABLE_BLOCK_CYCLES\n cpu->ld_absorb = 0u; cpu->ld_which_t = 3u;\n#endif";
|
|
PSXRecomp::append_pgxp_hooks(mfc0, code);
|
|
CHECK(code.find("\n PGXP_ALU(0x40036000u, cpu->gpr[3], 0u, 0u);") != std::string::npos,
|
|
"MFC0 resets the destination shadow on its own line");
|
|
|
|
const uint32_t cfc2 = (0x12u << 26) | (0x02u << 21) | (3u << 16) | (31u << 11);
|
|
code = "cpu->gpr[3] = gte_read_ctrl(cpu, 31);";
|
|
PSXRecomp::append_pgxp_hooks(cfc2, code);
|
|
CHECK(code.find("PGXP_COP2(0x4843F800u, cpu->gpr[3], 0u);") != std::string::npos,
|
|
"CFC2 resets the destination shadow");
|
|
|
|
code = "(void)cpu->cop0[12];";
|
|
PSXRecomp::append_pgxp_hooks(mfc0 & ~(31u << 16), code);
|
|
CHECK(code.find("PGXP_") == std::string::npos, "MFC0 to $zero is not hooked");
|
|
code = "(void)gte_read_ctrl(cpu, 31);";
|
|
PSXRecomp::append_pgxp_hooks(cfc2 & ~(31u << 16), code);
|
|
CHECK(code.find("PGXP_") == std::string::npos, "CFC2 to $zero is not hooked");
|
|
}
|
|
|
|
std::printf(failures ? "FAILED (%d)\n" : "ALL PASS\n", failures);
|
|
return failures ? 1 : 0;
|
|
}
|