/** * @file ppc/context.h * @brief PPC thread context, guest function macros, and interrupt handling * * @copyright Copyright (c) 2026 Tom Clay * 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 #include #include #include #include #include #include #include #include //============================================================================= // 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(0x403FFE01u); constexpr float kPack2101010_Max10 = std::bit_cast(0x404001FFu); constexpr float kPack2101010_Min2 = std::bit_cast(0x40400000u); constexpr float kPack2101010_Max2 = std::bit_cast(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 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 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