[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
This commit is contained in:
@@ -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<uint16_t>(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<uint32_t>(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<uint64_t>(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<LOAD_I64, I<OPCODE_LOAD, I64Op, I64Op>> {
|
||||
struct LOAD_F32 : Sequence<LOAD_F32, I<OPCODE_LOAD, F32Op, I64Op>> {
|
||||
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<LOAD_F32, I<OPCODE_LOAD, F32Op, I64Op>> {
|
||||
struct LOAD_F64 : Sequence<LOAD_F64, I<OPCODE_LOAD, F64Op, I64Op>> {
|
||||
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<STORE_I16, I<OPCODE_STORE, VoidOp, I64Op, I16Op>> {
|
||||
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<uint16_t>(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<STORE_I32, I<OPCODE_STORE, VoidOp, I64Op, I32Op>> {
|
||||
} 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<uint32_t>(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<STORE_I64, I<OPCODE_STORE, VoidOp, I64Op, I64Op>> {
|
||||
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<uint64_t>(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<STORE_F32, I<OPCODE_STORE, VoidOp, I64Op, F32Op>> {
|
||||
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<uint32_t>(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<STORE_F64, I<OPCODE_STORE, VoidOp, I64Op, F64Op>> {
|
||||
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<uint64_t>(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) {
|
||||
|
||||
288
src/xenia/cpu/testing/memory_store_load_test.cc
Normal file
288
src/xenia/cpu/testing/memory_store_load_test.cc
Normal file
@@ -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 <cstring>
|
||||
|
||||
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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(ctx->r[4]));
|
||||
uint16_t result;
|
||||
std::memcpy(&result, host, 2);
|
||||
REQUIRE(result == 0x3412);
|
||||
test.memory->SystemHeapFree(static_cast<uint32_t>(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<uint32_t>(ctx->r[4]));
|
||||
uint32_t result;
|
||||
std::memcpy(&result, host, 4);
|
||||
REQUIRE(result == 0x78563412);
|
||||
test.memory->SystemHeapFree(static_cast<uint32_t>(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<uint32_t>(ctx->r[4]));
|
||||
uint64_t result;
|
||||
std::memcpy(&result, host, 8);
|
||||
REQUIRE(result == 0xF0DEBC9A78563412ULL);
|
||||
test.memory->SystemHeapFree(static_cast<uint32_t>(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<uint32_t>(ctx->r[4]));
|
||||
uint32_t result;
|
||||
std::memcpy(&result, host, 4);
|
||||
// 0x3F800000 byte-reversed = 0x0000803F
|
||||
REQUIRE(result == 0x0000803F);
|
||||
test.memory->SystemHeapFree(static_cast<uint32_t>(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<uint32_t>(ctx->r[4]));
|
||||
uint32_t result;
|
||||
std::memcpy(&result, host, 4);
|
||||
REQUIRE(result == 0x0000803F);
|
||||
test.memory->SystemHeapFree(static_cast<uint32_t>(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<uint32_t>(ctx->r[4]));
|
||||
uint64_t result;
|
||||
std::memcpy(&result, host, 8);
|
||||
// 0x3FF0000000000000 byte-reversed = 0x000000000000F03F
|
||||
REQUIRE(result == 0x000000000000F03FULL);
|
||||
test.memory->SystemHeapFree(static_cast<uint32_t>(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<uint32_t>(ctx->r[4]));
|
||||
uint64_t result;
|
||||
std::memcpy(&result, host, 8);
|
||||
REQUIRE(result == 0x000000000000F03FULL);
|
||||
test.memory->SystemHeapFree(static_cast<uint32_t>(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<float>(ctx->f[3]);
|
||||
REQUIRE(result == 1.0f);
|
||||
test.memory->SystemHeapFree(static_cast<uint32_t>(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<uint32_t>(ctx->r[4]));
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user