608 lines
16 KiB
C++
608 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 = ExceptionMask | FlushMask | Platform::GuestToHost[kRoundNearest];
|
|
}
|
|
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;
|
|
fpu_csr |= ExceptionMask;
|
|
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 = ExceptionMask | FlushMask | Platform::GuestToHost[kRoundNearest];
|
|
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
|