Files
PSPRecomp/include/psprecomp/allegrex_context.hpp
T
Jessica_Natalia 37e5469cbd initial release
initial release
2026-08-12 13:56:12 -03:00

1456 lines
72 KiB
C++

#pragma once
#include <array>
#include <bit>
#include <cstddef>
#include <cstdint>
#include <cmath>
#include <limits>
namespace psprecomp {
#if defined(_MSC_VER)
#define PSPRECOMP_CONTEXT_FORCEINLINE __forceinline
#elif defined(__GNUC__) || defined(__clang__)
#define PSPRECOMP_CONTEXT_FORCEINLINE inline __attribute__((always_inline))
#else
#define PSPRECOMP_CONTEXT_FORCEINLINE inline
#endif
struct alignas(16) AllegrexContext {
std::array<std::uint32_t, 32> gpr{};
std::uint32_t hi{};
std::uint32_t lo{};
std::uint32_t pc{};
std::array<float, 32> fpr{};
std::uint32_t fcr31{};
// VFPU is represented as 128 scalar lanes for now. The physical PSP
// register views overlap; the final lowering layer will provide S/V/M views.
std::array<float, 128> vfpu{};
std::array<std::uint32_t, 16> vfpu_ctrl{};
[[nodiscard]] PSPRECOMP_CONTEXT_FORCEINLINE std::uint32_t fpr_bits(std::uint32_t index) const noexcept {
return index < fpr.size() ? std::bit_cast<std::uint32_t>(fpr[index]) : 0u;
}
PSPRECOMP_CONTEXT_FORCEINLINE void set_fpr_bits(std::uint32_t index, std::uint32_t value) noexcept {
if (index < fpr.size()) {
fpr[index] = std::bit_cast<float>(value);
}
}
[[nodiscard]] PSPRECOMP_CONTEXT_FORCEINLINE bool fpu_condition() const noexcept {
return (fcr31 & (1u << 23u)) != 0u;
}
PSPRECOMP_CONTEXT_FORCEINLINE void set_fpu_condition(bool value) noexcept {
if (value) fcr31 |= (1u << 23u);
else fcr31 &= ~(1u << 23u);
}
static std::int32_t clamp_fpu_word(double value) noexcept {
constexpr double min_value = static_cast<double>(std::numeric_limits<std::int32_t>::min());
constexpr double max_value = static_cast<double>(std::numeric_limits<std::int32_t>::max());
if (value <= min_value) return std::numeric_limits<std::int32_t>::min();
if (value >= max_value) return std::numeric_limits<std::int32_t>::max();
return static_cast<std::int32_t>(value);
}
static double round_ties_to_even(double value) noexcept {
const double lower = std::floor(value);
const double fraction = value - lower;
if (fraction < 0.5) return lower;
if (fraction > 0.5) return lower + 1.0;
const auto integral = static_cast<std::int64_t>(lower);
return (integral & 1LL) == 0LL ? lower : lower + 1.0;
}
[[nodiscard]] std::uint32_t fpu_float_to_word(float value, std::uint32_t mode) const noexcept {
if (!std::isfinite(value)) {
const std::int32_t result = std::isinf(value) && std::signbit(value)
? std::numeric_limits<std::int32_t>::min()
: std::numeric_limits<std::int32_t>::max();
return static_cast<std::uint32_t>(result);
}
double converted{};
switch (mode) {
case 0u: converted = std::floor(static_cast<double>(value) + 0.5); break; // ROUND.W.S
case 1u: converted = std::trunc(static_cast<double>(value)); break; // TRUNC.W.S
case 2u: converted = std::ceil(static_cast<double>(value)); break; // CEIL.W.S
case 3u: converted = std::floor(static_cast<double>(value)); break; // FLOOR.W.S
default:
switch (fcr31 & 3u) {
case 0u: converted = round_ties_to_even(static_cast<double>(value)); break;
case 1u: converted = std::trunc(static_cast<double>(value)); break;
case 2u: converted = std::ceil(static_cast<double>(value)); break;
default: converted = std::floor(static_cast<double>(value)); break;
}
break;
}
return static_cast<std::uint32_t>(clamp_fpu_word(converted));
}
// Generated ROUND/TRUNC/CEIL/FLOOR instructions carry a literal conversion
// mode. Make it a template parameter so the hot AOT path has no mode switch;
// TRUNC (a common generated-code case) also avoids a redundant std::trunc.
template <std::uint32_t Mode>
[[nodiscard]] PSPRECOMP_CONTEXT_FORCEINLINE std::uint32_t fpu_float_to_word_ct(float value) const noexcept {
static_assert(Mode <= 3u);
if (!std::isfinite(value)) {
const std::int32_t result = std::isinf(value) && std::signbit(value)
? std::numeric_limits<std::int32_t>::min()
: std::numeric_limits<std::int32_t>::max();
return static_cast<std::uint32_t>(result);
}
const double input = static_cast<double>(value);
if constexpr (Mode == 1u) {
constexpr double min_value = static_cast<double>(std::numeric_limits<std::int32_t>::min());
constexpr double max_value = static_cast<double>(std::numeric_limits<std::int32_t>::max());
if (input <= min_value) return static_cast<std::uint32_t>(std::numeric_limits<std::int32_t>::min());
if (input >= max_value) return static_cast<std::uint32_t>(std::numeric_limits<std::int32_t>::max());
return static_cast<std::uint32_t>(static_cast<std::int32_t>(input));
} else if constexpr (Mode == 0u) {
return static_cast<std::uint32_t>(clamp_fpu_word(std::floor(input + 0.5)));
} else if constexpr (Mode == 2u) {
return static_cast<std::uint32_t>(clamp_fpu_word(std::ceil(input)));
} else {
return static_cast<std::uint32_t>(clamp_fpu_word(std::floor(input)));
}
}
static constexpr std::size_t vfpu_scalar_index(std::uint32_t scalar_register) noexcept {
// PSP scalar register encoding is 0YYMMMXX. The storage below uses
// contiguous 4x4 matrices: matrix * 16 + Y * 4 + X.
const std::uint32_t matrix = (scalar_register >> 2u) & 7u;
const std::uint32_t x = (scalar_register >> 5u) & 3u;
const std::uint32_t y = scalar_register & 3u;
return static_cast<std::size_t>(matrix * 16u + y * 4u + x);
}
PSPRECOMP_CONTEXT_FORCEINLINE void set_vfpu_scalar_bits(std::uint32_t scalar_register, std::uint32_t value) noexcept {
if (scalar_register < 128u) {
vfpu[vfpu_scalar_index(scalar_register)] = std::bit_cast<float>(value);
} else if (scalar_register < 144u) {
vfpu_ctrl[scalar_register - 128u] = value;
}
}
[[nodiscard]] PSPRECOMP_CONTEXT_FORCEINLINE std::uint32_t vfpu_scalar_bits(std::uint32_t scalar_register) const noexcept {
if (scalar_register < 128u) {
return std::bit_cast<std::uint32_t>(vfpu[vfpu_scalar_index(scalar_register)]);
}
if (scalar_register < 144u) {
return vfpu_ctrl[scalar_register - 128u];
}
return 0u;
}
// Generated MTV/MFV/LVS/SVS always carry a literal encoded scalar register.
// Make that fact visible to the host compiler so both the register-class
// branch and the PSP VFPU lane shuffle disappear from the AOT hot path.
template <std::uint32_t ScalarRegister>
PSPRECOMP_CONTEXT_FORCEINLINE void set_vfpu_scalar_bits_ct(std::uint32_t value) noexcept {
static_assert(ScalarRegister < 144u);
if constexpr (ScalarRegister < 128u) {
constexpr std::size_t index = vfpu_scalar_index(ScalarRegister);
vfpu[index] = std::bit_cast<float>(value);
} else {
vfpu_ctrl[ScalarRegister - 128u] = value;
}
}
template <std::uint32_t ScalarRegister>
[[nodiscard]] PSPRECOMP_CONTEXT_FORCEINLINE std::uint32_t vfpu_scalar_bits_ct() const noexcept {
static_assert(ScalarRegister < 144u);
if constexpr (ScalarRegister < 128u) {
constexpr std::size_t index = vfpu_scalar_index(ScalarRegister);
return std::bit_cast<std::uint32_t>(vfpu[index]);
} else {
return vfpu_ctrl[ScalarRegister - 128u];
}
}
// AOT compile-time VFPU lane access. Generated code always
// knows the encoded vector register and vector length. The old generic
// helpers recomputed row/column/transpose and ran tiny loops at runtime;
// on giant generated units MSVC/Clang often kept the prefix helpers as
// real calls. These templates turn the lane mapping into constants and
// fully unroll 1..4 lanes at the call site.
template <std::uint32_t VectorRegister, std::uint32_t Length>
PSPRECOMP_CONTEXT_FORCEINLINE void read_vfpu_vector_ct(float *destination) const noexcept {
static_assert(Length >= 1u && Length <= 4u);
if constexpr (Length == 1u) {
constexpr std::size_t index = vfpu_scalar_index(VectorRegister & 0x7Fu);
destination[0] = vfpu[index];
} else {
constexpr std::uint32_t row = Length == 3u ? ((VectorRegister >> 6u) & 1u)
: ((VectorRegister >> 5u) & 2u);
constexpr bool transpose = ((VectorRegister >> 5u) & 1u) != 0u;
constexpr std::uint32_t matrix_base = ((VectorRegister << 2u) & 0x70u);
constexpr std::uint32_t column = VectorRegister & 3u;
if constexpr (transpose) {
constexpr std::uint32_t base = matrix_base + column;
destination[0] = vfpu[base + ((row + 0u) & 3u) * 4u];
if constexpr (Length >= 2u) destination[1] = vfpu[base + ((row + 1u) & 3u) * 4u];
if constexpr (Length >= 3u) destination[2] = vfpu[base + ((row + 2u) & 3u) * 4u];
if constexpr (Length >= 4u) destination[3] = vfpu[base + ((row + 3u) & 3u) * 4u];
} else {
constexpr std::uint32_t base = matrix_base + column * 4u;
destination[0] = vfpu[base + ((row + 0u) & 3u)];
if constexpr (Length >= 2u) destination[1] = vfpu[base + ((row + 1u) & 3u)];
if constexpr (Length >= 3u) destination[2] = vfpu[base + ((row + 2u) & 3u)];
if constexpr (Length >= 4u) destination[3] = vfpu[base + ((row + 3u) & 3u)];
}
}
}
template <std::uint32_t Length, std::uint32_t ControlIndex>
PSPRECOMP_CONTEXT_FORCEINLINE void apply_vfpu_source_prefix_ct(float *value) const noexcept {
static_assert(Length >= 1u && Length <= 4u);
static_assert(ControlIndex < 2u);
const std::uint32_t prefix = vfpu_ctrl[ControlIndex];
if (prefix == 0xE4u) return;
const float o0 = value[0];
const float o1 = Length >= 2u ? value[1] : 0.0f;
const float o2 = Length >= 3u ? value[2] : 0.0f;
const float o3 = Length >= 4u ? value[3] : 0.0f;
const float original[4]{o0, o1, o2, o3};
static constexpr float constants[8] = {
0.0f, 1.0f, 2.0f, 0.5f, 3.0f, 1.0f / 3.0f, 0.25f, 1.0f / 6.0f,
};
auto lane_value = [&](std::uint32_t i) {
const std::uint32_t lane = (prefix >> (i * 2u)) & 3u;
const bool absolute = ((prefix >> (8u + i)) & 1u) != 0u;
const bool use_constant = ((prefix >> (12u + i)) & 1u) != 0u;
const bool negate = ((prefix >> (16u + i)) & 1u) != 0u;
float out = use_constant ? constants[lane + (absolute ? 4u : 0u)]
: (lane < Length ? original[lane] : 0.0f);
if (!use_constant && absolute) out = std::fabs(out);
if (negate) out = std::bit_cast<float>(std::bit_cast<std::uint32_t>(out) ^ 0x80000000u);
return out;
};
value[0] = lane_value(0u);
if constexpr (Length >= 2u) value[1] = lane_value(1u);
if constexpr (Length >= 3u) value[2] = lane_value(2u);
if constexpr (Length >= 4u) value[3] = lane_value(3u);
}
template <std::uint32_t VectorRegister, std::uint32_t Length, std::uint32_t ControlIndex>
PSPRECOMP_CONTEXT_FORCEINLINE void read_vfpu_vector_with_source_prefix_ct(float *destination) const noexcept {
read_vfpu_vector_ct<VectorRegister, Length>(destination);
apply_vfpu_source_prefix_ct<Length, ControlIndex>(destination);
}
template <std::uint32_t VectorRegister, std::uint32_t Length>
PSPRECOMP_CONTEXT_FORCEINLINE void write_vfpu_vector_ct(const float *source) noexcept {
static_assert(Length >= 1u && Length <= 4u);
if constexpr (Length == 1u) {
constexpr std::size_t index = vfpu_scalar_index(VectorRegister & 0x7Fu);
vfpu[index] = source[0];
} else {
constexpr std::uint32_t row = Length == 3u ? ((VectorRegister >> 6u) & 1u)
: ((VectorRegister >> 5u) & 2u);
constexpr bool transpose = ((VectorRegister >> 5u) & 1u) != 0u;
constexpr std::uint32_t matrix_base = ((VectorRegister << 2u) & 0x70u);
constexpr std::uint32_t column = VectorRegister & 3u;
if constexpr (transpose) {
constexpr std::uint32_t base = matrix_base + column;
vfpu[base + ((row + 0u) & 3u) * 4u] = source[0];
if constexpr (Length >= 2u) vfpu[base + ((row + 1u) & 3u) * 4u] = source[1];
if constexpr (Length >= 3u) vfpu[base + ((row + 2u) & 3u) * 4u] = source[2];
if constexpr (Length >= 4u) vfpu[base + ((row + 3u) & 3u) * 4u] = source[3];
} else {
constexpr std::uint32_t base = matrix_base + column * 4u;
vfpu[base + ((row + 0u) & 3u)] = source[0];
if constexpr (Length >= 2u) vfpu[base + ((row + 1u) & 3u)] = source[1];
if constexpr (Length >= 3u) vfpu[base + ((row + 2u) & 3u)] = source[2];
if constexpr (Length >= 4u) vfpu[base + ((row + 3u) & 3u)] = source[3];
}
}
}
template <std::uint32_t VectorRegister, std::uint32_t Length>
PSPRECOMP_CONTEXT_FORCEINLINE void write_vfpu_vector_with_destination_prefix_ct(const float *source) noexcept {
static_assert(Length >= 1u && Length <= 4u);
const std::uint32_t destination_prefix = vfpu_ctrl[2];
float value[4]{source[0], Length >= 2u ? source[1] : 0.0f,
Length >= 3u ? source[2] : 0.0f, Length >= 4u ? source[3] : 0.0f};
auto saturate_lane = [&](std::uint32_t i) {
const std::uint32_t saturation = (destination_prefix >> (i * 2u)) & 3u;
if (saturation == 1u) value[i] = std::fmin(1.0f, std::fmax(0.0f, value[i]));
else if (saturation == 3u) value[i] = std::fmin(1.0f, std::fmax(-1.0f, value[i]));
};
saturate_lane(0u);
if constexpr (Length >= 2u) saturate_lane(1u);
if constexpr (Length >= 3u) saturate_lane(2u);
if constexpr (Length >= 4u) saturate_lane(3u);
if ((destination_prefix & (1u << 8u)) == 0u) {
constexpr std::size_t i0 = vfpu_vector_lane_index(VectorRegister, Length, 0u);
vfpu[i0] = value[0];
}
if constexpr (Length >= 2u) if ((destination_prefix & (1u << 9u)) == 0u) {
constexpr std::size_t i1 = vfpu_vector_lane_index(VectorRegister, Length, 1u);
vfpu[i1] = value[1];
}
if constexpr (Length >= 3u) if ((destination_prefix & (1u << 10u)) == 0u) {
constexpr std::size_t i2 = vfpu_vector_lane_index(VectorRegister, Length, 2u);
vfpu[i2] = value[2];
}
if constexpr (Length >= 4u) if ((destination_prefix & (1u << 11u)) == 0u) {
constexpr std::size_t i3 = vfpu_vector_lane_index(VectorRegister, Length, 3u);
vfpu[i3] = value[3];
}
eat_vfpu_prefixes();
}
void read_vfpu_vector(float *destination, std::uint32_t vector_register, std::uint32_t length) const noexcept {
if (length == 1u) {
destination[0] = vfpu[vfpu_scalar_index(vector_register & 0x7Fu)];
return;
}
const std::uint32_t row = length == 3u ? ((vector_register >> 6u) & 1u)
: ((vector_register >> 5u) & 2u);
const bool transpose = ((vector_register >> 5u) & 1u) != 0u;
const std::uint32_t matrix_base = ((vector_register << 2u) & 0x70u);
const std::uint32_t column = vector_register & 3u;
if (transpose) {
const std::uint32_t base = matrix_base + column;
for (std::uint32_t i = 0; i < length; ++i) {
destination[i] = vfpu[base + ((row + i) & 3u) * 4u];
}
} else {
const std::uint32_t base = matrix_base + column * 4u;
for (std::uint32_t i = 0; i < length; ++i) {
destination[i] = vfpu[base + ((row + i) & 3u)];
}
}
}
void apply_vfpu_source_prefix(float *value, std::uint32_t length, std::uint32_t control_index) const noexcept {
if (control_index >= 2u || length == 0u) return;
const std::uint32_t prefix = vfpu_ctrl[control_index];
if (prefix == 0xE4u) return;
float original[4]{};
for (std::uint32_t i = 0; i < length && i < 4u; ++i) original[i] = value[i];
static constexpr float constants[8] = {
0.0f, 1.0f, 2.0f, 0.5f, 3.0f, 1.0f / 3.0f, 0.25f, 1.0f / 6.0f,
};
for (std::uint32_t i = 0; i < length && i < 4u; ++i) {
const std::uint32_t lane = (prefix >> (i * 2u)) & 3u;
const bool absolute = ((prefix >> (8u + i)) & 1u) != 0u;
const bool use_constant = ((prefix >> (12u + i)) & 1u) != 0u;
const bool negate = ((prefix >> (16u + i)) & 1u) != 0u;
if (use_constant) {
value[i] = constants[lane + (absolute ? 4u : 0u)];
} else {
value[i] = lane < length ? original[lane] : 0.0f;
if (absolute) value[i] = std::fabs(value[i]);
}
if (negate) {
value[i] = std::bit_cast<float>(std::bit_cast<std::uint32_t>(value[i]) ^ 0x80000000u);
}
}
}
void read_vfpu_vector_with_source_prefix(float *destination, std::uint32_t vector_register,
std::uint32_t length, std::uint32_t control_index) const noexcept {
read_vfpu_vector(destination, vector_register, length);
apply_vfpu_source_prefix(destination, length, control_index);
}
void write_vfpu_vector(const float *source, std::uint32_t vector_register, std::uint32_t length) noexcept {
if (length == 1u) {
vfpu[vfpu_scalar_index(vector_register & 0x7Fu)] = source[0];
return;
}
const std::uint32_t row = length == 3u ? ((vector_register >> 6u) & 1u)
: ((vector_register >> 5u) & 2u);
const bool transpose = ((vector_register >> 5u) & 1u) != 0u;
const std::uint32_t matrix_base = ((vector_register << 2u) & 0x70u);
const std::uint32_t column = vector_register & 3u;
if (transpose) {
const std::uint32_t base = matrix_base + column;
for (std::uint32_t i = 0; i < length; ++i) {
vfpu[base + ((row + i) & 3u) * 4u] = source[i];
}
} else {
const std::uint32_t base = matrix_base + column * 4u;
for (std::uint32_t i = 0; i < length; ++i) {
vfpu[base + ((row + i) & 3u)] = source[i];
}
}
}
void write_vfpu_vector_with_destination_prefix(const float *source, std::uint32_t vector_register,
std::uint32_t length) noexcept {
float value[4]{};
const std::uint32_t destination_prefix = vfpu_ctrl[2];
for (std::uint32_t i = 0; i < length && i < 4u; ++i) {
value[i] = source[i];
const std::uint32_t saturation = (destination_prefix >> (i * 2u)) & 3u;
if (saturation == 1u) {
value[i] = std::fmin(1.0f, std::fmax(0.0f, value[i]));
} else if (saturation == 3u) {
value[i] = std::fmin(1.0f, std::fmax(-1.0f, value[i]));
}
}
const std::uint32_t row = length == 3u ? ((vector_register >> 6u) & 1u)
: ((vector_register >> 5u) & 2u);
const bool transpose = ((vector_register >> 5u) & 1u) != 0u;
const std::uint32_t matrix_base = ((vector_register << 2u) & 0x70u);
const std::uint32_t column = vector_register & 3u;
for (std::uint32_t i = 0; i < length && i < 4u; ++i) {
// Destination-prefix mask bit 1 preserves the old lane.
if (((destination_prefix >> (8u + i)) & 1u) != 0u) {
continue;
}
const std::size_t index = length == 1u
? vfpu_scalar_index(vector_register & 0x7Fu)
: (transpose
? static_cast<std::size_t>(matrix_base + column + ((row + i) & 3u) * 4u)
: static_cast<std::size_t>(matrix_base + column * 4u + ((row + i) & 3u)));
vfpu[index] = value[i];
}
eat_vfpu_prefixes();
}
[[nodiscard]] static constexpr std::size_t vfpu_vector_lane_index(
std::uint32_t vector_register, std::uint32_t length, std::uint32_t lane) noexcept {
if (length == 1u) return vfpu_scalar_index(vector_register & 0x7Fu);
const std::uint32_t row = length == 3u ? ((vector_register >> 6u) & 1u)
: ((vector_register >> 5u) & 2u);
const bool transpose = ((vector_register >> 5u) & 1u) != 0u;
const std::uint32_t matrix_base = ((vector_register << 2u) & 0x70u);
const std::uint32_t column = vector_register & 3u;
return transpose
? static_cast<std::size_t>(matrix_base + column + ((row + lane) & 3u) * 4u)
: static_cast<std::size_t>(matrix_base + column * 4u + ((row + lane) & 3u));
}
[[nodiscard]] static constexpr std::uint32_t vfpu_expand_half_bits(std::uint16_t half) noexcept {
const std::uint32_t sign = static_cast<std::uint32_t>(half & 0x8000u) << 16u;
const std::uint32_t exponent = (half >> 10u) & 0x1Fu;
std::uint32_t mantissa = half & 0x03FFu;
if (exponent == 0u) {
if (mantissa == 0u) return sign;
// Normalize a binary16 subnormal into binary32. Exponent 113 is
// the binary32 exponent corresponding to the smallest normal
// binary16 value; each normalization shift decreases it by one.
std::uint32_t float_exponent = 113u;
while ((mantissa & 0x0400u) == 0u) {
mantissa <<= 1u;
--float_exponent;
}
mantissa &= 0x03FFu;
return sign | (float_exponent << 23u) | (mantissa << 13u);
}
if (exponent == 31u) {
// The PSP VFPU preserves the binary16 NaN payload in the low ten
// binary32 mantissa bits for this conversion.
return sign | 0x7F800000u | mantissa;
}
return sign | ((exponent + 112u) << 23u) | (mantissa << 13u);
}
[[nodiscard]] static std::uint16_t vfpu_shrink_to_half_bits(float value) noexcept {
// Integer-only binary32 -> binary16 conversion. The VFPU rounds a
// discarded exact half-way bit upward, so the rounding step is kept
// explicit instead of depending on host floating-point state.
const std::uint32_t bits = std::bit_cast<std::uint32_t>(value);
const std::uint16_t sign = static_cast<std::uint16_t>((bits >> 16u) & 0x8000u);
const std::uint32_t exponent = (bits >> 23u) & 0xFFu;
const std::uint32_t fraction = bits & 0x007FFFFFu;
if (exponent == 0xFFu) {
if (fraction == 0u) return static_cast<std::uint16_t>(sign | 0x7C00u);
// Preserve the low ten payload bits and force a quiet half NaN.
return static_cast<std::uint16_t>(sign | 0x7E00u | (bits & 0x03FFu));
}
// Every binary32 subnormal is too small to survive as binary16.
if (exponent == 0u) return sign;
const std::int32_t unbiased = static_cast<std::int32_t>(exponent) - 127;
if (unbiased > 15) return static_cast<std::uint16_t>(sign | 0x7C00u);
const auto round_half_up = [](std::uint32_t value_bits, std::uint32_t shift) noexcept {
if (shift == 0u) return value_bits;
const std::uint32_t quotient = value_bits >> shift;
const std::uint32_t remainder_mask = (std::uint32_t{1} << shift) - 1u;
const std::uint32_t remainder = value_bits & remainder_mask;
const std::uint32_t halfway = std::uint32_t{1} << (shift - 1u);
return quotient + (remainder >= halfway ? 1u : 0u);
};
if (unbiased >= -14) {
std::uint32_t half_exponent = static_cast<std::uint32_t>(unbiased + 15);
std::uint32_t half_fraction = round_half_up(fraction, 13u);
if (half_fraction == 0x0400u) {
half_fraction = 0u;
++half_exponent;
if (half_exponent >= 31u)
return static_cast<std::uint16_t>(sign | 0x7C00u);
}
return static_cast<std::uint16_t>(
sign | (half_exponent << 10u) | half_fraction);
}
if (unbiased < -25) return sign;
const std::uint32_t significand = 0x00800000u | fraction;
const std::uint32_t shift = static_cast<std::uint32_t>(13 + (-14 - unbiased));
const std::uint32_t half_fraction = round_half_up(significand, shift);
if (half_fraction >= 0x0400u)
return static_cast<std::uint16_t>(sign | 0x0400u);
return static_cast<std::uint16_t>(sign | half_fraction);
}
void execute_vfpu_vf2h(std::uint32_t destination_register,
std::uint32_t source_register,
std::uint32_t source_length) noexcept {
if (source_length == 0u || source_length > 4u) return;
// VF2H applies S through a four-lane view, allowing prefix constants
// to supply lanes that are not present in a short encoded vector.
float source[4]{};
read_vfpu_vector(source, source_register, source_length);
apply_vfpu_source_prefix(source, 4u, 0u);
std::uint32_t packed[2]{};
packed[0] = static_cast<std::uint32_t>(vfpu_shrink_to_half_bits(source[0])) |
(static_cast<std::uint32_t>(vfpu_shrink_to_half_bits(source[1])) << 16u);
const std::uint32_t destination_length = source_length <= 2u ? 1u : 2u;
if (destination_length == 2u) {
packed[1] = static_cast<std::uint32_t>(vfpu_shrink_to_half_bits(source[2])) |
(static_cast<std::uint32_t>(vfpu_shrink_to_half_bits(source[3])) << 16u);
}
float result[2]{
std::bit_cast<float>(packed[0]),
std::bit_cast<float>(packed[1]),
};
write_vfpu_vector_with_destination_prefix(result, destination_register, destination_length);
}
void execute_vfpu_vh2f(std::uint32_t destination_register,
std::uint32_t source_register,
std::uint32_t source_length) noexcept {
if (source_length == 0u || source_length > 4u) return;
float source[4]{};
read_vfpu_vector(source, source_register, source_length);
apply_vfpu_source_prefix(source, source_length, 0u);
const std::uint32_t first_word = std::bit_cast<std::uint32_t>(source[0]);
float result[4]{
std::bit_cast<float>(vfpu_expand_half_bits(static_cast<std::uint16_t>(first_word))),
std::bit_cast<float>(vfpu_expand_half_bits(static_cast<std::uint16_t>(first_word >> 16u))),
0.0f,
0.0f,
};
const std::uint32_t destination_length = source_length == 1u ? 2u : 4u;
if (destination_length == 4u) {
const std::uint32_t second_word = std::bit_cast<std::uint32_t>(source[1]);
result[2] = std::bit_cast<float>(
vfpu_expand_half_bits(static_cast<std::uint16_t>(second_word)));
result[3] = std::bit_cast<float>(
vfpu_expand_half_bits(static_cast<std::uint16_t>(second_word >> 16u)));
}
write_vfpu_vector_with_destination_prefix(result, destination_register, destination_length);
}
void execute_vfpu_vx2i(std::uint32_t destination_register,
std::uint32_t source_register,
std::uint32_t source_length,
std::uint32_t operation) noexcept {
if (source_length == 0u || source_length > 4u || operation > 3u) return;
float source[4]{};
read_vfpu_vector(source, source_register, source_length);
apply_vfpu_source_prefix(source, source_length, 0u);
std::uint32_t result_bits[4]{};
std::uint32_t destination_length = 4u;
if (operation == 0u) { // VUC2I
std::uint32_t value = std::bit_cast<std::uint32_t>(source[0]);
for (std::uint32_t lane = 0u; lane < 4u; ++lane) {
result_bits[lane] = ((value & 0xFFu) * 0x01010101u) >> 1u;
value >>= 8u;
}
} else if (operation == 1u) { // VC2I
const std::uint32_t value = std::bit_cast<std::uint32_t>(source[0]);
result_bits[0] = (value & 0x000000FFu) << 24u;
result_bits[1] = (value & 0x0000FF00u) << 16u;
result_bits[2] = (value & 0x00FF0000u) << 8u;
result_bits[3] = value & 0xFF000000u;
} else { // VUS2I / VS2I
const std::uint32_t input_count = std::min(source_length, 2u);
destination_length = source_length == 1u ? 2u : 4u;
for (std::uint32_t lane = 0u; lane < input_count; ++lane) {
const std::uint32_t value = std::bit_cast<std::uint32_t>(source[lane]);
if (operation == 2u) {
result_bits[lane * 2u] = (value & 0x0000FFFFu) << 15u;
result_bits[lane * 2u + 1u] = (value & 0xFFFF0000u) >> 1u;
} else {
result_bits[lane * 2u] = (value & 0x0000FFFFu) << 16u;
result_bits[lane * 2u + 1u] = value & 0xFFFF0000u;
}
}
}
float result[4]{};
for (std::uint32_t lane = 0u; lane < destination_length; ++lane)
result[lane] = std::bit_cast<float>(result_bits[lane]);
write_vfpu_vector_with_destination_prefix(result, destination_register, destination_length);
}
template <std::uint32_t DestinationScalarRegister, std::uint32_t SourceRegister,
std::uint32_t TargetRegister, std::uint32_t Length>
PSPRECOMP_CONTEXT_FORCEINLINE void execute_vfpu_vdot_ct() noexcept {
static_assert(Length >= 1u && Length <= 4u);
float source[4]{};
float target[4]{};
read_vfpu_vector_ct<SourceRegister, Length>(source);
read_vfpu_vector_ct<TargetRegister, Length>(target);
// VDOT prefix semantics use a four-lane view even for a shorter encoded vector.
apply_vfpu_source_prefix_ct<4u, 0u>(source);
apply_vfpu_source_prefix_ct<4u, 1u>(target);
const float result[1]{
source[0] * target[0] + source[1] * target[1] +
source[2] * target[2] + source[3] * target[3]
};
write_vfpu_vector_with_destination_prefix_ct<DestinationScalarRegister, 1u>(result);
}
void execute_vfpu_vdot(std::uint32_t destination_scalar_register,
std::uint32_t source_register, std::uint32_t target_register,
std::uint32_t length) noexcept {
if (length == 0u || length > 4u) return;
// VDOT initializes the lanes beyond the encoded vector size to zero,
// then applies both source prefixes through a four-lane view. This is
// important because prefix constants may legally introduce values in
// those otherwise-unused lanes.
float source[4]{};
float target[4]{};
read_vfpu_vector(source, source_register, length);
read_vfpu_vector(target, target_register, length);
apply_vfpu_source_prefix(source, 4u, 0u);
apply_vfpu_source_prefix(target, 4u, 1u);
float result[1]{0.0f};
for (std::uint32_t lane = 0u; lane < 4u; ++lane) {
result[0] += source[lane] * target[lane];
}
write_vfpu_vector_with_destination_prefix(result, destination_scalar_register, 1u);
}
void execute_vfpu_vhdp(std::uint32_t destination_scalar_register,
std::uint32_t source_register, std::uint32_t target_register,
std::uint32_t length) noexcept {
if (length == 0u || length > 4u) return;
// VHDP is a four-lane dot product in which the final encoded source
// lane is forced to constant ONE. The VFPU rewrites only that lane's
// swizzle/constant controls: its original absolute and negate bits are
// deliberately retained. Short vectors still use a four-lane prefix
// view, so constants may populate lanes outside the nominal length.
float source[4]{};
float target[4]{};
read_vfpu_vector(source, source_register, length);
read_vfpu_vector(target, target_register, length);
const std::uint32_t forced_lane = length - 1u;
const std::uint32_t swizzle_shift = forced_lane * 2u;
const std::uint32_t rewritten_source_prefix =
(vfpu_ctrl[0] & ~(3u << swizzle_shift)) |
(1u << swizzle_shift) |
(1u << (12u + forced_lane));
const std::uint32_t original_source_prefix = vfpu_ctrl[0];
vfpu_ctrl[0] = rewritten_source_prefix;
apply_vfpu_source_prefix(source, 4u, 0u);
vfpu_ctrl[0] = original_source_prefix;
apply_vfpu_source_prefix(target, 4u, 1u);
float sum = 0.0f;
for (std::uint32_t lane = 0u; lane < 4u; ++lane) {
sum += source[lane] * target[lane];
}
if (std::isnan(sum)) sum = std::fabs(sum);
const float result[1]{sum};
write_vfpu_vector_with_destination_prefix(result, destination_scalar_register, 1u);
}
void execute_vfpu_horizontal(std::uint32_t destination_scalar_register,
std::uint32_t source_register,
std::uint32_t source_length,
bool average) noexcept {
if (source_length == 0u || source_length > 4u) return;
// The horizontal instructions use a four-lane view even for shorter
// encoded vectors. Prefix constants can therefore populate lanes
// beyond the nominal source size.
float source[4]{};
read_vfpu_vector(source, source_register, source_length);
apply_vfpu_source_prefix(source, 4u, 0u);
const std::uint32_t original_target_prefix = vfpu_ctrl[1];
float weights[4]{};
if (!average) {
// VFAD forces every T lane to constant ONE, but deliberately
// retains the original T absolute and negate controls. On the
// VFPU, an absolute bit changes forced ONE into constant 1/3.
vfpu_ctrl[1] = (original_target_prefix & ~0x000000FFu) | 0x0000F055u;
} else {
// VAVG forces 0, 1/2, 1/3, or 1/4 according to the encoded vector
// size. It discards T swizzle/absolute controls but retains T
// negate flags, matching the hardware prefix rewrite.
static constexpr std::uint32_t average_prefix[4]{
0x0000F000u, // scalar: 0
0x0000F0FFu, // pair: 1/2
0x0000FF55u, // triple: 1/3
0x0000FFAAu, // quad: 1/4
};
vfpu_ctrl[1] = (original_target_prefix & ~0x00000FFFu) |
average_prefix[source_length - 1u];
}
apply_vfpu_source_prefix(weights, 4u, 1u);
vfpu_ctrl[1] = original_target_prefix;
float result[1]{0.0f};
for (std::uint32_t lane = 0u; lane < 4u; ++lane) {
result[0] += source[lane] * weights[lane];
}
write_vfpu_vector_with_destination_prefix(result, destination_scalar_register, 1u);
}
[[nodiscard]] static constexpr bool vfpu_is_nan_or_inf_bits(std::uint32_t bits) noexcept {
return (bits & 0x7F800000u) == 0x7F800000u;
}
[[nodiscard]] static std::uint32_t vfpu_minmax_bits(std::uint32_t source_bits,
std::uint32_t target_bits,
bool maximum) noexcept {
const float source = std::bit_cast<float>(source_bits);
const float target = std::bit_cast<float>(target_bits);
if (vfpu_is_nan_or_inf_bits(source_bits) || vfpu_is_nan_or_inf_bits(target_bits)) {
const auto source_signed = static_cast<std::int32_t>(source_bits);
const auto target_signed = static_cast<std::int32_t>(target_bits);
const bool both_negative = source_signed < 0 && target_signed < 0;
const std::int32_t selected = maximum
? (both_negative ? std::min(target_signed, source_signed)
: std::max(target_signed, source_signed))
: (both_negative ? std::max(target_signed, source_signed)
: std::min(target_signed, source_signed));
return static_cast<std::uint32_t>(selected);
}
// The PSP chooses T when values compare equal, preserving T's sign for
// +0/-0. The operand order below deliberately matches std::min(T, S)
// and std::max(T, S) in the reference interpreter.
const float selected = maximum ? std::max(target, source) : std::min(target, source);
return std::bit_cast<std::uint32_t>(selected);
}
void execute_vfpu_cross_quat(std::uint32_t destination_register,
std::uint32_t source_register, std::uint32_t target_register,
std::uint32_t length) noexcept {
if (length == 0u || length > 4u) return;
float source[4]{};
float target[4]{};
float result[4]{};
read_vfpu_vector(source, source_register, length);
read_vfpu_vector(target, target_register, length);
constexpr std::uint32_t kSwizzleAndNegateMask = 0x000F00FFu;
if (length == 3u) { // VCRSP.T
// X/Y are produced directly from the unprefixed inputs. The PSP
// applies rewritten S/T prefixes only to the final dot-product lane.
result[0] = source[1] * target[2] - source[2] * target[1];
result[1] = source[2] * target[0] - source[0] * target[2];
// Forced T view: [T.y, -T.x, T.w, T.z], while retaining the
// original constant/absolute controls.
vfpu_ctrl[1] = (vfpu_ctrl[1] & ~kSwizzleAndNegateMask) | 0x000200B1u;
apply_vfpu_source_prefix(target, 4u, 1u);
apply_vfpu_source_prefix(source, 4u, 0u);
result[2] = source[0] * target[0] + source[1] * target[1] +
source[2] * target[2] + source[3] * target[3];
} else if (length == 4u) { // VQMUL.Q
result[0] = source[0] * target[3] + source[1] * target[2] -
source[2] * target[1] + source[3] * target[0];
result[1] = -source[0] * target[2] + source[1] * target[3] +
source[2] * target[0] + source[3] * target[1];
result[2] = source[0] * target[1] - source[1] * target[0] +
source[2] * target[3] + source[3] * target[2];
// Forced T view: [-T.x, -T.y, -T.z, T.w], retaining constants/abs.
vfpu_ctrl[1] = (vfpu_ctrl[1] & ~kSwizzleAndNegateMask) | 0x000700E4u;
apply_vfpu_source_prefix(target, 4u, 1u);
apply_vfpu_source_prefix(source, 4u, 0u);
result[3] = source[0] * target[0] + source[1] * target[1] +
source[2] * target[2] + source[3] * target[3];
} else if (length == 2u) {
result[0] = 0.0f;
// Pair form can source lane 2 through S-prefix swizzling.
vfpu_ctrl[1] = (vfpu_ctrl[1] & ~kSwizzleAndNegateMask);
apply_vfpu_source_prefix(target, 4u, 1u);
apply_vfpu_source_prefix(source, 4u, 0u);
result[1] = source[2] * target[2];
} else {
result[0] = 0.0f;
}
// Hardware applies the original D-prefix lane-0 controls to the last
// result lane only. All earlier lanes are written unmasked/unsaturated.
if (length == 1u) {
vfpu_ctrl[2] = 0u;
} else {
const std::uint32_t destination_prefix = vfpu_ctrl[2];
const std::uint32_t last_lane = length - 1u;
const std::uint32_t last_mask = ((destination_prefix >> 8u) & 1u) << (8u + last_lane);
const std::uint32_t last_saturation = (destination_prefix & 3u) << (last_lane * 2u);
vfpu_ctrl[2] = last_mask | last_saturation;
}
write_vfpu_vector_with_destination_prefix(result, destination_register, length);
}
void execute_vfpu_vminmax(std::uint32_t destination_register,
std::uint32_t source_register, std::uint32_t target_register,
std::uint32_t length, bool maximum) noexcept {
if (length == 0u || length > 4u) return;
float source[4]{};
float target[4]{};
float result[4]{};
read_vfpu_vector(source, source_register, length);
read_vfpu_vector(target, target_register, length);
apply_vfpu_source_prefix(source, length, 0u);
apply_vfpu_source_prefix(target, length, 1u);
const std::uint32_t source_prefix = vfpu_ctrl[0];
const std::uint32_t target_prefix = vfpu_ctrl[1];
for (std::uint32_t lane = 0u; lane < length; ++lane) {
const std::uint32_t source_swizzle = (source_prefix >> (lane * 2u)) & 3u;
const std::uint32_t target_swizzle = (target_prefix >> (lane * 2u)) & 3u;
const bool source_constant = ((source_prefix >> (12u + lane)) & 1u) != 0u;
const bool target_constant = ((target_prefix >> (12u + lane)) & 1u) != 0u;
if ((!source_constant && source_swizzle >= length) ||
(!target_constant && target_swizzle >= length)) {
// VFPU min/max wires an invalid swizzle to an exact +0 result.
result[lane] = 0.0f;
continue;
}
const std::uint32_t selected = vfpu_minmax_bits(
std::bit_cast<std::uint32_t>(source[lane]),
std::bit_cast<std::uint32_t>(target[lane]), maximum);
result[lane] = std::bit_cast<float>(selected);
}
write_vfpu_vector_with_destination_prefix(result, destination_register, length);
}
void execute_vfpu_compare3(std::uint32_t destination_register,
std::uint32_t source_register, std::uint32_t target_register,
std::uint32_t length, std::uint32_t operation) noexcept {
if (length == 0u || length > 4u || operation < 5u || operation > 7u) return;
float source[4]{};
float target[4]{};
float result[4]{};
read_vfpu_vector(source, source_register, length);
read_vfpu_vector(target, target_register, length);
apply_vfpu_source_prefix(source, length, 0u);
apply_vfpu_source_prefix(target, length, 1u);
const std::uint32_t source_prefix = vfpu_ctrl[0];
const std::uint32_t target_prefix = vfpu_ctrl[1];
for (std::uint32_t lane = 0u; lane < length; ++lane) {
const std::uint32_t source_swizzle = (source_prefix >> (lane * 2u)) & 3u;
const std::uint32_t target_swizzle = (target_prefix >> (lane * 2u)) & 3u;
const bool source_constant = ((source_prefix >> (12u + lane)) & 1u) != 0u;
const bool target_constant = ((target_prefix >> (12u + lane)) & 1u) != 0u;
if ((!source_constant && source_swizzle >= length) ||
(!target_constant && target_swizzle >= length)) {
result[lane] = 0.0f;
continue;
}
if (operation == 5u) { // VSCMP
const float difference = source[lane] - target[lane];
if (std::isnan(difference)) {
const std::uint32_t source_bits = std::bit_cast<std::uint32_t>(source[lane]);
const std::uint32_t target_bits = std::bit_cast<std::uint32_t>(target[lane]);
const std::int64_t source_magnitude = static_cast<std::int64_t>(source_bits & 0x7FFFFFFFu);
const std::int64_t target_magnitude = static_cast<std::int64_t>(target_bits & 0x7FFFFFFFu);
const std::int64_t ordered_source = (source_bits & 0x80000000u) != 0u
? -source_magnitude : source_magnitude;
const std::int64_t ordered_target = (target_bits & 0x80000000u) != 0u
? -target_magnitude : target_magnitude;
result[lane] = ordered_source > ordered_target ? 1.0f
: ordered_source < ordered_target ? -1.0f : 0.0f;
} else {
result[lane] = difference > 0.0f ? 1.0f
: difference < 0.0f ? -1.0f : 0.0f;
}
} else if (operation == 6u) { // VSGE
result[lane] = (!std::isnan(source[lane]) && !std::isnan(target[lane]) &&
source[lane] >= target[lane]) ? 1.0f : 0.0f;
} else { // VSLT
result[lane] = (!std::isnan(source[lane]) && !std::isnan(target[lane]) &&
source[lane] < target[lane]) ? 1.0f : 0.0f;
}
}
write_vfpu_vector_with_destination_prefix(result, destination_register, length);
}
template <std::uint32_t SourceRegister, std::uint32_t TargetRegister,
std::uint32_t Length, std::uint32_t Condition>
PSPRECOMP_CONTEXT_FORCEINLINE void execute_vfpu_vcmp_ct() noexcept {
static_assert(Length >= 1u && Length <= 4u);
static_assert(Condition < 16u);
float source[4]{};
float target[4]{};
read_vfpu_vector_with_source_prefix_ct<SourceRegister, Length, 0u>(source);
read_vfpu_vector_with_source_prefix_ct<TargetRegister, Length, 1u>(target);
auto compare_lane = [](float sv, float tv) -> bool {
if constexpr (Condition == 0u) return false;
else if constexpr (Condition == 1u) return sv == tv;
else if constexpr (Condition == 2u) return sv < tv;
else if constexpr (Condition == 3u) return sv <= tv;
else if constexpr (Condition == 4u) return true;
else if constexpr (Condition == 5u) return sv != tv;
else if constexpr (Condition == 6u) return sv >= tv;
else if constexpr (Condition == 7u) return sv > tv;
else if constexpr (Condition == 8u) return sv == 0.0f;
else if constexpr (Condition == 9u) return std::isnan(sv);
else if constexpr (Condition == 10u) return std::isinf(sv);
else if constexpr (Condition == 11u) return std::isnan(sv) || std::isinf(sv);
else if constexpr (Condition == 12u) return sv != 0.0f;
else if constexpr (Condition == 13u) return !std::isnan(sv);
else if constexpr (Condition == 14u) return !std::isinf(sv);
else return !(std::isnan(sv) || std::isinf(sv));
};
const bool r0 = compare_lane(source[0], target[0]);
const bool r1 = Length >= 2u ? compare_lane(source[1], target[1]) : false;
const bool r2 = Length >= 3u ? compare_lane(source[2], target[2]) : false;
const bool r3 = Length >= 4u ? compare_lane(source[3], target[3]) : false;
std::uint32_t lane_bits = static_cast<std::uint32_t>(r0);
if constexpr (Length >= 2u) lane_bits |= static_cast<std::uint32_t>(r1) << 1u;
if constexpr (Length >= 3u) lane_bits |= static_cast<std::uint32_t>(r2) << 2u;
if constexpr (Length >= 4u) lane_bits |= static_cast<std::uint32_t>(r3) << 3u;
bool any = r0;
bool all = r0;
if constexpr (Length >= 2u) { any = any || r1; all = all && r1; }
if constexpr (Length >= 3u) { any = any || r2; all = all && r2; }
if constexpr (Length >= 4u) { any = any || r3; all = all && r3; }
constexpr std::uint32_t affected = ((1u << Length) - 1u) | (1u << 4u) | (1u << 5u);
const std::uint32_t update = lane_bits | (static_cast<std::uint32_t>(any) << 4u) |
(static_cast<std::uint32_t>(all) << 5u);
vfpu_ctrl[3] = (vfpu_ctrl[3] & ~affected) | (update & affected);
eat_vfpu_prefixes();
}
void execute_vfpu_vcmp(std::uint32_t source_register, std::uint32_t target_register,
std::uint32_t length, std::uint32_t condition) noexcept {
if (length == 0u || length > 4u) return;
float source[4]{};
float target[4]{};
read_vfpu_vector_with_source_prefix(source, source_register, length, 0u);
read_vfpu_vector_with_source_prefix(target, target_register, length, 1u);
std::uint32_t lane_bits = 0u;
bool any = false;
bool all = true;
for (std::uint32_t lane = 0u; lane < length; ++lane) {
const float s = source[lane];
const float t = target[lane];
bool result = false;
switch (condition & 15u) {
case 0u: result = false; break; // FL
case 1u: result = s == t; break; // EQ
case 2u: result = s < t; break; // LT
case 3u: result = s <= t; break; // LE
case 4u: result = true; break; // TR
case 5u: result = s != t; break; // NE
case 6u: result = s >= t; break; // GE
case 7u: result = s > t; break; // GT
case 8u: result = s == 0.0f; break; // EZ
case 9u: result = std::isnan(s); break; // EN
case 10u: result = std::isinf(s); break; // EI
case 11u: result = std::isnan(s) || std::isinf(s); break; // ES
case 12u: result = s != 0.0f; break; // NZ
case 13u: result = !std::isnan(s); break; // NN
case 14u: result = !std::isinf(s); break; // NI
default: result = !(std::isnan(s) || std::isinf(s)); break; // NS
}
if (result) lane_bits |= 1u << lane;
any = any || result;
all = all && result;
}
// CC lanes x/y/z/w occupy bits 0..3; bit 4 is ANY and bit 5
// is ALL. A narrower comparison preserves untouched lane bits.
const std::uint32_t affected = ((1u << length) - 1u) | (1u << 4u) | (1u << 5u);
const std::uint32_t update = lane_bits | (static_cast<std::uint32_t>(any) << 4u) |
(static_cast<std::uint32_t>(all) << 5u);
vfpu_ctrl[3] = (vfpu_ctrl[3] & ~affected) | (update & affected);
eat_vfpu_prefixes();
}
template <std::uint32_t DestinationRegister, std::uint32_t SourceRegister,
std::uint32_t Length, std::uint32_t ConditionIndex, bool MoveIfFalse>
PSPRECOMP_CONTEXT_FORCEINLINE void execute_vfpu_vcmov_ct() noexcept {
static_assert(Length >= 1u && Length <= 4u);
static_assert(ConditionIndex < 8u);
float source[4]{};
float destination[4]{};
read_vfpu_vector_with_source_prefix_ct<SourceRegister, Length, 0u>(source);
read_vfpu_vector_ct<DestinationRegister, Length>(destination);
apply_vfpu_source_prefix_ct<Length, 1u>(destination);
const std::uint32_t condition_code = vfpu_ctrl[3];
if constexpr (ConditionIndex < 6u) {
const bool cc = ((condition_code >> ConditionIndex) & 1u) != 0u;
if (cc == !MoveIfFalse) {
destination[0] = source[0];
if constexpr (Length >= 2u) destination[1] = source[1];
if constexpr (Length >= 3u) destination[2] = source[2];
if constexpr (Length >= 4u) destination[3] = source[3];
}
} else if constexpr (ConditionIndex == 6u) {
const bool want = !MoveIfFalse;
if ((((condition_code >> 0u) & 1u) != 0u) == want) destination[0] = source[0];
if constexpr (Length >= 2u)
if ((((condition_code >> 1u) & 1u) != 0u) == want) destination[1] = source[1];
if constexpr (Length >= 3u)
if ((((condition_code >> 2u) & 1u) != 0u) == want) destination[2] = source[2];
if constexpr (Length >= 4u)
if ((((condition_code >> 3u) & 1u) != 0u) == want) destination[3] = source[3];
}
write_vfpu_vector_with_destination_prefix_ct<DestinationRegister, Length>(destination);
}
void execute_vfpu_vcmov(std::uint32_t destination_register, std::uint32_t source_register,
std::uint32_t length, std::uint32_t condition_index,
bool move_if_false) noexcept {
if (length == 0u || length > 4u) return;
float source[4]{};
float destination[4]{};
read_vfpu_vector_with_source_prefix(source, source_register, length, 0u);
// VCMOV unusually treats the old destination as its T input, so the
// guest T prefix is applied even when no source lane is selected.
read_vfpu_vector(destination, destination_register, length);
apply_vfpu_source_prefix(destination, length, 1u);
const std::uint32_t condition_code = vfpu_ctrl[3];
if (condition_index < 6u) {
const bool cc = ((condition_code >> condition_index) & 1u) != 0u;
if (cc == !move_if_false) {
for (std::uint32_t lane = 0u; lane < length; ++lane) destination[lane] = source[lane];
}
} else if (condition_index == 6u) {
for (std::uint32_t lane = 0u; lane < length; ++lane) {
const bool cc = ((condition_code >> lane) & 1u) != 0u;
if (cc == !move_if_false) destination[lane] = source[lane];
}
}
// condition_index 7 is invalid on hardware; preserving the T-prefixed
// destination is deterministic and leaves error reporting to callers.
write_vfpu_vector_with_destination_prefix(destination, destination_register, length);
}
template <std::uint32_t DestinationRegister, std::uint32_t SourceRegister,
std::uint32_t TargetScalarRegister, std::uint32_t Length>
PSPRECOMP_CONTEXT_FORCEINLINE void execute_vfpu_vscl_ct() noexcept {
static_assert(Length >= 1u && Length <= 4u);
float source[4]{};
read_vfpu_vector_with_source_prefix_ct<SourceRegister, Length, 0u>(source);
float target[4]{};
target[0] = std::bit_cast<float>(vfpu_scalar_bits_ct<(TargetScalarRegister & 0x7Fu)>());
const std::uint32_t original_target_prefix = vfpu_ctrl[1];
vfpu_ctrl[1] = original_target_prefix & ~0xFFu;
apply_vfpu_source_prefix_ct<Length, 1u>(target);
vfpu_ctrl[1] = original_target_prefix;
float result[4]{};
result[0] = source[0] * target[0];
if constexpr (Length >= 2u) result[1] = source[1] * target[1];
if constexpr (Length >= 3u) result[2] = source[2] * target[2];
if constexpr (Length >= 4u) result[3] = source[3] * target[3];
write_vfpu_vector_with_destination_prefix_ct<DestinationRegister, Length>(result);
}
void execute_vfpu_vscl(std::uint32_t destination_register, std::uint32_t source_register,
std::uint32_t target_scalar_register, std::uint32_t length) noexcept {
if (length == 0u || length > 4u) return;
float source[4]{};
read_vfpu_vector_with_source_prefix(source, source_register, length, 0u);
// VSCL reads VT as a scalar, but the T prefix still operates on a
// vector view. Materialize the scalar in lane 0 and broadcast from
// there, while preserving the guest prefix's abs, constant and negate
// flags (those flags are indexed by output lane).
//
// Do not place the scalar in its encoded physical lane. The source
// prefix helper only populates `length` lanes; for VSCL.T a scalar in
// physical lane w would therefore be outside the populated 3-lane
// range and the broadcast would become exactly zero. That produced
// null normalized vectors, breaking both collision normals and world
// vertex/lighting transforms.
float target[4]{};
target[0] = std::bit_cast<float>(vfpu_scalar_bits(target_scalar_register & 0x7Fu));
const std::uint32_t original_target_prefix = vfpu_ctrl[1];
vfpu_ctrl[1] = original_target_prefix & ~0xFFu; // every output lane selects lane 0
apply_vfpu_source_prefix(target, length, 1u);
vfpu_ctrl[1] = original_target_prefix;
float result[4]{};
for (std::uint32_t lane = 0u; lane < length; ++lane) {
result[lane] = source[lane] * target[lane];
}
write_vfpu_vector_with_destination_prefix(result, destination_register, length);
}
void execute_vfpu_vrot(std::uint32_t destination_register, std::uint32_t source_register,
std::uint32_t length, std::uint32_t immediate) noexcept {
float source[4]{};
read_vfpu_vector_with_source_prefix(source, source_register, 1u, 0u);
const float original_source =
std::bit_cast<float>(vfpu_scalar_bits(source_register & 0x7Fu));
constexpr float half_pi = 1.57079632679489661923f;
float sine = std::sin(source[0] * half_pi);
const float original_cosine = std::cos(original_source * half_pi);
if ((immediate & 0x10u) != 0u)
sine = std::bit_cast<float>(std::bit_cast<std::uint32_t>(sine) ^ 0x80000000u);
const std::uint32_t sine_lane = (immediate >> 2u) & 3u;
const std::uint32_t cosine_lane = immediate & 3u;
float value[4]{};
if (sine_lane == cosine_lane) {
for (std::uint32_t lane = 0u; lane < length && lane < 4u; ++lane) value[lane] = sine;
} else if (sine_lane < length) {
value[sine_lane] = sine;
}
float cosine = original_cosine;
if (((destination_register >> 2u) & 7u) == ((source_register >> 2u) & 7u)) {
const std::size_t source_index = vfpu_scalar_index(source_register & 0x7Fu);
for (std::uint32_t lane = 0u; lane < length && lane < 4u; ++lane) {
if (vfpu_vector_lane_index(destination_register, length, lane) == source_index) {
cosine = std::cos(value[lane] * half_pi);
break;
}
}
}
if (cosine_lane < length) value[cosine_lane] = cosine;
// VROT consumes all prefixes, but the cosine lane ignores destination
// saturation and write masking on the hardware.
if (cosine_lane < 4u) {
vfpu_ctrl[2] &= ~((3u << (cosine_lane * 2u)) | (1u << (8u + cosine_lane)));
}
write_vfpu_vector_with_destination_prefix(value, destination_register, length);
}
void execute_vfpu_vocp(std::uint32_t destination_register, std::uint32_t source_register,
std::uint32_t length) noexcept {
if (length == 0u || length > 4u) return;
const std::uint32_t source_prefix = vfpu_ctrl[0];
const std::uint32_t target_prefix = vfpu_ctrl[1];
// VOCP forces the S-prefix negate flags on, preserving its swizzle,
// abs, and constant controls. Therefore the common no-prefix case
// reads the source as -S.
float source[4]{};
read_vfpu_vector(source, source_register, length);
vfpu_ctrl[0] = source_prefix | 0x000F0000u;
apply_vfpu_source_prefix(source, length, 0u);
vfpu_ctrl[0] = source_prefix;
// VOCP forces every T lane to constant ONE while preserving the
// original abs and negate flags. In VFPU prefix encoding, ONE in all
// four lanes is swizzle 1 plus all constant bits: 0x0000F055.
float target[4]{};
vfpu_ctrl[1] = (target_prefix & ~0x000000FFu) | 0x0000F055u;
apply_vfpu_source_prefix(target, length, 1u);
vfpu_ctrl[1] = target_prefix;
float result[4]{};
for (std::uint32_t lane = 0u; lane < length; ++lane) {
// Hardware produces a positive NaN for a NaN source instead of
// adding the forced T constant.
result[lane] = std::isnan(source[lane]) ? std::fabs(source[lane])
: target[lane] + source[lane];
// Invalid swizzles are retained as zero based on the original
// prefixes, even though VOCP rewrites T to constants internally.
const std::uint32_t source_swizzle = (source_prefix >> (lane * 2u)) & 3u;
const std::uint32_t target_swizzle = (target_prefix >> (lane * 2u)) & 3u;
const bool source_constant = ((source_prefix >> (12u + lane)) & 1u) != 0u;
const bool target_constant = ((target_prefix >> (12u + lane)) & 1u) != 0u;
if ((source_swizzle >= length && !source_constant) ||
(target_swizzle >= length && !target_constant)) {
result[lane] = 0.0f;
}
}
write_vfpu_vector_with_destination_prefix(result, destination_register, length);
}
void read_vfpu_matrix(float *destination, std::uint32_t matrix_register, std::uint32_t side) const noexcept {
const std::uint32_t matrix = (matrix_register >> 2u) & 7u;
const std::uint32_t column = matrix_register & 3u;
bool transpose = ((matrix_register >> 5u) & 1u) != 0u;
std::uint32_t row = 0u;
if (side == 1u) { transpose = false; row = (matrix_register >> 5u) & 3u; }
else if (side == 2u || side == 4u) row = (matrix_register >> 5u) & 2u;
else if (side == 3u) row = (matrix_register >> 6u) & 1u;
const std::size_t base = static_cast<std::size_t>(matrix * 16u);
for (std::uint32_t j = 0; j < side; ++j) {
for (std::uint32_t i = 0; i < side; ++i) {
const std::size_t index = transpose
? base + static_cast<std::size_t>(((row + i) & 3u) * 4u + ((column + j) & 3u))
: base + static_cast<std::size_t>(((column + j) & 3u) * 4u + ((row + i) & 3u));
destination[j * 4u + i] = vfpu[index];
}
}
}
void write_vfpu_matrix(const float *source, std::uint32_t matrix_register, std::uint32_t side) noexcept {
const std::uint32_t matrix = (matrix_register >> 2u) & 7u;
const std::uint32_t column = matrix_register & 3u;
bool transpose = ((matrix_register >> 5u) & 1u) != 0u;
std::uint32_t row = 0u;
if (side == 1u) { transpose = false; row = (matrix_register >> 5u) & 3u; }
else if (side == 2u || side == 4u) row = (matrix_register >> 5u) & 2u;
else if (side == 3u) row = (matrix_register >> 6u) & 1u;
const std::size_t base = static_cast<std::size_t>(matrix * 16u);
for (std::uint32_t j = 0; j < side; ++j) {
for (std::uint32_t i = 0; i < side; ++i) {
const std::size_t index = transpose
? base + static_cast<std::size_t>(((row + i) & 3u) * 4u + ((column + j) & 3u))
: base + static_cast<std::size_t>(((column + j) & 3u) * 4u + ((row + i) & 3u));
vfpu[index] = source[j * 4u + i];
}
}
}
void execute_vfpu_vmscl(std::uint32_t destination_matrix_register,
std::uint32_t source_matrix_register,
std::uint32_t target_scalar_register,
std::uint32_t side) noexcept {
if (side == 0u || side > 4u) return;
float source[16]{};
float target[4]{};
float result[16]{};
float previous_destination[16]{};
read_vfpu_matrix(source, source_matrix_register, side);
read_vfpu_matrix(previous_destination, destination_matrix_register, side);
read_vfpu_vector(target, target_scalar_register, 1u);
const float scalar = target[0];
for (std::uint32_t row = 0u; row + 1u < side; ++row) {
for (std::uint32_t column = 0u; column < side; ++column) {
result[row * 4u + column] = source[row * 4u + column] * scalar;
}
}
// Hardware applies S/T prefixes only to the final matrix row. T is
// internally rewritten so every output lane selects the scalar's
// physical VFPU lane, while retaining constant/absolute/negate bits.
const std::uint32_t last_row = side - 1u;
apply_vfpu_source_prefix(source + last_row * 4u, 4u, 0u);
const std::uint32_t target_lane = (target_scalar_register >> 5u) & 3u;
target[target_lane] = scalar;
const std::uint32_t original_target_prefix = vfpu_ctrl[1];
const std::uint32_t replicated_swizzle = target_lane * 0x55u;
vfpu_ctrl[1] = (original_target_prefix & ~0xFFu) | replicated_swizzle;
apply_vfpu_source_prefix(target, 4u, 1u);
vfpu_ctrl[1] = original_target_prefix;
for (std::uint32_t column = 0u; column < side; ++column) {
result[last_row * 4u + column] = source[last_row * 4u + column] * target[column];
}
// D prefix saturation and mask apply only to the final row.
const std::uint32_t destination_prefix = vfpu_ctrl[2];
for (std::uint32_t column = 0u; column < side; ++column) {
if (((destination_prefix >> (8u + column)) & 1u) != 0u) {
result[last_row * 4u + column] = previous_destination[last_row * 4u + column];
continue;
}
const std::uint32_t saturation = (destination_prefix >> (column * 2u)) & 3u;
float &value = result[last_row * 4u + column];
if (saturation == 1u) value = std::fmin(1.0f, std::fmax(0.0f, value));
else if (saturation == 3u) value = std::fmin(1.0f, std::fmax(-1.0f, value));
}
write_vfpu_matrix(result, destination_matrix_register, side);
eat_vfpu_prefixes();
}
void execute_vfpu_vmmov(std::uint32_t destination_matrix_register,
std::uint32_t source_matrix_register,
std::uint32_t side) noexcept {
if (side == 0u || side > 4u) return;
float source[16]{};
float previous_destination[16]{};
read_vfpu_matrix(source, source_matrix_register, side);
read_vfpu_matrix(previous_destination, destination_matrix_register, side);
const std::uint32_t last_row = side - 1u;
apply_vfpu_source_prefix(source + last_row * 4u, 4u, 0u);
const std::uint32_t destination_prefix = vfpu_ctrl[2];
for (std::uint32_t column = 0u; column < side; ++column) {
float &value = source[last_row * 4u + column];
if (((destination_prefix >> (8u + column)) & 1u) != 0u) {
value = previous_destination[last_row * 4u + column];
continue;
}
const std::uint32_t saturation = (destination_prefix >> (column * 2u)) & 3u;
if (saturation == 1u) value = std::fmin(1.0f, std::fmax(0.0f, value));
else if (saturation == 3u) value = std::fmin(1.0f, std::fmax(-1.0f, value));
}
write_vfpu_matrix(source, destination_matrix_register, side);
eat_vfpu_prefixes();
}
void execute_vfpu_matrix_init(std::uint32_t destination_matrix_register,
std::uint32_t side,
std::uint32_t operation) noexcept {
if (side == 0u || side > 4u) return;
if (operation != 3u && operation != 6u && operation != 7u) return;
float matrix[16]{};
float previous_destination[16]{};
read_vfpu_matrix(previous_destination, destination_matrix_register, side);
for (std::uint32_t row = 0u; row < side; ++row) {
for (std::uint32_t column = 0u; column < side; ++column) {
matrix[row * 4u + column] = operation == 7u ? 1.0f
: (operation == 3u && row == column ? 1.0f : 0.0f);
}
}
// Matrix-init operations force the final row through source-prefix
// constants while retaining the original absolute/negate controls.
const std::uint32_t last_row = side - 1u;
std::uint32_t rewritten_source_prefix = vfpu_ctrl[0] & ~0xFFu;
for (std::uint32_t lane = 0u; lane < 4u; ++lane) {
const bool one = operation == 7u || (operation == 3u && lane == last_row);
rewritten_source_prefix |= (one ? 1u : 0u) << (lane * 2u);
rewritten_source_prefix |= 1u << (12u + lane);
}
const std::uint32_t original_source_prefix = vfpu_ctrl[0];
vfpu_ctrl[0] = rewritten_source_prefix;
apply_vfpu_source_prefix(matrix + last_row * 4u, 4u, 0u);
vfpu_ctrl[0] = original_source_prefix;
// Matrix-init saturation is undefined on hardware; honor only the
// architecturally useful final-row write mask.
const std::uint32_t destination_prefix = vfpu_ctrl[2];
for (std::uint32_t column = 0u; column < side; ++column) {
if (((destination_prefix >> (8u + column)) & 1u) != 0u) {
matrix[last_row * 4u + column] = previous_destination[last_row * 4u + column];
}
}
write_vfpu_matrix(matrix, destination_matrix_register, side);
eat_vfpu_prefixes();
}
void write_vfpu_identity_matrix(std::uint32_t matrix_register, std::uint32_t side) noexcept {
const std::uint32_t matrix = (matrix_register >> 2u) & 7u;
const std::uint32_t column = matrix_register & 3u;
const bool transpose = ((matrix_register >> 5u) & 1u) != 0u;
const std::uint32_t row = side == 3u ? ((matrix_register >> 6u) & 1u)
: (side == 1u ? ((matrix_register >> 5u) & 3u)
: ((matrix_register >> 5u) & 2u));
const std::size_t base = static_cast<std::size_t>(matrix * 16u);
for (std::uint32_t j = 0; j < side; ++j) {
for (std::uint32_t i = 0; i < side; ++i) {
const std::size_t index = transpose
? base + static_cast<std::size_t>(((row + i) & 3u) * 4u + ((column + j) & 3u))
: base + static_cast<std::size_t>(((column + j) & 3u) * 4u + ((row + i) & 3u));
vfpu[index] = i == j ? 1.0f : 0.0f;
}
}
eat_vfpu_prefixes();
}
PSPRECOMP_CONTEXT_FORCEINLINE void eat_vfpu_prefixes() noexcept {
vfpu_ctrl[0] = 0xE4u;
vfpu_ctrl[1] = 0xE4u;
vfpu_ctrl[2] = 0u;
}
[[nodiscard]] bool execute_signed_add(std::uint32_t destination, std::uint32_t source_a,
std::uint32_t source_b) noexcept {
const std::int64_t result = static_cast<std::int64_t>(static_cast<std::int32_t>(gpr[source_a & 31u])) +
static_cast<std::int64_t>(static_cast<std::int32_t>(gpr[source_b & 31u]));
if (result < std::numeric_limits<std::int32_t>::min() ||
result > std::numeric_limits<std::int32_t>::max()) {
return false;
}
set_gpr(destination, static_cast<std::uint32_t>(static_cast<std::int32_t>(result)));
return true;
}
[[nodiscard]] bool execute_signed_sub(std::uint32_t destination, std::uint32_t source_a,
std::uint32_t source_b) noexcept {
const std::int64_t result = static_cast<std::int64_t>(static_cast<std::int32_t>(gpr[source_a & 31u])) -
static_cast<std::int64_t>(static_cast<std::int32_t>(gpr[source_b & 31u]));
if (result < std::numeric_limits<std::int32_t>::min() ||
result > std::numeric_limits<std::int32_t>::max()) {
return false;
}
set_gpr(destination, static_cast<std::uint32_t>(static_cast<std::int32_t>(result)));
return true;
}
void set_gpr(std::uint32_t index, std::uint32_t value) noexcept {
// Allegrex $zero is initialized to zero and no supported path writes a
// non-zero value into gpr[0]. Generated AOT emits set_gpr() for every
// architectural register write (over half a million static call sites
// in generated AOT corpora), so redundantly storing gpr[0] after *every* write
// creates a hot dependency/store stream for no semantic benefit.
// Writes whose destination is register zero are simply discarded, as
// the hardware does. Runtime dispatch boundaries still assert gpr[0]
// explicitly as a defensive invariant.
if (index != 0u && index < gpr.size()) {
gpr[index] = value;
}
}
};
} // namespace psprecomp