diff --git a/src/xenia/cpu/backend/a64/a64_seq_memory.cc b/src/xenia/cpu/backend/a64/a64_seq_memory.cc index 788df7003..501fb74f3 100644 --- a/src/xenia/cpu/backend/a64/a64_seq_memory.cc +++ b/src/xenia/cpu/backend/a64/a64_seq_memory.cc @@ -1199,23 +1199,23 @@ struct RESERVED_STORE_I32 e.casal(e.w5, e.w6, ptr(e.x4)); e.cmp(e.w5, e.w0); e.cset(i.dest, Xbyak_aarch64::EQ); - return; + e.b(done); + } else { + // LDXR/STXR loop. + auto& cas_loop = e.NewCachedLabel(); + auto& cas_fail = e.NewCachedLabel(); + e.L(cas_loop); + e.ldaxr(e.w7, ptr(e.x4)); + e.cmp(e.w7, e.w5); + e.b(Xbyak_aarch64::NE, cas_fail); + e.stlxr(e.w7, e.w6, ptr(e.x4)); + e.cbnz(e.w7, cas_loop); + // Success. + e.mov(i.dest, 1); + e.b(done); + e.L(cas_fail); + e.clrex(15); } - - // LDXR/STXR loop. - auto& cas_loop = e.NewCachedLabel(); - auto& cas_fail = e.NewCachedLabel(); - e.L(cas_loop); - e.ldaxr(e.w7, ptr(e.x4)); - e.cmp(e.w7, e.w5); - e.b(Xbyak_aarch64::NE, cas_fail); - e.stlxr(e.w7, e.w6, ptr(e.x4)); - e.cbnz(e.w7, cas_loop); - // Success. - e.mov(i.dest, 1); - e.b(done); - e.L(cas_fail); - e.clrex(15); e.L(no_reserve); e.mov(i.dest, 0); e.L(done); @@ -1256,21 +1256,21 @@ struct RESERVED_STORE_I64 e.casal(e.x5, e.x6, ptr(e.x4)); e.cmp(e.x5, e.x0); e.cset(i.dest, Xbyak_aarch64::EQ); - return; + e.b(done); + } else { + auto& cas_loop = e.NewCachedLabel(); + auto& cas_fail = e.NewCachedLabel(); + e.L(cas_loop); + e.ldaxr(e.x7, ptr(e.x4)); + e.cmp(e.x7, e.x5); + e.b(Xbyak_aarch64::NE, cas_fail); + e.stlxr(e.w7, e.x6, ptr(e.x4)); + e.cbnz(e.w7, cas_loop); + e.mov(i.dest, 1); + e.b(done); + e.L(cas_fail); + e.clrex(15); } - - auto& cas_loop = e.NewCachedLabel(); - auto& cas_fail = e.NewCachedLabel(); - e.L(cas_loop); - e.ldaxr(e.x7, ptr(e.x4)); - e.cmp(e.x7, e.x5); - e.b(Xbyak_aarch64::NE, cas_fail); - e.stlxr(e.w7, e.x6, ptr(e.x4)); - e.cbnz(e.w7, cas_loop); - e.mov(i.dest, 1); - e.b(done); - e.L(cas_fail); - e.clrex(15); e.L(no_reserve); e.mov(i.dest, 0); e.L(done); diff --git a/src/xenia/cpu/testing/misc_opcode_test.cc b/src/xenia/cpu/testing/misc_opcode_test.cc index b77f5031b..73416ebeb 100644 --- a/src/xenia/cpu/testing/misc_opcode_test.cc +++ b/src/xenia/cpu/testing/misc_opcode_test.cc @@ -13,9 +13,12 @@ #include "xenia/cpu/testing/util.h" +#include #include +#include #include #include +#include using namespace xe; using namespace xe::cpu; @@ -356,6 +359,125 @@ TEST_CASE("RESERVED_LOAD_STORE_I32", "[atomic]") { test.memory->SystemHeapFree(guest_addr); } +TEST_CASE("RESERVED_LOAD_STORE_I64", "[atomic]") { + TestFunction test([](HIRBuilder& b) { + auto addr = LoadGPR(b, 4); + auto loaded = b.LoadWithReserve(addr, INT64_TYPE); + StoreGPR(b, 3, loaded); + auto new_val = b.Add(loaded, b.LoadConstantInt64(1)); + auto success = b.StoreWithReserve(addr, new_val, INT64_TYPE); + StoreGPR(b, 5, b.ZeroExtend(success, INT64_TYPE)); + b.Return(); + }); + + uint32_t guest_addr = test.memory->SystemHeapAlloc(8); + REQUIRE(guest_addr != 0); + auto* host_ptr = + reinterpret_cast(test.memory->TranslateVirtual(guest_addr)); + + test.Run( + [&](PPCContext* ctx) { + *host_ptr = 100; + ctx->r[4] = guest_addr; + }, + [&](PPCContext* ctx) { + REQUIRE(ctx->r[3] == 100); // loaded value + REQUIRE(ctx->r[5] == 1); // store succeeded + REQUIRE(*host_ptr == 101); // incremented + }); + + test.memory->SystemHeapFree(guest_addr); +} + +TEST_CASE("RESERVED_STORE_I32_NO_RESERVATION", "[atomic]") { + // StoreWithReserve without a preceding LoadWithReserve must fail (return 0). + // Run with a timeout: the bug this catches produces an infinite loop. + TestFunction test([](HIRBuilder& b) { + auto addr = LoadGPR(b, 4); + auto val = b.Truncate(LoadGPR(b, 5), INT32_TYPE); + auto success = b.StoreWithReserve(addr, val, INT32_TYPE); + StoreGPR(b, 3, b.ZeroExtend(success, INT64_TYPE)); + b.Return(); + }); + + uint32_t guest_addr = test.memory->SystemHeapAlloc(4); + REQUIRE(guest_addr != 0); + auto* host_ptr = + reinterpret_cast(test.memory->TranslateVirtual(guest_addr)); + + std::atomic completed{false}; + std::thread worker([&]() { + test.Run( + [&](PPCContext* ctx) { + *host_ptr = 42; + ctx->r[4] = guest_addr; + ctx->r[5] = 99; + }, + [&](PPCContext* ctx) { + CHECK(ctx->r[3] == 0); // store must fail + CHECK(*host_ptr == 42); // memory unchanged + }); + completed.store(true); + }); + worker.detach(); + + auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (!completed.load() && std::chrono::steady_clock::now() < deadline) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + if (!completed.load()) { + // Detached thread is stuck in JIT'd code — can't unwind safely. + FAIL("Timed out: no-reservation path likely has an infinite loop"); + std::_Exit(1); + } + + test.memory->SystemHeapFree(guest_addr); +} + +TEST_CASE("RESERVED_STORE_I64_NO_RESERVATION", "[atomic]") { + // Run with a timeout: the bug this catches produces an infinite loop. + TestFunction test([](HIRBuilder& b) { + auto addr = LoadGPR(b, 4); + auto val = LoadGPR(b, 5); + auto success = b.StoreWithReserve(addr, val, INT64_TYPE); + StoreGPR(b, 3, b.ZeroExtend(success, INT64_TYPE)); + b.Return(); + }); + + uint32_t guest_addr = test.memory->SystemHeapAlloc(8); + REQUIRE(guest_addr != 0); + auto* host_ptr = + reinterpret_cast(test.memory->TranslateVirtual(guest_addr)); + + std::atomic completed{false}; + std::thread worker([&]() { + test.Run( + [&](PPCContext* ctx) { + *host_ptr = 42; + ctx->r[4] = guest_addr; + ctx->r[5] = 99; + }, + [&](PPCContext* ctx) { + CHECK(ctx->r[3] == 0); // store must fail + CHECK(*host_ptr == 42); // memory unchanged + }); + completed.store(true); + }); + worker.detach(); + + auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (!completed.load() && std::chrono::steady_clock::now() < deadline) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + if (!completed.load()) { + // Detached thread is stuck in JIT'd code — can't unwind safely. + FAIL("Timed out: no-reservation path likely has an infinite loop"); + std::_Exit(1); + } + + test.memory->SystemHeapFree(guest_addr); +} + // ============================================================================ // LVL / LVR — partial vector load left/right // Detailed alignment semantics are tested by PPC instruction tests (lvlx etc.)