mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-10 17:16:47 -04:00
1057 lines
41 KiB
C++
1057 lines
41 KiB
C++
// 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;
|
|
}
|
|
|
|
#if defined(__x86_64__)
|
|
using PpcPairVec = __m128;
|
|
#elif defined(__aarch64__)
|
|
// Only 2 lanes are ever meaningful (a PPC paired-single register), so a 2-lane float32x2_t
|
|
// (one 64-bit D register) is a more natural fit than mirroring x86's 128-bit register usage.
|
|
using PpcPairVec = float32x2_t;
|
|
#endif
|
|
|
|
// Must stay inside the vector 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 a vector register, so these casts compile to nothing.
|
|
inline PpcPairVec PpcPsToM128Inline(double value)
|
|
{
|
|
#if defined(__x86_64__)
|
|
return _mm_castpd_ps(_mm_set_sd(value));
|
|
#elif defined(__aarch64__)
|
|
return vreinterpret_f32_f64(vdup_n_f64(value));
|
|
#endif
|
|
}
|
|
|
|
inline double PpcM128ToPsInline(PpcPairVec value)
|
|
{
|
|
#if defined(__x86_64__)
|
|
return _mm_cvtsd_f64(_mm_castps_pd(value));
|
|
#elif defined(__aarch64__)
|
|
return vget_lane_f64(vreinterpret_f64_f32(value), 0);
|
|
#endif
|
|
}
|
|
|
|
inline PpcPairVec PpcBroadcastPs0Inline(double value)
|
|
{
|
|
#if defined(__x86_64__)
|
|
const __m128 lanes = PpcPsToM128Inline(value);
|
|
return _mm_shuffle_ps(lanes, lanes, _MM_SHUFFLE(1, 1, 1, 1));
|
|
#elif defined(__aarch64__)
|
|
// ps0 lives in lane 1 (see the lane-accessor comment below).
|
|
return vdup_lane_f32(PpcPsToM128Inline(value), 1);
|
|
#endif
|
|
}
|
|
|
|
inline PpcPairVec PpcBroadcastPs1Inline(double value)
|
|
{
|
|
#if defined(__x86_64__)
|
|
const __m128 lanes = PpcPsToM128Inline(value);
|
|
return _mm_shuffle_ps(lanes, lanes, _MM_SHUFFLE(0, 0, 0, 0));
|
|
#elif defined(__aarch64__)
|
|
// ps1 is already lane 0.
|
|
return vdup_lane_f32(PpcPsToM128Inline(value), 0);
|
|
#endif
|
|
}
|
|
|
|
inline PpcPairVec PpcNegateNonNanLanesInline(PpcPairVec value)
|
|
{
|
|
#if defined(__x86_64__)
|
|
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));
|
|
#elif defined(__aarch64__)
|
|
const uint32x2_t signMask = vdup_n_u32(0x80000000u);
|
|
const float32x2_t negated = vreinterpret_f32_u32(veor_u32(vreinterpret_u32_f32(value), signMask));
|
|
// NEON has no direct "ordered compare"; a value compares equal to itself iff it's not NaN.
|
|
const uint32x2_t ordered = vceq_f32(value, value);
|
|
return vbsl_f32(ordered, negated, value);
|
|
#endif
|
|
}
|
|
|
|
// 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.
|
|
#if defined(__x86_64__)
|
|
return _mm_cvtss_f32(PpcBroadcastPs0Inline(value));
|
|
#elif defined(__aarch64__)
|
|
return vget_lane_f32(PpcBroadcastPs0Inline(value), 0);
|
|
#endif
|
|
}
|
|
|
|
inline float PpcGetPs1Inline(double value)
|
|
{
|
|
// ps1 is already lane 0 of the packed representation.
|
|
#if defined(__x86_64__)
|
|
return _mm_cvtss_f32(PpcPsToM128Inline(value));
|
|
#elif defined(__aarch64__)
|
|
return vget_lane_f32(PpcPsToM128Inline(value), 0);
|
|
#endif
|
|
}
|
|
|
|
inline double PpcPackPairedInline(float ps0, float ps1)
|
|
{
|
|
#if defined(__x86_64__)
|
|
// _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)));
|
|
#elif defined(__aarch64__)
|
|
// Lane 0 = ps1, lane 1 = ps0, matching the union layout bit for bit.
|
|
const float32x2_t lane0 = vdup_n_f32(ps1);
|
|
return PpcM128ToPsInline(vset_lane_f32(ps0, lane0, 1));
|
|
#endif
|
|
}
|
|
|
|
// 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 the double->float conversion. DAZ/FZ (set exactly when NI is) makes
|
|
// the compare itself treat a subnormal as zero, matching the mask's answer.
|
|
#if defined(__x86_64__)
|
|
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));
|
|
#elif defined(__aarch64__)
|
|
const float64x1_t v = vdup_n_f64(value);
|
|
const uint64x1_t signMask = vdup_n_u64(0x8000000000000000ULL);
|
|
const float64x1_t magnitude = vreinterpret_f64_u64(vbic_u64(vreinterpret_u64_f64(v), signMask));
|
|
const uint64x1_t flush = vclt_f64(magnitude, vdup_n_f64(g_mkwNiFlushThreshold));
|
|
const uint64x1_t signOnly = vand_u64(vreinterpret_u64_f64(v), signMask);
|
|
const uint64x1_t kept = vbsl_u64(flush, signOnly, vreinterpret_u64_f64(v));
|
|
return static_cast<float>(vget_lane_f64(vreinterpret_f64_u64(kept), 0));
|
|
#endif
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
#if defined(__aarch64__)
|
|
// x86 resolves a single NaN source operand (SNaN or QNaN alike) to that operand quieted with
|
|
// sign and payload kept, and an invalid op with no NaN input to 0xFFC00000; NEON prefers SNaNs,
|
|
// checks the accumulator first, and yields the positive default NaN 0x7FC00000. NaN result
|
|
// lanes (rare: one branch on the pair) are therefore rewritten to the x86 answer, keeping the
|
|
// two hosts bit-identical for every case where x86 itself is deterministic. With two or more
|
|
// NaN operands even x86 is not: the winning operand depends on which FMA form / commuted
|
|
// operand order clang picked per call site, so this resolver's fixed first-operand priority is
|
|
// one of the answers a real x86 build can give, not a guaranteed match.
|
|
inline uint64_t PpcPairNanLaneBitsInline(PpcPairVec value)
|
|
{
|
|
return vget_lane_u64(vreinterpret_u64_u32(vmvn_u32(vceq_f32(value, value))), 0);
|
|
}
|
|
|
|
inline PpcPairVec PpcQuietPairInline(PpcPairVec value)
|
|
{
|
|
return vreinterpret_f32_u32(vorr_u32(vreinterpret_u32_f32(value), vdup_n_u32(0x00400000u)));
|
|
}
|
|
|
|
inline PpcPairVec PpcResolveNanLanesInline(PpcPairVec result, PpcPairVec op1, PpcPairVec op2)
|
|
{
|
|
const uint32x2_t op1Nan = vmvn_u32(vceq_f32(op1, op1));
|
|
const uint32x2_t op2Nan = vmvn_u32(vceq_f32(op2, op2));
|
|
PpcPairVec replacement = vreinterpret_f32_u32(vdup_n_u32(0xFFC00000u));
|
|
replacement = vbsl_f32(op2Nan, PpcQuietPairInline(op2), replacement);
|
|
replacement = vbsl_f32(op1Nan, PpcQuietPairInline(op1), replacement);
|
|
const uint32x2_t resultNan = vmvn_u32(vceq_f32(result, result));
|
|
return vbsl_f32(resultNan, replacement, result);
|
|
}
|
|
|
|
inline PpcPairVec PpcResolveNanLanes3Inline(
|
|
PpcPairVec result, PpcPairVec op1, PpcPairVec op2, PpcPairVec op3)
|
|
{
|
|
const uint32x2_t op3Nan = vmvn_u32(vceq_f32(op3, op3));
|
|
PpcPairVec replacement = vreinterpret_f32_u32(vdup_n_u32(0xFFC00000u));
|
|
replacement = vbsl_f32(op3Nan, PpcQuietPairInline(op3), replacement);
|
|
const uint32x2_t op2Nan = vmvn_u32(vceq_f32(op2, op2));
|
|
replacement = vbsl_f32(op2Nan, PpcQuietPairInline(op2), replacement);
|
|
const uint32x2_t op1Nan = vmvn_u32(vceq_f32(op1, op1));
|
|
replacement = vbsl_f32(op1Nan, PpcQuietPairInline(op1), replacement);
|
|
const uint32x2_t resultNan = vmvn_u32(vceq_f32(result, result));
|
|
return vbsl_f32(resultNan, replacement, result);
|
|
}
|
|
#endif // defined(__aarch64__)
|
|
|
|
inline PpcPairVec PpcMulPairInline(PpcPairVec lhs, PpcPairVec rhs)
|
|
{
|
|
#if defined(__x86_64__)
|
|
return _mm_mul_ps(lhs, rhs);
|
|
#elif defined(__aarch64__)
|
|
const PpcPairVec result = vmul_f32(lhs, rhs);
|
|
if (PpcPairNanLaneBitsInline(result) != 0) [[unlikely]]
|
|
return PpcResolveNanLanesInline(result, lhs, rhs);
|
|
return result;
|
|
#endif
|
|
}
|
|
|
|
inline double PPC_PsMulInline(double lhs, double rhs)
|
|
{
|
|
return PpcFlushPairedForNiInline(
|
|
PpcM128ToPsInline(PpcMulPairInline(PpcPsToM128Inline(lhs), PpcPsToM128Inline(rhs))));
|
|
}
|
|
|
|
inline double PPC_PsMulNoNiInline(double lhs, double rhs)
|
|
{
|
|
return PpcM128ToPsInline(PpcMulPairInline(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.
|
|
|
|
// Fused multiply-add/subtract on a pair. NEON's vfma_f32(acc, a, b) = acc + a*b has an
|
|
// accumulator-first operand order, unlike x86's _mm_fmadd_ps(a, b, c) = a*b + c - msub is
|
|
// therefore an fma against a negated accumulator. That vneg would flip a sole-NaN subtractor's
|
|
// sign, which x86's vfmsub does not do, so the NaN resolver receives the original subtractor.
|
|
inline PpcPairVec PpcFmaddPairInline(PpcPairVec multiplicand, PpcPairVec multiplier, PpcPairVec addend)
|
|
{
|
|
#if defined(__x86_64__)
|
|
return _mm_fmadd_ps(multiplicand, multiplier, addend);
|
|
#elif defined(__aarch64__)
|
|
const PpcPairVec result = vfma_f32(addend, multiplicand, multiplier);
|
|
if (PpcPairNanLaneBitsInline(result) != 0) [[unlikely]]
|
|
return PpcResolveNanLanes3Inline(result, multiplicand, multiplier, addend);
|
|
return result;
|
|
#endif
|
|
}
|
|
|
|
inline PpcPairVec PpcFmsubPairInline(PpcPairVec multiplicand, PpcPairVec multiplier, PpcPairVec subtractor)
|
|
{
|
|
#if defined(__x86_64__)
|
|
return _mm_fmsub_ps(multiplicand, multiplier, subtractor);
|
|
#elif defined(__aarch64__)
|
|
const PpcPairVec result = vfma_f32(vneg_f32(subtractor), multiplicand, multiplier);
|
|
if (PpcPairNanLaneBitsInline(result) != 0) [[unlikely]]
|
|
return PpcResolveNanLanes3Inline(result, multiplicand, multiplier, subtractor);
|
|
return result;
|
|
#endif
|
|
}
|
|
|
|
inline double PPC_PsMsubInline(double multiplicand, double multiplier, double subtractor)
|
|
{
|
|
return PpcM128ToPsInline(PpcFmsubPairInline(
|
|
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(PpcFmaddPairInline(
|
|
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(PpcFmaddPairInline(
|
|
PpcPsToM128Inline(multiplicand), PpcBroadcastPs0Inline(multiplier), PpcPsToM128Inline(addend)));
|
|
}
|
|
|
|
inline double PPC_PsMadds1Inline(double multiplicand, double multiplier, double addend)
|
|
{
|
|
return PpcM128ToPsInline(PpcFmaddPairInline(
|
|
PpcPsToM128Inline(multiplicand), PpcBroadcastPs1Inline(multiplier), PpcPsToM128Inline(addend)));
|
|
}
|
|
|
|
inline double PPC_PsNmsubInline(double multiplicand, double multiplier, double subtractor)
|
|
{
|
|
return PpcM128ToPsInline(PpcNegateNonNanLanesInline(PpcFmsubPairInline(
|
|
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(PpcFmaddPairInline(
|
|
PpcPsToM128Inline(multiplicand), PpcPsToM128Inline(multiplier), PpcPsToM128Inline(addend))));
|
|
}
|
|
|
|
inline double PPC_PsMuls0Inline(double aValue, double cValue)
|
|
{
|
|
return PpcFlushPairedForNiInline(PpcM128ToPsInline(
|
|
PpcMulPairInline(PpcPsToM128Inline(aValue), PpcBroadcastPs0Inline(cValue))));
|
|
}
|
|
|
|
inline double PPC_PsMuls1Inline(double aValue, double cValue)
|
|
{
|
|
return PpcFlushPairedForNiInline(PpcM128ToPsInline(
|
|
PpcMulPairInline(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). On x86 two
|
|
// shuffles build the result bit-exact without round-tripping through the pack helper; NEON has
|
|
// no equally cheap 2-lane general shuffle, so its branch expresses the exact same selection
|
|
// (verified against the x86 comments below, lane for lane) directly in terms of the portable
|
|
// Get/Pack accessors instead.
|
|
inline double PPC_PsMerge00Inline(double aValue, double bValue)
|
|
{
|
|
// result.ps0 = a.ps0, result.ps1 = b.ps0 (lane0 = b.ps0/b lane1, lane1 = a.ps0/a lane1)
|
|
#if defined(__x86_64__)
|
|
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)));
|
|
#elif defined(__aarch64__)
|
|
return PpcPackPairedInline(PpcGetPs0Inline(aValue), PpcGetPs0Inline(bValue));
|
|
#endif
|
|
}
|
|
|
|
inline double PPC_PsMerge01Inline(double aValue, double bValue)
|
|
{
|
|
// result.ps0 = a.ps0, result.ps1 = b.ps1 (lane0 = b.ps1/b lane0, lane1 = a.ps0/a lane1)
|
|
#if defined(__x86_64__)
|
|
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)));
|
|
#elif defined(__aarch64__)
|
|
return PpcPackPairedInline(PpcGetPs0Inline(aValue), PpcGetPs1Inline(bValue));
|
|
#endif
|
|
}
|
|
|
|
inline double PPC_PsMerge10Inline(double aValue, double bValue)
|
|
{
|
|
// result.ps0 = a.ps1, result.ps1 = b.ps0 (lane0 = b.ps0/b lane1, lane1 = a.ps1/a lane0)
|
|
#if defined(__x86_64__)
|
|
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)));
|
|
#elif defined(__aarch64__)
|
|
return PpcPackPairedInline(PpcGetPs1Inline(aValue), PpcGetPs0Inline(bValue));
|
|
#endif
|
|
}
|
|
|
|
inline double PPC_PsMerge11Inline(double aValue, double bValue)
|
|
{
|
|
// result.ps0 = a.ps1, result.ps1 = b.ps1 (lane0 = b.ps1/b lane0, lane1 = a.ps1/a lane0):
|
|
// plain unpcklps on x86.
|
|
#if defined(__x86_64__)
|
|
return PpcM128ToPsInline(
|
|
_mm_unpacklo_ps(PpcPsToM128Inline(bValue), PpcPsToM128Inline(aValue)));
|
|
#elif defined(__aarch64__)
|
|
return PpcPackPairedInline(PpcGetPs1Inline(aValue), PpcGetPs1Inline(bValue));
|
|
#endif
|
|
}
|
|
|
|
inline PpcPairVec PpcAddPairInline(PpcPairVec lhs, PpcPairVec rhs)
|
|
{
|
|
#if defined(__x86_64__)
|
|
return _mm_add_ps(lhs, rhs);
|
|
#elif defined(__aarch64__)
|
|
const PpcPairVec result = vadd_f32(lhs, rhs);
|
|
if (PpcPairNanLaneBitsInline(result) != 0) [[unlikely]]
|
|
return PpcResolveNanLanesInline(result, lhs, rhs);
|
|
return result;
|
|
#endif
|
|
}
|
|
|
|
inline PpcPairVec PpcSubPairInline(PpcPairVec lhs, PpcPairVec rhs)
|
|
{
|
|
#if defined(__x86_64__)
|
|
return _mm_sub_ps(lhs, rhs);
|
|
#elif defined(__aarch64__)
|
|
const PpcPairVec result = vsub_f32(lhs, rhs);
|
|
if (PpcPairNanLaneBitsInline(result) != 0) [[unlikely]]
|
|
return PpcResolveNanLanesInline(result, lhs, rhs);
|
|
return result;
|
|
#endif
|
|
}
|
|
|
|
inline PpcPairVec PpcDivPairInline(PpcPairVec lhs, PpcPairVec rhs)
|
|
{
|
|
#if defined(__x86_64__)
|
|
return _mm_div_ps(lhs, rhs);
|
|
#elif defined(__aarch64__)
|
|
const PpcPairVec result = vdiv_f32(lhs, rhs);
|
|
if (PpcPairNanLaneBitsInline(result) != 0) [[unlikely]]
|
|
return PpcResolveNanLanesInline(result, lhs, rhs);
|
|
return result;
|
|
#endif
|
|
}
|
|
|
|
inline double PPC_PsAddInline(double aValue, double bValue)
|
|
{
|
|
return PpcFlushPairedForNiInline(
|
|
PpcM128ToPsInline(PpcAddPairInline(PpcPsToM128Inline(aValue), PpcPsToM128Inline(bValue))));
|
|
}
|
|
|
|
inline double PPC_PsAddNoNiInline(double aValue, double bValue)
|
|
{
|
|
return PpcM128ToPsInline(
|
|
PpcAddPairInline(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(PpcSubPairInline(PpcPsToM128Inline(aValue), PpcPsToM128Inline(bValue))));
|
|
}
|
|
|
|
inline double PPC_PsSubNoNiInline(double aValue, double bValue)
|
|
{
|
|
return PpcM128ToPsInline(
|
|
PpcSubPairInline(PpcPsToM128Inline(aValue), PpcPsToM128Inline(bValue)));
|
|
}
|
|
|
|
inline double PPC_PsDivInline(double aValue, double bValue)
|
|
{
|
|
return PpcFlushPairedForNiInline(
|
|
PpcM128ToPsInline(PpcDivPairInline(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);
|