mirror of
https://github.com/azahar-emu/dynarmic
synced 2025-11-07 23:50:00 +01:00
While initially done to potentially prevent creating bugs due to C++ having a silly type-promotion mechanism involving types < sizeof(int) and unsignedness, given that the bulk of these functions' usages are on exit paths, these can return the correct type to avoid the need to cast at every usage point.
193 lines
7.6 KiB
C++
193 lines
7.6 KiB
C++
/* This file is part of the dynarmic project.
|
|
* Copyright (c) 2018 MerryMage
|
|
* This software may be used and distributed according to the terms of the GNU
|
|
* General Public License version 2 or any later version.
|
|
*/
|
|
|
|
#include "common/fp/fpsr.h"
|
|
#include "common/fp/info.h"
|
|
#include "common/fp/mantissa_util.h"
|
|
#include "common/fp/process_exception.h"
|
|
#include "common/fp/rounding_mode.h"
|
|
#include "common/fp/unpacked.h"
|
|
#include "common/safe_ops.h"
|
|
|
|
namespace Dynarmic::FP {
|
|
|
|
template<typename FPT>
|
|
std::tuple<FPType, bool, FPUnpacked> FPUnpackBase(FPT op, FPCR fpcr, FPSR& fpsr) {
|
|
constexpr size_t sign_bit = FPInfo<FPT>::exponent_width + FPInfo<FPT>::explicit_mantissa_width;
|
|
constexpr size_t exponent_high_bit = FPInfo<FPT>::exponent_width + FPInfo<FPT>::explicit_mantissa_width - 1;
|
|
constexpr size_t exponent_low_bit = FPInfo<FPT>::explicit_mantissa_width;
|
|
constexpr size_t mantissa_high_bit = FPInfo<FPT>::explicit_mantissa_width - 1;
|
|
constexpr size_t mantissa_low_bit = 0;
|
|
constexpr int denormal_exponent = FPInfo<FPT>::exponent_min - int(FPInfo<FPT>::explicit_mantissa_width);
|
|
|
|
constexpr bool is_half_precision = std::is_same_v<FPT, u16>;
|
|
const bool sign = Common::Bit<sign_bit>(op);
|
|
const FPT exp_raw = Common::Bits<exponent_low_bit, exponent_high_bit>(op);
|
|
const FPT frac_raw = Common::Bits<mantissa_low_bit, mantissa_high_bit>(op);
|
|
|
|
if (exp_raw == 0) {
|
|
if constexpr (is_half_precision) {
|
|
if (frac_raw == 0 || fpcr.FZ16()) {
|
|
return {FPType::Zero, sign, {sign, 0, 0}};
|
|
}
|
|
return {FPType::Nonzero, sign, ToNormalized(sign, denormal_exponent, frac_raw)};
|
|
}
|
|
|
|
if (frac_raw == 0 || fpcr.FZ()) {
|
|
if (frac_raw != 0) {
|
|
FPProcessException(FPExc::InputDenorm, fpcr, fpsr);
|
|
}
|
|
return {FPType::Zero, sign, {sign, 0, 0}};
|
|
}
|
|
|
|
return {FPType::Nonzero, sign, ToNormalized(sign, denormal_exponent, frac_raw)};
|
|
}
|
|
|
|
const bool exp_all_ones = exp_raw == Common::Ones<FPT>(FPInfo<FPT>::exponent_width);
|
|
const bool ahp_disabled = is_half_precision && !fpcr.AHP();
|
|
if ((exp_all_ones && !is_half_precision) || (exp_all_ones && ahp_disabled)) {
|
|
if (frac_raw == 0) {
|
|
return {FPType::Infinity, sign, ToNormalized(sign, 1000000, 1)};
|
|
}
|
|
|
|
const bool is_quiet = Common::Bit<mantissa_high_bit>(frac_raw);
|
|
return {is_quiet ? FPType::QNaN : FPType::SNaN, sign, {sign, 0, 0}};
|
|
}
|
|
|
|
const int exp = static_cast<int>(exp_raw) - FPInfo<FPT>::exponent_bias;
|
|
const u64 frac = static_cast<u64>(frac_raw | FPInfo<FPT>::implicit_leading_bit) << (normalized_point_position - FPInfo<FPT>::explicit_mantissa_width);
|
|
return {FPType::Nonzero, sign, {sign, exp, frac}};
|
|
}
|
|
|
|
template std::tuple<FPType, bool, FPUnpacked> FPUnpackBase<u16>(u16 op, FPCR fpcr, FPSR& fpsr);
|
|
template std::tuple<FPType, bool, FPUnpacked> FPUnpackBase<u32>(u32 op, FPCR fpcr, FPSR& fpsr);
|
|
template std::tuple<FPType, bool, FPUnpacked> FPUnpackBase<u64>(u64 op, FPCR fpcr, FPSR& fpsr);
|
|
|
|
template<size_t F>
|
|
std::tuple<bool, int, u64, ResidualError> Normalize(FPUnpacked op, int extra_right_shift = 0) {
|
|
const int highest_set_bit = Common::HighestSetBit(op.mantissa);
|
|
const int shift_amount = highest_set_bit - static_cast<int>(F) + extra_right_shift;
|
|
const u64 mantissa = Safe::LogicalShiftRight(op.mantissa, shift_amount);
|
|
const ResidualError error = ResidualErrorOnRightShift(op.mantissa, shift_amount);
|
|
const int exponent = op.exponent + highest_set_bit - normalized_point_position;
|
|
return std::make_tuple(op.sign, exponent, mantissa, error);
|
|
}
|
|
|
|
template<typename FPT>
|
|
FPT FPRoundBase(FPUnpacked op, FPCR fpcr, RoundingMode rounding, FPSR& fpsr) {
|
|
ASSERT(op.mantissa != 0);
|
|
ASSERT(rounding != RoundingMode::ToNearest_TieAwayFromZero);
|
|
|
|
constexpr int minimum_exp = FPInfo<FPT>::exponent_min;
|
|
constexpr size_t E = FPInfo<FPT>::exponent_width;
|
|
constexpr size_t F = FPInfo<FPT>::explicit_mantissa_width;
|
|
constexpr bool isFP16 = FPInfo<FPT>::total_width == 16;
|
|
|
|
auto [sign, exponent, mantissa, error] = Normalize<F>(op);
|
|
|
|
if (((!isFP16 && fpcr.FZ()) || (isFP16 && fpcr.FZ16())) && exponent < minimum_exp) {
|
|
fpsr.UFC(true);
|
|
return FPInfo<FPT>::Zero(sign);
|
|
}
|
|
|
|
int biased_exp = std::max<int>(exponent - minimum_exp + 1, 0);
|
|
if (biased_exp == 0) {
|
|
std::tie(sign, exponent, mantissa, error) = Normalize<F>(op, minimum_exp - exponent);
|
|
}
|
|
|
|
if (biased_exp == 0 && (error != ResidualError::Zero || fpcr.UFE())) {
|
|
FPProcessException(FPExc::Underflow, fpcr, fpsr);
|
|
}
|
|
|
|
bool round_up = false, overflow_to_inf = false;
|
|
switch (rounding) {
|
|
case RoundingMode::ToNearest_TieEven: {
|
|
round_up = (error > ResidualError::Half) || (error == ResidualError::Half && Common::Bit<0>(mantissa));
|
|
overflow_to_inf = true;
|
|
break;
|
|
}
|
|
case RoundingMode::TowardsPlusInfinity:
|
|
round_up = error != ResidualError::Zero && !sign;
|
|
overflow_to_inf = !sign;
|
|
break;
|
|
case RoundingMode::TowardsMinusInfinity:
|
|
round_up = error != ResidualError::Zero && sign;
|
|
overflow_to_inf = sign;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (round_up) {
|
|
if ((mantissa & FPInfo<FPT>::mantissa_mask) == FPInfo<FPT>::mantissa_mask) {
|
|
// Overflow on rounding up is going to happen
|
|
if (mantissa == FPInfo<FPT>::mantissa_mask) {
|
|
// Rounding up from denormal to normal
|
|
mantissa++;
|
|
biased_exp++;
|
|
} else {
|
|
// Rounding up to next exponent
|
|
mantissa = (mantissa + 1) / 2;
|
|
biased_exp++;
|
|
}
|
|
} else {
|
|
mantissa++;
|
|
}
|
|
}
|
|
|
|
if (error != ResidualError::Zero && rounding == RoundingMode::ToOdd) {
|
|
mantissa = Common::ModifyBit<0>(mantissa, true);
|
|
}
|
|
|
|
FPT result = 0;
|
|
#ifdef _MSC_VER
|
|
#pragma warning(push)
|
|
#pragma warning(disable:4127) // C4127: conditional expression is constant
|
|
#endif
|
|
if (!isFP16 || !fpcr.AHP()) {
|
|
#ifdef _MSC_VER
|
|
#pragma warning(pop)
|
|
#endif
|
|
constexpr int max_biased_exp = (1 << E) - 1;
|
|
if (biased_exp >= max_biased_exp) {
|
|
result = overflow_to_inf ? FPInfo<FPT>::Infinity(sign) : FPInfo<FPT>::MaxNormal(sign);
|
|
FPProcessException(FPExc::Overflow, fpcr, fpsr);
|
|
FPProcessException(FPExc::Inexact, fpcr, fpsr);
|
|
} else {
|
|
result = sign ? 1 : 0;
|
|
result <<= E;
|
|
result += FPT(biased_exp);
|
|
result <<= F;
|
|
result |= static_cast<FPT>(mantissa) & FPInfo<FPT>::mantissa_mask;
|
|
if (error != ResidualError::Zero) {
|
|
FPProcessException(FPExc::Inexact, fpcr, fpsr);
|
|
}
|
|
}
|
|
} else {
|
|
constexpr int max_biased_exp = (1 << E);
|
|
if (biased_exp >= max_biased_exp) {
|
|
result = sign ? 0xFFFF : 0x7FFF;
|
|
FPProcessException(FPExc::InvalidOp, fpcr, fpsr);
|
|
} else {
|
|
result = sign ? 1 : 0;
|
|
result <<= E;
|
|
result += FPT(biased_exp);
|
|
result <<= F;
|
|
result |= static_cast<FPT>(mantissa) & FPInfo<FPT>::mantissa_mask;
|
|
if (error != ResidualError::Zero) {
|
|
FPProcessException(FPExc::Inexact, fpcr, fpsr);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
template u16 FPRoundBase<u16>(FPUnpacked op, FPCR fpcr, RoundingMode rounding, FPSR& fpsr);
|
|
template u32 FPRoundBase<u32>(FPUnpacked op, FPCR fpcr, RoundingMode rounding, FPSR& fpsr);
|
|
template u64 FPRoundBase<u64>(FPUnpacked op, FPCR fpcr, RoundingMode rounding, FPSR& fpsr);
|
|
|
|
} // namespace Dynarmic::FP
|