[libc-commits] [libc] [libc][NFC] Make FPRep more testable (PR #80453)

via libc-commits libc-commits at lists.llvm.org
Fri Feb 2 08:40:43 PST 2024


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libc

Author: Guillaume Chatelet (gchatelet)

<details>
<summary>Changes</summary>



---

Patch is 26.60 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/80453.diff


2 Files Affected:

- (modified) libc/src/__support/FPUtil/FPBits.h (+159-124) 
- (modified) libc/test/src/__support/FPUtil/fpbits_test.cpp (+34-34) 


``````````diff
diff --git a/libc/src/__support/FPUtil/FPBits.h b/libc/src/__support/FPUtil/FPBits.h
index d975638be348d..ef94bd3bb8f1f 100644
--- a/libc/src/__support/FPUtil/FPBits.h
+++ b/libc/src/__support/FPUtil/FPBits.h
@@ -75,27 +75,32 @@ LIBC_INLINE_VAR constexpr Sign Sign::POS = Sign(false);
 //          │                          │
 //          └────────────┬─────────────┘
 //                       │
-//                 ┌─────┴─────┐
-//                 │  FPRep<T> │
-//                 └───────────┘
+//               ┌───────┴───────┐
+//               │  FPRepImpl<T> │
+//               └───────▲───────┘
 //                       │
-//                 ┌─────┴─────┐
-//                 │ FPBits<T> │
-//                 └───────────┘
+//              ┌────────┴────────┐
+//        ┌─────┴─────┐     ┌─────┴─────┐
+//        │  FPRep<T> │     │ FPBits<T> │
+//        └───────────┘     └───────────┘
 //
 // - 'FPLayout' defines only a few constants, namely the 'StorageType' and
-// length of the sign, the exponent, fraction and significand parts.
+//   length of the sign, the exponent, fraction and significand parts.
 // - 'FPStorage' builds more constants on top of those from 'FPLayout' like
-// exponent bias and masks. It also holds the bit representation of the
-// floating point as a 'StorageType' type and defines tools to assemble or test
-// these parts.
+//   exponent bias and masks. It also holds the bit representation of the
+//   floating point as a 'StorageType' type and defines tools to assemble or
+//   test these parts.
 // - 'FPRepSem' defines functions to interact semantically with the floating
-// point representation. The default implementation is the one for 'IEEE754', a
-// specialization is provided for X86 Extended Precision.
-// - 'FPRep' derives from 'FPRepSem' and adds functions that are common to all
-// implementations.
-// - 'FPBits' exposes all functions from 'FPRep' but operates on the native C++
-// floating point type instead of 'FPType'.
+//   point representation. The default implementation is the one for 'IEEE754',
+//   a specialization is provided for X86 Extended Precision.
+// - 'FPRepImpl' derives from 'FPRepSem' and adds functions that are common to
+//   all implementations or build on the ones in 'FPRepSem'.
+// - 'FPRep' exposes all functions from 'FPRepImpl' and returns 'FPRep'
+//   instances when using Builders (static functions to create values).
+// - 'FPBits' exposes all the functions from 'FPRepImpl' but operates on the
+//   native C++ floating point type instead of 'FPType'. An additional 'get_val'
+//   function allows getting the C++ floating point type value back. Builders
+//   called from 'FPBits' return 'FPBits' instances.
 
 namespace internal {
 
@@ -197,12 +202,22 @@ template <FPType fp_type> struct FPStorage : public FPLayout<fp_type> {
   static_assert((SIG_MASK | EXP_MASK | SIGN_MASK) == FP_MASK, "masks cover");
 
 protected:
+  // 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.
+  LIBC_INLINE static constexpr StorageType merge(StorageType a, StorageType b,
+                                                 StorageType mask) {
+    // https://graphics.stanford.edu/~seander/bithacks.html#MaskedMerge
+    return a ^ ((a ^ b) & mask);
+  }
+
   // A stongly typed integer that prevents mixing and matching integers with
   // different semantics.
   template <typename T> struct TypedInt {
     using value_type = T;
     LIBC_INLINE constexpr explicit TypedInt(T value) : value(value) {}
     LIBC_INLINE constexpr TypedInt(const TypedInt &value) = default;
+    LIBC_INLINE constexpr TypedInt &operator=(const TypedInt &value) = default;
 
     LIBC_INLINE constexpr explicit operator T() const { return value; }
 
@@ -210,7 +225,14 @@ template <FPType fp_type> struct FPStorage : public FPLayout<fp_type> {
       return StorageType(value);
     }
 
-  private:
+    LIBC_INLINE friend constexpr bool operator==(TypedInt a, TypedInt b) {
+      return a.value == b.value;
+    }
+    LIBC_INLINE friend constexpr bool operator!=(TypedInt a, TypedInt b) {
+      return a.value != b.value;
+    }
+
+  protected:
     T value;
   };
 
@@ -220,10 +242,11 @@ template <FPType fp_type> struct FPStorage : public FPLayout<fp_type> {
   struct Exponent : public TypedInt<int32_t> {
     using UP = TypedInt<int32_t>;
     using UP::UP;
-    LIBC_INLINE
-    static constexpr auto MIN() { return Exponent(1 - EXP_BIAS); }
+    LIBC_INLINE static constexpr auto SUB() { return Exponent(-EXP_BIAS); }
+    LIBC_INLINE static constexpr auto MIN() { return Exponent(1 - EXP_BIAS); }
     LIBC_INLINE static constexpr auto ZERO() { return Exponent(0); }
     LIBC_INLINE static constexpr auto MAX() { return Exponent(EXP_BIAS); }
+    LIBC_INLINE static constexpr auto INF() { return Exponent(EXP_BIAS + 1); }
   };
 
   // An opaque type to store a floating point biased exponent.
@@ -236,13 +259,10 @@ template <FPType fp_type> struct FPStorage : public FPLayout<fp_type> {
 
     LIBC_INLINE constexpr BiasedExponent(Exponent exp)
         : UP(static_cast<int32_t>(exp) + EXP_BIAS) {}
-    // The exponent value for denormal numbers.
-    LIBC_INLINE static constexpr auto BITS_ALL_ZEROES() {
-      return BiasedExponent(uint32_t(0));
-    }
-    // The exponent value for infinity.
-    LIBC_INLINE static constexpr auto BITS_ALL_ONES() {
-      return BiasedExponent(uint32_t(2 * EXP_BIAS + 1));
+
+    // Cast operator to get convert from BiasedExponent to Exponent.
+    LIBC_INLINE constexpr operator Exponent() const {
+      return Exponent(UP::value - EXP_BIAS);
     }
   };
 
@@ -316,6 +336,23 @@ template <FPType fp_type> struct FPStorage : public FPLayout<fp_type> {
   LIBC_INLINE constexpr StorageType exp_sig_bits() const {
     return bits & EXP_SIG_MASK;
   }
+
+  // Parts
+  LIBC_INLINE constexpr BiasedExponent biased_exponent() const {
+    return BiasedExponent(static_cast<uint32_t>(exp_bits() >> SIG_LEN));
+  }
+  LIBC_INLINE constexpr void set_biased_exponent(BiasedExponent biased) {
+    bits = merge(bits, encode(biased), EXP_MASK);
+  }
+
+public:
+  LIBC_INLINE constexpr Sign sign() const {
+    return (bits & SIGN_MASK) ? Sign::NEG : Sign::POS;
+  }
+  LIBC_INLINE constexpr void set_sign(Sign signVal) {
+    if (sign() != signVal)
+      bits ^= SIGN_MASK;
+  }
 };
 
 // This layer defines all functions that are specific to how the the floating
@@ -329,9 +366,8 @@ struct FPRepSem : public FPStorage<fp_type> {
   using UP::FRACTION_MASK;
 
 protected:
-  using BiasedExp = typename UP::BiasedExponent;
-  using Exp = typename UP::Exponent;
-  using Sig = typename UP::Significand;
+  using typename UP::Exponent;
+  using typename UP::Significand;
   using UP::encode;
   using UP::exp_bits;
   using UP::exp_sig_bits;
@@ -340,61 +376,65 @@ struct FPRepSem : public FPStorage<fp_type> {
 
 public:
   // Builders
+  LIBC_INLINE static constexpr RetT zero(Sign sign = Sign::POS) {
+    return RetT(encode(sign, Exponent::SUB(), Significand::ZERO()));
+  }
   LIBC_INLINE static constexpr RetT one(Sign sign = Sign::POS) {
-    return RetT(encode(sign, Exp::ZERO(), Sig::ZERO()));
+    return RetT(encode(sign, Exponent::ZERO(), Significand::ZERO()));
   }
   LIBC_INLINE static constexpr RetT min_subnormal(Sign sign = Sign::POS) {
-    return RetT(encode(sign, BiasedExp::BITS_ALL_ZEROES(), Sig::LSB()));
+    return RetT(encode(sign, Exponent::SUB(), Significand::LSB()));
   }
   LIBC_INLINE static constexpr RetT max_subnormal(Sign sign = Sign::POS) {
-    return RetT(
-        encode(sign, BiasedExp::BITS_ALL_ZEROES(), Sig::BITS_ALL_ONES()));
+    return RetT(encode(sign, Exponent::SUB(), Significand::BITS_ALL_ONES()));
   }
   LIBC_INLINE static constexpr RetT min_normal(Sign sign = Sign::POS) {
-    return RetT(encode(sign, Exp::MIN(), Sig::ZERO()));
+    return RetT(encode(sign, Exponent::MIN(), Significand::ZERO()));
   }
   LIBC_INLINE static constexpr RetT max_normal(Sign sign = Sign::POS) {
-    return RetT(encode(sign, Exp::MAX(), Sig::BITS_ALL_ONES()));
+    return RetT(encode(sign, Exponent::MAX(), Significand::BITS_ALL_ONES()));
   }
   LIBC_INLINE static constexpr RetT inf(Sign sign = Sign::POS) {
-    return RetT(encode(sign, BiasedExp::BITS_ALL_ONES(), Sig::ZERO()));
+    return RetT(encode(sign, Exponent::INF(), Significand::ZERO()));
   }
   LIBC_INLINE static constexpr RetT signaling_nan(Sign sign = Sign::POS,
                                                   StorageType v = 0) {
-    return RetT(encode(sign, BiasedExp::BITS_ALL_ONES(),
-                       (v ? Sig(v) : (Sig::MSB() >> 1))));
+    return RetT(encode(sign, Exponent::INF(),
+                       (v ? Significand(v) : (Significand::MSB() >> 1))));
   }
   LIBC_INLINE static constexpr RetT quiet_nan(Sign sign = Sign::POS,
                                               StorageType v = 0) {
-    return RetT(encode(sign, BiasedExp::BITS_ALL_ONES(), Sig::MSB() | Sig(v)));
+    return RetT(
+        encode(sign, Exponent::INF(), Significand::MSB() | Significand(v)));
   }
 
   // Observers
+  LIBC_INLINE constexpr bool is_zero() const { return exp_sig_bits() == 0; }
   LIBC_INLINE constexpr bool is_nan() const {
-    return exp_sig_bits() > encode(BiasedExp::BITS_ALL_ONES(), Sig::ZERO());
+    return exp_sig_bits() > encode(Exponent::INF(), Significand::ZERO());
   }
   LIBC_INLINE constexpr bool is_quiet_nan() const {
-    return exp_sig_bits() >= encode(BiasedExp::BITS_ALL_ONES(), Sig::MSB());
+    return exp_sig_bits() >= encode(Exponent::INF(), 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(BiasedExp::BITS_ALL_ONES(), Sig::ZERO());
+    return exp_sig_bits() == encode(Exponent::INF(), Significand::ZERO());
   }
   LIBC_INLINE constexpr bool is_finite() const {
-    return exp_bits() != encode(BiasedExp::BITS_ALL_ONES());
+    return exp_bits() != encode(Exponent::INF());
   }
   LIBC_INLINE
   constexpr bool is_subnormal() const {
-    return exp_bits() == encode(BiasedExp::BITS_ALL_ZEROES());
+    return exp_bits() == encode(Exponent::SUB());
   }
   LIBC_INLINE constexpr bool is_normal() const {
     return is_finite() && !is_subnormal();
   }
   // Returns the mantissa with the implicit bit set iff the current
   // value is a valid normal number.
-  LIBC_INLINE constexpr StorageType get_explicit_mantissa() {
+  LIBC_INLINE constexpr StorageType get_explicit_mantissa() const {
     if (is_subnormal())
       return sig_bits();
     return (StorageType(1) << UP::SIG_LEN) | sig_bits();
@@ -422,44 +462,50 @@ struct FPRepSem<FPType::X86_Binary80, RetT>
                 "whole significand");
 
 protected:
-  using BiasedExp = typename UP::BiasedExponent;
-  using Sig = typename UP::Significand;
+  using typename UP::Exponent;
+  using typename UP::Significand;
   using UP::encode;
   using UP::UP;
 
 public:
   // Builders
+  LIBC_INLINE static constexpr RetT zero(Sign sign = Sign::POS) {
+    return RetT(encode(sign, Exponent::SUB(), Significand::ZERO()));
+  }
   LIBC_INLINE static constexpr RetT one(Sign sign = Sign::POS) {
-    return RetT(encode(sign, Exponent::ZERO(), Sig::MSB()));
+    return RetT(encode(sign, Exponent::ZERO(), Significand::MSB()));
   }
   LIBC_INLINE static constexpr RetT min_subnormal(Sign sign = Sign::POS) {
-    return RetT(encode(sign, BiasedExp::BITS_ALL_ZEROES(), Sig::LSB()));
+    return RetT(encode(sign, Exponent::SUB(), Significand::LSB()));
   }
   LIBC_INLINE static constexpr RetT max_subnormal(Sign sign = Sign::POS) {
-    return RetT(encode(sign, BiasedExp::BITS_ALL_ZEROES(),
-                       Sig::BITS_ALL_ONES() ^ Sig::MSB()));
+    return RetT(encode(sign, Exponent::SUB(),
+                       Significand::BITS_ALL_ONES() ^ Significand::MSB()));
   }
   LIBC_INLINE static constexpr RetT min_normal(Sign sign = Sign::POS) {
-    return RetT(encode(sign, Exponent::MIN(), Sig::MSB()));
+    return RetT(encode(sign, Exponent::MIN(), Significand::MSB()));
   }
   LIBC_INLINE static constexpr RetT max_normal(Sign sign = Sign::POS) {
-    return RetT(encode(sign, Exponent::MAX(), Sig::BITS_ALL_ONES()));
+    return RetT(encode(sign, Exponent::MAX(), Significand::BITS_ALL_ONES()));
   }
   LIBC_INLINE static constexpr RetT inf(Sign sign = Sign::POS) {
-    return RetT(encode(sign, BiasedExp::BITS_ALL_ONES(), Sig::MSB()));
+    return RetT(encode(sign, Exponent::INF(), Significand::MSB()));
   }
   LIBC_INLINE static constexpr RetT signaling_nan(Sign sign = Sign::POS,
                                                   StorageType v = 0) {
-    return RetT(encode(sign, BiasedExp::BITS_ALL_ONES(),
-                       Sig::MSB() | (v ? Sig(v) : (Sig::MSB() >> 2))));
+    return RetT(encode(sign, Exponent::INF(),
+                       Significand::MSB() |
+                           (v ? Significand(v) : (Significand::MSB() >> 2))));
   }
   LIBC_INLINE static constexpr RetT quiet_nan(Sign sign = Sign::POS,
                                               StorageType v = 0) {
-    return RetT(encode(sign, BiasedExp::BITS_ALL_ONES(),
-                       Sig::MSB() | (Sig::MSB() >> 1) | Sig(v)));
+    return RetT(encode(sign, Exponent::INF(),
+                       Significand::MSB() | (Significand::MSB() >> 1) |
+                           Significand(v)));
   }
 
   // Observers
+  LIBC_INLINE constexpr bool is_zero() const { return exp_sig_bits() == 0; }
   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
@@ -472,33 +518,33 @@ struct FPRepSem<FPType::X86_Binary80, RetT>
     // - Quiet Not a Number
     // - Unnormal
     // This can be reduced to the following logic:
-    if (exp_bits() == encode(BiasedExp::BITS_ALL_ONES()))
+    if (exp_bits() == encode(Exponent::INF()))
       return !is_inf();
-    if (exp_bits() != encode(BiasedExp::BITS_ALL_ZEROES()))
-      return (sig_bits() & encode(Sig::MSB())) == 0;
+    if (exp_bits() != encode(Exponent::SUB()))
+      return (sig_bits() & encode(Significand::MSB())) == 0;
     return false;
   }
   LIBC_INLINE constexpr bool is_quiet_nan() const {
     return exp_sig_bits() >=
-           encode(BiasedExp::BITS_ALL_ONES(), Sig::MSB() | (Sig::MSB() >> 1));
+           encode(Exponent::INF(),
+                  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(BiasedExp::BITS_ALL_ONES(), Sig::MSB());
+    return exp_sig_bits() == encode(Exponent::INF(), Significand::MSB());
   }
   LIBC_INLINE constexpr bool is_finite() const {
     return !is_inf() && !is_nan();
   }
   LIBC_INLINE
   constexpr bool is_subnormal() const {
-    return exp_bits() == encode(BiasedExp::BITS_ALL_ZEROES());
+    return exp_bits() == encode(Exponent::SUB());
   }
   LIBC_INLINE constexpr bool is_normal() const {
     const auto exp = exp_bits();
-    if (exp == encode(BiasedExp::BITS_ALL_ZEROES()) ||
-        exp == encode(BiasedExp::BITS_ALL_ONES()))
+    if (exp == encode(Exponent::SUB()) || exp == encode(Exponent::INF()))
       return false;
     return get_implicit_bit();
   }
@@ -520,21 +566,20 @@ struct FPRepSem<FPType::X86_Binary80, RetT>
   }
 };
 
-// 'FPRep' is the bottom of the class hierarchy that only deals with 'FPType'.
-// The operations dealing with specific float semantics are implemented by
-// 'FPRepSem' above and specialized when needed.
+// 'FPRepImpl' is the bottom of the class hierarchy that only deals with
+// 'FPType'. The operations dealing with specific float semantics are
+// implemented by 'FPRepSem' above and specialized when needed.
 //
 // The 'RetT' type is being propagated up to 'FPRepSem' so that the functions
 // creating new values (Builders) can return the appropriate type. That is, when
 // creating a value through 'FPBits' below the builder will return an 'FPBits'
 // value:
 // i.e., FPBits<float>::zero() // returns an FPBits<float>
-// When we don't care about specific C++ floating point type we can use 'FPRep'
-// directly and 'RetT' defaults to 'StorageType':
-// i.e., FPRep<FPType:IEEE754_Binary32:>::zero() // returns an 'uint32_t'
-template <FPType fp_type,
-          typename RetT = typename FPLayout<fp_type>::StorageType>
-struct FPRep : public FPRepSem<fp_type, RetT> {
+// When we don't care about specific C++ floating point type we can use
+// 'FPRepImpl' directly and 'RetT' defaults to 'StorageType': i.e.,
+// FPRepImpl<FPType:IEEE754_Binary32:>::zero() // returns an 'uint32_t'
+template <FPType fp_type, typename RetT>
+struct FPRepImpl : public FPRepSem<fp_type, RetT> {
   using UP = FPRepSem<fp_type, RetT>;
   using StorageType = typename UP::StorageType;
 
@@ -544,8 +589,9 @@ struct FPRep : public FPRepSem<fp_type, RetT> {
   using UP::exp_bits;
   using UP::exp_sig_bits;
 
-  using BiasedExp = typename UP::BiasedExponent;
-  using Sig = typename UP::Significand;
+  using typename UP::BiasedExponent;
+  using typename UP::Exponent;
+  using typename UP::Significand;
 
   using UP::FP_MASK;
   using UP::SIG_LEN;
@@ -559,14 +605,15 @@ struct FPRep : public FPRepSem<fp_type, RetT> {
   LIBC_INLINE_VAR static constexpr int MAX_BIASED_EXPONENT =
       (1 << UP::EXP_LEN) - 1;
 
-  LIBC_INLINE constexpr FPRep() = default;
-  LIBC_INLINE constexpr explicit FPRep(StorageType x) : UP(x) {}
+  // CTors
+  LIBC_INLINE constexpr FPRepImpl() = default;
+  LIBC_INLINE constexpr explicit FPRepImpl(StorageType x) : UP(x) {}
 
   // Comparison
-  LIBC_INLINE constexpr friend bool operator==(FPRep a, FPRep b) {
+  LIBC_INLINE constexpr friend bool operator==(FPRepImpl a, FPRepImpl b) {
     return a.uintval() == b.uintval();
   }
-  LIBC_INLINE constexpr friend bool operator!=(FPRep a, FPRep b) {
+  LIBC_INLINE constexpr friend bool operator!=(FPRepImpl a, FPRepImpl b) {
     return a.uintval() != b.uintval();
   }
 
@@ -577,9 +624,6 @@ struct FPRep : public FPRepSem<fp_type, RetT> {
   }
 
   // Builders
-  LIBC_INLINE static constexpr RetT zero(Sign sign = Sign::POS) {
-    return RetT(encode(sign, BiasedExp::BITS_ALL_ZEROES(), Sig::ZERO()));
-  }
   using UP::inf;
   using UP::max_normal;
   using UP::max_subnormal;
@@ -588,6 +632,7 @@ struct FPRep : public FPRepSem<fp_type, RetT> {
   using UP::one;
   using UP::quiet_nan;
   using UP::signaling_nan;
+  using UP::zero;
 
   // Modifiers
   LIBC_INLINE constexpr RetT abs() const {
@@ -596,8 +641,6 @@ struct FPRep : public FPRepSem<fp_type, RetT> {
 
   // Observers
   using UP::get_explicit_mantissa;
-  LIBC_INLINE constexpr bool is_zero() const { return exp_sig_bits() == 0; }
-  LIBC_INLINE constexpr bool is_inf_or_nan() const { return !is_finite(); }
   using UP::is_finite;
   using UP::is_inf;
   using UP::is_nan;
@@ -605,29 +648,22 @@ struct FPRep : public FPRepSem<fp_type, RetT> {
   using UP::is_quiet_nan;
   using UP::is_signaling_nan;
   using UP::is_subnormal;
+  using UP::is_zero;
+  using UP::sign;
+  LIBC_INLINE constexpr bool is_inf_or_nan() const { return !is_finite(); }
   LIBC_INLINE constexpr bool is_neg() const { return sign().is_neg(); }
   LIBC_INLINE constexpr bool is_pos() const { return sign().is_pos(); }
 
-  // Parts
-  LIBC_INLINE constexpr Sign sign() const {
-    return (bits & SIGN_MASK) ? Sign::NEG : Sign::POS;
-  }
-
-  LIBC_INLINE constexpr void set_sign(Sign signVal) {
-    if (sign() != signVal)
-      bits ^= SIGN_MASK;
-  }
-
   LIBC_INLINE constexpr uint16_t get_biased_exponent() const {
-    return uint16_t((bits & UP::EXP_MASK) >> UP::SIG_LEN);
+    return static_cast<uint16_t>(static_cast<uint32_t>(UP::biased_exponent()));
   }
 
   LIBC_INLINE constexpr void set_biased_exponent(StorageType biased) {
-    bits = merge(bits, biased << SIG_LEN, EXP_MASK);
+    UP::set_biased_exponent(BiasedExponent((int32_t)biased));
   }
 
   LIBC_INLINE constexpr int get_exponent() const {
-    return int(get_biased_exponent()) - EXP_BIAS;
+    return static_cast<int32_t>(Exponent(UP::biased_exponent()));
   }
 
   // If the number is subnormal, the exponent is treated as if it were the
@@ -637,14 +673,12 @@ struc...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/80453


More information about the libc-commits mailing list