Skip to content

[libc][NFC] Remove FloatProperties #76508

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions libc/fuzzing/stdlib/strtofloat_fuzz.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,17 @@

#include "utils/MPFRWrapper/mpfr_inc.h"

using LIBC_NAMESPACE::fputil::FloatProperties;
using LIBC_NAMESPACE::fputil::FPBits;

// This function calculates the effective precision for a given float type and
// exponent. Subnormals have a lower effective precision since they don't
// necessarily use all of the bits of the mantissa.
template <typename F> inline constexpr int effective_precision(int exponent) {
const int full_precision = FloatProperties<F>::MANTISSA_PRECISION;
const int full_precision = FPBits<F>::MANTISSA_PRECISION;

// This is intended to be 0 when the exponent is the lowest normal and
// increase as the exponent's magnitude increases.
const int bits_below_normal =
(-exponent) - (FloatProperties<F>::EXP_BIAS - 1);
const int bits_below_normal = (-exponent) - (FPBits<F>::EXP_BIAS - 1);

// The precision should be the normal, full precision, minus the bits lost
// by this being a subnormal, minus one for the implicit leading one.
Expand Down
131 changes: 66 additions & 65 deletions libc/src/__support/FPUtil/FPBits.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,64 +39,66 @@ enum class FPEncoding {
X86_ExtendedPrecision,
};

template <FPType> struct FPBaseProperties {};
// Defines the layout (sign, exponent, significand) of a floating point type in
// memory. It also defines its associated StorageType, i.e., the unsigned
// integer type used to manipulate its representation.
template <FPType> struct FPLayout {};

template <> struct FPBaseProperties<FPType::IEEE754_Binary16> {
template <> struct FPLayout<FPType::IEEE754_Binary16> {
using StorageType = uint16_t;
LIBC_INLINE_VAR static constexpr int TOTAL_LEN = 16;
LIBC_INLINE_VAR static constexpr int SIG_LEN = 10;
LIBC_INLINE_VAR static constexpr int SIGN_LEN = 1;
LIBC_INLINE_VAR static constexpr int EXP_LEN = 5;
LIBC_INLINE_VAR static constexpr int SIG_LEN = 10;
LIBC_INLINE_VAR static constexpr auto ENCODING = FPEncoding::IEEE754;
};

template <> struct FPBaseProperties<FPType::IEEE754_Binary32> {
template <> struct FPLayout<FPType::IEEE754_Binary32> {
using StorageType = uint32_t;
LIBC_INLINE_VAR static constexpr int TOTAL_LEN = 32;
LIBC_INLINE_VAR static constexpr int SIG_LEN = 23;
LIBC_INLINE_VAR static constexpr int SIGN_LEN = 1;
LIBC_INLINE_VAR static constexpr int EXP_LEN = 8;
LIBC_INLINE_VAR static constexpr int SIG_LEN = 23;
LIBC_INLINE_VAR static constexpr auto ENCODING = FPEncoding::IEEE754;
};

template <> struct FPBaseProperties<FPType::IEEE754_Binary64> {
template <> struct FPLayout<FPType::IEEE754_Binary64> {
using StorageType = uint64_t;
LIBC_INLINE_VAR static constexpr int TOTAL_LEN = 64;
LIBC_INLINE_VAR static constexpr int SIG_LEN = 52;
LIBC_INLINE_VAR static constexpr int SIGN_LEN = 1;
LIBC_INLINE_VAR static constexpr int EXP_LEN = 11;
LIBC_INLINE_VAR static constexpr int SIG_LEN = 52;
LIBC_INLINE_VAR static constexpr auto ENCODING = FPEncoding::IEEE754;
};

template <> struct FPBaseProperties<FPType::IEEE754_Binary128> {
template <> struct FPLayout<FPType::IEEE754_Binary128> {
using StorageType = UInt128;
LIBC_INLINE_VAR static constexpr int TOTAL_LEN = 128;
LIBC_INLINE_VAR static constexpr int SIG_LEN = 112;
LIBC_INLINE_VAR static constexpr int SIGN_LEN = 1;
LIBC_INLINE_VAR static constexpr int EXP_LEN = 15;
LIBC_INLINE_VAR static constexpr int SIG_LEN = 112;
LIBC_INLINE_VAR static constexpr auto ENCODING = FPEncoding::IEEE754;
};

template <> struct FPBaseProperties<FPType::X86_Binary80> {
template <> struct FPLayout<FPType::X86_Binary80> {
using StorageType = UInt128;
LIBC_INLINE_VAR static constexpr int TOTAL_LEN = 80;
LIBC_INLINE_VAR static constexpr int SIG_LEN = 64;
LIBC_INLINE_VAR static constexpr int SIGN_LEN = 1;
LIBC_INLINE_VAR static constexpr int EXP_LEN = 15;
LIBC_INLINE_VAR static constexpr int SIG_LEN = 64;
LIBC_INLINE_VAR static constexpr auto ENCODING =
FPEncoding::X86_ExtendedPrecision;
};

} // namespace internal

// FPBaseMasksAndShifts derives useful constants from the FPLayout.
template <FPType fp_type>
struct FPProperties : public internal::FPBaseProperties<fp_type> {
struct FPBaseMasksAndShifts : public internal::FPLayout<fp_type> {
private:
using UP = internal::FPBaseProperties<fp_type>;
using UP = internal::FPLayout<fp_type>;

public:
// The number of bits to represent sign. For documentation purpose, always 1.
LIBC_INLINE_VAR static constexpr int SIGN_LEN = 1;
using UP::EXP_LEN; // The number of bits for the *exponent* part
using UP::SIG_LEN; // The number of bits for the *significand* part
using UP::TOTAL_LEN; // For convenience, the sum of `SIG_LEN`, `EXP_LEN`,
// and `SIGN_LEN`.
static_assert(SIGN_LEN + EXP_LEN + SIG_LEN == TOTAL_LEN);
using UP::EXP_LEN; // The number of bits for the *exponent* part
using UP::SIG_LEN; // The number of bits for the *significand* part
using UP::SIGN_LEN; // The number of bits for the *sign* part
// For convenience, the sum of `SIG_LEN`, `EXP_LEN`, and `SIGN_LEN`.
LIBC_INLINE_VAR static constexpr int TOTAL_LEN = SIGN_LEN + EXP_LEN + SIG_LEN;

// An unsigned integer that is wide enough to contain all of the floating
// point bits.
Expand Down Expand Up @@ -173,45 +175,12 @@ struct FPProperties : public internal::FPBaseProperties<fp_type> {
: bit_at(SIG_LEN - 2); // 0b0100...
};

//-----------------------------------------------------------------------------
template <typename FP> LIBC_INLINE static constexpr FPType get_fp_type() {
if constexpr (cpp::is_same_v<FP, float> && __FLT_MANT_DIG__ == 24)
return FPType::IEEE754_Binary32;
else if constexpr (cpp::is_same_v<FP, double> && __DBL_MANT_DIG__ == 53)
return FPType::IEEE754_Binary64;
else if constexpr (cpp::is_same_v<FP, long double>) {
if constexpr (__LDBL_MANT_DIG__ == 53)
return FPType::IEEE754_Binary64;
else if constexpr (__LDBL_MANT_DIG__ == 64)
return FPType::X86_Binary80;
else if constexpr (__LDBL_MANT_DIG__ == 113)
return FPType::IEEE754_Binary128;
}
#if defined(LIBC_COMPILER_HAS_C23_FLOAT16)
else if constexpr (cpp::is_same_v<FP, _Float16>)
return FPType::IEEE754_Binary16;
#endif
#if defined(LIBC_COMPILER_HAS_C23_FLOAT128)
else if constexpr (cpp::is_same_v<FP, _Float128>)
return FPType::IEEE754_Binary128;
#endif
#if defined(LIBC_COMPILER_HAS_FLOAT128_EXTENSION)
else if constexpr (cpp::is_same_v<FP, __float128>)
return FPType::IEEE754_Binary128;
#endif
else
static_assert(cpp::always_false<FP>, "Unsupported type");
}

template <typename FP>
struct FloatProperties : public FPProperties<get_fp_type<FP>()> {};

namespace internal {

// This is a temporary class to unify common methods and properties between
// FPBits and FPBits<long double>.
template <FPType fp_type> struct FPBitsCommon : private FPProperties<fp_type> {
using UP = FPProperties<fp_type>;
template <FPType fp_type> struct FPRep : private FPBaseMasksAndShifts<fp_type> {
using UP = FPBaseMasksAndShifts<fp_type>;
using typename UP::StorageType;
using UP::TOTAL_LEN;

Expand All @@ -227,15 +196,17 @@ template <FPType fp_type> struct FPBitsCommon : private FPProperties<fp_type> {
using UP::FP_MASK;
using UP::FRACTION_LEN;
using UP::FRACTION_MASK;
using UP::MANTISSA_PRECISION;
using UP::SIGN_MASK;
using UP::STORAGE_LEN;

// Reinterpreting bits as an integer value and interpreting the bits of an
// integer value as a floating point value is used in tests. So, a convenient
// type is provided for such reinterpretations.
StorageType bits;

LIBC_INLINE constexpr FPBitsCommon() : bits(0) {}
LIBC_INLINE explicit constexpr FPBitsCommon(StorageType bits) : bits(bits) {}
LIBC_INLINE constexpr FPRep() : bits(0) {}
LIBC_INLINE explicit constexpr FPRep(StorageType bits) : bits(bits) {}

LIBC_INLINE constexpr void set_mantissa(StorageType mantVal) {
mantVal &= FRACTION_MASK;
Expand Down Expand Up @@ -297,6 +268,37 @@ template <FPType fp_type> struct FPBitsCommon : private FPProperties<fp_type> {

} // namespace internal

// Returns the FPType corresponding to C++ type T on the host.
template <typename T> LIBC_INLINE static constexpr FPType get_fp_type() {
using UnqualT = cpp::remove_cv_t<T>;
if constexpr (cpp::is_same_v<UnqualT, float> && __FLT_MANT_DIG__ == 24)
return FPType::IEEE754_Binary32;
else if constexpr (cpp::is_same_v<UnqualT, double> && __DBL_MANT_DIG__ == 53)
return FPType::IEEE754_Binary64;
else if constexpr (cpp::is_same_v<UnqualT, long double>) {
if constexpr (__LDBL_MANT_DIG__ == 53)
return FPType::IEEE754_Binary64;
else if constexpr (__LDBL_MANT_DIG__ == 64)
return FPType::X86_Binary80;
else if constexpr (__LDBL_MANT_DIG__ == 113)
return FPType::IEEE754_Binary128;
}
#if defined(LIBC_COMPILER_HAS_C23_FLOAT16)
else if constexpr (cpp::is_same_v<UnqualT, _Float16>)
return FPType::IEEE754_Binary16;
#endif
#if defined(LIBC_COMPILER_HAS_C23_FLOAT128)
else if constexpr (cpp::is_same_v<UnqualT, _Float128>)
return FPType::IEEE754_Binary128;
#endif
#if defined(LIBC_COMPILER_HAS_FLOAT128_EXTENSION)
else if constexpr (cpp::is_same_v<UnqualT, __float128>)
return FPType::IEEE754_Binary128;
#endif
else
static_assert(cpp::always_false<UnqualT>, "Unsupported type");
}

// A generic class to represent single precision, double precision, and quad
// precision IEEE 754 floating point formats.
// On most platforms, the 'float' type corresponds to single precision floating
Expand All @@ -305,11 +307,10 @@ template <FPType fp_type> struct FPBitsCommon : private FPProperties<fp_type> {
// floating numbers. On x86 platforms however, the 'long double' type maps to
// an x87 floating point format. This format is an IEEE 754 extension format.
// It is handled as an explicit specialization of this class.
template <typename T>
struct FPBits : public internal::FPBitsCommon<get_fp_type<T>()> {
template <typename T> struct FPBits : public internal::FPRep<get_fp_type<T>()> {
static_assert(cpp::is_floating_point_v<T>,
"FPBits instantiated with invalid type.");
using UP = internal::FPBitsCommon<get_fp_type<T>()>;
using UP = internal::FPRep<get_fp_type<T>()>;
using StorageType = typename UP::StorageType;
using UP::bits;

Expand Down
6 changes: 3 additions & 3 deletions libc/src/__support/FPUtil/ManipulationFunctions.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,13 @@ LIBC_INLINE T nextafter(T from, U to) {
} else {
int_val = FPBits<T>::MIN_SUBNORMAL;
if (to_bits.get_sign())
int_val |= FloatProperties<T>::SIGN_MASK;
int_val |= FPBits<T>::SIGN_MASK;
}

StorageType exponent_bits = int_val & FloatProperties<T>::EXP_MASK;
StorageType exponent_bits = int_val & FPBits<T>::EXP_MASK;
if (exponent_bits == StorageType(0))
raise_except_if_required(FE_UNDERFLOW | FE_INEXACT);
else if (exponent_bits == FloatProperties<T>::EXP_MASK)
else if (exponent_bits == FPBits<T>::EXP_MASK)
raise_except_if_required(FE_OVERFLOW | FE_INEXACT);

return cpp::bit_cast<T>(int_val);
Expand Down
37 changes: 17 additions & 20 deletions libc/src/__support/FPUtil/dyadic_float.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ template <size_t Bits> struct DyadicFloat {

template <typename T, cpp::enable_if_t<cpp::is_floating_point_v<T>, int> = 0>
DyadicFloat(T x) {
static_assert(FloatProperties<T>::FRACTION_LEN < Bits);
static_assert(FPBits<T>::FRACTION_LEN < Bits);
FPBits<T> x_bits(x);
sign = x_bits.get_sign();
exponent = x_bits.get_exponent() - FloatProperties<T>::FRACTION_LEN;
exponent = x_bits.get_exponent() - FPBits<T>::FRACTION_LEN;
mantissa = MantissaType(x_bits.get_explicit_mantissa());
normalize();
}
Expand Down Expand Up @@ -83,21 +83,20 @@ template <size_t Bits> struct DyadicFloat {
// Output is rounded correctly with respect to the current rounding mode.
// TODO(lntue): Add support for underflow.
// TODO(lntue): Test or add specialization for x86 long double.
template <typename T, typename = cpp::enable_if_t<
cpp::is_floating_point_v<T> &&
(FloatProperties<T>::FRACTION_LEN < Bits),
void>>
template <typename T,
typename = cpp::enable_if_t<cpp::is_floating_point_v<T> &&
(FPBits<T>::FRACTION_LEN < Bits),
void>>
explicit operator T() const {
// TODO(lntue): Do we need to treat signed zeros properly?
if (mantissa.is_zero())
return 0.0;

// Assume that it is normalized, and output is also normal.
constexpr uint32_t PRECISION = FloatProperties<T>::MANTISSA_PRECISION;
constexpr uint32_t PRECISION = FPBits<T>::MANTISSA_PRECISION;
using output_bits_t = typename FPBits<T>::StorageType;

int exp_hi =
exponent + static_cast<int>((Bits - 1) + FloatProperties<T>::EXP_BIAS);
int exp_hi = exponent + static_cast<int>((Bits - 1) + FPBits<T>::EXP_BIAS);

bool denorm = false;
uint32_t shift = Bits - PRECISION;
Expand All @@ -106,7 +105,7 @@ template <size_t Bits> struct DyadicFloat {
denorm = true;
shift = (Bits - PRECISION) + static_cast<uint32_t>(1 - exp_hi);

exp_hi = FloatProperties<T>::EXP_BIAS;
exp_hi = FPBits<T>::EXP_BIAS;
}

int exp_lo = exp_hi - static_cast<int>(PRECISION) - 1;
Expand All @@ -115,7 +114,7 @@ template <size_t Bits> struct DyadicFloat {

T d_hi = FPBits<T>::create_value(sign, exp_hi,
static_cast<output_bits_t>(m_hi) &
FloatProperties<T>::FRACTION_MASK)
FPBits<T>::FRACTION_MASK)
.get_val();

const MantissaType round_mask = MantissaType(1) << (shift - 1);
Expand All @@ -129,15 +128,13 @@ template <size_t Bits> struct DyadicFloat {
if (LIBC_UNLIKELY(exp_lo <= 0)) {
// d_lo is denormal, but the output is normal.
int scale_up_exponent = 2 * PRECISION;
T scale_up_factor = FPBits<T>::create_value(sign,
FloatProperties<T>::EXP_BIAS +
scale_up_exponent,
output_bits_t(0))
.get_val();
T scale_up_factor =
FPBits<T>::create_value(sign, FPBits<T>::EXP_BIAS + scale_up_exponent,
output_bits_t(0))
.get_val();
T scale_down_factor =
FPBits<T>::create_value(
sign, FloatProperties<T>::EXP_BIAS - scale_up_exponent,
output_bits_t(0))
FPBits<T>::create_value(sign, FPBits<T>::EXP_BIAS - scale_up_exponent,
output_bits_t(0))
.get_val();

d_lo = FPBits<T>::create_value(sign, exp_lo + scale_up_exponent,
Expand All @@ -156,7 +153,7 @@ template <size_t Bits> struct DyadicFloat {
if (LIBC_UNLIKELY(denorm)) {
// Output is denormal, simply clear the exponent field.
output_bits_t clear_exp = output_bits_t(exp_hi)
<< FloatProperties<T>::FRACTION_LEN;
<< FPBits<T>::FRACTION_LEN;
output_bits_t r_bits = FPBits<T>(r).uintval() - clear_exp;
return FPBits<T>(r_bits).get_val();
}
Expand Down
7 changes: 3 additions & 4 deletions libc/src/__support/FPUtil/generic/FMA.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ LIBC_INLINE bool shift_mantissa(int shift_length, UInt128 &mant) {

template <> LIBC_INLINE double fma<double>(double x, double y, double z) {
using FPBits = fputil::FPBits<double>;
using FloatProp = fputil::FloatProperties<double>;

if (LIBC_UNLIKELY(x == 0 || y == 0 || z == 0)) {
return x * y + z;
Expand Down Expand Up @@ -267,10 +266,10 @@ template <> LIBC_INLINE double fma<double>(double x, double y, double z) {
}

// Remove hidden bit and append the exponent field and sign bit.
result = (result & FloatProp::FRACTION_MASK) |
(static_cast<uint64_t>(r_exp) << FloatProp::FRACTION_LEN);
result = (result & FPBits::FRACTION_MASK) |
(static_cast<uint64_t>(r_exp) << FPBits::FRACTION_LEN);
if (prod_sign) {
result |= FloatProp::SIGN_MASK;
result |= FPBits::SIGN_MASK;
}

// Rounding.
Expand Down
5 changes: 2 additions & 3 deletions libc/src/__support/FPUtil/x86_64/LongDoubleBits.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@ namespace LIBC_NAMESPACE {
namespace fputil {

template <>
struct FPBits<long double>
: public internal::FPBitsCommon<FPType::X86_Binary80> {
using UP = internal::FPBitsCommon<FPType::X86_Binary80>;
struct FPBits<long double> : public internal::FPRep<FPType::X86_Binary80> {
using UP = internal::FPRep<FPType::X86_Binary80>;
using StorageType = typename UP::StorageType;
using UP::bits;

Expand Down
Loading