Skip to content

[libc][NFC] Simplify FPBits #76835

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jan 4, 2024
Merged
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