Skip to content

Commit d02471e

Browse files
authored
[libc][NFC] Simplify FPBits (#76835)
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.
1 parent 79e6231 commit d02471e

File tree

11 files changed

+172
-190
lines changed

11 files changed

+172
-190
lines changed

libc/src/__support/FPUtil/FPBits.h

Lines changed: 96 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,6 @@ enum class FPType {
3333

3434
namespace internal {
3535

36-
// The type of encoding for supported floating point types.
37-
enum class FPEncoding {
38-
IEEE754,
39-
X86_ExtendedPrecision,
40-
};
41-
4236
// Defines the layout (sign, exponent, significand) of a floating point type in
4337
// memory. It also defines its associated StorageType, i.e., the unsigned
4438
// integer type used to manipulate its representation.
@@ -49,47 +43,41 @@ template <> struct FPLayout<FPType::IEEE754_Binary16> {
4943
LIBC_INLINE_VAR static constexpr int SIGN_LEN = 1;
5044
LIBC_INLINE_VAR static constexpr int EXP_LEN = 5;
5145
LIBC_INLINE_VAR static constexpr int SIG_LEN = 10;
52-
LIBC_INLINE_VAR static constexpr auto ENCODING = FPEncoding::IEEE754;
5346
};
5447

5548
template <> struct FPLayout<FPType::IEEE754_Binary32> {
5649
using StorageType = uint32_t;
5750
LIBC_INLINE_VAR static constexpr int SIGN_LEN = 1;
5851
LIBC_INLINE_VAR static constexpr int EXP_LEN = 8;
5952
LIBC_INLINE_VAR static constexpr int SIG_LEN = 23;
60-
LIBC_INLINE_VAR static constexpr auto ENCODING = FPEncoding::IEEE754;
6153
};
6254

6355
template <> struct FPLayout<FPType::IEEE754_Binary64> {
6456
using StorageType = uint64_t;
6557
LIBC_INLINE_VAR static constexpr int SIGN_LEN = 1;
6658
LIBC_INLINE_VAR static constexpr int EXP_LEN = 11;
6759
LIBC_INLINE_VAR static constexpr int SIG_LEN = 52;
68-
LIBC_INLINE_VAR static constexpr auto ENCODING = FPEncoding::IEEE754;
6960
};
7061

7162
template <> struct FPLayout<FPType::IEEE754_Binary128> {
7263
using StorageType = UInt128;
7364
LIBC_INLINE_VAR static constexpr int SIGN_LEN = 1;
7465
LIBC_INLINE_VAR static constexpr int EXP_LEN = 15;
7566
LIBC_INLINE_VAR static constexpr int SIG_LEN = 112;
76-
LIBC_INLINE_VAR static constexpr auto ENCODING = FPEncoding::IEEE754;
7767
};
7868

7969
template <> struct FPLayout<FPType::X86_Binary80> {
8070
using StorageType = UInt128;
8171
LIBC_INLINE_VAR static constexpr int SIGN_LEN = 1;
8272
LIBC_INLINE_VAR static constexpr int EXP_LEN = 15;
8373
LIBC_INLINE_VAR static constexpr int SIG_LEN = 64;
84-
LIBC_INLINE_VAR static constexpr auto ENCODING =
85-
FPEncoding::X86_ExtendedPrecision;
8674
};
8775

8876
} // namespace internal
8977

90-
// FPBaseMasksAndShifts derives useful constants from the FPLayout.
78+
// FPRepBase derives useful constants from the FPLayout.
9179
template <FPType fp_type>
92-
struct FPBaseMasksAndShifts : public internal::FPLayout<fp_type> {
80+
struct FPRepBase : public internal::FPLayout<fp_type> {
9381
private:
9482
using UP = internal::FPLayout<fp_type>;
9583

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

152-
public:
140+
// Merge bits from 'a' and 'b' values according to 'mask'.
141+
// Use 'a' bits when corresponding 'mask' bits are zeroes and 'b' bits when
142+
// corresponding bits are ones.
143+
LIBC_INLINE static constexpr StorageType merge(StorageType a, StorageType b,
144+
StorageType mask) {
145+
// https://graphics.stanford.edu/~seander/bithacks.html#MaskedMerge
146+
return a ^ ((a ^ b) & mask);
147+
}
148+
149+
protected:
153150
// The number of bits after the decimal dot when the number is in normal form.
154151
LIBC_INLINE_VAR static constexpr int FRACTION_LEN =
155-
UP::ENCODING == internal::FPEncoding::X86_ExtendedPrecision ? SIG_LEN - 1
156-
: SIG_LEN;
152+
fp_type == FPType::X86_Binary80 ? SIG_LEN - 1 : SIG_LEN;
157153
LIBC_INLINE_VAR static constexpr uint32_t MANTISSA_PRECISION =
158154
FRACTION_LEN + 1;
159155
LIBC_INLINE_VAR static constexpr StorageType FRACTION_MASK =
160156
mask_trailing_ones<StorageType, FRACTION_LEN>();
161157

162-
protected:
163158
// If a number x is a NAN, then it is a quiet NAN if:
164159
// QUIET_NAN_MASK & bits(x) != 0
165160
LIBC_INLINE_VAR static constexpr StorageType QUIET_NAN_MASK =
166-
UP::ENCODING == internal::FPEncoding::X86_ExtendedPrecision
161+
fp_type == FPType::X86_Binary80
167162
? bit_at(SIG_LEN - 1) | bit_at(SIG_LEN - 2) // 0b1100...
168163
: bit_at(SIG_LEN - 1); // 0b1000...
169164

170165
// If a number x is a NAN, then it is a signalling NAN if:
171166
// SIGNALING_NAN_MASK & bits(x) != 0
172167
LIBC_INLINE_VAR static constexpr StorageType SIGNALING_NAN_MASK =
173-
UP::ENCODING == internal::FPEncoding::X86_ExtendedPrecision
168+
fp_type == FPType::X86_Binary80
174169
? bit_at(SIG_LEN - 1) | bit_at(SIG_LEN - 3) // 0b1010...
175170
: bit_at(SIG_LEN - 2); // 0b0100...
176-
};
177-
178-
namespace internal {
179171

180-
// This is a temporary class to unify common methods and properties between
181-
// FPBits and FPBits<long double>.
182-
template <FPType fp_type> struct FPRep : private FPBaseMasksAndShifts<fp_type> {
183-
using UP = FPBaseMasksAndShifts<fp_type>;
184-
using typename UP::StorageType;
185-
using UP::TOTAL_LEN;
186-
187-
protected:
188-
using UP::EXP_SIG_MASK;
189-
using UP::QUIET_NAN_MASK;
172+
// The floating point number representation as an unsigned integer.
173+
StorageType bits = 0;
190174

191175
public:
192-
using UP::EXP_BIAS;
193-
using UP::EXP_LEN;
194-
using UP::EXP_MASK;
195-
using UP::EXP_MASK_SHIFT;
196-
using UP::FP_MASK;
197-
using UP::FRACTION_LEN;
198-
using UP::FRACTION_MASK;
199-
using UP::MANTISSA_PRECISION;
200-
using UP::SIGN_MASK;
201-
using UP::STORAGE_LEN;
202-
203-
// Reinterpreting bits as an integer value and interpreting the bits of an
204-
// integer value as a floating point value is used in tests. So, a convenient
205-
// type is provided for such reinterpretations.
206-
StorageType bits;
207-
208-
LIBC_INLINE constexpr FPRep() : bits(0) {}
209-
LIBC_INLINE explicit constexpr FPRep(StorageType bits) : bits(bits) {}
210-
211-
LIBC_INLINE constexpr void set_mantissa(StorageType mantVal) {
212-
mantVal &= FRACTION_MASK;
213-
bits &= ~FRACTION_MASK;
214-
bits |= mantVal;
215-
}
216-
217-
LIBC_INLINE constexpr StorageType get_mantissa() const {
218-
return bits & FRACTION_MASK;
176+
LIBC_INLINE constexpr bool get_sign() const {
177+
return (bits & SIGN_MASK) != 0;
219178
}
220179

221180
LIBC_INLINE constexpr void set_sign(bool signVal) {
222181
if (get_sign() != signVal)
223182
bits ^= SIGN_MASK;
224183
}
225184

226-
LIBC_INLINE constexpr bool get_sign() const {
227-
return (bits & SIGN_MASK) != 0;
185+
LIBC_INLINE constexpr StorageType get_mantissa() const {
186+
return bits & FRACTION_MASK;
228187
}
229188

230-
LIBC_INLINE constexpr void set_biased_exponent(StorageType biased) {
231-
// clear exponent bits
232-
bits &= ~EXP_MASK;
233-
// set exponent bits
234-
bits |= (biased << EXP_MASK_SHIFT) & EXP_MASK;
189+
LIBC_INLINE constexpr void set_mantissa(StorageType mantVal) {
190+
bits = merge(bits, mantVal, FRACTION_MASK);
235191
}
236192

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

197+
LIBC_INLINE constexpr void set_biased_exponent(StorageType biased) {
198+
bits = merge(bits, biased << EXP_MASK_SHIFT, EXP_MASK);
199+
}
200+
241201
LIBC_INLINE constexpr int get_exponent() const {
242202
return int(get_biased_exponent()) - EXP_BIAS;
243203
}
@@ -266,6 +226,23 @@ template <FPType fp_type> struct FPRep : private FPBaseMasksAndShifts<fp_type> {
266226
}
267227
};
268228

229+
namespace internal {
230+
231+
// Manipulates the representation of a floating point number defined by its
232+
// FPType. This layer is architecture agnostic and does not handle C++ floating
233+
// point types directly ('float', 'double' and 'long double'). Use the FPBits
234+
// below if needed.
235+
//
236+
// TODO: Specialize this class for FPType::X86_Binary80 and remove ad-hoc logic
237+
// from FPRepBase.
238+
template <FPType fp_type> struct FPRep : public FPRepBase<fp_type> {
239+
using UP = FPRepBase<fp_type>;
240+
using typename UP::StorageType;
241+
using UP::FRACTION_LEN;
242+
using UP::FRACTION_MASK;
243+
using UP::MANTISSA_PRECISION;
244+
};
245+
269246
} // namespace internal
270247

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

317292
private:
318293
using UP::EXP_SIG_MASK;
319294
using UP::QUIET_NAN_MASK;
295+
using UP::SIG_LEN;
296+
using UP::SIG_MASK;
320297

321298
public:
299+
using StorageType = typename UP::StorageType;
300+
using UP::bits;
322301
using UP::EXP_BIAS;
323302
using UP::EXP_LEN;
324303
using UP::EXP_MASK;
@@ -327,46 +306,47 @@ template <typename T> struct FPBits : public internal::FPRep<get_fp_type<T>()> {
327306
using UP::FRACTION_MASK;
328307
using UP::SIGN_MASK;
329308
using UP::TOTAL_LEN;
309+
using UP::UP;
330310

331311
using UP::get_biased_exponent;
332312
using UP::is_zero;
333-
334-
// The function return mantissa with the implicit bit set iff the current
335-
// value is a valid normal number.
336-
LIBC_INLINE constexpr StorageType get_explicit_mantissa() {
337-
return ((get_biased_exponent() > 0 && !is_inf_or_nan())
338-
? (FRACTION_MASK + 1)
339-
: 0) |
340-
(FRACTION_MASK & bits);
341-
}
342-
313+
// Constants.
343314
static constexpr int MAX_BIASED_EXPONENT = (1 << EXP_LEN) - 1;
344315
static constexpr StorageType MIN_SUBNORMAL = StorageType(1);
345316
static constexpr StorageType MAX_SUBNORMAL = FRACTION_MASK;
346317
static constexpr StorageType MIN_NORMAL = (StorageType(1) << FRACTION_LEN);
347318
static constexpr StorageType MAX_NORMAL =
348-
((StorageType(MAX_BIASED_EXPONENT) - 1) << FRACTION_LEN) | MAX_SUBNORMAL;
349-
350-
// We don't want accidental type promotions/conversions, so we require exact
351-
// type match.
352-
template <typename XType, cpp::enable_if_t<cpp::is_same_v<T, XType>, int> = 0>
353-
LIBC_INLINE constexpr explicit FPBits(XType x)
354-
: UP(cpp::bit_cast<StorageType>(x)) {}
319+
(StorageType(MAX_BIASED_EXPONENT - 1) << SIG_LEN) | SIG_MASK;
355320

356-
template <typename XType,
357-
cpp::enable_if_t<cpp::is_same_v<XType, StorageType>, int> = 0>
358-
LIBC_INLINE constexpr explicit FPBits(XType x) : UP(x) {}
321+
// Constructors.
322+
LIBC_INLINE constexpr FPBits() = default;
359323

360-
LIBC_INLINE constexpr FPBits() : UP() {}
361-
362-
LIBC_INLINE constexpr void set_val(T value) {
363-
bits = cpp::bit_cast<StorageType>(value);
324+
template <typename XType> LIBC_INLINE constexpr explicit FPBits(XType x) {
325+
using Unqual = typename cpp::remove_cv_t<XType>;
326+
if constexpr (cpp::is_same_v<Unqual, T>) {
327+
bits = cpp::bit_cast<StorageType>(x);
328+
} else if constexpr (cpp::is_same_v<Unqual, StorageType>) {
329+
bits = x;
330+
} else {
331+
// We don't want accidental type promotions/conversions, so we require
332+
// exact type match.
333+
static_assert(cpp::always_false<XType>);
334+
}
364335
}
365-
336+
// Floating-point conversions.
366337
LIBC_INLINE constexpr T get_val() const { return cpp::bit_cast<T>(bits); }
367338

368339
LIBC_INLINE constexpr explicit operator T() const { return get_val(); }
369340

341+
// The function return mantissa with the implicit bit set iff the current
342+
// value is a valid normal number.
343+
LIBC_INLINE constexpr StorageType get_explicit_mantissa() {
344+
return ((get_biased_exponent() > 0 && !is_inf_or_nan())
345+
? (FRACTION_MASK + 1)
346+
: 0) |
347+
(FRACTION_MASK & bits);
348+
}
349+
370350
LIBC_INLINE constexpr bool is_inf() const {
371351
return (bits & EXP_SIG_MASK) == EXP_MASK;
372352
}
@@ -387,14 +367,22 @@ template <typename T> struct FPBits : public internal::FPRep<get_fp_type<T>()> {
387367
return FPBits(bits & EXP_SIG_MASK);
388368
}
389369

370+
// Methods below this are used by tests.
371+
390372
LIBC_INLINE static constexpr T zero(bool sign = false) {
391-
return FPBits(sign ? SIGN_MASK : StorageType(0)).get_val();
373+
StorageType rep = (sign ? SIGN_MASK : StorageType(0)) // sign
374+
| 0 // exponent
375+
| 0; // mantissa
376+
return FPBits(rep).get_val();
392377
}
393378

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

396381
LIBC_INLINE static constexpr T inf(bool sign = false) {
397-
return FPBits((sign ? SIGN_MASK : StorageType(0)) | EXP_MASK).get_val();
382+
StorageType rep = (sign ? SIGN_MASK : StorageType(0)) // sign
383+
| EXP_MASK // exponent
384+
| 0; // mantissa
385+
return FPBits(rep).get_val();
398386
}
399387

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

418406
LIBC_INLINE static constexpr T build_nan(StorageType v) {
419-
FPBits<T> bits(inf());
420-
bits.set_mantissa(v);
421-
return T(bits);
407+
StorageType rep = 0 // sign
408+
| EXP_MASK // exponent
409+
| (v & FRACTION_MASK); // mantissa
410+
return FPBits(rep).get_val();
422411
}
423412

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

417+
LIBC_INLINE static constexpr FPBits<T>
418+
create_value(bool sign, StorageType biased_exp, StorageType mantissa) {
419+
StorageType rep = (sign ? SIGN_MASK : StorageType(0)) // sign
420+
| ((biased_exp << EXP_MASK_SHIFT) & EXP_MASK) // exponent
421+
| (mantissa & FRACTION_MASK); // mantissa
422+
return FPBits(rep);
423+
}
424+
428425
// The function convert integer number and unbiased exponent to proper float
429426
// T type:
430427
// Result = number * 2^(ep+1 - exponent_bias)
@@ -452,15 +449,6 @@ template <typename T> struct FPBits : public internal::FPRep<get_fp_type<T>()> {
452449
}
453450
return result;
454451
}
455-
456-
LIBC_INLINE static constexpr FPBits<T>
457-
create_value(bool sign, StorageType biased_exp, StorageType mantissa) {
458-
FPBits<T> result;
459-
result.set_sign(sign);
460-
result.set_biased_exponent(biased_exp);
461-
result.set_mantissa(mantissa);
462-
return result;
463-
}
464452
};
465453

466454
} // namespace fputil

libc/src/__support/FPUtil/fpbits_str.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ template <typename T> LIBC_INLINE cpp::string str(fputil::FPBits<T> x) {
4545

4646
cpp::string s;
4747

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

5151
s += " = (S: ";

0 commit comments

Comments
 (0)