Files
AnyPS5/core/relinker/codegen/src/x86/ReciprocalLowering.cpp
T
Dean Galvin 097566d547 fix(relinker): lower VRSQRTPS and VRCPPS to correctly rounded results for --to-intel
RSQRTPS and RCPPS are approximations whose low bits differ between Intel
and AMD. Intel returns 0x3F7FF000 for 1/sqrt(1.0), and two Newton-Raphson
steps on it settle on 0.99999994 instead of 1.0. PPSA21564 renormalises
its spring quaternions that way: an identity quaternion keeps w at
0.99999994 with a zero axis, its axis-angle conversion then normalises the
zero axis into NaNs, and the NaN bone poses reach keyframed physics bodies.
A NaN body later moves to an invalid broadphase cell and the game crashes
a few seconds into its hub level.

--to-intel now replaces the VEX.128 register forms with an out-of-line stub
that computes 1/sqrt(x) with SQRTPS and DIVPS (1/x with DIVPS), keeping
the upper half of the destination cleared and the scratch register intact.
A site with no room for the jump, including one followed by a branch
target or a RIP-relative instruction, keeps the native instruction and is
reported as kept instead of failing the relink.
2026-10-04 14:40:16 +13:00

66 lines
2.0 KiB
C++

#include <codegen/x86/ReciprocalLowering.hpp>
#include <array>
#include <cstring>
namespace Codegen {
namespace {
constexpr std::uint8_t kSqrtps = 0x51;
constexpr std::uint8_t kDivps = 0x5E;
constexpr std::uint8_t kMovaps = 0x28;
constexpr float kOne = 1.0f;
StubConstant _ones() {
StubConstant constant{};
for (std::size_t lane = 0; lane < 4; ++lane)
std::memcpy(constant.data() + lane * sizeof(kOne), &kOne, sizeof(kOne));
return constant;
}
std::uint8_t _scratch(const ReciprocalOperands& operands) {
std::uint8_t reg = 0;
while (reg == operands.Destination || reg == operands.Source)
++reg;
return reg;
}
void _vexMovaps(StubBodyBuilder& body, const std::uint8_t dst, const std::uint8_t src) {
const std::array<std::uint8_t, 5> bytes{
0xC4,
static_cast<std::uint8_t>(((dst & 8) != 0 ? 0x00 : 0x80) | 0x40 | ((src & 8) != 0 ? 0x00 : 0x20) | 0x01),
0x78,
kMovaps,
static_cast<std::uint8_t>(0xC0 | ((dst & 7) << 3) | (src & 7))
};
body.Raw(bytes);
}
}
void ReciprocalLowering::EmitOutOfLine(StubBodyBuilder& body, const ReciprocalOperands& operands) const {
const auto scratch = _scratch(operands);
const auto dst = operands.Destination;
body.Spill(scratch);
if (operands.Operation == ReciprocalOperation::ReciprocalSquareRoot) {
body.SsePlain({0x0F, kSqrtps}, scratch, operands.Source);
body.RipOperand({0x0F, kMovaps}, dst, _ones());
body.SsePlain({0x0F, kDivps}, dst, scratch);
_vexMovaps(body, dst, dst);
} else {
body.RipOperand({0x0F, kMovaps}, scratch, _ones());
body.SsePlain({0x0F, kDivps}, scratch, operands.Source);
_vexMovaps(body, dst, scratch);
}
body.Restore(scratch);
}
LoweredBody ReciprocalLowering::LowerOutOfLine(const ReciprocalOperands& operands, std::span<const std::uint8_t> trailing) const {
StubBodyBuilder body;
EmitOutOfLine(body, operands);
body.Raw(trailing);
return body.Finish();
}
}