dynarmic/src/common/fp/unpacked.cpp
Lioncash c9777ef997 common/fp/info: Make half-precision info struct functions return correctly sized types
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.
2020-04-22 21:01:45 +01:00

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