This commit is contained in:
patchzyy
2026-08-23 17:10:50 +02:00
commit ec226e8348
1099 changed files with 400981 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <type_traits>
namespace BigEndian {
inline uint16_t Read16(const uint8_t* src) {
return (static_cast<uint16_t>(src[0]) << 8) |
static_cast<uint16_t>(src[1]);
}
inline uint32_t Read32(const uint8_t* src) {
return (static_cast<uint32_t>(src[0]) << 24) |
(static_cast<uint32_t>(src[1]) << 16) |
(static_cast<uint32_t>(src[2]) << 8) |
static_cast<uint32_t>(src[3]);
}
inline float ReadFloat32(const uint8_t* src) {
const uint32_t bits = Read32(src);
float value = 0.0f;
static_assert(sizeof(bits) == sizeof(value));
std::memcpy(&value, &bits, sizeof(value));
return value;
}
inline void Write16(uint8_t* dst, uint16_t value) {
dst[0] = static_cast<uint8_t>(value >> 8);
dst[1] = static_cast<uint8_t>(value);
}
inline void Write32(uint8_t* dst, uint32_t value) {
dst[0] = static_cast<uint8_t>(value >> 24);
dst[1] = static_cast<uint8_t>(value >> 16);
dst[2] = static_cast<uint8_t>(value >> 8);
dst[3] = static_cast<uint8_t>(value);
}
inline void Write64(uint8_t* dst, uint64_t value) {
Write32(dst, static_cast<uint32_t>(value >> 32));
Write32(dst + sizeof(uint32_t), static_cast<uint32_t>(value));
}
inline void WriteFloat32(uint8_t* dst, float value) {
uint32_t bits = 0;
static_assert(sizeof(bits) == sizeof(value));
std::memcpy(&bits, &value, sizeof(bits));
Write32(dst, bits);
}
inline void Write16(uint8_t* dst, std::size_t offset, uint16_t value) {
Write16(dst + offset, value);
}
inline void Write32(uint8_t* dst, std::size_t offset, uint32_t value) {
Write32(dst + offset, value);
}
inline void Write64(uint8_t* dst, std::size_t offset, uint64_t value) {
Write64(dst + offset, value);
}
inline void WriteFloat32(uint8_t* dst, std::size_t offset, float value) {
WriteFloat32(dst + offset, value);
}
template <typename Offset>
inline void Append16(uint8_t* dst, Offset& offset, uint16_t value) {
static_assert(std::is_integral_v<Offset>);
Write16(dst + offset, value);
offset += static_cast<Offset>(sizeof(value));
}
template <typename Offset>
inline void Append32(uint8_t* dst, Offset& offset, uint32_t value) {
static_assert(std::is_integral_v<Offset>);
Write32(dst + offset, value);
offset += static_cast<Offset>(sizeof(value));
}
template <typename Offset>
inline void AppendFloat32(uint8_t* dst, Offset& offset, float value) {
static_assert(std::is_integral_v<Offset>);
WriteFloat32(dst + offset, value);
offset += static_cast<Offset>(sizeof(value));
}
} // namespace BigEndian
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include <atomic>
#include <cstdint>
#define MKW_RESTRICT __restrict
#include <immintrin.h>
inline constexpr bool MkwStateFreeAbiEnabled(uint32_t) noexcept
{
return true;
}
#define MKW_PPC_FORCE_INLINE __forceinline
#define MKW_PPC_NO_INLINE __declspec(noinline)
#define MKW_PPC_ALWAYS_INLINE_BODY __attribute__((always_inline))
#define MKW_PPC_COLD __attribute__((cold))
#define MKW_PPC_INTERNAL_CALL __regcall
using MkwStateFreeResult2 = uint64_t __attribute__((ext_vector_type(2)));
+188
View File
@@ -0,0 +1,188 @@
#pragma once
#include "ppc_isa_fpenv.h"
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <string_view>
void ShowRuntimeFatalPopup(std::string_view category, std::string_view details) noexcept;
union PPC_FPR {
uint64_t raw;
double d;
struct {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
float ps1; // Low word (Least Significant)
float ps0; // High word (Most Significant)
#else
float ps0; // High word
float ps1; // Low word
#endif
} paired;
};
// PowerPC CPU Context
struct CpuContext {
// Standard GPRs
uint32_t gpr[32];
// Special Purpose Registers defined by standard PPC
uint32_t cr; // Condition Register
uint32_t lr; // Link Register
uint32_t ctr; // Count Register
uint32_t xer; // Integer Exception Register
uint32_t fpscr; // Floating-Point Status and Control Register
// Program State
uint32_t pc; // Program Counter (not used alot tho)
// Floating Point Registers (Modified for Paired Single support)
PPC_FPR fpr[32];
// Broadway Specific Extensions
uint32_t gqr[8]; // Graphics Quantization Registers
uint32_t hid0; // HID0
uint32_t hid1; // HID1
uint32_t hid2; // HID2
uint32_t srr0; // Save/Restore Register 0
uint32_t srr1; // Save/Restore Register 1
uint32_t msr; // Machine State Register
};
inline thread_local CpuContext* g_currentCpuContext = nullptr;
class CpuContextScope {
public:
explicit CpuContextScope(CpuContext* ctx)
: previous_(g_currentCpuContext)
{
g_currentCpuContext = ctx;
savedMxcsr_ = _mm_getcsr();
if (ctx != nullptr)
MkwApplyHostNiMode(ctx->fpscr);
}
~CpuContextScope()
{
g_currentCpuContext = previous_;
if (previous_ != nullptr)
MkwApplyHostNiMode(previous_->fpscr);
else
MkwRestoreHostMxcsr(savedMxcsr_);
}
CpuContextScope(const CpuContextScope&) = delete;
CpuContextScope& operator=(const CpuContextScope&) = delete;
private:
CpuContext* previous_ = nullptr;
uint32_t savedMxcsr_ = 0;
};
inline CpuContext* TryGetCpuContext() noexcept
{
return g_currentCpuContext;
}
inline CpuContext* CurrentCpuContext()
{
CpuContext* cpu = TryGetCpuContext();
if (!cpu) {
std::cerr << "[runtime] CRITICAL: CurrentCpuContext is NULL. "
<< "Did you forget to create a CpuContextScope?" << std::endl;
ShowRuntimeFatalPopup("Runtime context failure",
"The game stopped because a translated function tried to run without a CPU context.");
std::abort();
}
if (cpu->gpr[1] == 0) {
std::cerr << "[runtime] CRITICAL: Guest Stack Pointer (r1) is NULL (0x00000000). "
<< "The emulated program has crashed." << std::endl;
ShowRuntimeFatalPopup("Guest execution failure",
"The game stopped because the guest stack pointer became null while translated code was running.");
std::abort(); // Stop immediately so you can debug the cause.
}
return cpu;
}
// Condition Register Fields
#define CR_LT 0
#define CR_GT 1
#define CR_EQ 2
#define CR_SO 3
extern "C" void DumpHostStackTraceForRuntimeHelper();
void MarkFatalErrorReported();
[[noreturn]] inline void PPC_Undefined(uint32_t pc, uint32_t rawInstruction, const char* details)
{
std::fprintf(stderr,
"[runtime] UNDEFINED guest instruction: pc=0x%08X raw=0x%08X %s\n",
pc,
rawInstruction,
details ? details : "");
char message[256]{};
std::snprintf(message, sizeof(message),
"The game stopped because it reached an unsupported guest instruction at PC 0x%08X (instruction 0x%08X).\n\n%s",
pc, rawInstruction, details ? details : "No additional details were provided.");
ShowRuntimeFatalPopup("Unsupported guest instruction", message);
std::abort();
}
// Used by generated code when the translator encounters privileged/unmodeled PPC instructions
// (e.g. rfi). This is intentionally loud so missing HLE hooks are easy to find.
#define UNDEFINED(pc, rawInstruction, details) PPC_Undefined((pc), (rawInstruction), (details))
// Helper to set CR bits (Signed)
inline void SetCR(CpuContext* cpu, int field, int32_t a, int32_t b) {
uint32_t crField = 0;
if (a < b) crField |= 0x8; // LT
if (a > b) crField |= 0x4; // GT
if (a == b) crField |= 0x2; // EQ
crField |= (cpu->xer >> 31) & 1u; // SO
int shift = (7 - field) * 4;
uint32_t mask = 0xF << shift;
cpu->cr = (cpu->cr & ~mask) | (crField << shift);
}
// Helper to set CR bits (Unsigned)
inline void SetCR(CpuContext* cpu, int field, uint32_t a, uint32_t b) {
uint32_t crField = 0;
if (a < b) crField |= 0x8; // LT
if (a > b) crField |= 0x4; // GT
if (a == b) crField |= 0x2; // EQ
crField |= (cpu->xer >> 31) & 1u; // SO
int shift = (7 - field) * 4;
uint32_t mask = 0xF << shift;
cpu->cr = (cpu->cr & ~mask) | (crField << shift);
}
// Helper to set CR bits (Floating-point)
inline void SetCRFloat(CpuContext* cpu, int field, double a, double b) {
uint32_t crField = 0;
if (std::isnan(a) || std::isnan(b)) {
// Unordered: LT/GT/EQ clear, SO set.
crField = 0x1;
} else {
if (a < b) crField |= 0x8; // LT
if (a > b) crField |= 0x4; // GT
if (a == b) crField |= 0x2; // EQ
}
int shift = (7 - field) * 4;
uint32_t mask = 0xF << shift;
cpu->cr = (cpu->cr & ~mask) | (crField << shift);
}
// Helper to get CR bit
inline bool GetCRBit(CpuContext* cpu, int field, int bit) {
int shift = (7 - field) * 4 + (3 - bit);
return (cpu->cr >> shift) & 1;
}
+62
View File
@@ -0,0 +1,62 @@
#pragma once
#include <cmath>
#include <cstdint>
inline void SetCRResident(uint32_t& cr, uint32_t xer, int field, int32_t a, int32_t b) noexcept {
uint32_t value = (a < b ? 0x8u : 0u) | (a > b ? 0x4u : 0u) | (a == b ? 0x2u : 0u) | ((xer >> 31) & 1u);
const int shift = (7 - field) * 4;
cr = (cr & ~(0xFu << shift)) | (value << shift);
}
inline void SetCRResident(uint32_t& cr, uint32_t xer, int field, uint32_t a, uint32_t b) noexcept {
uint32_t value = (a < b ? 0x8u : 0u) | (a > b ? 0x4u : 0u) | (a == b ? 0x2u : 0u) | ((xer >> 31) & 1u);
const int shift = (7 - field) * 4;
cr = (cr & ~(0xFu << shift)) | (value << shift);
}
inline void SetCRFloatResident(uint32_t& cr, int field, double a, double b) noexcept {
uint32_t value = (std::isnan(a) || std::isnan(b)) ? 0x1u :
((a < b ? 0x8u : 0u) | (a > b ? 0x4u : 0u) | (a == b ? 0x2u : 0u));
const int shift = (7 - field) * 4;
cr = (cr & ~(0xFu << shift)) | (value << shift);
}
inline bool GetCRBitResident(uint32_t cr, int field, int bit) noexcept {
const int shift = (7 - field) * 4 + (3 - bit);
return ((cr >> shift) & 1u) != 0u;
}
inline uint32_t PpcCrSetBitResident(uint32_t cr, uint32_t bitIndex, uint32_t value) noexcept {
const uint32_t mask = 1u << (31u - (bitIndex & 31u));
return (value & 1u) != 0 ? (cr | mask) : (cr & ~mask);
}
inline uint32_t PpcCrLogicalResident(
uint32_t cr, uint32_t op, uint32_t bt, uint32_t ba, uint32_t bb) noexcept {
const auto readBit = [cr](uint32_t index) noexcept {
return (cr >> (31u - (index & 31u))) & 1u;
};
const uint32_t a = readBit(ba);
const uint32_t b = readBit(bb);
uint32_t result = 0;
switch (op & 7u) {
case 0: result = ~(a | b) & 1u; break;
case 1: result = a & (~b & 1u); break;
case 2: result = a ^ b; break;
case 3: result = ~(a & b) & 1u; break;
case 4: result = a & b; break;
case 5: result = ~(a ^ b) & 1u; break;
case 6: result = (~a & 1u) | b; break;
case 7: result = a | b; break;
}
return PpcCrSetBitResident(cr, bt, result);
}
inline uint32_t PpcMcrfResident(uint32_t cr, uint32_t dstField, uint32_t srcField) noexcept {
dstField &= 7u;
srcField &= 7u;
const uint32_t dstShift = (7u - dstField) * 4u;
const uint32_t srcShift = (7u - srcField) * 4u;
const uint32_t field = (cr >> srcShift) & 0xFu;
return (cr & ~(0xFu << dstShift)) | (field << dstShift);
}
+858
View File
@@ -0,0 +1,858 @@
// SPDX-License-Identifier: GPL-2.0-or-later
//
// Copyright 2018 Dolphin Emulator Project
//
// Portions of this file are derived from the Dolphin Emulator
// (https://github.com/dolphin-emu/dolphin):
//
// * Source/Core/Common/FloatUtils.cpp - the Gekko/Broadway fres and frsqrte
// estimate tables (kPpcFresEstimateInline / kPpcFrsqrteEstimateInline,
// upstream fres_expected / frsqrte_expected) together with the
// PpcApproximateReciprocalInline and PpcApproximateReciprocalSquareRootInline
// interpolation routines, which are ports of upstream ApproximateReciprocal
// and ApproximateReciprocalSquareRoot.
//
// The remainder of this header is original to this project. It is
// GPL-2.0-or-later as a consequence of the above; see THIRD-PARTY-NOTICES.md.
#pragma once
// Pure floating-point and paired-single PowerPC semantics: the scalar single
// and double families, every ps_* arithmetic form, the NI flush rules and the
// Gekko fres/frsqrte estimates. Nothing in this header touches guest memory -
// the psq_l/psq_st tier that does lives in ppc_isa_quantized.h.
#include "ppc_isa_config.h"
#include "ppc_isa_context.h"
#include <array>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <limits>
inline uint64_t PpcBitCastToU64Inline(double value)
{
uint64_t integral = 0;
std::memcpy(&integral, &value, sizeof(integral));
return integral;
}
inline uint32_t PPC_FprLowWordInline(double value)
{
return static_cast<uint32_t>(PpcBitCastToU64Inline(value));
}
inline double PpcBitCastToDoubleInline(uint64_t value)
{
double result = 0.0;
std::memcpy(&result, &value, sizeof(result));
return result;
}
inline uint32_t PpcBitCastToU32Inline(float value)
{
uint32_t integral = 0;
std::memcpy(&integral, &value, sizeof(integral));
return integral;
}
inline float PpcBitCastToFloatInline(uint32_t value)
{
float result = 0.0f;
std::memcpy(&result, &value, sizeof(result));
return result;
}
// NOTE: PpcGetPs0Inline / PpcGetPs1Inline / PpcPackPairedInline are defined
// below the paired-single helper block (PpcPsToM128Inline and
// friends) because their register-domain implementations are written in terms
// of those helpers. Nothing between here and there uses them.
inline double PpcGetPairedFprInline(const PPC_FPR& fpr)
{
return fpr.d;
}
inline void PpcSetPairedFprInline(PPC_FPR& fpr, double packed)
{
fpr.d = packed;
}
// Must stay inside the XMM register domain. Bitcasting through a 64-bit GPR added a movq
// domain crossing on every paired-single op (630 in the THP IDCT region alone); a double
// local already lives in an XMM register, so these casts compile to nothing.
inline __m128 PpcPsToM128Inline(double value)
{
return _mm_castpd_ps(_mm_set_sd(value));
}
inline double PpcM128ToPsInline(__m128 value)
{
return _mm_cvtsd_f64(_mm_castps_pd(value));
}
inline __m128 PpcBroadcastPs0Inline(double value)
{
const __m128 lanes = PpcPsToM128Inline(value);
return _mm_shuffle_ps(lanes, lanes, _MM_SHUFFLE(1, 1, 1, 1));
}
inline __m128 PpcBroadcastPs1Inline(double value)
{
const __m128 lanes = PpcPsToM128Inline(value);
return _mm_shuffle_ps(lanes, lanes, _MM_SHUFFLE(0, 0, 0, 0));
}
inline __m128 PpcNegateNonNanLanesInline(__m128 value)
{
const __m128 signMask = _mm_castsi128_ps(_mm_set1_epi32(static_cast<int>(0x80000000u)));
const __m128 negated = _mm_xor_ps(value, signMask);
const __m128 ordered = _mm_cmpord_ps(value, value);
return _mm_or_ps(_mm_and_ps(ordered, negated), _mm_andnot_ps(ordered, value));
}
// Paired-single lane accessors. The packed double's LOW 32 bits hold ps1 and HIGH 32 bits
// hold ps0, so in the SSE float-lane view lane 0 == ps1 and lane 1 == ps0. Must stay pure
// register-domain shuffles (no arithmetic/conversion, so NaN/denormal bits pass through);
// the former union-based forms store-then-reloaded through memory, a guaranteed
// store-to-load-forwarding stall on every scalar-lane op.
inline float PpcGetPs0Inline(double value)
{
// ps0 lives in lane 1; PpcBroadcastPs0Inline already splats it.
return _mm_cvtss_f32(PpcBroadcastPs0Inline(value));
}
inline float PpcGetPs1Inline(double value)
{
// ps1 is already lane 0 of the packed representation.
return _mm_cvtss_f32(PpcPsToM128Inline(value));
}
inline double PpcPackPairedInline(float ps0, float ps1)
{
// _mm_unpacklo_ps(x, y) -> { x[0], y[0], x[1], y[1] }, so lane 0 becomes
// ps1 and lane 1 becomes ps0, matching the union layout bit for bit.
return PpcM128ToPsInline(_mm_unpacklo_ps(_mm_set_ss(ps1), _mm_set_ss(ps0)));
}
// FPSCR[NI] is modeled by MXCSR FTZ/DAZ, so arithmetic output flushing compiles to nothing.
// Two cases still need a software check against the mirrored bit (not STMXCSR, too hot):
// double->single conversion (CVTSD2SS isn't covered by FTZ) and raw lane pass-through moves.
inline bool MkwHostNiActiveInline() noexcept
{
return g_mkwHostNiActive;
}
inline float PpcForceSingleValueInline(double value)
{
// FPSCR[NI] flushes an exact pre-round single-subnormal even when rounding would promote it
// to the smallest normal. g_mkwNiFlushThreshold (2^-126 active, 0.0 inactive) turns the
// flush into a branchless mask: a set compare lane keeps just the sign bit, a clear lane
// passes the value to CVTSD2SS. DAZ (set exactly when NI is) makes the compare itself treat
// a subnormal as zero, matching the mask's answer.
const __m128d v = _mm_set_sd(value);
const __m128d signMask = _mm_set_sd(-0.0);
const __m128d magnitude = _mm_andnot_pd(signMask, v);
const __m128d flush = _mm_cmplt_sd(magnitude, _mm_set_sd(g_mkwNiFlushThreshold));
const __m128d kept = _mm_andnot_pd(_mm_andnot_pd(signMask, flush), v);
return static_cast<float>(_mm_cvtsd_f64(kept));
}
inline float PpcFlushSingleForNiInline(float value)
{
if (!MkwHostNiActiveInline())
return value;
const uint32_t bits = PpcBitCastToU32Inline(value);
if ((bits & 0x7FFFFFFFu) < 0x00800000u) [[unlikely]]
return PpcBitCastToFloatInline(bits & 0x80000000u);
return value;
}
inline double PpcFlushPairedForNiInline(double value)
{
// Callers pass results of SSE arithmetic; MXCSR.FTZ already flushed them.
return value;
}
inline double PpcForce25BitInline(double value)
{
constexpr uint64_t kDoubleExpMask = 0x7FF0000000000000ULL;
constexpr uint64_t kDoubleFracMask = 0x000FFFFFFFFFFFFFULL;
constexpr int kDoubleFracWidth = 52;
uint64_t integral = PpcBitCastToU64Inline(value);
const uint64_t exponent = integral & kDoubleExpMask;
const uint64_t fraction = integral & kDoubleFracMask;
if (exponent == 0 && fraction != 0)
{
int64_t keepMask = 0xFFFFFFFFF8000000LL;
uint64_t round = 0x8000000ULL;
uint32_t leadingZeros = 0;
uint64_t normalizedFraction = fraction;
while ((normalizedFraction & (1ULL << 63)) == 0)
{
normalizedFraction <<= 1;
++leadingZeros;
}
const uint32_t shift = leadingZeros - (63 - kDoubleFracWidth);
keepMask >>= shift;
round >>= shift;
integral = (integral & static_cast<uint64_t>(keepMask)) + (integral & round);
}
else
{
integral = (integral & 0xFFFFFFFFF8000000ULL) + (integral & 0x8000000ULL);
}
return PpcBitCastToDoubleInline(integral);
}
struct PpcEstimateEntryInline
{
int32_t base;
int32_t decrement;
};
inline constexpr std::array<PpcEstimateEntryInline, 32> kPpcFresEstimateInline = {{
{0x7ff800, 0x3e1}, {0x783800, 0x3a7}, {0x70ea00, 0x371}, {0x6a0800, 0x340},
{0x638800, 0x313}, {0x5d6200, 0x2ea}, {0x579000, 0x2c4}, {0x520800, 0x2a0},
{0x4cc800, 0x27f}, {0x47ca00, 0x261}, {0x430800, 0x245}, {0x3e8000, 0x22a},
{0x3a2c00, 0x212}, {0x360800, 0x1fb}, {0x321400, 0x1e5}, {0x2e4a00, 0x1d1},
{0x2aa800, 0x1be}, {0x272c00, 0x1ac}, {0x23d600, 0x19b}, {0x209e00, 0x18b},
{0x1d8800, 0x17c}, {0x1a9000, 0x16e}, {0x17ae00, 0x15b}, {0x14f800, 0x15b},
{0x124400, 0x143}, {0x0fbe00, 0x143}, {0x0d3800, 0x12d}, {0x0ade00, 0x12d},
{0x088400, 0x11a}, {0x065000, 0x11a}, {0x041c00, 0x108}, {0x020c00, 0x106},
}};
inline double PpcApproximateReciprocalInline(double value)
{
constexpr uint64_t kSign = 0x8000000000000000ULL;
constexpr uint64_t kExponent = 0x7FF0000000000000ULL;
constexpr uint64_t kFraction = 0x000FFFFFFFFFFFFFULL;
constexpr uint64_t kQuietBit = 0x0008000000000000ULL;
const uint64_t input = PpcBitCastToU64Inline(value);
const uint64_t mantissa = input & kFraction;
const uint64_t sign = input & kSign;
uint64_t exponent = input & kExponent;
if (mantissa == 0 && exponent == 0)
return PpcBitCastToDoubleInline(sign | kExponent);
if (exponent == kExponent)
{
if (mantissa == 0)
return PpcBitCastToDoubleInline(sign);
return PpcBitCastToDoubleInline(input | kQuietBit);
}
if (exponent < (uint64_t{895} << 52))
return std::copysign(static_cast<double>(std::numeric_limits<float>::max()), value);
if (exponent >= (uint64_t{1149} << 52))
return std::copysign(0.0, value);
exponent = (uint64_t{0x7FD} << 52) - exponent;
const int index = static_cast<int>(mantissa >> 37);
const auto& entry = kPpcFresEstimateInline[static_cast<size_t>(index / 1024)];
const int64_t estimate = static_cast<int64_t>(entry.base) -
(static_cast<int64_t>(entry.decrement) * (index % 1024) + 1) / 2;
return PpcBitCastToDoubleInline(
sign | exponent | (static_cast<uint64_t>(estimate) << 29));
}
// Broadway/Gekko frsqrte lookup table. These constants and the interpolation
// below match the algorithm used by the checked-in Dolphin reference rather
// than substituting an exact host reciprocal square root. This lives in the
// header because frsqrte is emitted at 281 translated sites and an out-of-line
// call is a full register barrier at each of them.
inline constexpr std::array<PpcEstimateEntryInline, 32> kPpcFrsqrteEstimateInline = {{
{0x1a7e800, -0x568}, {0x17cb800, -0x4f3}, {0x1552800, -0x48d}, {0x130c000, -0x435},
{0x10f2000, -0x3e7}, {0x0eff000, -0x3a2}, {0x0d2e000, -0x365}, {0x0b7c000, -0x32e},
{0x09e5000, -0x2fc}, {0x0867000, -0x2d0}, {0x06ff000, -0x2a8}, {0x05ab800, -0x283},
{0x046a000, -0x261}, {0x0339800, -0x243}, {0x0218800, -0x226}, {0x0105800, -0x20b},
{0x3ffa000, -0x7a4}, {0x3c29000, -0x700}, {0x38aa000, -0x670}, {0x3572000, -0x5f2},
{0x3279000, -0x584}, {0x2fb7000, -0x524}, {0x2d26000, -0x4cc}, {0x2ac0000, -0x47e},
{0x2881000, -0x43a}, {0x2665000, -0x3fa}, {0x2468000, -0x3c2}, {0x2287000, -0x38e},
{0x20c1000, -0x35e}, {0x1f12000, -0x332}, {0x1d79000, -0x30a}, {0x1bf4000, -0x2e6},
}};
inline double PpcApproximateReciprocalSquareRootInline(double value)
{
constexpr uint64_t kSign = 0x8000000000000000ULL;
constexpr uint64_t kExponent = 0x7FF0000000000000ULL;
constexpr uint64_t kFraction = 0x000FFFFFFFFFFFFFULL;
constexpr uint64_t kQuietBit = 0x0008000000000000ULL;
constexpr uint64_t kCanonicalQuietNaN = kExponent | kQuietBit;
const uint64_t input = PpcBitCastToU64Inline(value);
uint64_t mantissa = input & kFraction;
const uint64_t sign = input & kSign;
int64_t exponent = static_cast<int64_t>(input & kExponent);
if (mantissa == 0 && exponent == 0)
{
return PpcBitCastToDoubleInline(sign | kExponent);
}
if (static_cast<uint64_t>(exponent) == kExponent)
{
if (mantissa == 0)
{
return sign ? PpcBitCastToDoubleInline(kCanonicalQuietNaN) : 0.0;
}
return PpcBitCastToDoubleInline(input | kQuietBit);
}
if (sign != 0)
{
return PpcBitCastToDoubleInline(kCanonicalQuietNaN);
}
if (exponent == 0)
{
// Normalize a subnormal while allowing the signed exponent to extend
// below the IEEE-754 encoded range, exactly as the hardware estimate
// interpolation expects.
do
{
exponent -= int64_t{1} << 52;
mantissa <<= 1;
} while ((mantissa & (uint64_t{1} << 52)) == 0);
mantissa &= kFraction;
exponent += int64_t{1} << 52;
}
const int64_t exponentLsb = exponent & (int64_t{1} << 52);
exponent = (((int64_t{0x3FF} << 52) -
((exponent - (int64_t{0x3FE} << 52)) / 2)) &
static_cast<int64_t>(kExponent));
const int index = static_cast<int>(
(static_cast<uint64_t>(exponentLsb) | mantissa) >> 37);
const auto& entry = kPpcFrsqrteEstimateInline[static_cast<size_t>(index / 2048)];
const int64_t estimate =
static_cast<int64_t>(entry.base) +
static_cast<int64_t>(entry.decrement) * (index % 2048);
const uint64_t result = sign | static_cast<uint64_t>(exponent) |
(static_cast<uint64_t>(estimate) << 26);
return PpcBitCastToDoubleInline(result);
}
// fctiwz: convert to a 32-bit signed integer with round-toward-zero and place
// the result in the LOW 32 bits of the FPR. The upper 32 bits are
// architecturally undefined; zero is what the hardware leaves in practice and
// what stfd/lwz+4 sequences in guest code expect to read back.
inline int32_t PpcClampIntegerWordInline(double value)
{
if (std::isnan(value)) {
return static_cast<int32_t>(0x80000000u);
}
if (value >= 2147483647.0) {
return 2147483647;
}
if (value <= -2147483648.0) {
return static_cast<int32_t>(0x80000000u);
}
return static_cast<int32_t>(value);
}
inline double PpcPackIntegerWordInline(int32_t value)
{
return PpcBitCastToDoubleInline(
static_cast<uint64_t>(static_cast<uint32_t>(value)));
}
template <bool Subtract>
inline float PpcAccuratePsMaddLaneInline(float a, float c, float b)
{
const float signedB = Subtract ? -b : b;
// Paired values are stored as exact float32 lanes in this runtime. An
// explicit float FMA therefore matches Gekko's single rounding point;
// multiplying and adding separately can differ by an ULP on ordinary
// vector and matrix workloads.
return PpcFlushSingleForNiInline(std::fma(a, c, signedB));
}
template <bool Subtract>
inline float PpcAccuratePsMaddLaneNoNiInline(float a, float c, float b)
{
const float signedB = Subtract ? -b : b;
return std::fma(a, c, signedB);
}
template <bool Subtract>
inline double PpcAccurateSingleMaddIntermediateInline(double a, double c, double b)
{
// Gekko single-precision fused operations keep the full precision of A
// and B, round C to a 25-bit significand, and round only the final result
// to float32. A double FMA is almost sufficient, but an exact result just
// beyond a float32 halfway point can be rounded to a double tie first.
// Recover the direction of that discarded error for those tie cases.
const double roundedC = PpcForce25BitInline(c);
const double signedB = Subtract ? -b : b;
double result = std::fma(a, roundedC, signedB);
const uint64_t resultBits = PpcBitCastToU64Inline(result);
constexpr uint64_t kDiscardedMask = 0x000000001FFFFFFFULL;
constexpr uint64_t kEvenTie = 0x0000000010000000ULL;
if ((resultBits & kDiscardedMask) == kEvenTie)
{
const double aPrime = signedB - result;
const double bPrime = result + aPrime;
const double deltaA = std::fma(a, roundedC, aPrime);
const double deltaB = signedB - bPrime;
const double error = deltaA + deltaB;
if (error != 0.0)
{
result = PpcBitCastToDoubleInline(
(error > 0.0) == (result > 0.0) ? resultBits + 1 : resultBits - 1);
}
}
return result;
}
inline double PpcFmulsInline(double a, double c)
{
return static_cast<double>(
PpcForceSingleValueInline(a * PpcForce25BitInline(c)));
}
inline double PpcFmulsNoNiInline(double a, double c)
{
return static_cast<double>(static_cast<float>(a * PpcForce25BitInline(c)));
}
inline double PpcFmaddInline(double a, double c, double b)
{
return std::fma(a, c, b);
}
inline double PpcFmsubInline(double a, double c, double b)
{
return std::fma(a, c, -b);
}
inline double PpcFnmaddInline(double a, double c, double b)
{
const double result = std::fma(a, c, b);
return std::isnan(result) ? result : -result;
}
inline double PpcFnmsubInline(double a, double c, double b)
{
const double result = std::fma(a, c, -b);
return std::isnan(result) ? result : -result;
}
inline double PpcFmaddsInline(double a, double c, double b)
{
return static_cast<double>(
PpcForceSingleValueInline(PpcAccurateSingleMaddIntermediateInline<false>(a, c, b)));
}
inline double PpcFmsubsInline(double a, double c, double b)
{
return static_cast<double>(
PpcForceSingleValueInline(PpcAccurateSingleMaddIntermediateInline<true>(a, c, b)));
}
inline double PpcFnmaddsInline(double a, double c, double b)
{
const float result = PpcForceSingleValueInline(
PpcAccurateSingleMaddIntermediateInline<false>(a, c, b));
return static_cast<double>(std::isnan(result) ? result : -result);
}
inline double PpcFnmsubsInline(double a, double c, double b)
{
const float result = PpcForceSingleValueInline(
PpcAccurateSingleMaddIntermediateInline<true>(a, c, b));
return static_cast<double>(std::isnan(result) ? result : -result);
}
inline double PPC_PsMulInline(double lhs, double rhs)
{
return PpcFlushPairedForNiInline(
PpcM128ToPsInline(_mm_mul_ps(PpcPsToM128Inline(lhs), PpcPsToM128Inline(rhs))));
}
inline double PPC_PsMulNoNiInline(double lhs, double rhs)
{
return PpcM128ToPsInline(_mm_mul_ps(PpcPsToM128Inline(lhs), PpcPsToM128Inline(rhs)));
}
// The paired madd family lowers to one hardware FMA. Semantics match the scalar lanes exactly: a
// single fused rounding per lane (std::fma(float) == vfmaddps per lane), and
// the negate-unless-NaN behavior of the nmadd/nmsub forms is expressed with
// PpcNegateNonNanLanesInline. NI flushing is handled by MXCSR (see
// MkwApplyHostNiMode), so the NI and NoNi entry points are identical here.
inline double PPC_PsMsubInline(double multiplicand, double multiplier, double subtractor)
{
return PpcM128ToPsInline(_mm_fmsub_ps(
PpcPsToM128Inline(multiplicand), PpcPsToM128Inline(multiplier), PpcPsToM128Inline(subtractor)));
}
inline double PPC_PsMsubNoNiInline(double multiplicand, double multiplier, double subtractor)
{
return PPC_PsMsubInline(multiplicand, multiplier, subtractor);
}
inline double PPC_PsMaddInline(double multiplicand, double multiplier, double addend)
{
return PpcM128ToPsInline(_mm_fmadd_ps(
PpcPsToM128Inline(multiplicand), PpcPsToM128Inline(multiplier), PpcPsToM128Inline(addend)));
}
inline double PPC_PsMaddNoNiInline(double multiplicand, double multiplier, double addend)
{
return PPC_PsMaddInline(multiplicand, multiplier, addend);
}
inline double PPC_PsMadds0Inline(double multiplicand, double multiplier, double addend)
{
return PpcM128ToPsInline(_mm_fmadd_ps(
PpcPsToM128Inline(multiplicand), PpcBroadcastPs0Inline(multiplier), PpcPsToM128Inline(addend)));
}
inline double PPC_PsMadds1Inline(double multiplicand, double multiplier, double addend)
{
return PpcM128ToPsInline(_mm_fmadd_ps(
PpcPsToM128Inline(multiplicand), PpcBroadcastPs1Inline(multiplier), PpcPsToM128Inline(addend)));
}
inline double PPC_PsNmsubInline(double multiplicand, double multiplier, double subtractor)
{
return PpcM128ToPsInline(PpcNegateNonNanLanesInline(_mm_fmsub_ps(
PpcPsToM128Inline(multiplicand), PpcPsToM128Inline(multiplier), PpcPsToM128Inline(subtractor))));
}
inline double PPC_PsNmsubNoNiInline(double multiplicand, double multiplier, double subtractor)
{
return PPC_PsNmsubInline(multiplicand, multiplier, subtractor);
}
inline double PPC_PsNmaddInline(double multiplicand, double multiplier, double addend)
{
return PpcM128ToPsInline(PpcNegateNonNanLanesInline(_mm_fmadd_ps(
PpcPsToM128Inline(multiplicand), PpcPsToM128Inline(multiplier), PpcPsToM128Inline(addend))));
}
inline double PPC_PsMuls0Inline(double aValue, double cValue)
{
return PpcFlushPairedForNiInline(PpcM128ToPsInline(
_mm_mul_ps(PpcPsToM128Inline(aValue), PpcBroadcastPs0Inline(cValue))));
}
inline double PPC_PsMuls1Inline(double aValue, double cValue)
{
return PpcFlushPairedForNiInline(PpcM128ToPsInline(
_mm_mul_ps(PpcPsToM128Inline(aValue), PpcBroadcastPs1Inline(cValue))));
}
inline PPC_FPR PpcMakePairedResultInline(float ps0, float ps1);
inline double PPC_PsFromScalarInline(double value)
{
// Representation conversion, not an architectural operation: Gekko has no
// "scalar to paired" instruction, so there is no NI rounding point here.
// If the scalar is a single-denormal it stays one; MXCSR.DAZ flushes it as
// an input at the next real arithmetic op, exactly like the hardware.
const float single = static_cast<float>(value);
return PpcPackPairedInline(single, single);
}
inline double PPC_PsFromScalarNoNiInline(double value)
{
const float single = static_cast<float>(value);
return PpcPackPairedInline(single, single);
}
inline double PPC_PsToScalarInline(double value)
{
return static_cast<double>(PpcGetPs0Inline(value));
}
// ps_merge* are pure lane selections (result.ps0 from frA, result.ps1 from frB); with lane 0
// == ps1 and lane 1 == ps0, two shuffles build the result bit-exact instead of round-tripping
// through the pack helper.
inline double PPC_PsMerge00Inline(double aValue, double bValue)
{
// lane0 = b.ps0 (b lane 1), lane1 = a.ps0 (a lane 1)
const __m128 gathered = _mm_shuffle_ps(
PpcPsToM128Inline(bValue), PpcPsToM128Inline(aValue), _MM_SHUFFLE(1, 1, 1, 1));
return PpcM128ToPsInline(_mm_shuffle_ps(gathered, gathered, _MM_SHUFFLE(0, 0, 2, 0)));
}
inline double PPC_PsMerge01Inline(double aValue, double bValue)
{
// lane0 = b.ps1 (b lane 0), lane1 = a.ps0 (a lane 1)
const __m128 gathered = _mm_shuffle_ps(
PpcPsToM128Inline(bValue), PpcPsToM128Inline(aValue), _MM_SHUFFLE(1, 1, 0, 0));
return PpcM128ToPsInline(_mm_shuffle_ps(gathered, gathered, _MM_SHUFFLE(0, 0, 2, 0)));
}
inline double PPC_PsMerge10Inline(double aValue, double bValue)
{
// lane0 = b.ps0 (b lane 1), lane1 = a.ps1 (a lane 0)
const __m128 gathered = _mm_shuffle_ps(
PpcPsToM128Inline(bValue), PpcPsToM128Inline(aValue), _MM_SHUFFLE(0, 0, 1, 1));
return PpcM128ToPsInline(_mm_shuffle_ps(gathered, gathered, _MM_SHUFFLE(0, 0, 2, 0)));
}
inline double PPC_PsMerge11Inline(double aValue, double bValue)
{
// lane0 = b.ps1 (b lane 0), lane1 = a.ps1 (a lane 0): plain unpcklps.
return PpcM128ToPsInline(
_mm_unpacklo_ps(PpcPsToM128Inline(bValue), PpcPsToM128Inline(aValue)));
}
inline double PPC_PsAddInline(double aValue, double bValue)
{
return PpcFlushPairedForNiInline(
PpcM128ToPsInline(_mm_add_ps(PpcPsToM128Inline(aValue), PpcPsToM128Inline(bValue))));
}
inline double PPC_PsAddNoNiInline(double aValue, double bValue)
{
return PpcM128ToPsInline(
_mm_add_ps(PpcPsToM128Inline(aValue), PpcPsToM128Inline(bValue)));
}
inline double PPC_PsSelInline(double lhsValue, double controlValue, double rhsValue)
{
const float lhs0 = PpcGetPs0Inline(lhsValue);
const float lhs1 = PpcGetPs1Inline(lhsValue);
const float control0 = PpcGetPs0Inline(controlValue);
const float control1 = PpcGetPs1Inline(controlValue);
const float rhs0 = PpcGetPs0Inline(rhsValue);
const float rhs1 = PpcGetPs1Inline(rhsValue);
return PpcPackPairedInline(
control0 >= -0.0f ? lhs0 : rhs0,
control1 >= -0.0f ? lhs1 : rhs1);
}
inline double PPC_PsSubInline(double aValue, double bValue)
{
return PpcFlushPairedForNiInline(
PpcM128ToPsInline(_mm_sub_ps(PpcPsToM128Inline(aValue), PpcPsToM128Inline(bValue))));
}
inline double PPC_PsSubNoNiInline(double aValue, double bValue)
{
return PpcM128ToPsInline(
_mm_sub_ps(PpcPsToM128Inline(aValue), PpcPsToM128Inline(bValue)));
}
inline double PPC_PsDivInline(double aValue, double bValue)
{
return PpcFlushPairedForNiInline(
PpcM128ToPsInline(_mm_div_ps(PpcPsToM128Inline(aValue), PpcPsToM128Inline(bValue))));
}
inline double PPC_PsNegInline(double value)
{
return PpcPackPairedInline(-PpcGetPs0Inline(value), -PpcGetPs1Inline(value));
}
inline double PPC_PsAbsInline(double value)
{
return PpcPackPairedInline(std::abs(PpcGetPs0Inline(value)), std::abs(PpcGetPs1Inline(value)));
}
inline double PPC_PsSum0Inline(double aValue, double bValue, double cValue)
{
return PpcPackPairedInline(
PpcForceSingleValueInline(static_cast<double>(PpcGetPs0Inline(aValue)) + PpcGetPs1Inline(bValue)),
PpcFlushSingleForNiInline(PpcGetPs1Inline(cValue)));
}
inline double PPC_PsSum1Inline(double aValue, double bValue, double cValue)
{
return PpcPackPairedInline(
PpcFlushSingleForNiInline(PpcGetPs0Inline(cValue)),
PpcForceSingleValueInline(static_cast<double>(PpcGetPs0Inline(aValue)) + PpcGetPs1Inline(bValue)));
}
inline uint32_t PpcConvertToSingleFTZInline(uint64_t value)
{
const uint32_t exp = static_cast<uint32_t>((value >> 52) & 0x7FFu);
if (exp > 896u || (value & 0x7FFFFFFFFFFFFFFFULL) == 0)
{
return static_cast<uint32_t>(((value >> 32) & 0xC0000000ULL) |
((value >> 29) & 0x3FFFFFFFULL));
}
return static_cast<uint32_t>((value >> 32) & 0x80000000ULL);
}
inline uint64_t PpcConvertToDoubleBitsInline(uint32_t value)
{
uint64_t x = value;
uint64_t exp = (x >> 23) & 0xFFu;
uint64_t frac = x & 0x007FFFFFu;
if (exp > 0 && exp < 255)
{
const uint64_t y = !(exp >> 7);
const uint64_t z = (y << 61) | (y << 60) | (y << 59);
return ((x & 0xC0000000ULL) << 32) | z | ((x & 0x3FFFFFFFULL) << 29);
}
if (exp == 0 && frac != 0)
{
exp = 1023 - 126;
do
{
frac <<= 1;
--exp;
} while ((frac & 0x00800000u) == 0);
return ((x & 0x80000000ULL) << 32) | (exp << 52) | ((frac & 0x007FFFFFULL) << 29);
}
const uint64_t y = exp >> 7;
const uint64_t z = (y << 61) | (y << 60) | (y << 59);
return ((x & 0xC0000000ULL) << 32) | z | ((x & 0x3FFFFFFFULL) << 29);
}
inline double PpcPackPairedBitsInline(uint32_t ps0, uint32_t ps1)
{
return PpcBitCastToDoubleInline((static_cast<uint64_t>(ps0) << 32) | ps1);
}
inline PPC_FPR PpcMakePairedResultInline(float ps0, float ps1)
{
PPC_FPR result{};
result.paired.ps0 = ps0;
result.paired.ps1 = ps1;
return result;
}
extern "C" void PPC_Mtfsf(uint32_t fieldMask, double source);
extern "C" void PPC_Mtfsfi(uint32_t field, uint32_t value);
extern "C" void PPC_Mtfsb0(uint32_t bit);
extern "C" void PPC_Mtfsb1(uint32_t bit);
extern "C" double PPC_Mffs();
extern "C" double PPC_PsAdd(double lhs, double rhs);
extern "C" double PPC_PsSub(double lhs, double rhs);
extern "C" double PPC_PsDiv(double lhs, double rhs);
extern "C" double PPC_PsNeg(double value);
extern "C" double PPC_PsMul(double lhs, double rhs);
extern "C" double PPC_PsMsub(double lhs, double mul, double sub);
extern "C" double PPC_PsMadd(double lhs, double mul, double add);
extern "C" double PPC_PsNmsub(double lhs, double mul, double sub);
extern "C" double PPC_PsMadds0(double lhs, double mul, double add);
extern "C" double PPC_PsMadds1(double lhs, double mul, double add);
extern "C" double PPC_PsNmadd(double lhs, double mul, double add);
extern "C" double PPC_PsSel(double lhs, double control, double rhs);
// ps_res is hot in the THP dequant path (148 static call sites in the IDCT
// region); the out-of-line definition cost a call + full spill barrier per
// use, so it is defined inline here. The estimate logic is byte-identical to
// the old ppc_helpers.cpp body (both used PpcApproximateReciprocalInline).
extern "C" inline double PPC_PsRes(double value)
{
const float ps0 = static_cast<float>(
PpcApproximateReciprocalInline(static_cast<double>(PpcGetPs0Inline(value))));
const float ps1 = static_cast<float>(
PpcApproximateReciprocalInline(static_cast<double>(PpcGetPs1Inline(value))));
return PpcPackPairedInline(ps0, ps1);
}
extern "C" double PPC_PsRsqrte(double value);
// Pack a scalar double into a paired-single FPR value (ps0=ps1=float(value)).
extern "C" double PPC_PsFromScalar(double value);
// Extract the ps0 lane as a scalar double (used to feed single-precision ops).
extern "C" double PPC_PsToScalar(double value);
extern "C" double PPC_PsMerge00(double a, double b);
extern "C" double PPC_PsMerge01(double a, double b);
extern "C" double PPC_PsMerge10(double a, double b);
extern "C" double PPC_PsMerge11(double a, double b);
extern "C" double PPC_Fsqrt(double value);
// Hot scalar float helpers, defined inline so the compiler can see through them instead of
// taking a cross-TU caller-saved register barrier at ~2,500 call sites in the hottest float
// code in the game (bodies moved verbatim from ppc_helpers.cpp/fpu_helpers.cpp). PPC_PsRes
// above uses the same pattern.
extern "C" inline double PPC_Fres(double value)
{
// The generated paired/scalar boundary presents fres as a packed input and
// expects its architecturally replicated single result in the same format.
const float result = static_cast<float>(
PpcApproximateReciprocalInline(static_cast<double>(PpcGetPs0Inline(value))));
return PpcPackPairedInline(result, result);
}
extern "C" inline double PPC_Frsqrte(double value)
{
// frsqrte (opcode 63) takes the full scalar-double input (generated code normalizes
// scalar/paired ownership beforehand) and returns exact Gekko estimate bits only; FPSCR/
// Rc/FPRF updates need instruction-level context this value-only helper doesn't have.
return PpcApproximateReciprocalSquareRootInline(value);
}
extern "C" inline double PPC_Fsel(double control, double negative, double positive)
{
// Dolphin models fsel/ps_sel as "fra >= -0.0 ? frC : frB".
// That comparison deliberately sends unordered/NaN controls to frB.
return (control >= -0.0) ? positive : negative;
}
// PowerPC fctiwz instruction: Float Convert to Integer Word with Round toward
// Zero. The result goes in the LOWER 32 bits of the FPR (bits 32-63); the upper
// 32 bits are undefined. When the value is stored via stfd and reloaded via lwz
// at offset +4, the integer is correctly retrieved.
extern "C" inline double PPC_Fctiwz(double value)
{
return PpcPackIntegerWordInline(PpcClampIntegerWordInline(value));
}
extern "C" double PPC_Fmadd(double multiplicand, double multiplier, double addend);
extern "C" double PPC_Fmsub(double multiplicand, double multiplier, double subtractor);
extern "C" double PPC_Fnmadd(double multiplicand, double multiplier, double addend);
extern "C" double PPC_Fnmsub(double multiplicand, double multiplier, double subtractor);
// Single-precision scalar helpers (fadds/fsubs/fmuls/fdivs and fused variants).
extern "C" double PPC_Fadds(double a, double b);
extern "C" double PPC_Fsubs(double a, double b);
extern "C" double PPC_Fmuls(double a, double b);
extern "C" double PPC_Fdivs(double a, double b);
// The fused single-precision family is a thin wrapper over the Ppc*Inline
// bodies above; keeping the wrapper out of line meant the wrapper itself was
// the register barrier. See the block at PPC_Fres for why there is no LTO to
// fall back on.
extern "C" inline double PPC_Fmadds(double multiplicand, double multiplier, double addend)
{
return PpcFmaddsInline(multiplicand, multiplier, addend);
}
extern "C" inline double PPC_Fmsubs(double multiplicand, double multiplier, double subtractor)
{
return PpcFmsubsInline(multiplicand, multiplier, subtractor);
}
extern "C" inline double PPC_Fnmadds(double multiplicand, double multiplier, double addend)
{
return PpcFnmaddsInline(multiplicand, multiplier, addend);
}
extern "C" inline double PPC_Fnmsubs(double multiplicand, double multiplier, double subtractor)
{
return PpcFnmsubsInline(multiplicand, multiplier, subtractor);
}
extern "C" double PPC_Fctiw(double value); // Convert float to int using FPSCR rounding mode
extern "C" void PPC_Stfiwx(uint32_t addr, double fprValue); // Store Float as Integer Word (indexed)
extern "C" double PPC_PsSum0(double a, double b, double c);
extern "C" double PPC_PsSum1(double a, double b, double c);
extern "C" double PPC_PsMuls0(double a, double c);
extern "C" double PPC_PsMuls1(double a, double c);
extern "C" double PPC_PsAbs(double value);
// Floating-point comparison helper (fcmpu/fcmpo)
extern "C" void PPC_Fcmp(uint32_t crField, double a, double b);
extern "C" void PPC_PsCmpo0(uint32_t crField, double a, double b);
extern "C" void PPC_PsCmpu0(uint32_t crField, double a, double b);
extern "C" void PPC_PsCmpo1(uint32_t crField, double a, double b);
extern "C" void PPC_PsCmpu1(uint32_t crField, double a, double b);
extern "C" double PPC_PsNabs(double value);
extern "C" double PPC_PsMr(double value);
+53
View File
@@ -0,0 +1,53 @@
#pragma once
// FPSCR[NI] (non-IEEE flush-to-zero) modeled on the host FP environment, plus
// the thread-local mirror of that state the hot paths read instead of MXCSR.
#include "ppc_isa_config.h"
#include <cstdint>
// Software-flushing Gekko's single-precision denormals per op roughly doubled the THP IDCT
// kernel's cycle count, so instead the runtime mirrors guest FPSCR[NI] into host MXCSR FTZ+DAZ
// wherever FPSCR can change (PPC_Mtfs*, fiber context switches, CpuContextScope), making per-op
// flushes free. Accepted deviations (same trade Dolphin makes): FTZ also flushes double
// denormals unlike real NI, and a pre-round-flush edge near FLT_MIN rounds via cvtsd2ss instead.
inline constexpr uint32_t kMkwMxcsrFlushToZeroBits = (1u << 15) | (1u << 6); // FTZ | DAZ
inline thread_local bool g_mkwHostNiActive = false;
// Same state in the form PpcForceSingleValueInline consumes: the pre-round subnormal threshold
// while NI is active, 0.0 (identity, `|value| < 0.0` is always false) otherwise, so that path
// needs no branch. Every writer of g_mkwHostNiActive must write this beside it in agreement.
inline constexpr double kMkwNiFlushThreshold = 0x1p-126; // 0x3810000000000000
inline thread_local double g_mkwNiFlushThreshold = 0.0;
inline void MkwApplyHostNiMode(uint32_t fpscr) noexcept
{
const uint32_t csr = _mm_getcsr();
const bool wantNi = (fpscr & 0x4u) != 0;
const uint32_t want = wantNi
? (csr | kMkwMxcsrFlushToZeroBits)
: (csr & ~kMkwMxcsrFlushToZeroBits);
if (want != csr)
_mm_setcsr(want);
// `want` has both bits set or both clear, so this is exactly
// `(_mm_getcsr() & kMkwMxcsrFlushToZeroBits) != 0` after the write - the
// mirror cannot disagree with the register even if the incoming CSR held
// only one of the two bits.
g_mkwHostNiActive = wantNi;
g_mkwNiFlushThreshold = wantNi ? kMkwNiFlushThreshold : 0.0;
}
/// <summary>
/// Restores a previously captured MXCSR value and re-derives the mirror from
/// it. Every raw restore has to go through here; a bare _mm_setcsr would leave
/// the mirror describing the FP environment that was just replaced.
/// </summary>
inline void MkwRestoreHostMxcsr(uint32_t csr) noexcept
{
_mm_setcsr(csr);
const bool niActive = (csr & kMkwMxcsrFlushToZeroBits) != 0;
g_mkwHostNiActive = niActive;
g_mkwNiFlushThreshold = niActive ? kMkwNiFlushThreshold : 0.0;
}
+127
View File
@@ -0,0 +1,127 @@
#pragma once
// Pure integer PowerPC semantics, together with the declarations of the
// integer, condition/SPR and system helpers the host runtime implements out of
// line. Nothing here depends on another isa/ header beyond the configuration.
#include "ppc_isa_config.h"
#include <cstdint>
#include <cstdlib>
#include <limits>
inline uint32_t PpcRotl32Inline(uint32_t value, uint32_t shift)
{
return __builtin_rotateleft32(value, shift);
}
extern "C" uint32_t OSSystemCall();
extern "C" int32_t memset_zero_32(int32_t address);
extern "C" void OS_HLE_ProcessAlarms(int maxToProcess);
extern "C" uint32_t PPC_Mcrxr(uint32_t crField);
extern "C" uint32_t PPC_ReadSpr(uint32_t spr);
extern "C" void PPC_WriteSpr(uint32_t spr, uint32_t value);
extern "C" uint32_t PPC_CrSetBit(uint32_t bitIndex, uint32_t value);
extern "C" uint32_t PPC_CrLogical(uint32_t op, uint32_t bt, uint32_t ba, uint32_t bb);
extern "C" uint32_t PPC_Mcrf(uint32_t dstField, uint32_t srcField);
// Time base helpers (PowerPC 'mftb' / 'mftbu') - declared so generated code can call them.
extern "C" uint32_t PPC_Mftb();
extern "C" uint32_t PPC_Mftbu();
// Carry helpers used by translated arithmetic that depends on XER[CA].
extern "C" uint32_t PPC_UpdateCarryAdd(uint32_t lhs, uint32_t rhs, uint32_t carryIn);
extern "C" uint32_t PPC_UpdateCarrySub(uint32_t lhs, uint32_t rhs);
extern "C" uint32_t PPC_UpdateCarryShiftRight(uint32_t value, uint32_t shift);
extern "C" uint32_t PPC_GetCarry();
extern "C" uint32_t PPC_Addo(uint32_t lhs, uint32_t rhs);
extern "C" uint32_t PPC_Addco(uint32_t lhs, uint32_t rhs);
extern "C" uint32_t PPC_Addeo(uint32_t lhs, uint32_t rhs);
extern "C" uint32_t PPC_Addmeo(uint32_t value);
extern "C" uint32_t PPC_Addzeo(uint32_t value);
extern "C" uint32_t PPC_Subfo(uint32_t subtrahend, uint32_t minuend);
extern "C" uint32_t PPC_Subfco(uint32_t subtrahend, uint32_t minuend);
extern "C" uint32_t PPC_Subfeo(uint32_t subtrahend, uint32_t minuend);
extern "C" uint32_t PPC_Subfmeo(uint32_t value);
extern "C" uint32_t PPC_Subfzeo(uint32_t value);
extern "C" uint32_t PPC_Nego(uint32_t value);
extern "C" uint32_t PPC_Mullwo(uint32_t lhs, uint32_t rhs);
extern "C" uint32_t PPC_Divwo(uint32_t lhs, uint32_t rhs);
extern "C" uint32_t PPC_Divwuo(uint32_t lhs, uint32_t rhs);
extern "C" void PPC_Lswi(uint32_t rD, uint32_t addr, uint32_t byteCount);
extern "C" void PPC_Lswx(uint32_t rD, uint32_t addr);
extern "C" void PPC_Stswi(uint32_t rS, uint32_t addr, uint32_t byteCount);
extern "C" void PPC_Stswx(uint32_t rS, uint32_t addr);
extern "C" uint32_t PPC_Lwarx(uint32_t addr);
extern "C" uint32_t PPC_Stwcx(uint32_t addr, uint32_t value);
extern "C" uint32_t PPC_Mcrfs(uint32_t dstField, uint32_t srcField);
extern "C" uint32_t PPC_Eciwx(uint32_t addr);
extern "C" void PPC_Ecowx(uint32_t addr, uint32_t value);
extern "C" void PPC_TrapWord(uint32_t trapOptions, uint32_t lhs, uint32_t rhs);
extern "C" uint32_t PPC_Cntlzw(uint32_t value);
MKW_PPC_FORCE_INLINE uint32_t PPC_CntlzwInline(uint32_t value)
{
return value == 0 ? 32u : static_cast<uint32_t>(__builtin_clz(value));
}
// Byte-reverse helpers (PowerPC 'lwbrx' / 'stwbrx' / 'lhbrx' / 'sthbrx')
extern "C" uint32_t PPC_LoadWordByteReverse(uint32_t addr);
extern "C" void PPC_StoreWordByteReverse(uint32_t addr, uint32_t value);
extern "C" uint32_t PPC_LoadHalfwordByteReverse(uint32_t addr);
extern "C" void PPC_StoreHalfwordByteReverse(uint32_t addr, uint32_t value);
template <typename T>
inline int32_t CompareUnsigned(T a, T b) {
uint32_t ua = static_cast<uint32_t>(a);
uint32_t ub = static_cast<uint32_t>(b);
if (ua < ub) return -1;
if (ua > ub) return 1;
return 0;
}
template <int Bits>
inline int32_t SignExtend(uint32_t val) {
struct { int32_t x : Bits; } s;
s.x = val;
return s.x;
}
inline int32_t ArithmeticShiftRight(int32_t val, int amount) {
return val >> amount;
}
inline uint32_t PPC_Slw(uint32_t value, uint32_t amount)
{
return (amount & 0x20u) != 0 ? 0u : value << (amount & 0x1Fu);
}
inline uint32_t PPC_Srw(uint32_t value, uint32_t amount)
{
return (amount & 0x20u) != 0 ? 0u : value >> (amount & 0x1Fu);
}
inline uint32_t PPC_Sraw(uint32_t value, uint32_t amount)
{
if ((amount & 0x20u) != 0)
{
return (value & 0x80000000u) != 0 ? 0xFFFFFFFFu : 0u;
}
return static_cast<uint32_t>(static_cast<int32_t>(value) >> (amount & 0x1Fu));
}
inline uint32_t PPC_Divwu(uint32_t dividend, uint32_t divisor)
{
// Gekko does not raise a program exception for non-OE division by zero.
// Match the hardware result used by Dolphin's interpreter/JIT.
return divisor == 0 ? 0u : dividend / divisor;
}
inline int32_t PPC_Divw(int32_t dividend, int32_t divisor)
{
if (divisor == 0 || (dividend == std::numeric_limits<int32_t>::min() && divisor == -1))
return dividend < 0 ? -1 : 0;
return dividend / divisor;
}
File diff suppressed because it is too large Load Diff