Skip to content
This repository has been archived by the owner on Jan 20, 2024. It is now read-only.

Commit

Permalink
[libc][NFC] Simplify FPBits (#76835)
Browse files Browse the repository at this point in the history
This patch reduces the scope of `FPBits` exported variables and
functions.
It also moves storage up into `FPRep` and tries to make the default and
specialized versions of `FPBits` more uniform.

The next step is to move the specialization from `FPBits` to `FPRep` so
we can manipulate floating point representations through `FPType` 
alone - that is - independently from the host architecture.
  • Loading branch information
gchatelet committed Jan 4, 2024
1 parent 79e6231 commit d02471e
Show file tree
Hide file tree
Showing 11 changed files with 172 additions and 190 deletions.
204 changes: 96 additions & 108 deletions libc/src/__support/FPUtil/FPBits.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,6 @@ enum class FPType {

namespace internal {

// The type of encoding for supported floating point types.
enum class FPEncoding {
IEEE754,
X86_ExtendedPrecision,
};

// Defines the layout (sign, exponent, significand) of a floating point type in
// memory. It also defines its associated StorageType, i.e., the unsigned
// integer type used to manipulate its representation.
Expand All @@ -49,47 +43,41 @@ template <> struct FPLayout<FPType::IEEE754_Binary16> {
LIBC_INLINE_VAR static constexpr int SIGN_LEN = 1;
LIBC_INLINE_VAR static constexpr int EXP_LEN = 5;
LIBC_INLINE_VAR static constexpr int SIG_LEN = 10;
LIBC_INLINE_VAR static constexpr auto ENCODING = FPEncoding::IEEE754;
};

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

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

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

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

} // namespace internal

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

Expand Down Expand Up @@ -149,95 +137,67 @@ struct FPBaseMasksAndShifts : public internal::FPLayout<fp_type> {
return StorageType(1) << position;
}

public:
// 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);
}

protected:
// The number of bits after the decimal dot when the number is in normal form.
LIBC_INLINE_VAR static constexpr int FRACTION_LEN =
UP::ENCODING == internal::FPEncoding::X86_ExtendedPrecision ? SIG_LEN - 1
: SIG_LEN;
fp_type == FPType::X86_Binary80 ? SIG_LEN - 1 : SIG_LEN;
LIBC_INLINE_VAR static constexpr uint32_t MANTISSA_PRECISION =
FRACTION_LEN + 1;
LIBC_INLINE_VAR static constexpr StorageType FRACTION_MASK =
mask_trailing_ones<StorageType, FRACTION_LEN>();

protected:
// 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 =
UP::ENCODING == internal::FPEncoding::X86_ExtendedPrecision
fp_type == FPType::X86_Binary80
? bit_at(SIG_LEN - 1) | bit_at(SIG_LEN - 2) // 0b1100...
: bit_at(SIG_LEN - 1); // 0b1000...

// If a number x is a NAN, then it is a signalling NAN if:
// SIGNALING_NAN_MASK & bits(x) != 0
LIBC_INLINE_VAR static constexpr StorageType SIGNALING_NAN_MASK =
UP::ENCODING == internal::FPEncoding::X86_ExtendedPrecision
fp_type == FPType::X86_Binary80
? bit_at(SIG_LEN - 1) | bit_at(SIG_LEN - 3) // 0b1010...
: bit_at(SIG_LEN - 2); // 0b0100...
};

namespace internal {

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

protected:
using UP::EXP_SIG_MASK;
using UP::QUIET_NAN_MASK;
// The floating point number representation as an unsigned integer.
StorageType bits = 0;

public:
using UP::EXP_BIAS;
using UP::EXP_LEN;
using UP::EXP_MASK;
using UP::EXP_MASK_SHIFT;
using UP::FP_MASK;
using UP::FRACTION_LEN;
using UP::FRACTION_MASK;
using UP::MANTISSA_PRECISION;
using UP::SIGN_MASK;
using UP::STORAGE_LEN;

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

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

LIBC_INLINE constexpr void set_mantissa(StorageType mantVal) {
mantVal &= FRACTION_MASK;
bits &= ~FRACTION_MASK;
bits |= mantVal;
}

LIBC_INLINE constexpr StorageType get_mantissa() const {
return bits & FRACTION_MASK;
LIBC_INLINE constexpr bool get_sign() const {
return (bits & SIGN_MASK) != 0;
}

LIBC_INLINE constexpr void set_sign(bool signVal) {
if (get_sign() != signVal)
bits ^= SIGN_MASK;
}

LIBC_INLINE constexpr bool get_sign() const {
return (bits & SIGN_MASK) != 0;
LIBC_INLINE constexpr StorageType get_mantissa() const {
return bits & FRACTION_MASK;
}

LIBC_INLINE constexpr void set_biased_exponent(StorageType biased) {
// clear exponent bits
bits &= ~EXP_MASK;
// set exponent bits
bits |= (biased << EXP_MASK_SHIFT) & EXP_MASK;
LIBC_INLINE constexpr void set_mantissa(StorageType mantVal) {
bits = merge(bits, mantVal, FRACTION_MASK);
}

LIBC_INLINE constexpr uint16_t get_biased_exponent() const {
return uint16_t((bits & EXP_MASK) >> EXP_MASK_SHIFT);
}

LIBC_INLINE constexpr void set_biased_exponent(StorageType biased) {
bits = merge(bits, biased << EXP_MASK_SHIFT, EXP_MASK);
}

LIBC_INLINE constexpr int get_exponent() const {
return int(get_biased_exponent()) - EXP_BIAS;
}
Expand Down Expand Up @@ -266,6 +226,23 @@ template <FPType fp_type> struct FPRep : private FPBaseMasksAndShifts<fp_type> {
}
};

namespace internal {

// Manipulates the representation of a floating point number defined by its
// FPType. This layer is architecture agnostic and does not handle C++ floating
// point types directly ('float', 'double' and 'long double'). Use the FPBits
// below if needed.
//
// TODO: Specialize this class for FPType::X86_Binary80 and remove ad-hoc logic
// from FPRepBase.
template <FPType fp_type> struct FPRep : public FPRepBase<fp_type> {
using UP = FPRepBase<fp_type>;
using typename UP::StorageType;
using UP::FRACTION_LEN;
using UP::FRACTION_MASK;
using UP::MANTISSA_PRECISION;
};

} // namespace internal

// Returns the FPType corresponding to C++ type T on the host.
Expand Down Expand Up @@ -311,14 +288,16 @@ template <typename T> struct FPBits : public internal::FPRep<get_fp_type<T>()> {
static_assert(cpp::is_floating_point_v<T>,
"FPBits instantiated with invalid type.");
using UP = internal::FPRep<get_fp_type<T>()>;
using StorageType = typename UP::StorageType;
using UP::bits;

private:
using UP::EXP_SIG_MASK;
using UP::QUIET_NAN_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;
Expand All @@ -327,46 +306,47 @@ template <typename T> struct FPBits : public internal::FPRep<get_fp_type<T>()> {
using UP::FRACTION_MASK;
using UP::SIGN_MASK;
using UP::TOTAL_LEN;
using UP::UP;

using UP::get_biased_exponent;
using UP::is_zero;

// 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);
}

// 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) << FRACTION_LEN) | MAX_SUBNORMAL;

// We don't want accidental type promotions/conversions, so we require exact
// type match.
template <typename XType, cpp::enable_if_t<cpp::is_same_v<T, XType>, int> = 0>
LIBC_INLINE constexpr explicit FPBits(XType x)
: UP(cpp::bit_cast<StorageType>(x)) {}
(StorageType(MAX_BIASED_EXPONENT - 1) << SIG_LEN) | SIG_MASK;

template <typename XType,
cpp::enable_if_t<cpp::is_same_v<XType, StorageType>, int> = 0>
LIBC_INLINE constexpr explicit FPBits(XType x) : UP(x) {}
// Constructors.
LIBC_INLINE constexpr FPBits() = default;

LIBC_INLINE constexpr FPBits() : UP() {}

LIBC_INLINE constexpr void set_val(T value) {
bits = cpp::bit_cast<StorageType>(value);
template <typename XType> LIBC_INLINE constexpr explicit FPBits(XType x) {
using Unqual = typename cpp::remove_cv_t<XType>;
if constexpr (cpp::is_same_v<Unqual, T>) {
bits = cpp::bit_cast<StorageType>(x);
} else if constexpr (cpp::is_same_v<Unqual, StorageType>) {
bits = x;
} else {
// We don't want accidental type promotions/conversions, so we require
// exact type match.
static_assert(cpp::always_false<XType>);
}
}

// Floating-point conversions.
LIBC_INLINE constexpr T get_val() const { return cpp::bit_cast<T>(bits); }

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;
}
Expand All @@ -387,14 +367,22 @@ template <typename T> struct FPBits : public internal::FPRep<get_fp_type<T>()> {
return FPBits(bits & EXP_SIG_MASK);
}

// Methods below this are used by tests.

LIBC_INLINE static constexpr T zero(bool sign = false) {
return FPBits(sign ? SIGN_MASK : StorageType(0)).get_val();
StorageType rep = (sign ? SIGN_MASK : StorageType(0)) // sign
| 0 // exponent
| 0; // mantissa
return FPBits(rep).get_val();
}

LIBC_INLINE static constexpr T neg_zero() { return zero(true); }

LIBC_INLINE static constexpr T inf(bool sign = false) {
return FPBits((sign ? SIGN_MASK : StorageType(0)) | EXP_MASK).get_val();
StorageType rep = (sign ? SIGN_MASK : StorageType(0)) // sign
| EXP_MASK // exponent
| 0; // mantissa
return FPBits(rep).get_val();
}

LIBC_INLINE static constexpr T neg_inf() { return inf(true); }
Expand All @@ -416,15 +404,24 @@ template <typename T> struct FPBits : public internal::FPRep<get_fp_type<T>()> {
}

LIBC_INLINE static constexpr T build_nan(StorageType v) {
FPBits<T> bits(inf());
bits.set_mantissa(v);
return T(bits);
StorageType rep = 0 // sign
| EXP_MASK // exponent
| (v & FRACTION_MASK); // mantissa
return FPBits(rep).get_val();
}

LIBC_INLINE static constexpr T build_quiet_nan(StorageType v) {
return build_nan(QUIET_NAN_MASK | v);
}

LIBC_INLINE static constexpr FPBits<T>
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);
}

// The function convert integer number and unbiased exponent to proper float
// T type:
// Result = number * 2^(ep+1 - exponent_bias)
Expand Down Expand Up @@ -452,15 +449,6 @@ template <typename T> struct FPBits : public internal::FPRep<get_fp_type<T>()> {
}
return result;
}

LIBC_INLINE static constexpr FPBits<T>
create_value(bool sign, StorageType biased_exp, StorageType mantissa) {
FPBits<T> result;
result.set_sign(sign);
result.set_biased_exponent(biased_exp);
result.set_mantissa(mantissa);
return result;
}
};

} // namespace fputil
Expand Down
2 changes: 1 addition & 1 deletion libc/src/__support/FPUtil/fpbits_str.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ template <typename T> LIBC_INLINE cpp::string str(fputil::FPBits<T> x) {

cpp::string s;

const details::ZeroPaddedHexFmt<StorageType> bits(x.bits);
const details::ZeroPaddedHexFmt<StorageType> bits(x.uintval());
s += bits.view();

s += " = (S: ";
Expand Down
Loading

0 comments on commit d02471e

Please sign in to comment.