rexglue-skate3/include/rex/ppc/stack.h
2026-05-18 22:41:13 +01:00

166 lines
5.3 KiB
C++

/**
* @file ppc/stack.h
* @brief Guest PPC stack push/pop operations and scope guard.
*
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
* All rights reserved.
*
* @license BSD 3-Clause License
* See LICENSE file in the project root for full license text.
*/
#pragma once
#include <concepts>
#include <cstdint>
#include <cstring>
#include <rex/logging/assert.h>
#include <rex/ppc/context.h>
#include <rex/system/kernel_state.h>
#include <rex/system/thread_state.h>
#include <rex/types.h>
namespace rex::ppc {
//=============================================================================
// Stack bounds helper
//=============================================================================
/// Read stack_end_ptr from KPCR at r13 + 0x74.
/// Returns 0 if r13 is not set (e.g. in unit tests without a live thread).
inline uint32_t stack_limit_from_pcr(PPCContext& ctx, uint8_t* base) {
if (ctx.r13.u32 == 0)
return 0;
uint32_t raw;
std::memcpy(&raw, base + ctx.r13.u32 + 0x74, sizeof(raw));
return __builtin_bswap32(raw);
}
inline void stack_bounds_check(PPCContext& ctx, uint8_t* base, uint32_t new_r1) {
uint32_t limit = stack_limit_from_pcr(ctx, base);
if (limit != 0) {
REX_FATAL_IF(new_r1 > limit, "PPC stack overflow: r1 ({:#x}) below stack_end_ptr ({:#x})",
new_r1, limit);
}
}
// note(tomc): PPC64 ABI requires 16-byte frame alignment, but r1 is already frame-aligned
// on entry; individual pushes round to 8 bytes to maintain doubleword alignment.
inline constexpr uint32_t stack_align(uint32_t size) {
return (size + 7u) & ~7u;
}
//=============================================================================
// Core push/pop (explicit ctx/base)
//=============================================================================
/// Push a byte-swapped scalar onto the guest stack.
template <typename T>
requires std::is_arithmetic_v<T>
inline uint32_t stack_push(PPCContext& ctx, uint8_t* base, T value) {
uint32_t alloc = stack_align(sizeof(T));
uint32_t new_r1 = ctx.r1.u32 - alloc;
stack_bounds_check(ctx, base, new_r1);
ctx.r1.u32 = new_r1;
// Byte-swap and write
T swapped;
if constexpr (sizeof(T) == 1) {
swapped = value;
} else if constexpr (sizeof(T) == 2) {
uint16_t bits;
std::memcpy(&bits, &value, 2);
bits = __builtin_bswap16(bits);
std::memcpy(&swapped, &bits, 2);
} else if constexpr (sizeof(T) == 4) {
uint32_t bits;
std::memcpy(&bits, &value, 4);
bits = __builtin_bswap32(bits);
std::memcpy(&swapped, &bits, 4);
} else if constexpr (sizeof(T) == 8) {
uint64_t bits;
std::memcpy(&bits, &value, 8);
bits = __builtin_bswap64(bits);
std::memcpy(&swapped, &bits, 8);
}
std::memcpy(base + new_r1, &swapped, sizeof(T));
return new_r1;
}
/// Push a NUL-terminated string onto the guest stack (no byte-swap, raw bytes).
inline uint32_t stack_push_string(PPCContext& ctx, uint8_t* base, const char* str) {
uint32_t len = static_cast<uint32_t>(std::strlen(str)) + 1;
uint32_t alloc = stack_align(len);
uint32_t new_r1 = ctx.r1.u32 - alloc;
stack_bounds_check(ctx, base, new_r1);
ctx.r1.u32 = new_r1;
std::memcpy(base + new_r1, str, len);
return new_r1;
}
/// Push raw bytes onto the guest stack (no byte-swap).
inline uint32_t stack_push(PPCContext& ctx, uint8_t* base, const void* data, uint32_t len) {
uint32_t alloc = stack_align(len);
uint32_t new_r1 = ctx.r1.u32 - alloc;
stack_bounds_check(ctx, base, new_r1);
ctx.r1.u32 = new_r1;
std::memcpy(base + new_r1, data, len);
return new_r1;
}
/// Pop bytes from the guest stack.
inline void stack_pop(PPCContext& ctx, uint32_t size) {
ctx.r1.u32 += stack_align(size);
}
//=============================================================================
// Scope guard
//=============================================================================
/// RAII guard that saves r1 on construction and restores on destruction.
class stack_guard {
PPCContext& ctx_;
uint32_t saved_r1_;
public:
explicit stack_guard(PPCContext& ctx) : ctx_(ctx), saved_r1_(ctx.r1.u32) {}
stack_guard() : stack_guard(*rex::runtime::ThreadState::Get()->context()) {}
~stack_guard() { ctx_.r1.u32 = saved_r1_; }
stack_guard(const stack_guard&) = delete;
stack_guard& operator=(const stack_guard&) = delete;
};
//=============================================================================
// Implicit ctx/base overloads (use current thread context)
//=============================================================================
namespace detail {
inline PPCContext& current_ctx() {
return *rex::runtime::ThreadState::Get()->context();
}
inline uint8_t* current_base() {
return rex::system::kernel_state()->memory()->virtual_membase();
}
} // namespace detail
template <typename T>
requires std::is_arithmetic_v<T>
inline uint32_t stack_push(T value) {
return stack_push(detail::current_ctx(), detail::current_base(), value);
}
inline uint32_t stack_push_string(const char* str) {
return stack_push_string(detail::current_ctx(), detail::current_base(), str);
}
inline uint32_t stack_push(const void* data, uint32_t len) {
return stack_push(detail::current_ctx(), detail::current_base(), data, len);
}
inline void stack_pop(uint32_t size) {
stack_pop(detail::current_ctx(), size);
}
} // namespace rex::ppc