rexglue-skate3/include/rex/ppc/context.h
2026-06-06 15:13:35 +01:00

610 lines
16 KiB
C++

/**
* @file ppc/context.h
* @brief PPC thread context, guest function macros, and interrupt handling
*
* @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.
*
* @remarks Based on XenonRecomp/UnleashedRecomp PPCContext infrastructure
*/
#pragma once
#include <atomic>
#include <bit>
#include <cstdint>
#include <cstring>
#include <rex/platform/fpscr.h>
#include <rex/types.h>
#include <simde/x86/avx.h>
#include <simde/x86/sse.h>
#include <simde/x86/sse4.1.h>
//=============================================================================
// PPCFunc Type Definition
//=============================================================================
// Function signature for recompiled PPC functions.
// All recompiled functions take a context reference and memory base pointer.
// Forward declaration of the PPC execution context
struct PPCContext;
// Function signature for recompiled PPC functions
using PPCFunc = void(PPCContext& ctx, uint8_t* base);
namespace rex::runtime {
PPCFunc* ResolveIndirectFunction(uint32_t guest_address);
} // namespace rex::runtime
//=============================================================================
// PPC Function Macros
//=============================================================================
#define REX_JOIN(x, y) x##y
#define REX_XSTRINGIFY(x) #x
#define REX_STRINGIFY(x) REX_XSTRINGIFY(x)
#define REX_FUNC(x) void x([[maybe_unused]] PPCContext& __restrict ctx, uint8_t* base)
#define REX_EXTERN(x) extern "C" REX_FUNC(x)
#define REX_WEAK_FUNC(x) __attribute__((weak, noinline)) REX_FUNC(x)
//=============================================================================
// Function Mapping
//=============================================================================
struct PPCFuncMapping {
size_t guest;
PPCFunc* host;
};
extern PPCFuncMapping PPCFuncMappings[];
//=============================================================================
// Pack/Unpack Constants (NORMPACKED32 - 2:10:10:10 format)
//=============================================================================
constexpr float kPack2101010_Min10 = std::bit_cast<float>(0x403FFE01u);
constexpr float kPack2101010_Max10 = std::bit_cast<float>(0x404001FFu);
constexpr float kPack2101010_Min2 = std::bit_cast<float>(0x40400000u);
constexpr float kPack2101010_Max2 = std::bit_cast<float>(0x40400003u);
namespace rex::ppc {
//=============================================================================
// General Purpose Register
//=============================================================================
union Register {
int8_t s8;
uint8_t u8;
int16_t s16;
uint16_t u16;
int32_t s32;
uint32_t u32;
int64_t s64;
uint64_t u64;
float f32;
double f64;
};
//=============================================================================
// Fixed-Point Exception Register (XER)
//=============================================================================
struct XERRegister {
uint8_t so;
uint8_t ov;
uint8_t ca;
};
//=============================================================================
// Condition Register (CR) Field
//=============================================================================
struct CRRegister {
uint8_t lt;
uint8_t gt;
uint8_t eq;
union {
uint8_t so;
uint8_t un;
};
inline uint32_t raw() const noexcept { return (lt << 3) | (gt << 2) | (eq << 1) | so; }
inline void set_raw(uint32_t value) noexcept {
lt = (value >> 3) & 1;
gt = (value >> 2) & 1;
eq = (value >> 1) & 1;
so = value & 1;
}
template <typename T>
inline void compare(T left, T right, const XERRegister& xer) noexcept {
lt = left < right;
gt = left > right;
eq = left == right;
so = xer.so;
}
inline void compare(double left, double right) noexcept {
un = __builtin_isnan(left) || __builtin_isnan(right);
lt = !un && (left < right);
gt = !un && (left > right);
eq = !un && (left == right);
}
inline void setFromMask(simde__m128 mask, int imm) noexcept {
int m = simde_mm_movemask_ps(mask);
lt = m == imm;
gt = 0;
eq = m == 0;
so = 0;
}
inline void setFromMask(simde__m128i mask, int imm) noexcept {
int m = simde_mm_movemask_epi8(mask);
lt = m == imm;
gt = 0;
eq = m == 0;
so = 0;
}
};
//=============================================================================
// Vector Register (128-bit)
//=============================================================================
union alignas(0x10) VRegister {
int8_t s8[16];
uint8_t u8[16];
int16_t s16[8];
uint16_t u16[8];
int32_t s32[4];
uint32_t u32[4];
int64_t s64[2];
uint64_t u64[2];
float f32[4];
double f64[2];
};
//=============================================================================
// Floating-Point Status and Control Register (FPSCR)
//=============================================================================
constexpr uint32_t kRoundNearest = 0x00;
constexpr uint32_t kRoundTowardZero = 0x01;
constexpr uint32_t kRoundUp = 0x02;
constexpr uint32_t kRoundDown = 0x03;
constexpr uint32_t kRoundMask = 0x03;
struct FPSCRRegister {
uint32_t csr;
uint32_t fpu_csr;
uint32_t vmx_csr;
bool vmx_mode;
static constexpr size_t HostToGuest[] = {kRoundNearest, kRoundDown, kRoundUp, kRoundTowardZero};
using Platform = platform::FPSCRPlatform;
static constexpr size_t RoundShift = Platform::RoundShift;
static constexpr size_t RoundMaskVal = Platform::RoundMaskVal;
static constexpr size_t FlushMask = Platform::FlushMask;
static constexpr size_t ExceptionMask = Platform::ExceptionMask;
inline uint32_t getcsr() noexcept { return Platform::getcsr(); }
inline void setcsr(uint32_t csr) noexcept { Platform::setcsr(csr); }
inline uint32_t loadFromHost() noexcept {
if (!vmx_mode) {
fpu_csr = getcsr();
Platform::InitHostExceptions(fpu_csr);
fpu_csr &= ~FlushMask;
if (vmx_csr == 0) {
vmx_csr = FlushMask | Platform::GuestToHost[kRoundNearest];
Platform::InitHostExceptions(vmx_csr);
}
csr = fpu_csr;
}
return HostToGuest[(fpu_csr & RoundMaskVal) >> RoundShift];
}
inline void storeFromGuest(uint32_t value) noexcept {
fpu_csr &= ~RoundMaskVal;
fpu_csr |= Platform::GuestToHost[value & kRoundMask];
fpu_csr &= ~FlushMask;
Platform::InitHostExceptions(fpu_csr);
csr = fpu_csr;
vmx_mode = false;
setcsr(csr);
}
inline void enableFlushModeUnconditional() noexcept {
csr = vmx_csr;
vmx_mode = true;
setcsr(csr);
}
inline void disableFlushModeUnconditional() noexcept {
csr = fpu_csr;
vmx_mode = false;
setcsr(csr);
}
inline void enableFlushMode() noexcept {
if (!vmx_mode || getcsr() != vmx_csr) [[unlikely]] {
csr = vmx_csr;
vmx_mode = true;
setcsr(csr);
}
}
inline void disableFlushMode() noexcept {
if (vmx_mode || getcsr() != fpu_csr) [[unlikely]] {
csr = fpu_csr;
vmx_mode = false;
setcsr(csr);
}
}
inline void InitHost() noexcept {
fpu_csr = getcsr();
Platform::InitHostExceptions(fpu_csr);
fpu_csr &= ~FlushMask;
vmx_csr = FlushMask | Platform::GuestToHost[kRoundNearest];
Platform::InitHostExceptions(vmx_csr);
csr = fpu_csr;
vmx_mode = false;
setcsr(csr);
}
};
} // namespace rex::ppc
using PPCRegister = rex::ppc::Register;
using PPCXERRegister = rex::ppc::XERRegister;
using PPCCRRegister = rex::ppc::CRRegister;
using PPCVRegister = rex::ppc::VRegister;
using PPCFPSCRRegister = rex::ppc::FPSCRRegister;
//=============================================================================
// PPCContext Structure
//=============================================================================
struct alignas(0x40) PPCContext {
PPCRegister r3;
#if !defined(REX_CONFIG_NON_ARGUMENT_AS_LOCAL)
PPCRegister r0;
#endif
PPCRegister r1;
#if !defined(REX_CONFIG_NON_ARGUMENT_AS_LOCAL)
PPCRegister r2;
#endif
PPCRegister r4;
PPCRegister r5;
PPCRegister r6;
PPCRegister r7;
PPCRegister r8;
PPCRegister r9;
PPCRegister r10;
#if !defined(REX_CONFIG_NON_ARGUMENT_AS_LOCAL)
PPCRegister r11;
PPCRegister r12;
#endif
PPCRegister r13;
#if !defined(REX_CONFIG_NON_VOLATILE_AS_LOCAL)
PPCRegister r14;
PPCRegister r15;
PPCRegister r16;
PPCRegister r17;
PPCRegister r18;
PPCRegister r19;
PPCRegister r20;
PPCRegister r21;
PPCRegister r22;
PPCRegister r23;
PPCRegister r24;
PPCRegister r25;
PPCRegister r26;
PPCRegister r27;
PPCRegister r28;
PPCRegister r29;
PPCRegister r30;
PPCRegister r31;
#endif
#if !defined(REX_CONFIG_SKIP_LR)
uint64_t lr;
#endif
#if !defined(REX_CONFIG_CTR_AS_LOCAL)
PPCRegister ctr;
#endif
#if !defined(REX_CONFIG_XER_AS_LOCAL)
PPCXERRegister xer;
#endif
#if !defined(REX_CONFIG_RESERVED_AS_LOCAL)
PPCRegister reserved;
uint32_t reserved_address;
uint32_t reserved_size;
uint32_t reserved_bitmap_word;
uint64_t reserved_bitmap_mask;
bool reserved_global_acquired;
bool reserved_valid;
#endif
#if !defined(REX_CONFIG_SKIP_MSR)
uint32_t msr = 0x200A000;
#endif
#if !defined(REX_CONFIG_CR_AS_LOCAL)
PPCCRRegister cr0;
PPCCRRegister cr1;
PPCCRRegister cr2;
PPCCRRegister cr3;
PPCCRRegister cr4;
PPCCRRegister cr5;
PPCCRRegister cr6;
PPCCRRegister cr7;
#endif
PPCFPSCRRegister fpscr;
uint8_t vscr_sat = 0; // VSCR saturation flag (for vector ops)
/**
* Last indirect call target address. Set by REX_CALL_INDIRECT_FUNC before
* dispatch. Used by the invalid-function trap to report the faulting address.
* Unconditional (not guarded by config flags) because ctr may be optimized
* to a local variable via REX_CONFIG_CTR_AS_LOCAL.
*/
uint32_t last_indirect_target = 0;
#if !defined(REX_CONFIG_NON_ARGUMENT_AS_LOCAL)
PPCRegister f0;
#endif
PPCRegister f1;
PPCRegister f2;
PPCRegister f3;
PPCRegister f4;
PPCRegister f5;
PPCRegister f6;
PPCRegister f7;
PPCRegister f8;
PPCRegister f9;
PPCRegister f10;
PPCRegister f11;
PPCRegister f12;
PPCRegister f13;
#if !defined(REX_CONFIG_NON_VOLATILE_AS_LOCAL)
PPCRegister f14;
PPCRegister f15;
PPCRegister f16;
PPCRegister f17;
PPCRegister f18;
PPCRegister f19;
PPCRegister f20;
PPCRegister f21;
PPCRegister f22;
PPCRegister f23;
PPCRegister f24;
PPCRegister f25;
PPCRegister f26;
PPCRegister f27;
PPCRegister f28;
PPCRegister f29;
PPCRegister f30;
PPCRegister f31;
#endif
PPCVRegister v0;
PPCVRegister v1;
PPCVRegister v2;
PPCVRegister v3;
PPCVRegister v4;
PPCVRegister v5;
PPCVRegister v6;
PPCVRegister v7;
PPCVRegister v8;
PPCVRegister v9;
PPCVRegister v10;
PPCVRegister v11;
PPCVRegister v12;
PPCVRegister v13;
#if !defined(REX_CONFIG_NON_VOLATILE_AS_LOCAL)
PPCVRegister v14;
PPCVRegister v15;
PPCVRegister v16;
PPCVRegister v17;
PPCVRegister v18;
PPCVRegister v19;
PPCVRegister v20;
PPCVRegister v21;
PPCVRegister v22;
PPCVRegister v23;
PPCVRegister v24;
PPCVRegister v25;
PPCVRegister v26;
PPCVRegister v27;
PPCVRegister v28;
PPCVRegister v29;
PPCVRegister v30;
PPCVRegister v31;
#endif
#if !defined(REX_CONFIG_NON_ARGUMENT_AS_LOCAL)
PPCVRegister v32;
PPCVRegister v33;
PPCVRegister v34;
PPCVRegister v35;
PPCVRegister v36;
PPCVRegister v37;
PPCVRegister v38;
PPCVRegister v39;
PPCVRegister v40;
PPCVRegister v41;
PPCVRegister v42;
PPCVRegister v43;
PPCVRegister v44;
PPCVRegister v45;
PPCVRegister v46;
PPCVRegister v47;
PPCVRegister v48;
PPCVRegister v49;
PPCVRegister v50;
PPCVRegister v51;
PPCVRegister v52;
PPCVRegister v53;
PPCVRegister v54;
PPCVRegister v55;
PPCVRegister v56;
PPCVRegister v57;
PPCVRegister v58;
PPCVRegister v59;
PPCVRegister v60;
PPCVRegister v61;
PPCVRegister v62;
PPCVRegister v63;
#endif
#if !defined(REX_CONFIG_NON_VOLATILE_AS_LOCAL)
PPCVRegister v64;
PPCVRegister v65;
PPCVRegister v66;
PPCVRegister v67;
PPCVRegister v68;
PPCVRegister v69;
PPCVRegister v70;
PPCVRegister v71;
PPCVRegister v72;
PPCVRegister v73;
PPCVRegister v74;
PPCVRegister v75;
PPCVRegister v76;
PPCVRegister v77;
PPCVRegister v78;
PPCVRegister v79;
PPCVRegister v80;
PPCVRegister v81;
PPCVRegister v82;
PPCVRegister v83;
PPCVRegister v84;
PPCVRegister v85;
PPCVRegister v86;
PPCVRegister v87;
PPCVRegister v88;
PPCVRegister v89;
PPCVRegister v90;
PPCVRegister v91;
PPCVRegister v92;
PPCVRegister v93;
PPCVRegister v94;
PPCVRegister v95;
PPCVRegister v96;
PPCVRegister v97;
PPCVRegister v98;
PPCVRegister v99;
PPCVRegister v100;
PPCVRegister v101;
PPCVRegister v102;
PPCVRegister v103;
PPCVRegister v104;
PPCVRegister v105;
PPCVRegister v106;
PPCVRegister v107;
PPCVRegister v108;
PPCVRegister v109;
PPCVRegister v110;
PPCVRegister v111;
PPCVRegister v112;
PPCVRegister v113;
PPCVRegister v114;
PPCVRegister v115;
PPCVRegister v116;
PPCVRegister v117;
PPCVRegister v118;
PPCVRegister v119;
PPCVRegister v120;
PPCVRegister v121;
PPCVRegister v122;
PPCVRegister v123;
PPCVRegister v124;
PPCVRegister v125;
PPCVRegister v126;
PPCVRegister v127;
#endif
#if !defined(REX_CONFIG_NON_VOLATILE_AS_LOCAL)
//--- Non-volatile register save/restore --------
// Layout: r14-r31 (144) | f14-f31 (144) | v14-v31 (288) | v64-v127 (1024)
// Total: 1600 bytes. Buffer must be at least this large.
static constexpr size_t kNonVolatileSaveSize =
18 * sizeof(PPCRegister) + 18 * sizeof(PPCRegister) + 18 * sizeof(PPCVRegister) +
64 * sizeof(PPCVRegister);
inline void SaveNonVolatiles(uint8_t* dst) const {
std::memcpy(dst, &r14, 18 * sizeof(PPCRegister));
dst += 18 * sizeof(PPCRegister);
std::memcpy(dst, &f14, 18 * sizeof(PPCRegister));
dst += 18 * sizeof(PPCRegister);
std::memcpy(dst, &v14, 18 * sizeof(PPCVRegister));
dst += 18 * sizeof(PPCVRegister);
std::memcpy(dst, &v64, 64 * sizeof(PPCVRegister));
}
inline void RestoreNonVolatiles(const uint8_t* src) {
std::memcpy(&r14, src, 18 * sizeof(PPCRegister));
src += 18 * sizeof(PPCRegister);
std::memcpy(&f14, src, 18 * sizeof(PPCRegister));
src += 18 * sizeof(PPCRegister);
std::memcpy(&v14, src, 18 * sizeof(PPCVRegister));
src += 18 * sizeof(PPCVRegister);
std::memcpy(&v64, src, 64 * sizeof(PPCVRegister));
}
#else
static constexpr size_t kNonVolatileSaveSize = 0;
inline void SaveNonVolatiles(uint8_t*) const {}
inline void RestoreNonVolatiles(const uint8_t*) {}
#endif
};
namespace rex::ppc {
constexpr uint32_t kReserveBlockShift = 16;
constexpr uint32_t kReserveBlockCount = 1u << (32 - kReserveBlockShift);
constexpr uint32_t kReserveBitmapWordCount = kReserveBlockCount / 64;
inline std::atomic<uint64_t> g_reservation_bitmap[kReserveBitmapWordCount] = {};
inline void ReleaseReservation(PPCContext& ctx) {
ctx.reserved_global_acquired = false;
ctx.reserved_valid = false;
}
inline bool AcquireReservation(PPCContext& ctx, uint32_t address, uint32_t size) {
ReleaseReservation(ctx);
ctx.reserved_address = address;
ctx.reserved_size = size;
ctx.reserved_bitmap_word = 0;
ctx.reserved_bitmap_mask = 0;
ctx.reserved_global_acquired = false;
ctx.reserved_valid = true;
return ctx.reserved_valid;
}
inline bool ReservationMatches(const PPCContext& ctx, uint32_t address) {
if (!ctx.reserved_valid) {
return false;
}
return ctx.reserved_address == address;
}
inline void ImportReservation(PPCContext& ctx, uint32_t address, uint64_t value) {
ReleaseReservation(ctx);
ctx.reserved.u64 = value;
ctx.reserved_address = address;
ctx.reserved_size = 0;
ctx.reserved_bitmap_word = 0;
ctx.reserved_bitmap_mask = 0;
ctx.reserved_global_acquired = false;
ctx.reserved_valid = true;
}
} // namespace rex::ppc