#include "pgxp.h" #include "pgxp_hooks.h" #include #include #include #define CHECK(c) do { if (!(c)) std::abort(); } while (0) extern "C" int gte_geometry_correction_lookup(uint32_t, int32_t*, int32_t*) { return 0; } extern "C" int gte_geometry_correction_lookup_probe(uint32_t, int32_t*, int32_t*) { return 0; } extern "C" int psx_mod_gpu_dma_memory_contains(uint32_t, uint32_t) { return 0; } static constexpr uint32_t address = 0x80001000, packed = 0x000C0008; static void seed(int32_t x, uint32_t at = address) { pgxp_gte_push_sxy(x, 12 << 16, 300, packed); pgxp_store_gte_reg(at, 15); } static void check(int32_t x, uint32_t at = address) { int32_t actual_x, y; uint16_t z; CHECK(pgxp_load_precise_word(at, packed, &actual_x, &y, &z)); CHECK(actual_x == x && y == 12 << 16 && z == 300); } int main() { pgxp_set_enabled(1); pgxp_set_cpu_mode(1); seed(8 << 16); PGXPExecutionSnapshot *snapshot = nullptr; std::thread prepare([&] { snapshot = pgxp_execution_snapshot_create(); }); prepare.join(); CHECK(snapshot && pgxp_execution_snapshot_capture(snapshot)); std::atomic phase{0}; std::thread worker([&] { CHECK(pgxp_execution_snapshot_bind(snapshot)); check(8 << 16); seed((8 << 16) + 32768); check((8 << 16) + 32768); phase.store(1, std::memory_order_release); while (phase.load(std::memory_order_acquire) != 2) { } check((8 << 16) + 32768); pgxp_execution_snapshot_unbind(); }); while (phase.load(std::memory_order_acquire) != 1) { } check(8 << 16); seed((8 << 16) + 16384); phase.store(2, std::memory_order_release); worker.join(); check((8 << 16) + 16384); CHECK(pgxp_execution_snapshot_capture(snapshot)); std::thread next([&] { CHECK(pgxp_execution_snapshot_bind(snapshot)); check((8 << 16) + 16384); seed((8 << 16) + 49152); pgxp_execution_snapshot_unbind(); }); next.join(); /* Re-publication must undo worker writes even if live RAM shadows have * not changed since the preceding capture. */ CHECK(pgxp_execution_snapshot_capture(snapshot)); std::thread unchanged([&] { CHECK(pgxp_execution_snapshot_bind(snapshot)); check((8 << 16) + 16384); pgxp_execution_snapshot_unbind(); }); unchanged.join(); /* Independent consumers retain their publication even after several * source stores to the same page in one epoch. */ PGXPExecutionSnapshot *other = pgxp_execution_snapshot_create(); CHECK(other); seed((8 << 16) + 32768); CHECK(pgxp_execution_snapshot_capture(snapshot)); seed((8 << 16) + 49152); CHECK(pgxp_execution_snapshot_capture(other)); std::thread consumers([&] { CHECK(pgxp_execution_snapshot_bind(snapshot)); check((8 << 16) + 32768); pgxp_execution_snapshot_unbind(); CHECK(pgxp_execution_snapshot_bind(other)); check((8 << 16) + 49152); pgxp_execution_snapshot_unbind(); }); consumers.join(); /* A load that invalidates precision really does mutate a RAM shadow; * unlike a matching read, that mutation must reach the next publication. */ psx_pgxp_load(nullptr, (0x23u << 26) | (2u << 16), address, packed ^ 1u); CHECK(pgxp_execution_snapshot_capture(snapshot)); std::thread invalidated([&] { CHECK(pgxp_execution_snapshot_bind(snapshot)); int32_t x, y; uint16_t z; CHECK(!pgxp_load_precise_word(address, packed ^ 1u, &x, &y, &z)); pgxp_execution_snapshot_unbind(); }); invalidated.join(); /* Adjacent words across a publication-page boundary remain independent, * including worker writes to a page the source has not changed. */ seed(8 << 16, address + 1020); seed(9 << 16, address + 1024); CHECK(pgxp_execution_snapshot_capture(snapshot)); std::thread boundary([&] { CHECK(pgxp_execution_snapshot_bind(snapshot)); check(8 << 16, address + 1020); check(9 << 16, address + 1024); seed(10 << 16, address + 1020); seed(11 << 16, address + 1024); pgxp_execution_snapshot_unbind(); }); boundary.join(); seed(12 << 16, address + 1024); CHECK(pgxp_execution_snapshot_capture(snapshot)); std::thread recapture([&] { CHECK(pgxp_execution_snapshot_bind(snapshot)); check(8 << 16, address + 1020); check(12 << 16, address + 1024); pgxp_execution_snapshot_unbind(); }); recapture.join(); pgxp_execution_snapshot_destroy(other); pgxp_execution_snapshot_destroy(snapshot); return 0; }