From 5e636ac335dc0b24d36c174be1d263ec33d8b53b Mon Sep 17 00:00:00 2001 From: "Herman S." <429230+has207@users.noreply.github.com> Date: Mon, 16 Mar 2026 02:52:26 +0900 Subject: [PATCH] [x64] Implement missing byte-swap store/load paths Fill in all unimplemented LOAD_STORE_BYTE_SWAP code paths in the x64 backend that previously hit assert_false or assert_always and add tests --- src/xenia/cpu/backend/x64/x64_seq_memory.cc | 108 +++++-- .../cpu/testing/memory_store_load_test.cc | 288 ++++++++++++++++++ 2 files changed, 366 insertions(+), 30 deletions(-) create mode 100644 src/xenia/cpu/testing/memory_store_load_test.cc diff --git a/src/xenia/cpu/backend/x64/x64_seq_memory.cc b/src/xenia/cpu/backend/x64/x64_seq_memory.cc index da0e55f33..6931104c7 100644 --- a/src/xenia/cpu/backend/x64/x64_seq_memory.cc +++ b/src/xenia/cpu/backend/x64/x64_seq_memory.cc @@ -1349,11 +1349,15 @@ struct STORE_OFFSET_I16 static void Emit(X64Emitter& e, const EmitArgType& i) { auto addr = ComputeMemoryAddressOffset(e, i.src1, i.src2); if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { - assert_false(i.src3.is_constant); - if (e.IsFeatureEnabled(kX64EmitMovbe)) { + if (i.src3.is_constant) { + e.mov(e.word[addr], + xe::byte_swap(static_cast(i.src3.constant()))); + } else if (e.IsFeatureEnabled(kX64EmitMovbe)) { e.movbe(e.word[addr], i.src3); } else { - assert_always("not implemented"); + e.movzx(e.ecx, i.src3); + e.ror(e.cx, 8); + e.mov(e.word[addr], e.cx); } } else { if (i.src3.is_constant) { @@ -1399,11 +1403,15 @@ struct STORE_OFFSET_I32 } else { auto addr = ComputeMemoryAddressOffset(e, i.src1, i.src2); if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { - assert_false(i.src3.is_constant); - if (e.IsFeatureEnabled(kX64EmitMovbe)) { + if (i.src3.is_constant) { + e.mov(e.dword[addr], + xe::byte_swap(static_cast(i.src3.constant()))); + } else if (e.IsFeatureEnabled(kX64EmitMovbe)) { e.movbe(e.dword[addr], i.src3); } else { - assert_always("not implemented"); + e.mov(e.ecx, i.src3); + e.bswap(e.ecx); + e.mov(e.dword[addr], e.ecx); } } else { if (i.src3.is_constant) { @@ -1426,11 +1434,15 @@ struct STORE_OFFSET_I64 static void Emit(X64Emitter& e, const EmitArgType& i) { auto addr = ComputeMemoryAddressOffset(e, i.src1, i.src2); if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { - assert_false(i.src3.is_constant); - if (e.IsFeatureEnabled(kX64EmitMovbe)) { + if (i.src3.is_constant) { + e.MovMem64(addr, + xe::byte_swap(static_cast(i.src3.constant()))); + } else if (e.IsFeatureEnabled(kX64EmitMovbe)) { e.movbe(e.qword[addr], i.src3); } else { - assert_always("not implemented"); + e.mov(e.rcx, i.src3); + e.bswap(e.rcx); + e.mov(e.qword[addr], e.rcx); } } else { if (i.src3.is_constant) { @@ -1537,9 +1549,13 @@ struct LOAD_I64 : Sequence> { struct LOAD_F32 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { auto addr = ComputeMemoryAddress(e, i.src1); - e.vmovss(i.dest, e.dword[addr]); if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { - assert_always("not implemented yet"); + // Load as integer, byte-swap, move to XMM. + e.mov(e.eax, e.dword[addr]); + e.bswap(e.eax); + e.vmovd(i.dest, e.eax); + } else { + e.vmovss(i.dest, e.dword[addr]); } if (IsTracingData()) { e.lea(e.GetNativeParam(1), e.dword[addr]); @@ -1551,9 +1567,13 @@ struct LOAD_F32 : Sequence> { struct LOAD_F64 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { auto addr = ComputeMemoryAddress(e, i.src1); - e.vmovsd(i.dest, e.qword[addr]); if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { - assert_always("not implemented yet"); + // Load as integer, byte-swap, move to XMM. + e.mov(e.rax, e.qword[addr]); + e.bswap(e.rax); + e.vmovq(i.dest, e.rax); + } else { + e.vmovsd(i.dest, e.qword[addr]); } if (IsTracingData()) { e.lea(e.GetNativeParam(1), e.qword[addr]); @@ -1605,11 +1625,15 @@ struct STORE_I16 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { auto addr = ComputeMemoryAddress(e, i.src1); if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { - assert_false(i.src2.is_constant); - if (e.IsFeatureEnabled(kX64EmitMovbe)) { + if (i.src2.is_constant) { + e.mov(e.word[addr], + xe::byte_swap(static_cast(i.src2.constant()))); + } else if (e.IsFeatureEnabled(kX64EmitMovbe)) { e.movbe(e.word[addr], i.src2); } else { - assert_always("not implemented"); + e.movzx(e.ecx, i.src2); + e.ror(e.cx, 8); + e.mov(e.word[addr], e.cx); } } else { if (i.src2.is_constant) { @@ -1649,11 +1673,15 @@ struct STORE_I32 : Sequence> { } else { auto addr = ComputeMemoryAddress(e, i.src1); if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { - assert_false(i.src2.is_constant); - if (e.IsFeatureEnabled(kX64EmitMovbe)) { + if (i.src2.is_constant) { + e.mov(e.dword[addr], + xe::byte_swap(static_cast(i.src2.constant()))); + } else if (e.IsFeatureEnabled(kX64EmitMovbe)) { e.movbe(e.dword[addr], i.src2); } else { - assert_always("not implemented"); + e.mov(e.ecx, i.src2); + e.bswap(e.ecx); + e.mov(e.dword[addr], e.ecx); } } else { if (i.src2.is_constant) { @@ -1674,11 +1702,16 @@ struct STORE_I64 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { auto addr = ComputeMemoryAddress(e, i.src1); if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { - assert_false(i.src2.is_constant); - if (e.IsFeatureEnabled(kX64EmitMovbe)) { + if (i.src2.is_constant) { + // MovMem64 avoids clobbering rax (used by ComputeMemoryAddress). + e.MovMem64(addr, + xe::byte_swap(static_cast(i.src2.constant()))); + } else if (e.IsFeatureEnabled(kX64EmitMovbe)) { e.movbe(e.qword[addr], i.src2); } else { - assert_always("not implemented"); + e.mov(e.rcx, i.src2); + e.bswap(e.rcx); + e.mov(e.qword[addr], e.rcx); } } else { if (i.src2.is_constant) { @@ -1699,8 +1732,14 @@ struct STORE_F32 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { auto addr = ComputeMemoryAddress(e, i.src1); if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { - assert_false(i.src2.is_constant); - assert_always("not yet implemented"); + if (i.src2.is_constant) { + e.mov(e.dword[addr], + xe::byte_swap(static_cast(i.src2.value->constant.i32))); + } else { + e.vmovd(e.ecx, i.src2); + e.bswap(e.ecx); + e.mov(e.dword[addr], e.ecx); + } } else { if (i.src2.is_constant) { e.mov(e.dword[addr], i.src2.value->constant.i32); @@ -1720,8 +1759,15 @@ struct STORE_F64 : Sequence> { static void Emit(X64Emitter& e, const EmitArgType& i) { auto addr = ComputeMemoryAddress(e, i.src1); if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { - assert_false(i.src2.is_constant); - assert_always("not yet implemented"); + if (i.src2.is_constant) { + e.MovMem64( + addr, + xe::byte_swap(static_cast(i.src2.value->constant.i64))); + } else { + e.vmovq(e.rcx, i.src2); + e.bswap(e.rcx); + e.mov(e.qword[addr], e.rcx); + } } else { if (i.src2.is_constant) { e.MovMem64(addr, i.src2.value->constant.i64); @@ -1742,10 +1788,12 @@ struct STORE_V128 static void Emit(X64Emitter& e, const EmitArgType& i) { auto addr = ComputeMemoryAddress(e, i.src1); if (i.instr->flags & LoadStoreFlags::LOAD_STORE_BYTE_SWAP) { - assert_false(i.src2.is_constant); - e.vpshufb(e.xmm0, i.src2, e.GetXmmConstPtr(XMMByteSwapMask)); - // changed from vmovaps, the penalty on the vpshufb is unavoidable but - // we dont need to incur another here too + if (i.src2.is_constant) { + e.LoadConstantXmm(e.xmm0, i.src2.constant()); + e.vpshufb(e.xmm0, e.xmm0, e.GetXmmConstPtr(XMMByteSwapMask)); + } else { + e.vpshufb(e.xmm0, i.src2, e.GetXmmConstPtr(XMMByteSwapMask)); + } e.vmovdqa(e.ptr[addr], e.xmm0); } else { if (i.src2.is_constant) { diff --git a/src/xenia/cpu/testing/memory_store_load_test.cc b/src/xenia/cpu/testing/memory_store_load_test.cc new file mode 100644 index 000000000..471dd66a8 --- /dev/null +++ b/src/xenia/cpu/testing/memory_store_load_test.cc @@ -0,0 +1,288 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2026 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/cpu/testing/util.h" + +#include + +using namespace xe; +using namespace xe::cpu; +using namespace xe::cpu::hir; +using namespace xe::cpu::testing; +using xe::cpu::ppc::PPCContext; + +// ============================================================================= +// STORE_V128 — constant source +// ============================================================================= +TEST_CASE("STORE_V128_CONSTANT", "[instr]") { + TestFunction test([](HIRBuilder& b) { + auto addr = LoadGPR(b, 4); + auto value = b.LoadConstantVec128( + vec128i(0xDEADBEEF, 0xCAFEBABE, 0x12345678, 0x9ABCDEF0)); + b.Store(addr, value); + b.Return(); + }); + test.Run( + [&test](PPCContext* ctx) { + uint32_t addr = test.memory->SystemHeapAlloc(16, 16); + ctx->r[4] = addr; + std::memset(test.memory->TranslateVirtual(addr), 0, 16); + }, + [&test](PPCContext* ctx) { + auto* host = + test.memory->TranslateVirtual(static_cast(ctx->r[4])); + vec128_t result; + std::memcpy(&result, host, 16); + REQUIRE(result.u32[0] == 0xDEADBEEF); + REQUIRE(result.u32[1] == 0xCAFEBABE); + REQUIRE(result.u32[2] == 0x12345678); + REQUIRE(result.u32[3] == 0x9ABCDEF0); + test.memory->SystemHeapFree(static_cast(ctx->r[4])); + }); +} + +TEST_CASE("STORE_V128_CONSTANT_BYTE_SWAP", "[instr]") { + TestFunction test([](HIRBuilder& b) { + auto addr = LoadGPR(b, 4); + auto value = b.LoadConstantVec128( + vec128i(0xDEADBEEF, 0xCAFEBABE, 0x12345678, 0x9ABCDEF0)); + b.Store(addr, value, LOAD_STORE_BYTE_SWAP); + b.Return(); + }); + test.Run( + [&test](PPCContext* ctx) { + uint32_t addr = test.memory->SystemHeapAlloc(16, 16); + ctx->r[4] = addr; + std::memset(test.memory->TranslateVirtual(addr), 0, 16); + }, + [&test](PPCContext* ctx) { + auto* host = + test.memory->TranslateVirtual(static_cast(ctx->r[4])); + vec128_t result; + std::memcpy(&result, host, 16); + REQUIRE(result.u32[0] == 0xEFBEADDE); + REQUIRE(result.u32[1] == 0xBEBAFECA); + REQUIRE(result.u32[2] == 0x78563412); + REQUIRE(result.u32[3] == 0xF0DEBC9A); + test.memory->SystemHeapFree(static_cast(ctx->r[4])); + }); +} + +// ============================================================================= +// STORE integer byte-swap with constant source +// ============================================================================= +TEST_CASE("STORE_I16_CONSTANT_BYTE_SWAP", "[instr]") { + TestFunction test([](HIRBuilder& b) { + auto addr = LoadGPR(b, 4); + b.Store(addr, b.LoadConstantInt16(0x1234), LOAD_STORE_BYTE_SWAP); + b.Return(); + }); + test.Run( + [&test](PPCContext* ctx) { + uint32_t addr = test.memory->SystemHeapAlloc(4, 4); + ctx->r[4] = addr; + std::memset(test.memory->TranslateVirtual(addr), 0, 4); + }, + [&test](PPCContext* ctx) { + auto* host = + test.memory->TranslateVirtual(static_cast(ctx->r[4])); + uint16_t result; + std::memcpy(&result, host, 2); + REQUIRE(result == 0x3412); + test.memory->SystemHeapFree(static_cast(ctx->r[4])); + }); +} + +TEST_CASE("STORE_I32_CONSTANT_BYTE_SWAP", "[instr]") { + TestFunction test([](HIRBuilder& b) { + auto addr = LoadGPR(b, 4); + b.Store(addr, b.LoadConstantInt32(0x12345678), LOAD_STORE_BYTE_SWAP); + b.Return(); + }); + test.Run( + [&test](PPCContext* ctx) { + uint32_t addr = test.memory->SystemHeapAlloc(4, 4); + ctx->r[4] = addr; + std::memset(test.memory->TranslateVirtual(addr), 0, 4); + }, + [&test](PPCContext* ctx) { + auto* host = + test.memory->TranslateVirtual(static_cast(ctx->r[4])); + uint32_t result; + std::memcpy(&result, host, 4); + REQUIRE(result == 0x78563412); + test.memory->SystemHeapFree(static_cast(ctx->r[4])); + }); +} + +TEST_CASE("STORE_I64_CONSTANT_BYTE_SWAP", "[instr]") { + TestFunction test([](HIRBuilder& b) { + auto addr = LoadGPR(b, 4); + b.Store(addr, b.LoadConstantInt64(0x123456789ABCDEF0LL), + LOAD_STORE_BYTE_SWAP); + b.Return(); + }); + test.Run( + [&test](PPCContext* ctx) { + uint32_t addr = test.memory->SystemHeapAlloc(8, 8); + ctx->r[4] = addr; + std::memset(test.memory->TranslateVirtual(addr), 0, 8); + }, + [&test](PPCContext* ctx) { + auto* host = + test.memory->TranslateVirtual(static_cast(ctx->r[4])); + uint64_t result; + std::memcpy(&result, host, 8); + REQUIRE(result == 0xF0DEBC9A78563412ULL); + test.memory->SystemHeapFree(static_cast(ctx->r[4])); + }); +} + +// ============================================================================= +// STORE_F32 / STORE_F64 byte-swap (entirely unimplemented on x64) +// ============================================================================= +TEST_CASE("STORE_F32_BYTE_SWAP", "[instr]") { + TestFunction test([](HIRBuilder& b) { + auto addr = LoadGPR(b, 4); + auto val = b.Convert(LoadFPR(b, 5), FLOAT32_TYPE); + b.Store(addr, val, LOAD_STORE_BYTE_SWAP); + b.Return(); + }); + test.Run( + [&test](PPCContext* ctx) { + uint32_t addr = test.memory->SystemHeapAlloc(4, 4); + ctx->r[4] = addr; + ctx->f[5] = 1.0; // 1.0f = 0x3F800000 + std::memset(test.memory->TranslateVirtual(addr), 0, 4); + }, + [&test](PPCContext* ctx) { + auto* host = + test.memory->TranslateVirtual(static_cast(ctx->r[4])); + uint32_t result; + std::memcpy(&result, host, 4); + // 0x3F800000 byte-reversed = 0x0000803F + REQUIRE(result == 0x0000803F); + test.memory->SystemHeapFree(static_cast(ctx->r[4])); + }); +} + +TEST_CASE("STORE_F32_CONSTANT_BYTE_SWAP", "[instr]") { + TestFunction test([](HIRBuilder& b) { + auto addr = LoadGPR(b, 4); + b.Store(addr, b.LoadConstantFloat32(1.0f), LOAD_STORE_BYTE_SWAP); + b.Return(); + }); + test.Run( + [&test](PPCContext* ctx) { + uint32_t addr = test.memory->SystemHeapAlloc(4, 4); + ctx->r[4] = addr; + std::memset(test.memory->TranslateVirtual(addr), 0, 4); + }, + [&test](PPCContext* ctx) { + auto* host = + test.memory->TranslateVirtual(static_cast(ctx->r[4])); + uint32_t result; + std::memcpy(&result, host, 4); + REQUIRE(result == 0x0000803F); + test.memory->SystemHeapFree(static_cast(ctx->r[4])); + }); +} + +TEST_CASE("STORE_F64_BYTE_SWAP", "[instr]") { + TestFunction test([](HIRBuilder& b) { + auto addr = LoadGPR(b, 4); + auto val = LoadFPR(b, 5); + b.Store(addr, val, LOAD_STORE_BYTE_SWAP); + b.Return(); + }); + test.Run( + [&test](PPCContext* ctx) { + uint32_t addr = test.memory->SystemHeapAlloc(8, 8); + ctx->r[4] = addr; + ctx->f[5] = 1.0; // 1.0 = 0x3FF0000000000000 + std::memset(test.memory->TranslateVirtual(addr), 0, 8); + }, + [&test](PPCContext* ctx) { + auto* host = + test.memory->TranslateVirtual(static_cast(ctx->r[4])); + uint64_t result; + std::memcpy(&result, host, 8); + // 0x3FF0000000000000 byte-reversed = 0x000000000000F03F + REQUIRE(result == 0x000000000000F03FULL); + test.memory->SystemHeapFree(static_cast(ctx->r[4])); + }); +} + +TEST_CASE("STORE_F64_CONSTANT_BYTE_SWAP", "[instr]") { + TestFunction test([](HIRBuilder& b) { + auto addr = LoadGPR(b, 4); + b.Store(addr, b.LoadConstantFloat64(1.0), LOAD_STORE_BYTE_SWAP); + b.Return(); + }); + test.Run( + [&test](PPCContext* ctx) { + uint32_t addr = test.memory->SystemHeapAlloc(8, 8); + ctx->r[4] = addr; + std::memset(test.memory->TranslateVirtual(addr), 0, 8); + }, + [&test](PPCContext* ctx) { + auto* host = + test.memory->TranslateVirtual(static_cast(ctx->r[4])); + uint64_t result; + std::memcpy(&result, host, 8); + REQUIRE(result == 0x000000000000F03FULL); + test.memory->SystemHeapFree(static_cast(ctx->r[4])); + }); +} + +// ============================================================================= +// LOAD_F32 / LOAD_F64 byte-swap (entirely unimplemented on x64) +// ============================================================================= +TEST_CASE("LOAD_F32_BYTE_SWAP", "[instr]") { + TestFunction test([](HIRBuilder& b) { + auto addr = LoadGPR(b, 4); + auto val = b.Load(addr, FLOAT32_TYPE, LOAD_STORE_BYTE_SWAP); + StoreFPR(b, 3, b.Convert(val, FLOAT64_TYPE)); + b.Return(); + }); + test.Run( + [&test](PPCContext* ctx) { + uint32_t addr = test.memory->SystemHeapAlloc(4, 4); + ctx->r[4] = addr; + // Write 1.0f (0x3F800000) byte-swapped into memory: 0x0000803F. + uint32_t swapped = 0x0000803F; + std::memcpy(test.memory->TranslateVirtual(addr), &swapped, 4); + }, + [&test](PPCContext* ctx) { + auto result = static_cast(ctx->f[3]); + REQUIRE(result == 1.0f); + test.memory->SystemHeapFree(static_cast(ctx->r[4])); + }); +} + +TEST_CASE("LOAD_F64_BYTE_SWAP", "[instr]") { + TestFunction test([](HIRBuilder& b) { + auto addr = LoadGPR(b, 4); + auto val = b.Load(addr, FLOAT64_TYPE, LOAD_STORE_BYTE_SWAP); + StoreFPR(b, 3, val); + b.Return(); + }); + test.Run( + [&test](PPCContext* ctx) { + uint32_t addr = test.memory->SystemHeapAlloc(8, 8); + ctx->r[4] = addr; + // Write 1.0 (0x3FF0000000000000) byte-swapped into memory. + uint64_t swapped = 0x000000000000F03FULL; + std::memcpy(test.memory->TranslateVirtual(addr), &swapped, 8); + }, + [&test](PPCContext* ctx) { + REQUIRE(ctx->f[3] == 1.0); + test.memory->SystemHeapFree(static_cast(ctx->r[4])); + }); +}