From d97b3198ce7d4460256574d09bbd3c267c4bfdbe Mon Sep 17 00:00:00 2001 From: Guillaume Chatelet Date: Mon, 15 Jan 2024 16:46:49 +0000 Subject: [PATCH 1/9] [libc][NFC] Refactor FPBits and remove LongDoubleBits specialization --- libc/src/__support/FPUtil/FPBits.h | 222 +++++++++++++----- .../__support/FPUtil/x86_64/LongDoubleBits.h | 205 ++++++++++------ .../test/src/__support/FPUtil/fpbits_test.cpp | 216 +++++++++++++++++ .../test/src/__support/FPUtil/BUILD.bazel | 42 ++++ 4 files changed, 548 insertions(+), 137 deletions(-) create mode 100644 utils/bazel/llvm-project-overlay/libc/test/src/__support/FPUtil/BUILD.bazel diff --git a/libc/src/__support/FPUtil/FPBits.h b/libc/src/__support/FPUtil/FPBits.h index 93e32ba7cc941..9067e37c52a6d 100644 --- a/libc/src/__support/FPUtil/FPBits.h +++ b/libc/src/__support/FPUtil/FPBits.h @@ -132,6 +132,70 @@ struct FPRepBase : public internal::FPLayout { static_assert((SIG_MASK & EXP_MASK & SIGN_MASK) == 0, "masks disjoint"); static_assert((SIG_MASK | EXP_MASK | SIGN_MASK) == FP_MASK, "masks cover"); +protected: + enum class Exp : int32_t { + ZERO = 0, + // The exponent value for denormal numbers. + SUBNORMAL = -EXP_BIAS, + // The minimum exponent value for normal numbers. + MIN = SUBNORMAL + 1, + // The maximum exponent value for normal numbers. + MAX = EXP_BIAS, + // Special value all ones. + INF = MAX + 1, + // Aliases + BITS_ALL_ZEROES = SUBNORMAL, + BITS_ALL_ONES = INF, + }; + + enum class Sig : StorageType { + ZERO = 0, + ONE = 1, + MSB = StorageType(1) << (SIG_LEN - 1), + // Aliases + BITS_ALL_ZEROES = ZERO, + BITS_ALL_ONES = SIG_MASK, + }; + + template + LIBC_INLINE static constexpr auto storage_cast(T value) { + return static_cast(value); + } + + LIBC_INLINE friend constexpr Sig operator|(const Sig a, const Sig b) { + return Sig{storage_cast(storage_cast(a) | storage_cast(b))}; + } + LIBC_INLINE friend constexpr Sig operator^(const Sig a, const Sig b) { + return Sig{storage_cast(storage_cast(a) ^ storage_cast(b))}; + } + LIBC_INLINE friend constexpr Sig operator>>(const Sig a, int shift) { + return Sig{storage_cast(storage_cast(a) >> shift)}; + } + + LIBC_INLINE static constexpr StorageType encode(Exp exp) { + return storage_cast(static_cast(exp) + EXP_BIAS) << SIG_LEN; + } + + LIBC_INLINE static constexpr StorageType encode(Sig value) { + return storage_cast(value) & SIG_MASK; + } + + LIBC_INLINE static constexpr StorageType encode(Exp exp, Sig sig) { + return encode(exp) | encode(sig); + } + + LIBC_INLINE static constexpr StorageType encode(bool sign, Exp exp, Sig sig) { + if (sign) + return SIGN_MASK | encode(exp, sig); + return encode(exp, sig); + } + + LIBC_INLINE constexpr StorageType exp_bits() const { return bits & EXP_MASK; } + LIBC_INLINE constexpr StorageType sig_bits() const { return bits & SIG_MASK; } + LIBC_INLINE constexpr StorageType exp_sig_bits() const { + return bits & EXP_SIG_MASK; + } + private: LIBC_INLINE static constexpr StorageType bit_at(int position) { return StorageType(1) << position; @@ -155,20 +219,6 @@ struct FPRepBase : public internal::FPLayout { LIBC_INLINE_VAR static constexpr StorageType FRACTION_MASK = mask_trailing_ones(); - // If a number x is a NAN, then it is a quiet NAN if: - // QUIET_NAN_MASK & bits(x) != 0 - LIBC_INLINE_VAR static constexpr StorageType QUIET_NAN_MASK = - fp_type == FPType::X86_Binary80 - ? bit_at(SIG_LEN - 1) | bit_at(SIG_LEN - 2) // 0b1100... - : bit_at(SIG_LEN - 1); // 0b1000... - - // Mask to generate a default signaling NAN. Any NAN that is not - // a quiet NAN is considered a signaling NAN. - LIBC_INLINE_VAR static constexpr StorageType DEFAULT_SIGNALING_NAN = - fp_type == FPType::X86_Binary80 - ? bit_at(SIG_LEN - 1) | bit_at(SIG_LEN - 3) // 0b1010... - : bit_at(SIG_LEN - 2); // 0b0100... - // The floating point number representation as an unsigned integer. StorageType bits = 0; @@ -220,6 +270,9 @@ struct FPRepBase : public internal::FPLayout { } LIBC_INLINE constexpr StorageType uintval() const { return bits & FP_MASK; } + LIBC_INLINE constexpr StorageType set_uintval(StorageType value) { + return bits = (value & FP_MASK); + } LIBC_INLINE constexpr bool is_zero() const { return (bits & EXP_SIG_MASK) == 0; @@ -241,6 +294,79 @@ template struct FPRep : public FPRepBase { using UP::FRACTION_LEN; using UP::FRACTION_MASK; using UP::MANTISSA_PRECISION; + +protected: + using typename UP::Exp; + using typename UP::Sig; + using UP::encode; + using UP::exp_bits; + using UP::exp_sig_bits; + using UP::sig_bits; + +public: + LIBC_INLINE constexpr bool is_nan() const { + return exp_sig_bits() > encode(Exp::BITS_ALL_ONES, Sig::BITS_ALL_ZEROES); + } + LIBC_INLINE constexpr bool is_quiet_nan() const { + return exp_sig_bits() >= encode(Exp::BITS_ALL_ONES, Sig::MSB); + } + LIBC_INLINE constexpr bool is_signaling_nan() const { + return is_nan() && !is_quiet_nan(); + } + LIBC_INLINE constexpr bool is_inf() const { + return exp_sig_bits() == encode(Exp::BITS_ALL_ONES, Sig::BITS_ALL_ZEROES); + } + LIBC_INLINE constexpr bool is_zero() const { + return exp_sig_bits() == encode(Exp::BITS_ALL_ZEROES, Sig::BITS_ALL_ZEROES); + } + LIBC_INLINE constexpr bool is_finite() const { + return exp_bits() != encode(Exp::BITS_ALL_ONES); + } + LIBC_INLINE + constexpr bool is_subnormal() const { + return exp_bits() == encode(Exp::BITS_ALL_ZEROES); + } + LIBC_INLINE constexpr bool is_normal() const { + return is_finite() && !is_subnormal(); + } + + LIBC_INLINE static constexpr StorageType zero(bool sign = false) { + return encode(sign, Exp::BITS_ALL_ZEROES, Sig::BITS_ALL_ZEROES); + } + LIBC_INLINE static constexpr StorageType one(bool sign = false) { + return encode(sign, Exp::ZERO, Sig::BITS_ALL_ZEROES); + } + LIBC_INLINE static constexpr StorageType min_subnormal(bool sign = false) { + return encode(sign, Exp::BITS_ALL_ZEROES, Sig::ONE); + } + LIBC_INLINE static constexpr StorageType max_subnormal(bool sign = false) { + return encode(sign, Exp::BITS_ALL_ZEROES, Sig::BITS_ALL_ONES); + } + LIBC_INLINE static constexpr StorageType min_normal(bool sign = false) { + return encode(sign, Exp::MIN, Sig::BITS_ALL_ZEROES); + } + LIBC_INLINE static constexpr StorageType max_normal(bool sign = false) { + return encode(sign, Exp::MAX, Sig::BITS_ALL_ONES); + } + LIBC_INLINE static constexpr StorageType inf(bool sign = false) { + return encode(sign, Exp::BITS_ALL_ONES, Sig::BITS_ALL_ZEROES); + } + LIBC_INLINE static constexpr StorageType build_nan(bool sign = false, + StorageType v = 0) { + return encode(sign, Exp::BITS_ALL_ONES, (v ? Sig{v} : (Sig::MSB >> 1))); + } + LIBC_INLINE static constexpr StorageType build_quiet_nan(bool sign = false, + StorageType v = 0) { + return encode(sign, Exp::BITS_ALL_ONES, Sig::MSB | Sig{v}); + } + + // The function return mantissa with the implicit bit set iff the current + // value is a valid normal number. + LIBC_INLINE constexpr StorageType get_explicit_mantissa() { + if (is_subnormal()) + return sig_bits(); + return (StorageType(1) << UP::SIG_LEN) | sig_bits(); + } }; } // namespace internal @@ -288,10 +414,10 @@ template struct FPBits : public internal::FPRep()> { static_assert(cpp::is_floating_point_v, "FPBits instantiated with invalid type."); using UP = internal::FPRep()>; + using Rep = UP; private: using UP::EXP_SIG_MASK; - using UP::QUIET_NAN_MASK; using UP::SIG_LEN; using UP::SIG_MASK; @@ -308,15 +434,15 @@ template struct FPBits : public internal::FPRep()> { using UP::TOTAL_LEN; using UP::UP; + using UP::encode; using UP::get_biased_exponent; using UP::is_zero; // Constants. static constexpr int MAX_BIASED_EXPONENT = (1 << EXP_LEN) - 1; - static constexpr StorageType MIN_SUBNORMAL = StorageType(1); - static constexpr StorageType MAX_SUBNORMAL = FRACTION_MASK; - static constexpr StorageType MIN_NORMAL = (StorageType(1) << FRACTION_LEN); - static constexpr StorageType MAX_NORMAL = - (StorageType(MAX_BIASED_EXPONENT - 1) << SIG_LEN) | SIG_MASK; + static constexpr StorageType MIN_NORMAL = UP::min_normal(false); + static constexpr StorageType MAX_NORMAL = UP::max_normal(false); + static constexpr StorageType MIN_SUBNORMAL = UP::min_subnormal(false); + static constexpr StorageType MAX_SUBNORMAL = UP::max_subnormal(false); // Constructors. LIBC_INLINE constexpr FPBits() = default; @@ -338,30 +464,7 @@ template struct FPBits : public internal::FPRep()> { LIBC_INLINE constexpr explicit operator T() const { return get_val(); } - // The function return mantissa with the implicit bit set iff the current - // value is a valid normal number. - LIBC_INLINE constexpr StorageType get_explicit_mantissa() { - return ((get_biased_exponent() > 0 && !is_inf_or_nan()) - ? (FRACTION_MASK + 1) - : 0) | - (FRACTION_MASK & bits); - } - - LIBC_INLINE constexpr bool is_inf() const { - return (bits & EXP_SIG_MASK) == EXP_MASK; - } - - LIBC_INLINE constexpr bool is_nan() const { - return (bits & EXP_SIG_MASK) > EXP_MASK; - } - - LIBC_INLINE constexpr bool is_quiet_nan() const { - return (bits & EXP_SIG_MASK) >= (EXP_MASK | QUIET_NAN_MASK); - } - - LIBC_INLINE constexpr bool is_inf_or_nan() const { - return (bits & EXP_MASK) == EXP_MASK; - } + LIBC_INLINE constexpr bool is_inf_or_nan() const { return !UP::is_finite(); } LIBC_INLINE constexpr FPBits abs() const { return FPBits(bits & EXP_SIG_MASK); @@ -370,56 +473,45 @@ template struct FPBits : public internal::FPRep()> { // Methods below this are used by tests. LIBC_INLINE static constexpr T zero(bool sign = false) { - StorageType rep = (sign ? SIGN_MASK : StorageType(0)) // sign - | 0 // exponent - | 0; // mantissa - return FPBits(rep).get_val(); + return FPBits(UP::zero(sign)).get_val(); } LIBC_INLINE static constexpr T neg_zero() { return zero(true); } LIBC_INLINE static constexpr T inf(bool sign = false) { - StorageType rep = (sign ? SIGN_MASK : StorageType(0)) // sign - | EXP_MASK // exponent - | 0; // mantissa - return FPBits(rep).get_val(); + return FPBits(UP::inf(sign)).get_val(); } LIBC_INLINE static constexpr T neg_inf() { return inf(true); } LIBC_INLINE static constexpr T min_normal() { - return FPBits(MIN_NORMAL).get_val(); + return FPBits(UP::min_normal(false)).get_val(); } LIBC_INLINE static constexpr T max_normal() { - return FPBits(MAX_NORMAL).get_val(); + return FPBits(UP::max_normal(false)).get_val(); } LIBC_INLINE static constexpr T min_denormal() { - return FPBits(MIN_SUBNORMAL).get_val(); + return FPBits(UP::min_subnormal(false)).get_val(); } LIBC_INLINE static constexpr T max_denormal() { - return FPBits(MAX_SUBNORMAL).get_val(); + return FPBits(UP::max_subnormal(false)).get_val(); } LIBC_INLINE static constexpr T build_nan(StorageType v) { - StorageType rep = 0 // sign - | EXP_MASK // exponent - | (v & FRACTION_MASK); // mantissa - return FPBits(rep).get_val(); + return FPBits(UP::build_nan(false, v)).get_val(); } LIBC_INLINE static constexpr T build_quiet_nan(StorageType v) { - return build_nan(QUIET_NAN_MASK | v); + return FPBits(UP::build_quiet_nan(false, v)).get_val(); } LIBC_INLINE static constexpr FPBits create_value(bool sign, StorageType biased_exp, StorageType mantissa) { - StorageType rep = (sign ? SIGN_MASK : StorageType(0)) // sign - | ((biased_exp << EXP_MASK_SHIFT) & EXP_MASK) // exponent - | (mantissa & FRACTION_MASK); // mantissa - return FPBits(rep); + return FPBits(encode(sign, typename UP::Exp(biased_exp - UP::EXP_BIAS), + typename UP::Sig(mantissa))); } // The function convert integer number and unbiased exponent to proper float diff --git a/libc/src/__support/FPUtil/x86_64/LongDoubleBits.h b/libc/src/__support/FPUtil/x86_64/LongDoubleBits.h index c18abcee77ea5..34e685d3f888a 100644 --- a/libc/src/__support/FPUtil/x86_64/LongDoubleBits.h +++ b/libc/src/__support/FPUtil/x86_64/LongDoubleBits.h @@ -26,19 +26,22 @@ namespace LIBC_NAMESPACE { namespace fputil { -template <> -struct FPBits : public internal::FPRep { - using UP = internal::FPRep; - using StorageType = typename UP::StorageType; +namespace internal { -private: - using UP::bits; - using UP::EXP_SIG_MASK; - using UP::QUIET_NAN_MASK; +template <> +struct FPRep : public FPRepBase { + using UP = FPRepBase; + using typename UP::StorageType; + using UP::FRACTION_LEN; + using UP::FRACTION_MASK; + using UP::MANTISSA_PRECISION; + +protected: + using typename UP::Exp; + using typename UP::Sig; + using UP::encode; public: - // Constants. - static constexpr int MAX_BIASED_EXPONENT = (1 << EXP_LEN) - 1; // The x86 80 bit float represents the leading digit of the mantissa // explicitly. This is the mask for that bit. static constexpr StorageType EXPLICIT_BIT_MASK = StorageType(1) @@ -49,13 +52,117 @@ struct FPBits : public internal::FPRep { static_assert((EXPLICIT_BIT_MASK | FRACTION_MASK) == SIG_MASK, "the explicit bit and the fractional part should cover the " "whole significand"); - static constexpr StorageType MIN_SUBNORMAL = StorageType(1); - // Subnormal numbers include the implicit bit in x86 long double formats. - static constexpr StorageType MAX_SUBNORMAL = FRACTION_MASK; - static constexpr StorageType MIN_NORMAL = - (StorageType(1) << SIG_LEN) | EXPLICIT_BIT_MASK; - static constexpr StorageType MAX_NORMAL = - (StorageType(MAX_BIASED_EXPONENT - 1) << SIG_LEN) | SIG_MASK; + + LIBC_INLINE constexpr bool is_nan() const { + // Most encoding forms from the table found in + // https://en.wikipedia.org/wiki/Extended_precision#x86_extended_precision_format + // are interpreted as NaN. + // More precisely : + // - Pseudo-Infinity + // - Pseudo Not a Number + // - Signalling Not a Number + // - Floating-point Indefinite + // - Quiet Not a Number + // - Unnormal + // This can be reduced to the following logic: + if (exp_bits() == encode(Exp::BITS_ALL_ONES)) + return !is_inf(); + if (exp_bits() != encode(Exp::BITS_ALL_ZEROES)) + return (sig_bits() & encode(Sig::MSB)) == 0; + return false; + } + LIBC_INLINE constexpr bool is_quiet_nan() const { + return exp_sig_bits() >= + encode(Exp::BITS_ALL_ONES, Sig::MSB | (Sig::MSB >> 1)); + } + LIBC_INLINE constexpr bool is_signaling_nan() const { + return is_nan() && !is_quiet_nan(); + } + LIBC_INLINE constexpr bool is_inf() const { + return exp_sig_bits() == encode(Exp::BITS_ALL_ONES, Sig::MSB); + } + LIBC_INLINE constexpr bool is_zero() const { + return exp_sig_bits() == encode(Exp::BITS_ALL_ZEROES, Sig::BITS_ALL_ZEROES); + } + LIBC_INLINE constexpr bool is_finite() const { + return !is_inf() && !is_nan(); + } + LIBC_INLINE + constexpr bool is_subnormal() const { + return exp_sig_bits() > encode(Exp::BITS_ALL_ZEROES, Sig::BITS_ALL_ZEROES); + } + LIBC_INLINE constexpr bool is_normal() const { + const auto exp = exp_bits(); + if (exp == encode(Exp::BITS_ALL_ZEROES) || + exp == encode(Exp::BITS_ALL_ONES)) + return false; + return get_implicit_bit(); + } + + LIBC_INLINE static constexpr StorageType zero(bool sign = false) { + return encode(sign, Exp::BITS_ALL_ZEROES, Sig::BITS_ALL_ZEROES); + } + LIBC_INLINE static constexpr StorageType one(bool sign = false) { + return encode(sign, Exp::ZERO, Sig::MSB); + } + LIBC_INLINE static constexpr StorageType min_subnormal(bool sign = false) { + return encode(sign, Exp::BITS_ALL_ZEROES, Sig::ONE); + } + LIBC_INLINE static constexpr StorageType max_subnormal(bool sign = false) { + return encode(sign, Exp::BITS_ALL_ZEROES, Sig::BITS_ALL_ONES ^ Sig::MSB); + } + LIBC_INLINE static constexpr StorageType min_normal(bool sign = false) { + return encode(sign, Exp::MIN, Sig::MSB); + } + LIBC_INLINE static constexpr StorageType max_normal(bool sign = false) { + return encode(sign, Exp::MAX, Sig::BITS_ALL_ONES); + } + LIBC_INLINE static constexpr StorageType inf(bool sign = false) { + return encode(sign, Exp::BITS_ALL_ONES, Sig::MSB); + } + LIBC_INLINE static constexpr StorageType build_nan(bool sign = false, + StorageType v = 0) { + return encode(sign, Exp::BITS_ALL_ONES, + Sig::MSB | (v ? Sig{v} : (Sig::MSB >> 2))); + } + LIBC_INLINE static constexpr StorageType build_quiet_nan(bool sign = false, + StorageType v = 0) { + return encode(sign, Exp::BITS_ALL_ONES, + Sig::MSB | (Sig::MSB >> 1) | Sig{v}); + } + + LIBC_INLINE constexpr StorageType get_explicit_mantissa() const { + return sig_bits(); + } + + LIBC_INLINE constexpr bool get_implicit_bit() const { + return bits & EXPLICIT_BIT_MASK; + } + + LIBC_INLINE constexpr void set_implicit_bit(bool implicitVal) { + if (get_implicit_bit() != implicitVal) + bits ^= EXPLICIT_BIT_MASK; + } +}; +} // namespace internal + +template <> +struct FPBits : public internal::FPRep { + using UP = internal::FPRep; + using Rep = UP; + using StorageType = typename UP::StorageType; + +private: + using UP::bits; + using UP::EXP_SIG_MASK; + +public: + // Constants. + static constexpr int MAX_BIASED_EXPONENT = (1 << EXP_LEN) - 1; + static constexpr StorageType MIN_NORMAL = UP::min_normal(false); + static constexpr StorageType MAX_NORMAL = UP::max_normal(false); + static constexpr StorageType MIN_SUBNORMAL = UP::min_subnormal(false); + static constexpr StorageType MAX_SUBNORMAL = UP::max_subnormal(false); // Constructors. LIBC_INLINE constexpr FPBits() = default; @@ -82,90 +189,44 @@ struct FPBits : public internal::FPRep { return cpp::bit_cast(bits); } - LIBC_INLINE constexpr StorageType get_explicit_mantissa() const { - return bits & SIG_MASK; - } - - LIBC_INLINE constexpr bool get_implicit_bit() const { - return bits & EXPLICIT_BIT_MASK; - } - - LIBC_INLINE constexpr void set_implicit_bit(bool implicitVal) { - if (get_implicit_bit() != implicitVal) - bits ^= EXPLICIT_BIT_MASK; - } - - LIBC_INLINE constexpr bool is_inf() const { - return get_biased_exponent() == MAX_BIASED_EXPONENT && - get_mantissa() == 0 && get_implicit_bit() == 1; - } - - LIBC_INLINE constexpr bool is_nan() const { - if (get_biased_exponent() == MAX_BIASED_EXPONENT) { - return (get_implicit_bit() == 0) || get_mantissa() != 0; - } else if (get_biased_exponent() != 0) { - return get_implicit_bit() == 0; - } - return false; - } - - LIBC_INLINE constexpr bool is_inf_or_nan() const { - return (get_biased_exponent() == MAX_BIASED_EXPONENT) || - (get_biased_exponent() != 0 && get_implicit_bit() == 0); - } - - LIBC_INLINE constexpr bool is_quiet_nan() const { - return (bits & EXP_SIG_MASK) >= (EXP_MASK | QUIET_NAN_MASK); - } + LIBC_INLINE constexpr bool is_inf_or_nan() const { return !UP::is_finite(); } // Methods below this are used by tests. LIBC_INLINE static constexpr long double zero(bool sign = false) { - StorageType rep = (sign ? SIGN_MASK : StorageType(0)) // sign - | 0 // exponent - | 0 // explicit bit - | 0; // mantissa - return FPBits(rep).get_val(); + return FPBits(UP::zero(sign)).get_val(); } LIBC_INLINE static constexpr long double neg_zero() { return zero(true); } LIBC_INLINE static constexpr long double inf(bool sign = false) { - StorageType rep = (sign ? SIGN_MASK : StorageType(0)) // sign - | EXP_MASK // exponent - | EXPLICIT_BIT_MASK // explicit bit - | 0; // mantissa - return FPBits(rep).get_val(); + return FPBits(UP::inf(sign)).get_val(); } LIBC_INLINE static constexpr long double neg_inf() { return inf(true); } LIBC_INLINE static constexpr long double min_normal() { - return FPBits(MIN_NORMAL).get_val(); + return FPBits(UP::min_normal(false)).get_val(); } LIBC_INLINE static constexpr long double max_normal() { - return FPBits(MAX_NORMAL).get_val(); + return FPBits(UP::max_normal(false)).get_val(); } LIBC_INLINE static constexpr long double min_denormal() { - return FPBits(MIN_SUBNORMAL).get_val(); + return FPBits(UP::min_subnormal(false)).get_val(); } LIBC_INLINE static constexpr long double max_denormal() { - return FPBits(MAX_SUBNORMAL).get_val(); + return FPBits(UP::max_subnormal(false)).get_val(); } LIBC_INLINE static constexpr long double build_nan(StorageType v) { - StorageType rep = 0 // sign - | EXP_MASK // exponent - | EXPLICIT_BIT_MASK // explicit bit - | (v & FRACTION_MASK); // mantissa - return FPBits(rep).get_val(); + return FPBits(UP::build_nan(false, v)).get_val(); } LIBC_INLINE static constexpr long double build_quiet_nan(StorageType v) { - return build_nan(QUIET_NAN_MASK | v); + return FPBits(UP::build_quiet_nan(false, v)).get_val(); } }; diff --git a/libc/test/src/__support/FPUtil/fpbits_test.cpp b/libc/test/src/__support/FPUtil/fpbits_test.cpp index e2dbe248ef213..8a5b3c7370ef9 100644 --- a/libc/test/src/__support/FPUtil/fpbits_test.cpp +++ b/libc/test/src/__support/FPUtil/fpbits_test.cpp @@ -12,6 +12,222 @@ using LIBC_NAMESPACE::fputil::FPBits; +TEST(LlvmLibcFPBitsTest, FPType_IEEE754_Binary16) { + using LIBC_NAMESPACE::fputil::FPType; + using LIBC_NAMESPACE::fputil::internal::FPRep; + using Rep = FPRep; + using u16 = uint16_t; + + EXPECT_EQ(u16(0b0'00000'0000000000), Rep::zero()); + EXPECT_EQ(u16(0b0'01111'0000000000), Rep::one()); + EXPECT_EQ(u16(0b0'00000'0000000001), Rep::min_subnormal()); + EXPECT_EQ(u16(0b0'00000'1111111111), Rep::max_subnormal()); + EXPECT_EQ(u16(0b0'00001'0000000000), Rep::min_normal()); + EXPECT_EQ(u16(0b0'11110'1111111111), Rep::max_normal()); + EXPECT_EQ(u16(0b0'11111'0000000000), Rep::inf()); + EXPECT_EQ(u16(0b0'11111'0100000000), Rep::build_nan()); + EXPECT_EQ(u16(0b0'11111'1000000000), Rep::build_quiet_nan()); +} + +TEST(LlvmLibcFPBitsTest, FPType_IEEE754_Binary32) { + using LIBC_NAMESPACE::fputil::FPType; + using LIBC_NAMESPACE::fputil::internal::FPRep; + using Rep = FPRep; + using u32 = uint32_t; + + EXPECT_EQ(u32(0b0'00000000'00000000000000000000000), Rep::zero()); + EXPECT_EQ(u32(0b0'01111111'00000000000000000000000), Rep::one()); + EXPECT_EQ(u32(0b0'00000000'00000000000000000000001), Rep::min_subnormal()); + EXPECT_EQ(u32(0b0'00000000'11111111111111111111111), Rep::max_subnormal()); + EXPECT_EQ(u32(0b0'00000001'00000000000000000000000), Rep::min_normal()); + EXPECT_EQ(u32(0b0'11111110'11111111111111111111111), Rep::max_normal()); + EXPECT_EQ(u32(0b0'11111111'00000000000000000000000), Rep::inf()); + EXPECT_EQ(u32(0b0'11111111'01000000000000000000000), Rep::build_nan()); + EXPECT_EQ(u32(0b0'11111111'10000000000000000000000), Rep::build_quiet_nan()); +} + +TEST(LlvmLibcFPBitsTest, FPType_IEEE754_Binary64) { + using LIBC_NAMESPACE::fputil::FPType; + using LIBC_NAMESPACE::fputil::internal::FPRep; + using Rep = FPRep; + using u64 = uint64_t; + + EXPECT_EQ( + u64(0b0'00000000000'0000000000000000000000000000000000000000000000000000), + Rep::zero()); + EXPECT_EQ( + u64(0b0'01111111111'0000000000000000000000000000000000000000000000000000), + Rep::one()); + EXPECT_EQ( + u64(0b0'00000000000'0000000000000000000000000000000000000000000000000001), + Rep::min_subnormal()); + EXPECT_EQ( + u64(0b0'00000000000'1111111111111111111111111111111111111111111111111111), + Rep::max_subnormal()); + EXPECT_EQ( + u64(0b0'00000000001'0000000000000000000000000000000000000000000000000000), + Rep::min_normal()); + EXPECT_EQ( + u64(0b0'11111111110'1111111111111111111111111111111111111111111111111111), + Rep::max_normal()); + EXPECT_EQ( + u64(0b0'11111111111'0000000000000000000000000000000000000000000000000000), + Rep::inf()); + EXPECT_EQ( + u64(0b0'11111111111'0100000000000000000000000000000000000000000000000000), + Rep::build_nan()); + EXPECT_EQ( + u64(0b0'11111111111'1000000000000000000000000000000000000000000000000000), + Rep::build_quiet_nan()); +} + +static constexpr UInt128 u128(uint64_t hi, uint64_t lo) { +#if defined(__SIZEOF_INT128__) + return __uint128_t(hi) << 64 | __uint128_t(lo); +#else + return UInt128({hi, lo}); +#endif +} + +TEST(LlvmLibcFPBitsTest, FPType_X86_Binary80) { + using LIBC_NAMESPACE::fputil::FPType; + using LIBC_NAMESPACE::fputil::internal::FPRep; + using Rep = FPRep; + + EXPECT_EQ( + u128(0b0'000000000000000, + 0b0000000000000000000000000000000000000000000000000000000000000000), + Rep::zero()); + EXPECT_EQ( + u128(0b0'011111111111111, + 0b1000000000000000000000000000000000000000000000000000000000000000), + Rep::one()); + EXPECT_EQ( + u128(0b0'000000000000000, + 0b0000000000000000000000000000000000000000000000000000000000000001), + Rep::min_subnormal()); + EXPECT_EQ( + u128(0b0'000000000000000, + 0b0111111111111111111111111111111111111111111111111111111111111111), + Rep::max_subnormal()); + EXPECT_EQ( + u128(0b0'000000000000001, + 0b1000000000000000000000000000000000000000000000000000000000000000), + Rep::min_normal()); + EXPECT_EQ( + u128(0b0'111111111111110, + 0b1111111111111111111111111111111111111111111111111111111111111111), + Rep::max_normal()); + EXPECT_EQ( + u128(0b0'111111111111111, + 0b1000000000000000000000000000000000000000000000000000000000000000), + Rep::inf()); + EXPECT_EQ( + u128(0b0'111111111111111, + 0b1010000000000000000000000000000000000000000000000000000000000000), + Rep::build_nan()); + EXPECT_EQ( + u128(0b0'111111111111111, + 0b1100000000000000000000000000000000000000000000000000000000000000), + Rep::build_quiet_nan()); +} + +TEST(LlvmLibcFPBitsTest, FPType_X86_Binary80_IsNan) { + using LIBC_NAMESPACE::fputil::FPType; + using LIBC_NAMESPACE::fputil::internal::FPRep; + using Rep = FPRep; + + const auto is_nan = [](uint64_t hi, uint64_t lo) { + Rep rep; + rep.set_uintval(u128(hi, lo)); + return rep.is_nan(); + }; + + EXPECT_TRUE(is_nan( + 0b0'111111111111111, // NAN : Pseudo-Infinity + 0b0000000000000000000000000000000000000000000000000000000000000000)); + EXPECT_TRUE(is_nan( + 0b0'111111111111111, // NAN : Pseudo-Infinity + 0b0000000000000000000000000000000000000000000000000000000000000000)); + EXPECT_TRUE(is_nan( + 0b0'111111111111111, // NAN : Pseudo Not a Number + 0b0000000000000000000000000000000000000000000000000000000000000001)); + EXPECT_TRUE(is_nan( + 0b0'111111111111111, // NAN : Pseudo Not a Number + 0b0100000000000000000000000000000000000000000000000000000000000000)); + EXPECT_TRUE(is_nan( + 0b0'111111111111111, // NAN : Signalling Not a Number + 0b1000000000000000000000000000000000000000000000000000000000000001)); + EXPECT_TRUE(is_nan( + 0b0'111111111111111, // NAN : Floating-point Indefinite + 0b1100000000000000000000000000000000000000000000000000000000000000)); + EXPECT_TRUE(is_nan( + 0b0'111111111111111, // NAN : Quiet Not a Number + 0b1100000000000000000000000000000000000000000000000000000000000001)); + EXPECT_TRUE(is_nan( + 0b0'111111111111110, // NAN : Unnormal + 0b0000000000000000000000000000000000000000000000000000000000000000)); + + EXPECT_FALSE(is_nan( + 0b0'000000000000000, // Zero + 0b0000000000000000000000000000000000000000000000000000000000000000)); + EXPECT_FALSE(is_nan( + 0b0'000000000000000, // Subnormal + 0b0000000000000000000000000000000000000000000000000000000000000001)); + EXPECT_FALSE(is_nan( + 0b0'000000000000000, // Pseudo Denormal + 0b1000000000000000000000000000000000000000000000000000000000000001)); + EXPECT_FALSE(is_nan( + 0b0'111111111111111, // Infinity + 0b1000000000000000000000000000000000000000000000000000000000000000)); + EXPECT_FALSE(is_nan( + 0b0'111111111111110, // Normalized + 0b1000000000000000000000000000000000000000000000000000000000000000)); +} + +TEST(LlvmLibcFPBitsTest, FPType_IEEE754_Binary128) { + using LIBC_NAMESPACE::fputil::FPType; + using LIBC_NAMESPACE::fputil::internal::FPRep; + using Rep = FPRep; + + EXPECT_EQ( + u128(0b0'000000000000000'000000000000000000000000000000000000000000000000, + 0b0000000000000000000000000000000000000000000000000000000000000000), + Rep::zero()); + EXPECT_EQ( + u128(0b0'011111111111111'000000000000000000000000000000000000000000000000, + 0b0000000000000000000000000000000000000000000000000000000000000000), + Rep::one()); + EXPECT_EQ( + u128(0b0'000000000000000'000000000000000000000000000000000000000000000000, + 0b0000000000000000000000000000000000000000000000000000000000000001), + Rep::min_subnormal()); + EXPECT_EQ( + u128(0b0'000000000000000'111111111111111111111111111111111111111111111111, + 0b1111111111111111111111111111111111111111111111111111111111111111), + Rep::max_subnormal()); + EXPECT_EQ( + u128(0b0'000000000000001'000000000000000000000000000000000000000000000000, + 0b0000000000000000000000000000000000000000000000000000000000000000), + Rep::min_normal()); + EXPECT_EQ( + u128(0b0'111111111111110'111111111111111111111111111111111111111111111111, + 0b1111111111111111111111111111111111111111111111111111111111111111), + Rep::max_normal()); + EXPECT_EQ( + u128(0b0'111111111111111'000000000000000000000000000000000000000000000000, + 0b0000000000000000000000000000000000000000000000000000000000000000), + Rep::inf()); + EXPECT_EQ( + u128(0b0'111111111111111'010000000000000000000000000000000000000000000000, + 0b0000000000000000000000000000000000000000000000000000000000000000), + Rep::build_nan()); + EXPECT_EQ( + u128(0b0'111111111111111'100000000000000000000000000000000000000000000000, + 0b0000000000000000000000000000000000000000000000000000000000000000), + Rep::build_quiet_nan()); +} + TEST(LlvmLibcFPBitsTest, FloatType) { using FloatBits = FPBits; diff --git a/utils/bazel/llvm-project-overlay/libc/test/src/__support/FPUtil/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/test/src/__support/FPUtil/BUILD.bazel new file mode 100644 index 0000000000000..4f206b21e478b --- /dev/null +++ b/utils/bazel/llvm-project-overlay/libc/test/src/__support/FPUtil/BUILD.bazel @@ -0,0 +1,42 @@ +# This file is licensed under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +# Tests for LLVM libc __support functions. + +load("//libc/test:libc_test_rules.bzl", "libc_test") + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +libc_test( + name = "fpbits_test", + srcs = ["fpbits_test.cpp"], + deps = [ + "//libc:__support_fputil_fp_bits", + "//libc:__support_fputil_fpbits_str", + ], +) + +libc_test( + name = "dyadic_float_test", + srcs = ["dyadic_float_test.cpp"], + deps = [ + "//libc:__support_fputil_dyadic_float", + "//libc:__support_uint", + "//libc:__support_uint128", + "//libc/test/UnitTest:fp_test_helpers", + "//libc/utils/MPFRWrapper:mpfr_wrapper", + ], +) + +libc_test( + name = "rounding_mode_test", + srcs = ["rounding_mode_test.cpp"], + deps = [ + "//libc:__support_fputil_rounding_mode", + "//libc:__support_uint128", + "//libc/utils/MPFRWrapper:mpfr_wrapper", + ], +) From e9b580c29719c71d7ed21b621d96460892ec8675 Mon Sep 17 00:00:00 2001 From: Guillaume Chatelet Date: Mon, 15 Jan 2024 17:01:39 +0000 Subject: [PATCH 2/9] Actually remove LongDoubleBits --- libc/src/__support/FPUtil/FPBits.h | 158 ++++++++++-- .../FPUtil/generic/sqrt_80_bit_long_double.h | 2 +- .../__support/FPUtil/x86_64/LongDoubleBits.h | 240 ------------------ .../FPUtil/x86_64/NextAfterLongDouble.h | 8 +- .../utils/FPUtil/x86_long_double_test.cpp | 12 +- .../llvm-project-overlay/libc/BUILD.bazel | 1 - 6 files changed, 141 insertions(+), 280 deletions(-) delete mode 100644 libc/src/__support/FPUtil/x86_64/LongDoubleBits.h diff --git a/libc/src/__support/FPUtil/FPBits.h b/libc/src/__support/FPUtil/FPBits.h index 9067e37c52a6d..58d63a7d01fb8 100644 --- a/libc/src/__support/FPUtil/FPBits.h +++ b/libc/src/__support/FPUtil/FPBits.h @@ -369,6 +369,126 @@ template struct FPRep : public FPRepBase { } }; +// Specialization for the X86 Extended Precision type. +template <> +struct FPRep : public FPRepBase { + using UP = FPRepBase; + using typename UP::StorageType; + using UP::FRACTION_LEN; + using UP::FRACTION_MASK; + using UP::MANTISSA_PRECISION; + +protected: + using typename UP::Exp; + using typename UP::Sig; + using UP::encode; + +public: + // The x86 80 bit float represents the leading digit of the mantissa + // explicitly. This is the mask for that bit. + static constexpr StorageType EXPLICIT_BIT_MASK = StorageType(1) + << FRACTION_LEN; + // The X80 significand is made of an explicit bit and the fractional part. + static_assert((EXPLICIT_BIT_MASK & FRACTION_MASK) == 0, + "the explicit bit and the fractional part should not overlap"); + static_assert((EXPLICIT_BIT_MASK | FRACTION_MASK) == SIG_MASK, + "the explicit bit and the fractional part should cover the " + "whole significand"); + + LIBC_INLINE constexpr bool is_nan() const { + // Most encoding forms from the table found in + // https://en.wikipedia.org/wiki/Extended_precision#x86_extended_precision_format + // are interpreted as NaN. + // More precisely : + // - Pseudo-Infinity + // - Pseudo Not a Number + // - Signalling Not a Number + // - Floating-point Indefinite + // - Quiet Not a Number + // - Unnormal + // This can be reduced to the following logic: + if (exp_bits() == encode(Exp::BITS_ALL_ONES)) + return !is_inf(); + if (exp_bits() != encode(Exp::BITS_ALL_ZEROES)) + return (sig_bits() & encode(Sig::MSB)) == 0; + return false; + } + LIBC_INLINE constexpr bool is_quiet_nan() const { + return exp_sig_bits() >= + encode(Exp::BITS_ALL_ONES, Sig::MSB | (Sig::MSB >> 1)); + } + LIBC_INLINE constexpr bool is_signaling_nan() const { + return is_nan() && !is_quiet_nan(); + } + LIBC_INLINE constexpr bool is_inf() const { + return exp_sig_bits() == encode(Exp::BITS_ALL_ONES, Sig::MSB); + } + LIBC_INLINE constexpr bool is_zero() const { + return exp_sig_bits() == encode(Exp::BITS_ALL_ZEROES, Sig::BITS_ALL_ZEROES); + } + LIBC_INLINE constexpr bool is_finite() const { + return !is_inf() && !is_nan(); + } + LIBC_INLINE + constexpr bool is_subnormal() const { + return exp_sig_bits() > encode(Exp::BITS_ALL_ZEROES, Sig::BITS_ALL_ZEROES); + } + LIBC_INLINE constexpr bool is_normal() const { + const auto exp = exp_bits(); + if (exp == encode(Exp::BITS_ALL_ZEROES) || + exp == encode(Exp::BITS_ALL_ONES)) + return false; + return get_implicit_bit(); + } + + LIBC_INLINE static constexpr StorageType zero(bool sign = false) { + return encode(sign, Exp::BITS_ALL_ZEROES, Sig::BITS_ALL_ZEROES); + } + LIBC_INLINE static constexpr StorageType one(bool sign = false) { + return encode(sign, Exp::ZERO, Sig::MSB); + } + LIBC_INLINE static constexpr StorageType min_subnormal(bool sign = false) { + return encode(sign, Exp::BITS_ALL_ZEROES, Sig::ONE); + } + LIBC_INLINE static constexpr StorageType max_subnormal(bool sign = false) { + return encode(sign, Exp::BITS_ALL_ZEROES, Sig::BITS_ALL_ONES ^ Sig::MSB); + } + LIBC_INLINE static constexpr StorageType min_normal(bool sign = false) { + return encode(sign, Exp::MIN, Sig::MSB); + } + LIBC_INLINE static constexpr StorageType max_normal(bool sign = false) { + return encode(sign, Exp::MAX, Sig::BITS_ALL_ONES); + } + LIBC_INLINE static constexpr StorageType inf(bool sign = false) { + return encode(sign, Exp::BITS_ALL_ONES, Sig::MSB); + } + LIBC_INLINE static constexpr StorageType build_nan(bool sign = false, + StorageType v = 0) { + return encode(sign, Exp::BITS_ALL_ONES, + Sig::MSB | (v ? Sig{v} : (Sig::MSB >> 2))); + } + LIBC_INLINE static constexpr StorageType build_quiet_nan(bool sign = false, + StorageType v = 0) { + return encode(sign, Exp::BITS_ALL_ONES, + Sig::MSB | (Sig::MSB >> 1) | Sig{v}); + } + + LIBC_INLINE constexpr StorageType get_explicit_mantissa() const { + return sig_bits(); + } + + // The following functions are specific to this implementation. + // TODO: Remove if possible. + LIBC_INLINE constexpr bool get_implicit_bit() const { + return bits & EXPLICIT_BIT_MASK; + } + + LIBC_INLINE constexpr void set_implicit_bit(bool implicitVal) { + if (get_implicit_bit() != implicitVal) + bits ^= EXPLICIT_BIT_MASK; + } +}; + } // namespace internal // Returns the FPType corresponding to C++ type T on the host. @@ -402,41 +522,23 @@ template LIBC_INLINE static constexpr FPType get_fp_type() { static_assert(cpp::always_false, "Unsupported type"); } -// A generic class to represent single precision, double precision, and quad -// precision IEEE 754 floating point formats. +// A generic class to represent floating point formats. // On most platforms, the 'float' type corresponds to single precision floating // point numbers, the 'double' type corresponds to double precision floating // point numers, and the 'long double' type corresponds to the quad precision // 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. +// an x87 floating point format. template struct FPBits : public internal::FPRep()> { static_assert(cpp::is_floating_point_v, "FPBits instantiated with invalid type."); using UP = internal::FPRep()>; using Rep = UP; - -private: - using UP::EXP_SIG_MASK; - using UP::SIG_LEN; - using UP::SIG_MASK; - -public: using StorageType = typename UP::StorageType; + using UP::bits; - using UP::EXP_BIAS; using UP::EXP_LEN; - using UP::EXP_MASK; - using UP::EXP_MASK_SHIFT; - using UP::FRACTION_LEN; - using UP::FRACTION_MASK; - using UP::SIGN_MASK; - using UP::TOTAL_LEN; using UP::UP; - using UP::encode; - using UP::get_biased_exponent; - using UP::is_zero; // Constants. static constexpr int MAX_BIASED_EXPONENT = (1 << EXP_LEN) - 1; static constexpr StorageType MIN_NORMAL = UP::min_normal(false); @@ -467,7 +569,7 @@ template struct FPBits : public internal::FPRep()> { LIBC_INLINE constexpr bool is_inf_or_nan() const { return !UP::is_finite(); } LIBC_INLINE constexpr FPBits abs() const { - return FPBits(bits & EXP_SIG_MASK); + return FPBits(bits & UP::EXP_SIG_MASK); } // Methods below this are used by tests. @@ -510,8 +612,10 @@ template struct FPBits : public internal::FPRep()> { LIBC_INLINE static constexpr FPBits create_value(bool sign, StorageType biased_exp, StorageType mantissa) { - return FPBits(encode(sign, typename UP::Exp(biased_exp - UP::EXP_BIAS), - typename UP::Sig(mantissa))); + static_assert(get_fp_type() != FPType::X86_Binary80, + "This function is not tested for X86 Extended Precision"); + return FPBits(UP::encode(sign, typename UP::Exp(biased_exp - UP::EXP_BIAS), + typename UP::Sig(mantissa))); } // The function convert integer number and unbiased exponent to proper float @@ -526,6 +630,8 @@ template struct FPBits : public internal::FPRep()> { // 5) Number is unsigned, so the result can be only positive. LIBC_INLINE static constexpr FPBits make_value(StorageType number, int ep) { + static_assert(get_fp_type() != FPType::X86_Binary80, + "This function is not tested for X86 Extended Precision"); FPBits result; // offset: +1 for sign, but -1 for implicit first bit int lz = cpp::countl_zero(number) - EXP_LEN; @@ -546,8 +652,4 @@ template struct FPBits : public internal::FPRep()> { } // namespace fputil } // namespace LIBC_NAMESPACE -#ifdef LIBC_LONG_DOUBLE_IS_X86_FLOAT80 -#include "x86_64/LongDoubleBits.h" -#endif - #endif // LLVM_LIBC_SRC___SUPPORT_FPUTIL_FPBITS_H diff --git a/libc/src/__support/FPUtil/generic/sqrt_80_bit_long_double.h b/libc/src/__support/FPUtil/generic/sqrt_80_bit_long_double.h index 257c02e17d004..8815a18cfbc39 100644 --- a/libc/src/__support/FPUtil/generic/sqrt_80_bit_long_double.h +++ b/libc/src/__support/FPUtil/generic/sqrt_80_bit_long_double.h @@ -131,7 +131,7 @@ LIBC_INLINE long double sqrt(long double x) { out.set_implicit_bit(1); out.set_mantissa((y & (ONE - 1))); - return out; + return out.get_val(); } } #endif // LIBC_LONG_DOUBLE_IS_X86_FLOAT80 diff --git a/libc/src/__support/FPUtil/x86_64/LongDoubleBits.h b/libc/src/__support/FPUtil/x86_64/LongDoubleBits.h deleted file mode 100644 index 34e685d3f888a..0000000000000 --- a/libc/src/__support/FPUtil/x86_64/LongDoubleBits.h +++ /dev/null @@ -1,240 +0,0 @@ -//===-- Bit representation of x86 long double numbers -----------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_LIBC_SRC___SUPPORT_FPUTIL_X86_64_LONGDOUBLEBITS_H -#define LLVM_LIBC_SRC___SUPPORT_FPUTIL_X86_64_LONGDOUBLEBITS_H - -#include "src/__support/CPP/bit.h" -#include "src/__support/UInt128.h" -#include "src/__support/common.h" -#include "src/__support/macros/attributes.h" // LIBC_INLINE -#include "src/__support/macros/properties/architectures.h" - -#if !defined(LIBC_TARGET_ARCH_IS_X86) -#error "Invalid include" -#endif - -#include "src/__support/FPUtil/FPBits.h" - -#include - -namespace LIBC_NAMESPACE { -namespace fputil { - -namespace internal { - -template <> -struct FPRep : public FPRepBase { - using UP = FPRepBase; - using typename UP::StorageType; - using UP::FRACTION_LEN; - using UP::FRACTION_MASK; - using UP::MANTISSA_PRECISION; - -protected: - using typename UP::Exp; - using typename UP::Sig; - using UP::encode; - -public: - // The x86 80 bit float represents the leading digit of the mantissa - // explicitly. This is the mask for that bit. - static constexpr StorageType EXPLICIT_BIT_MASK = StorageType(1) - << FRACTION_LEN; - // The X80 significand is made of an explicit bit and the fractional part. - static_assert((EXPLICIT_BIT_MASK & FRACTION_MASK) == 0, - "the explicit bit and the fractional part should not overlap"); - static_assert((EXPLICIT_BIT_MASK | FRACTION_MASK) == SIG_MASK, - "the explicit bit and the fractional part should cover the " - "whole significand"); - - LIBC_INLINE constexpr bool is_nan() const { - // Most encoding forms from the table found in - // https://en.wikipedia.org/wiki/Extended_precision#x86_extended_precision_format - // are interpreted as NaN. - // More precisely : - // - Pseudo-Infinity - // - Pseudo Not a Number - // - Signalling Not a Number - // - Floating-point Indefinite - // - Quiet Not a Number - // - Unnormal - // This can be reduced to the following logic: - if (exp_bits() == encode(Exp::BITS_ALL_ONES)) - return !is_inf(); - if (exp_bits() != encode(Exp::BITS_ALL_ZEROES)) - return (sig_bits() & encode(Sig::MSB)) == 0; - return false; - } - LIBC_INLINE constexpr bool is_quiet_nan() const { - return exp_sig_bits() >= - encode(Exp::BITS_ALL_ONES, Sig::MSB | (Sig::MSB >> 1)); - } - LIBC_INLINE constexpr bool is_signaling_nan() const { - return is_nan() && !is_quiet_nan(); - } - LIBC_INLINE constexpr bool is_inf() const { - return exp_sig_bits() == encode(Exp::BITS_ALL_ONES, Sig::MSB); - } - LIBC_INLINE constexpr bool is_zero() const { - return exp_sig_bits() == encode(Exp::BITS_ALL_ZEROES, Sig::BITS_ALL_ZEROES); - } - LIBC_INLINE constexpr bool is_finite() const { - return !is_inf() && !is_nan(); - } - LIBC_INLINE - constexpr bool is_subnormal() const { - return exp_sig_bits() > encode(Exp::BITS_ALL_ZEROES, Sig::BITS_ALL_ZEROES); - } - LIBC_INLINE constexpr bool is_normal() const { - const auto exp = exp_bits(); - if (exp == encode(Exp::BITS_ALL_ZEROES) || - exp == encode(Exp::BITS_ALL_ONES)) - return false; - return get_implicit_bit(); - } - - LIBC_INLINE static constexpr StorageType zero(bool sign = false) { - return encode(sign, Exp::BITS_ALL_ZEROES, Sig::BITS_ALL_ZEROES); - } - LIBC_INLINE static constexpr StorageType one(bool sign = false) { - return encode(sign, Exp::ZERO, Sig::MSB); - } - LIBC_INLINE static constexpr StorageType min_subnormal(bool sign = false) { - return encode(sign, Exp::BITS_ALL_ZEROES, Sig::ONE); - } - LIBC_INLINE static constexpr StorageType max_subnormal(bool sign = false) { - return encode(sign, Exp::BITS_ALL_ZEROES, Sig::BITS_ALL_ONES ^ Sig::MSB); - } - LIBC_INLINE static constexpr StorageType min_normal(bool sign = false) { - return encode(sign, Exp::MIN, Sig::MSB); - } - LIBC_INLINE static constexpr StorageType max_normal(bool sign = false) { - return encode(sign, Exp::MAX, Sig::BITS_ALL_ONES); - } - LIBC_INLINE static constexpr StorageType inf(bool sign = false) { - return encode(sign, Exp::BITS_ALL_ONES, Sig::MSB); - } - LIBC_INLINE static constexpr StorageType build_nan(bool sign = false, - StorageType v = 0) { - return encode(sign, Exp::BITS_ALL_ONES, - Sig::MSB | (v ? Sig{v} : (Sig::MSB >> 2))); - } - LIBC_INLINE static constexpr StorageType build_quiet_nan(bool sign = false, - StorageType v = 0) { - return encode(sign, Exp::BITS_ALL_ONES, - Sig::MSB | (Sig::MSB >> 1) | Sig{v}); - } - - LIBC_INLINE constexpr StorageType get_explicit_mantissa() const { - return sig_bits(); - } - - LIBC_INLINE constexpr bool get_implicit_bit() const { - return bits & EXPLICIT_BIT_MASK; - } - - LIBC_INLINE constexpr void set_implicit_bit(bool implicitVal) { - if (get_implicit_bit() != implicitVal) - bits ^= EXPLICIT_BIT_MASK; - } -}; -} // namespace internal - -template <> -struct FPBits : public internal::FPRep { - using UP = internal::FPRep; - using Rep = UP; - using StorageType = typename UP::StorageType; - -private: - using UP::bits; - using UP::EXP_SIG_MASK; - -public: - // Constants. - static constexpr int MAX_BIASED_EXPONENT = (1 << EXP_LEN) - 1; - static constexpr StorageType MIN_NORMAL = UP::min_normal(false); - static constexpr StorageType MAX_NORMAL = UP::max_normal(false); - static constexpr StorageType MIN_SUBNORMAL = UP::min_subnormal(false); - static constexpr StorageType MAX_SUBNORMAL = UP::max_subnormal(false); - - // Constructors. - LIBC_INLINE constexpr FPBits() = default; - - template LIBC_INLINE constexpr explicit FPBits(XType x) { - using Unqual = typename cpp::remove_cv_t; - if constexpr (cpp::is_same_v) { - bits = cpp::bit_cast(x); - } else if constexpr (cpp::is_same_v) { - bits = x; - } else { - // We don't want accidental type promotions/conversions, so we require - // exact type match. - static_assert(cpp::always_false); - } - } - - // Floating-point conversions. - LIBC_INLINE constexpr long double get_val() const { - return cpp::bit_cast(bits); - } - - LIBC_INLINE constexpr operator long double() const { - return cpp::bit_cast(bits); - } - - LIBC_INLINE constexpr bool is_inf_or_nan() const { return !UP::is_finite(); } - - // Methods below this are used by tests. - - LIBC_INLINE static constexpr long double zero(bool sign = false) { - return FPBits(UP::zero(sign)).get_val(); - } - - LIBC_INLINE static constexpr long double neg_zero() { return zero(true); } - - LIBC_INLINE static constexpr long double inf(bool sign = false) { - return FPBits(UP::inf(sign)).get_val(); - } - - LIBC_INLINE static constexpr long double neg_inf() { return inf(true); } - - LIBC_INLINE static constexpr long double min_normal() { - return FPBits(UP::min_normal(false)).get_val(); - } - - LIBC_INLINE static constexpr long double max_normal() { - return FPBits(UP::max_normal(false)).get_val(); - } - - LIBC_INLINE static constexpr long double min_denormal() { - return FPBits(UP::min_subnormal(false)).get_val(); - } - - LIBC_INLINE static constexpr long double max_denormal() { - return FPBits(UP::max_subnormal(false)).get_val(); - } - - LIBC_INLINE static constexpr long double build_nan(StorageType v) { - return FPBits(UP::build_nan(false, v)).get_val(); - } - - LIBC_INLINE static constexpr long double build_quiet_nan(StorageType v) { - return FPBits(UP::build_quiet_nan(false, v)).get_val(); - } -}; - -static_assert( - sizeof(FPBits) == sizeof(long double), - "Internal long double representation does not match the machine format."); - -} // namespace fputil -} // namespace LIBC_NAMESPACE - -#endif // LLVM_LIBC_SRC___SUPPORT_FPUTIL_X86_64_LONGDOUBLEBITS_H diff --git a/libc/src/__support/FPUtil/x86_64/NextAfterLongDouble.h b/libc/src/__support/FPUtil/x86_64/NextAfterLongDouble.h index b461da3a4c0ab..b6e56b459997f 100644 --- a/libc/src/__support/FPUtil/x86_64/NextAfterLongDouble.h +++ b/libc/src/__support/FPUtil/x86_64/NextAfterLongDouble.h @@ -61,7 +61,7 @@ LIBC_INLINE long double nextafter(long double from, long double to) { from_bits.set_biased_exponent(from_bits.get_biased_exponent() + 1); if (from_bits.is_inf()) raise_except_if_required(FE_OVERFLOW | FE_INEXACT); - return from_bits; + return from_bits.get_val();; } else { ++int_val; } @@ -75,7 +75,7 @@ LIBC_INLINE long double nextafter(long double from, long double to) { // from == 0 is handled separately so decrementing the exponent will not // lead to underflow. from_bits.set_biased_exponent(from_bits.get_biased_exponent() - 1); - return from_bits; + return from_bits.get_val();; } else { --int_val; } @@ -94,7 +94,7 @@ LIBC_INLINE long double nextafter(long double from, long double to) { // from == 0 is handled separately so decrementing the exponent will not // lead to underflow. from_bits.set_biased_exponent(from_bits.get_biased_exponent() - 1); - return from_bits; + return from_bits.get_val();; } else { --int_val; } @@ -109,7 +109,7 @@ LIBC_INLINE long double nextafter(long double from, long double to) { from_bits.set_biased_exponent(from_bits.get_biased_exponent() + 1); if (from_bits.is_inf()) raise_except_if_required(FE_OVERFLOW | FE_INEXACT); - return from_bits; + return from_bits.get_val();; } else { ++int_val; } diff --git a/libc/test/utils/FPUtil/x86_long_double_test.cpp b/libc/test/utils/FPUtil/x86_long_double_test.cpp index 7da835fc95fc9..bafbbe2a41075 100644 --- a/libc/test/utils/FPUtil/x86_long_double_test.cpp +++ b/libc/test/utils/FPUtil/x86_long_double_test.cpp @@ -27,7 +27,7 @@ TEST(LlvmLibcX86LongDoubleTest, is_nan) { // If exponent has the max value and the implicit bit is 0, // then the number is a NaN for all values of mantissa. bits.set_mantissa(i); - long double nan = bits; + long double nan = bits.get_val(); ASSERT_NE(static_cast(isnan(nan)), 0); ASSERT_TRUE(bits.is_nan()); } @@ -38,7 +38,7 @@ TEST(LlvmLibcX86LongDoubleTest, is_nan) { // then the number is a NaN for all non-zero values of mantissa. // Note the initial value of |i| of 1 to avoid a zero mantissa. bits.set_mantissa(i); - long double nan = bits; + long double nan = bits.get_val(); ASSERT_NE(static_cast(isnan(nan)), 0); ASSERT_TRUE(bits.is_nan()); } @@ -49,7 +49,7 @@ TEST(LlvmLibcX86LongDoubleTest, is_nan) { // If exponent is non-zero and also not max, and the implicit bit is 0, // then the number is a NaN for all values of mantissa. bits.set_mantissa(i); - long double nan = bits; + long double nan = bits.get_val(); ASSERT_NE(static_cast(isnan(nan)), 0); ASSERT_TRUE(bits.is_nan()); } @@ -60,7 +60,7 @@ TEST(LlvmLibcX86LongDoubleTest, is_nan) { // If exponent is non-zero and also not max, and the implicit bit is 1, // then the number is normal value for all values of mantissa. bits.set_mantissa(i); - long double valid = bits; + long double valid = bits.get_val(); ASSERT_EQ(static_cast(isnan(valid)), 0); ASSERT_FALSE(bits.is_nan()); } @@ -70,7 +70,7 @@ TEST(LlvmLibcX86LongDoubleTest, is_nan) { for (unsigned int i = 0; i < COUNT; ++i) { // If exponent is zero, then the number is a valid but denormal value. bits.set_mantissa(i); - long double valid = bits; + long double valid = bits.get_val(); ASSERT_EQ(static_cast(isnan(valid)), 0); ASSERT_FALSE(bits.is_nan()); } @@ -80,7 +80,7 @@ TEST(LlvmLibcX86LongDoubleTest, is_nan) { for (unsigned int i = 0; i < COUNT; ++i) { // If exponent is zero, then the number is a valid but denormal value. bits.set_mantissa(i); - long double valid = bits; + long double valid = bits.get_val(); ASSERT_EQ(static_cast(isnan(valid)), 0); ASSERT_FALSE(bits.is_nan()); } diff --git a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel index f222831eefd76..6fa47c0090b87 100644 --- a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel +++ b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel @@ -662,7 +662,6 @@ libc_support_library( libc_support_library( name = "__support_fputil_fp_bits", hdrs = ["src/__support/FPUtil/FPBits.h"], - textual_hdrs = ["src/__support/FPUtil/x86_64/LongDoubleBits.h"], deps = [ ":__support_common", ":__support_cpp_bit", From b897c069c3e0420f194a2db7840549cd6cac543a Mon Sep 17 00:00:00 2001 From: Guillaume Chatelet Date: Mon, 15 Jan 2024 17:04:25 +0000 Subject: [PATCH 3/9] Remove duplicated EXPECT --- libc/test/src/__support/FPUtil/fpbits_test.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/libc/test/src/__support/FPUtil/fpbits_test.cpp b/libc/test/src/__support/FPUtil/fpbits_test.cpp index 8a5b3c7370ef9..3a452f0b7993c 100644 --- a/libc/test/src/__support/FPUtil/fpbits_test.cpp +++ b/libc/test/src/__support/FPUtil/fpbits_test.cpp @@ -143,9 +143,6 @@ TEST(LlvmLibcFPBitsTest, FPType_X86_Binary80_IsNan) { return rep.is_nan(); }; - EXPECT_TRUE(is_nan( - 0b0'111111111111111, // NAN : Pseudo-Infinity - 0b0000000000000000000000000000000000000000000000000000000000000000)); EXPECT_TRUE(is_nan( 0b0'111111111111111, // NAN : Pseudo-Infinity 0b0000000000000000000000000000000000000000000000000000000000000000)); From b249b1bd78771ec8f1d5efa5363feab696aa9467 Mon Sep 17 00:00:00 2001 From: Guillaume Chatelet Date: Mon, 15 Jan 2024 17:06:33 +0000 Subject: [PATCH 4/9] Remove duplicate semicolon --- libc/src/__support/FPUtil/x86_64/NextAfterLongDouble.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libc/src/__support/FPUtil/x86_64/NextAfterLongDouble.h b/libc/src/__support/FPUtil/x86_64/NextAfterLongDouble.h index b6e56b459997f..5f15bac5df77f 100644 --- a/libc/src/__support/FPUtil/x86_64/NextAfterLongDouble.h +++ b/libc/src/__support/FPUtil/x86_64/NextAfterLongDouble.h @@ -61,7 +61,7 @@ LIBC_INLINE long double nextafter(long double from, long double to) { from_bits.set_biased_exponent(from_bits.get_biased_exponent() + 1); if (from_bits.is_inf()) raise_except_if_required(FE_OVERFLOW | FE_INEXACT); - return from_bits.get_val();; + return from_bits.get_val(); } else { ++int_val; } @@ -75,7 +75,7 @@ LIBC_INLINE long double nextafter(long double from, long double to) { // from == 0 is handled separately so decrementing the exponent will not // lead to underflow. from_bits.set_biased_exponent(from_bits.get_biased_exponent() - 1); - return from_bits.get_val();; + return from_bits.get_val(); } else { --int_val; } @@ -94,7 +94,7 @@ LIBC_INLINE long double nextafter(long double from, long double to) { // from == 0 is handled separately so decrementing the exponent will not // lead to underflow. from_bits.set_biased_exponent(from_bits.get_biased_exponent() - 1); - return from_bits.get_val();; + return from_bits.get_val(); } else { --int_val; } @@ -109,7 +109,7 @@ LIBC_INLINE long double nextafter(long double from, long double to) { from_bits.set_biased_exponent(from_bits.get_biased_exponent() + 1); if (from_bits.is_inf()) raise_except_if_required(FE_OVERFLOW | FE_INEXACT); - return from_bits.get_val();; + return from_bits.get_val(); } else { ++int_val; } From 7d826ccb7f6f525d4ff8fe5d2de673f33c0163be Mon Sep 17 00:00:00 2001 From: Guillaume Chatelet Date: Tue, 16 Jan 2024 09:23:43 +0000 Subject: [PATCH 5/9] Add implementation overview --- libc/src/__support/FPUtil/FPBits.h | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/libc/src/__support/FPUtil/FPBits.h b/libc/src/__support/FPUtil/FPBits.h index 58d63a7d01fb8..53bb7cee7e95c 100644 --- a/libc/src/__support/FPUtil/FPBits.h +++ b/libc/src/__support/FPUtil/FPBits.h @@ -31,6 +31,40 @@ enum class FPType { X86_Binary80, }; +// The classes hierarchy are as follows: +// +// ┌───────────────────┐ +// │ FPLayout │ +// └─────────▲─────────┘ +// │ +// ┌─────────┴─────────┐ +// │ FPRepBase │ +// └─────────▲─────────┘ +// │ +// ┌────────────┴─────────────┐ +// │ │ +// ┌────────┴──────┐ ┌─────────────┴──────────────┐ +// │ FPRep │ │ FPRep │ +// └───────────┘ +// +// - 'FPLayout' defines only a few constants, namely the 'StorageType' and the +// length of the sign, the exponent and significand parts. +// - 'FPRepBase' builds more constants on top of those from 'FPLayout' like +// exponent bias, shifts and masks. It also defines tools to assemble or test +// these parts. +// - 'FPRep' defines functions to interact with the floating point +// representation. The default implementation is the one for 'IEEE754', a +// specialization is provided for X86 Extended Precision that has a different +// encoding. +// - 'FPBits' is templated on the platform floating point types. Contrary to +// 'FPRep' that is platform agnostic 'FPBits' is architecture dependent. + namespace internal { // Defines the layout (sign, exponent, significand) of a floating point type in From c30b795d3680c4b957f59fcbdae6ba185b7fe287 Mon Sep 17 00:00:00 2001 From: Guillaume Chatelet Date: Tue, 16 Jan 2024 10:30:31 +0000 Subject: [PATCH 6/9] Address comments --- libc/src/__support/FPUtil/FPBits.h | 169 ++++++++++++++++------------- 1 file changed, 95 insertions(+), 74 deletions(-) diff --git a/libc/src/__support/FPUtil/FPBits.h b/libc/src/__support/FPUtil/FPBits.h index 53bb7cee7e95c..c2315630e7826 100644 --- a/libc/src/__support/FPUtil/FPBits.h +++ b/libc/src/__support/FPUtil/FPBits.h @@ -167,25 +167,33 @@ struct FPRepBase : public internal::FPLayout { static_assert((SIG_MASK | EXP_MASK | SIGN_MASK) == FP_MASK, "masks cover"); protected: - enum class Exp : int32_t { - ZERO = 0, + LIBC_INLINE static constexpr StorageType bit_at(int position) { + return StorageType(1) << position; + } + + // An opaque type to store a floating point biased exponent. + // We define special values but it is valid to create arbitrary values as long + // as they are in the range [BITS_ALL_ZEROES, BITS_ALL_ONES]. + // Values greater than BITS_ALL_ONES are truncated. + enum class BiasedExponent : uint32_t { // The exponent value for denormal numbers. - SUBNORMAL = -EXP_BIAS, - // The minimum exponent value for normal numbers. - MIN = SUBNORMAL + 1, - // The maximum exponent value for normal numbers. - MAX = EXP_BIAS, - // Special value all ones. - INF = MAX + 1, - // Aliases - BITS_ALL_ZEROES = SUBNORMAL, - BITS_ALL_ONES = INF, + BITS_ALL_ZEROES = 0, + NORMAL_MIN = 1, + NORMAL_ZERO = EXP_BIAS, + NORMAL_MAX = 2 * EXP_BIAS, + // The exponent value for infinity. + BITS_ALL_ONES = NORMAL_MAX + 1, }; - enum class Sig : StorageType { + // An opaque type to store a floating point significand. + // We define special values but it is valid to create arbitrary values as long + // as they are in the range [BITS_ALL_ZEROES, BITS_ALL_ONES]. + // Note that the semantics of the Significand are implementation dependent. + // Values greater than BITS_ALL_ONES are truncated. + enum class Significand : StorageType { ZERO = 0, - ONE = 1, - MSB = StorageType(1) << (SIG_LEN - 1), + LSB = 1, + MSB = bit_at(SIG_LEN - 1), // Aliases BITS_ALL_ZEROES = ZERO, BITS_ALL_ONES = SIG_MASK, @@ -196,29 +204,34 @@ struct FPRepBase : public internal::FPLayout { return static_cast(value); } - LIBC_INLINE friend constexpr Sig operator|(const Sig a, const Sig b) { - return Sig{storage_cast(storage_cast(a) | storage_cast(b))}; + LIBC_INLINE friend constexpr Significand operator|(const Significand a, + const Significand b) { + return Significand{storage_cast(storage_cast(a) | storage_cast(b))}; } - LIBC_INLINE friend constexpr Sig operator^(const Sig a, const Sig b) { - return Sig{storage_cast(storage_cast(a) ^ storage_cast(b))}; + LIBC_INLINE friend constexpr Significand operator^(const Significand a, + const Significand b) { + return Significand{storage_cast(storage_cast(a) ^ storage_cast(b))}; } - LIBC_INLINE friend constexpr Sig operator>>(const Sig a, int shift) { - return Sig{storage_cast(storage_cast(a) >> shift)}; + LIBC_INLINE friend constexpr Significand operator>>(const Significand a, + int shift) { + return Significand{storage_cast(storage_cast(a) >> shift)}; } - LIBC_INLINE static constexpr StorageType encode(Exp exp) { - return storage_cast(static_cast(exp) + EXP_BIAS) << SIG_LEN; + LIBC_INLINE static constexpr StorageType encode(BiasedExponent exp) { + return (storage_cast(exp) << SIG_LEN) & EXP_MASK; } - LIBC_INLINE static constexpr StorageType encode(Sig value) { + LIBC_INLINE static constexpr StorageType encode(Significand value) { return storage_cast(value) & SIG_MASK; } - LIBC_INLINE static constexpr StorageType encode(Exp exp, Sig sig) { + LIBC_INLINE static constexpr StorageType encode(BiasedExponent exp, + Significand sig) { return encode(exp) | encode(sig); } - LIBC_INLINE static constexpr StorageType encode(bool sign, Exp exp, Sig sig) { + LIBC_INLINE static constexpr StorageType encode(bool sign, BiasedExponent exp, + Significand sig) { if (sign) return SIGN_MASK | encode(exp, sig); return encode(exp, sig); @@ -231,10 +244,6 @@ struct FPRepBase : public internal::FPLayout { } private: - LIBC_INLINE static constexpr StorageType bit_at(int position) { - return StorageType(1) << position; - } - // Merge bits from 'a' and 'b' values according to 'mask'. // Use 'a' bits when corresponding 'mask' bits are zeroes and 'b' bits when // corresponding bits are ones. @@ -304,8 +313,8 @@ struct FPRepBase : public internal::FPLayout { } LIBC_INLINE constexpr StorageType uintval() const { return bits & FP_MASK; } - LIBC_INLINE constexpr StorageType set_uintval(StorageType value) { - return bits = (value & FP_MASK); + LIBC_INLINE constexpr void set_uintval(StorageType value) { + bits = (value & FP_MASK); } LIBC_INLINE constexpr bool is_zero() const { @@ -330,8 +339,8 @@ template struct FPRep : public FPRepBase { using UP::MANTISSA_PRECISION; protected: - using typename UP::Exp; - using typename UP::Sig; + using typename UP::BiasedExponent; + using typename UP::Significand; using UP::encode; using UP::exp_bits; using UP::exp_sig_bits; @@ -339,59 +348,66 @@ template struct FPRep : public FPRepBase { public: LIBC_INLINE constexpr bool is_nan() const { - return exp_sig_bits() > encode(Exp::BITS_ALL_ONES, Sig::BITS_ALL_ZEROES); + return exp_sig_bits() > + encode(BiasedExponent::BITS_ALL_ONES, Significand::ZERO); } LIBC_INLINE constexpr bool is_quiet_nan() const { - return exp_sig_bits() >= encode(Exp::BITS_ALL_ONES, Sig::MSB); + return exp_sig_bits() >= + encode(BiasedExponent::BITS_ALL_ONES, Significand::MSB); } LIBC_INLINE constexpr bool is_signaling_nan() const { return is_nan() && !is_quiet_nan(); } LIBC_INLINE constexpr bool is_inf() const { - return exp_sig_bits() == encode(Exp::BITS_ALL_ONES, Sig::BITS_ALL_ZEROES); + return exp_sig_bits() == + encode(BiasedExponent::BITS_ALL_ONES, Significand::ZERO); } LIBC_INLINE constexpr bool is_zero() const { - return exp_sig_bits() == encode(Exp::BITS_ALL_ZEROES, Sig::BITS_ALL_ZEROES); + return exp_sig_bits() == + encode(BiasedExponent::BITS_ALL_ZEROES, Significand::ZERO); } LIBC_INLINE constexpr bool is_finite() const { - return exp_bits() != encode(Exp::BITS_ALL_ONES); + return exp_bits() != encode(BiasedExponent::BITS_ALL_ONES); } LIBC_INLINE constexpr bool is_subnormal() const { - return exp_bits() == encode(Exp::BITS_ALL_ZEROES); + return exp_bits() == encode(BiasedExponent::BITS_ALL_ZEROES); } LIBC_INLINE constexpr bool is_normal() const { return is_finite() && !is_subnormal(); } LIBC_INLINE static constexpr StorageType zero(bool sign = false) { - return encode(sign, Exp::BITS_ALL_ZEROES, Sig::BITS_ALL_ZEROES); + return encode(sign, BiasedExponent::BITS_ALL_ZEROES, Significand::ZERO); } LIBC_INLINE static constexpr StorageType one(bool sign = false) { - return encode(sign, Exp::ZERO, Sig::BITS_ALL_ZEROES); + return encode(sign, BiasedExponent::NORMAL_ZERO, Significand::ZERO); } LIBC_INLINE static constexpr StorageType min_subnormal(bool sign = false) { - return encode(sign, Exp::BITS_ALL_ZEROES, Sig::ONE); + return encode(sign, BiasedExponent::BITS_ALL_ZEROES, Significand::LSB); } LIBC_INLINE static constexpr StorageType max_subnormal(bool sign = false) { - return encode(sign, Exp::BITS_ALL_ZEROES, Sig::BITS_ALL_ONES); + return encode(sign, BiasedExponent::BITS_ALL_ZEROES, + Significand::BITS_ALL_ONES); } LIBC_INLINE static constexpr StorageType min_normal(bool sign = false) { - return encode(sign, Exp::MIN, Sig::BITS_ALL_ZEROES); + return encode(sign, BiasedExponent::NORMAL_MIN, Significand::ZERO); } LIBC_INLINE static constexpr StorageType max_normal(bool sign = false) { - return encode(sign, Exp::MAX, Sig::BITS_ALL_ONES); + return encode(sign, BiasedExponent::NORMAL_MAX, Significand::BITS_ALL_ONES); } LIBC_INLINE static constexpr StorageType inf(bool sign = false) { - return encode(sign, Exp::BITS_ALL_ONES, Sig::BITS_ALL_ZEROES); + return encode(sign, BiasedExponent::BITS_ALL_ONES, Significand::ZERO); } LIBC_INLINE static constexpr StorageType build_nan(bool sign = false, StorageType v = 0) { - return encode(sign, Exp::BITS_ALL_ONES, (v ? Sig{v} : (Sig::MSB >> 1))); + return encode(sign, BiasedExponent::BITS_ALL_ONES, + (v ? Significand{v} : (Significand::MSB >> 1))); } LIBC_INLINE static constexpr StorageType build_quiet_nan(bool sign = false, StorageType v = 0) { - return encode(sign, Exp::BITS_ALL_ONES, Sig::MSB | Sig{v}); + return encode(sign, BiasedExponent::BITS_ALL_ONES, + Significand::MSB | Significand{v}); } // The function return mantissa with the implicit bit set iff the current @@ -413,8 +429,8 @@ struct FPRep : public FPRepBase { using UP::MANTISSA_PRECISION; protected: - using typename UP::Exp; - using typename UP::Sig; + using typename UP::BiasedExponent; + using typename UP::Significand; using UP::encode; public: @@ -441,70 +457,75 @@ struct FPRep : public FPRepBase { // - Quiet Not a Number // - Unnormal // This can be reduced to the following logic: - if (exp_bits() == encode(Exp::BITS_ALL_ONES)) + if (exp_bits() == encode(BiasedExponent::BITS_ALL_ONES)) return !is_inf(); - if (exp_bits() != encode(Exp::BITS_ALL_ZEROES)) - return (sig_bits() & encode(Sig::MSB)) == 0; + if (exp_bits() != encode(BiasedExponent::BITS_ALL_ZEROES)) + return (sig_bits() & encode(Significand::MSB)) == 0; return false; } LIBC_INLINE constexpr bool is_quiet_nan() const { - return exp_sig_bits() >= - encode(Exp::BITS_ALL_ONES, Sig::MSB | (Sig::MSB >> 1)); + return exp_sig_bits() >= encode(BiasedExponent::BITS_ALL_ONES, + Significand::MSB | (Significand::MSB >> 1)); } LIBC_INLINE constexpr bool is_signaling_nan() const { return is_nan() && !is_quiet_nan(); } LIBC_INLINE constexpr bool is_inf() const { - return exp_sig_bits() == encode(Exp::BITS_ALL_ONES, Sig::MSB); + return exp_sig_bits() == + encode(BiasedExponent::BITS_ALL_ONES, Significand::MSB); } LIBC_INLINE constexpr bool is_zero() const { - return exp_sig_bits() == encode(Exp::BITS_ALL_ZEROES, Sig::BITS_ALL_ZEROES); + return exp_sig_bits() == + encode(BiasedExponent::BITS_ALL_ZEROES, Significand::ZERO); } LIBC_INLINE constexpr bool is_finite() const { return !is_inf() && !is_nan(); } LIBC_INLINE constexpr bool is_subnormal() const { - return exp_sig_bits() > encode(Exp::BITS_ALL_ZEROES, Sig::BITS_ALL_ZEROES); + return exp_sig_bits() > + encode(BiasedExponent::BITS_ALL_ZEROES, Significand::ZERO); } LIBC_INLINE constexpr bool is_normal() const { const auto exp = exp_bits(); - if (exp == encode(Exp::BITS_ALL_ZEROES) || - exp == encode(Exp::BITS_ALL_ONES)) + if (exp == encode(BiasedExponent::BITS_ALL_ZEROES) || + exp == encode(BiasedExponent::BITS_ALL_ONES)) return false; return get_implicit_bit(); } LIBC_INLINE static constexpr StorageType zero(bool sign = false) { - return encode(sign, Exp::BITS_ALL_ZEROES, Sig::BITS_ALL_ZEROES); + return encode(sign, BiasedExponent::BITS_ALL_ZEROES, Significand::ZERO); } LIBC_INLINE static constexpr StorageType one(bool sign = false) { - return encode(sign, Exp::ZERO, Sig::MSB); + return encode(sign, BiasedExponent::NORMAL_ZERO, Significand::MSB); } LIBC_INLINE static constexpr StorageType min_subnormal(bool sign = false) { - return encode(sign, Exp::BITS_ALL_ZEROES, Sig::ONE); + return encode(sign, BiasedExponent::BITS_ALL_ZEROES, Significand::LSB); } LIBC_INLINE static constexpr StorageType max_subnormal(bool sign = false) { - return encode(sign, Exp::BITS_ALL_ZEROES, Sig::BITS_ALL_ONES ^ Sig::MSB); + return encode(sign, BiasedExponent::BITS_ALL_ZEROES, + Significand::BITS_ALL_ONES ^ Significand::MSB); } LIBC_INLINE static constexpr StorageType min_normal(bool sign = false) { - return encode(sign, Exp::MIN, Sig::MSB); + return encode(sign, BiasedExponent::NORMAL_MIN, Significand::MSB); } LIBC_INLINE static constexpr StorageType max_normal(bool sign = false) { - return encode(sign, Exp::MAX, Sig::BITS_ALL_ONES); + return encode(sign, BiasedExponent::NORMAL_MAX, Significand::BITS_ALL_ONES); } LIBC_INLINE static constexpr StorageType inf(bool sign = false) { - return encode(sign, Exp::BITS_ALL_ONES, Sig::MSB); + return encode(sign, BiasedExponent::BITS_ALL_ONES, Significand::MSB); } LIBC_INLINE static constexpr StorageType build_nan(bool sign = false, StorageType v = 0) { - return encode(sign, Exp::BITS_ALL_ONES, - Sig::MSB | (v ? Sig{v} : (Sig::MSB >> 2))); + return encode(sign, BiasedExponent::BITS_ALL_ONES, + Significand::MSB | + (v ? Significand{v} : (Significand::MSB >> 2))); } LIBC_INLINE static constexpr StorageType build_quiet_nan(bool sign = false, StorageType v = 0) { - return encode(sign, Exp::BITS_ALL_ONES, - Sig::MSB | (Sig::MSB >> 1) | Sig{v}); + return encode(sign, BiasedExponent::BITS_ALL_ONES, + Significand::MSB | (Significand::MSB >> 1) | Significand{v}); } LIBC_INLINE constexpr StorageType get_explicit_mantissa() const { @@ -648,8 +669,8 @@ template struct FPBits : public internal::FPRep()> { create_value(bool sign, StorageType biased_exp, StorageType mantissa) { static_assert(get_fp_type() != FPType::X86_Binary80, "This function is not tested for X86 Extended Precision"); - return FPBits(UP::encode(sign, typename UP::Exp(biased_exp - UP::EXP_BIAS), - typename UP::Sig(mantissa))); + return FPBits(UP::encode(sign, typename UP::BiasedExponent(biased_exp), + typename UP::Significand(mantissa))); } // The function convert integer number and unbiased exponent to proper float From e9fcc5d41493e388799dd40f68b47abf8b0ba5a4 Mon Sep 17 00:00:00 2001 From: Guillaume Chatelet Date: Tue, 16 Jan 2024 10:36:32 +0000 Subject: [PATCH 7/9] Fix typo --- libc/src/__support/FPUtil/FPBits.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libc/src/__support/FPUtil/FPBits.h b/libc/src/__support/FPUtil/FPBits.h index c2315630e7826..2f4c7eb37fcc9 100644 --- a/libc/src/__support/FPUtil/FPBits.h +++ b/libc/src/__support/FPUtil/FPBits.h @@ -31,7 +31,7 @@ enum class FPType { X86_Binary80, }; -// The classes hierarchy are as follows: +// The classes hierarchy is as follows: // // ┌───────────────────┐ // │ FPLayout │ From cc4d4ff4d851894b237460f12dc3193553c8680b Mon Sep 17 00:00:00 2001 From: Guillaume Chatelet Date: Tue, 16 Jan 2024 13:05:10 +0000 Subject: [PATCH 8/9] Add an Exponent type with normal exponent semantics --- libc/src/__support/FPUtil/FPBits.h | 32 ++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/libc/src/__support/FPUtil/FPBits.h b/libc/src/__support/FPUtil/FPBits.h index 2f4c7eb37fcc9..92a120a5d420d 100644 --- a/libc/src/__support/FPUtil/FPBits.h +++ b/libc/src/__support/FPUtil/FPBits.h @@ -171,6 +171,15 @@ struct FPRepBase : public internal::FPLayout { return StorageType(1) << position; } + // An opaque type to store a floating point exponent. + // We define special values but it is valid to create arbitrary values as long + // as they are in the range [MIN, MAX]. + enum class Exponent : int32_t { + MIN = 1 - EXP_BIAS, + ZERO = 0, + MAX = EXP_BIAS, + }; + // An opaque type to store a floating point biased exponent. // We define special values but it is valid to create arbitrary values as long // as they are in the range [BITS_ALL_ZEROES, BITS_ALL_ONES]. @@ -178,13 +187,14 @@ struct FPRepBase : public internal::FPLayout { enum class BiasedExponent : uint32_t { // The exponent value for denormal numbers. BITS_ALL_ZEROES = 0, - NORMAL_MIN = 1, - NORMAL_ZERO = EXP_BIAS, - NORMAL_MAX = 2 * EXP_BIAS, // The exponent value for infinity. - BITS_ALL_ONES = NORMAL_MAX + 1, + BITS_ALL_ONES = 2 * EXP_BIAS + 1, }; + LIBC_INLINE static constexpr BiasedExponent biased(Exponent value) { + return static_cast(static_cast(value) + EXP_BIAS); + } + // An opaque type to store a floating point significand. // We define special values but it is valid to create arbitrary values as long // as they are in the range [BITS_ALL_ZEROES, BITS_ALL_ONES]. @@ -340,11 +350,13 @@ template struct FPRep : public FPRepBase { protected: using typename UP::BiasedExponent; + using typename UP::Exponent; using typename UP::Significand; using UP::encode; using UP::exp_bits; using UP::exp_sig_bits; using UP::sig_bits; + using UP::biased; public: LIBC_INLINE constexpr bool is_nan() const { @@ -381,7 +393,7 @@ template struct FPRep : public FPRepBase { return encode(sign, BiasedExponent::BITS_ALL_ZEROES, Significand::ZERO); } LIBC_INLINE static constexpr StorageType one(bool sign = false) { - return encode(sign, BiasedExponent::NORMAL_ZERO, Significand::ZERO); + return encode(sign, biased(Exponent::ZERO), Significand::ZERO); } LIBC_INLINE static constexpr StorageType min_subnormal(bool sign = false) { return encode(sign, BiasedExponent::BITS_ALL_ZEROES, Significand::LSB); @@ -391,10 +403,10 @@ template struct FPRep : public FPRepBase { Significand::BITS_ALL_ONES); } LIBC_INLINE static constexpr StorageType min_normal(bool sign = false) { - return encode(sign, BiasedExponent::NORMAL_MIN, Significand::ZERO); + return encode(sign, biased(Exponent::MIN), Significand::ZERO); } LIBC_INLINE static constexpr StorageType max_normal(bool sign = false) { - return encode(sign, BiasedExponent::NORMAL_MAX, Significand::BITS_ALL_ONES); + return encode(sign, biased(Exponent::MAX), Significand::BITS_ALL_ONES); } LIBC_INLINE static constexpr StorageType inf(bool sign = false) { return encode(sign, BiasedExponent::BITS_ALL_ONES, Significand::ZERO); @@ -498,7 +510,7 @@ struct FPRep : public FPRepBase { return encode(sign, BiasedExponent::BITS_ALL_ZEROES, Significand::ZERO); } LIBC_INLINE static constexpr StorageType one(bool sign = false) { - return encode(sign, BiasedExponent::NORMAL_ZERO, Significand::MSB); + return encode(sign, biased(Exponent::ZERO), Significand::MSB); } LIBC_INLINE static constexpr StorageType min_subnormal(bool sign = false) { return encode(sign, BiasedExponent::BITS_ALL_ZEROES, Significand::LSB); @@ -508,10 +520,10 @@ struct FPRep : public FPRepBase { Significand::BITS_ALL_ONES ^ Significand::MSB); } LIBC_INLINE static constexpr StorageType min_normal(bool sign = false) { - return encode(sign, BiasedExponent::NORMAL_MIN, Significand::MSB); + return encode(sign, biased(Exponent::MIN), Significand::MSB); } LIBC_INLINE static constexpr StorageType max_normal(bool sign = false) { - return encode(sign, BiasedExponent::NORMAL_MAX, Significand::BITS_ALL_ONES); + return encode(sign, biased(Exponent::MAX), Significand::BITS_ALL_ONES); } LIBC_INLINE static constexpr StorageType inf(bool sign = false) { return encode(sign, BiasedExponent::BITS_ALL_ONES, Significand::MSB); From 1fa0b75c659658a5a7d1d9b6d4f50b03b0127e19 Mon Sep 17 00:00:00 2001 From: Guillaume Chatelet Date: Tue, 16 Jan 2024 13:24:55 +0000 Subject: [PATCH 9/9] Clarified documentation --- libc/src/__support/FPUtil/FPBits.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libc/src/__support/FPUtil/FPBits.h b/libc/src/__support/FPUtil/FPBits.h index 92a120a5d420d..d6a9f60f71208 100644 --- a/libc/src/__support/FPUtil/FPBits.h +++ b/libc/src/__support/FPUtil/FPBits.h @@ -352,11 +352,11 @@ template struct FPRep : public FPRepBase { using typename UP::BiasedExponent; using typename UP::Exponent; using typename UP::Significand; + using UP::biased; using UP::encode; using UP::exp_bits; using UP::exp_sig_bits; using UP::sig_bits; - using UP::biased; public: LIBC_INLINE constexpr bool is_nan() const { @@ -544,7 +544,7 @@ struct FPRep : public FPRepBase { return sig_bits(); } - // The following functions are specific to this implementation. + // The following functions are specific to FPRep. // TODO: Remove if possible. LIBC_INLINE constexpr bool get_implicit_bit() const { return bits & EXPLICIT_BIT_MASK;