rexglue-skate3/tests/unit/ppc/stack_test.cpp
2026-05-18 22:41:13 +01:00

144 lines
3.7 KiB
C++

/**
* Tests for rex::ppc stack push/pop/guard operations.
*/
#include <cstdint>
#include <cstring>
#include <catch2/catch_test_macros.hpp>
#include <rex/ppc/context.h>
#include <rex/ppc/stack.h>
// 64KB test memory, aligned
alignas(64) static uint8_t g_mem[0x10000] = {};
TEST_CASE("stack_push scalar decrements r1 and writes byte-swapped value", "[ppc][stack]") {
PPCContext ctx{};
ctx.r1.u32 = 0x8000;
uint32_t addr = rex::ppc::stack_push(ctx, g_mem, uint32_t{0x12345678});
CHECK(ctx.r1.u32 == 0x8000 - 8);
CHECK(addr == ctx.r1.u32);
uint32_t raw;
std::memcpy(&raw, g_mem + addr, 4);
CHECK(raw == __builtin_bswap32(0x12345678));
}
TEST_CASE("stack_push float writes byte-swapped float", "[ppc][stack]") {
PPCContext ctx{};
ctx.r1.u32 = 0x8000;
float val = 3.14f;
uint32_t addr = rex::ppc::stack_push(ctx, g_mem, val);
CHECK(ctx.r1.u32 == 0x8000 - 8);
uint32_t raw;
std::memcpy(&raw, g_mem + addr, 4);
uint32_t val_bits;
std::memcpy(&val_bits, &val, 4);
CHECK(raw == __builtin_bswap32(val_bits));
}
TEST_CASE("stack_push_string writes NUL-terminated string", "[ppc][stack]") {
PPCContext ctx{};
ctx.r1.u32 = 0x8000;
std::memset(g_mem + 0x7F00, 0, 0x100);
uint32_t addr = rex::ppc::stack_push_string(ctx, g_mem, "hello");
CHECK(ctx.r1.u32 == 0x8000 - 8);
CHECK(addr == ctx.r1.u32);
CHECK(std::strcmp(reinterpret_cast<char*>(g_mem + addr), "hello") == 0);
}
TEST_CASE("stack_push_string aligns to 8 bytes", "[ppc][stack]") {
PPCContext ctx{};
ctx.r1.u32 = 0x8000;
uint32_t addr = rex::ppc::stack_push_string(ctx, g_mem, "longstring1");
CHECK(ctx.r1.u32 == 0x8000 - 16);
CHECK(addr == ctx.r1.u32);
}
TEST_CASE("stack_push raw bytes", "[ppc][stack]") {
PPCContext ctx{};
ctx.r1.u32 = 0x8000;
uint8_t data[] = {0xAA, 0xBB, 0xCC};
uint32_t addr = rex::ppc::stack_push(ctx, g_mem, data, 3);
CHECK(ctx.r1.u32 == 0x8000 - 8);
CHECK(g_mem[addr] == 0xAA);
CHECK(g_mem[addr + 1] == 0xBB);
CHECK(g_mem[addr + 2] == 0xCC);
}
TEST_CASE("stack_pop restores r1", "[ppc][stack]") {
PPCContext ctx{};
ctx.r1.u32 = 0x8000;
rex::ppc::stack_push(ctx, g_mem, uint32_t{42});
CHECK(ctx.r1.u32 == 0x8000 - 8);
rex::ppc::stack_pop(ctx, 8);
CHECK(ctx.r1.u32 == 0x8000);
}
TEST_CASE("multiple pushes return stable addresses", "[ppc][stack]") {
PPCContext ctx{};
ctx.r1.u32 = 0x8000;
uint32_t addr1 = rex::ppc::stack_push_string(ctx, g_mem, "first");
uint32_t addr2 = rex::ppc::stack_push_string(ctx, g_mem, "second");
CHECK(addr1 != addr2);
CHECK(std::strcmp(reinterpret_cast<char*>(g_mem + addr1), "first") == 0);
CHECK(std::strcmp(reinterpret_cast<char*>(g_mem + addr2), "second") == 0);
}
TEST_CASE("stack_guard restores r1 on scope exit", "[ppc][stack]") {
PPCContext ctx{};
ctx.r1.u32 = 0x8000;
{
rex::ppc::stack_guard guard(ctx);
rex::ppc::stack_push_string(ctx, g_mem, "temporary");
CHECK(ctx.r1.u32 < 0x8000);
}
CHECK(ctx.r1.u32 == 0x8000);
}
TEST_CASE("stack_guard restores after multiple pushes", "[ppc][stack]") {
PPCContext ctx{};
ctx.r1.u32 = 0x8000;
{
rex::ppc::stack_guard guard(ctx);
rex::ppc::stack_push(ctx, g_mem, uint32_t{1});
rex::ppc::stack_push(ctx, g_mem, uint32_t{2});
rex::ppc::stack_push_string(ctx, g_mem, "three");
CHECK(ctx.r1.u32 == 0x8000 - 8 - 8 - 8);
}
CHECK(ctx.r1.u32 == 0x8000);
}
TEST_CASE("nested stack_guards restore correctly", "[ppc][stack]") {
PPCContext ctx{};
ctx.r1.u32 = 0x8000;
{
rex::ppc::stack_guard outer(ctx);
rex::ppc::stack_push_string(ctx, g_mem, "outer");
{
rex::ppc::stack_guard inner(ctx);
rex::ppc::stack_push_string(ctx, g_mem, "inner");
}
CHECK(ctx.r1.u32 == 0x8000 - 8);
}
CHECK(ctx.r1.u32 == 0x8000);
}