Skip to content
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

[libc] Add user defined literals to initialize BigInt and __uint128_t constants #81267

Merged
merged 9 commits into from
Feb 14, 2024

Conversation

gchatelet
Copy link
Contributor

@gchatelet gchatelet commented Feb 9, 2024

Adds user defined literal to construct unsigned integer constants. This is useful when constructing constants for non native C++ types like __uint128_t or our custom BigInt type.

@llvmbot llvmbot added the libc label Feb 9, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Feb 9, 2024

@llvm/pr-subscribers-libc

Author: Guillaume Chatelet (gchatelet)

Changes

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

6 Files Affected:

  • (added) libc/src/__support/integer_literals.h (+166)
  • (modified) libc/test/src/__support/FPUtil/fpbits_test.cpp (+177-213)
  • (added) libc/test/src/__support/integer_literals_test.cpp (+134)
  • (modified) utils/bazel/llvm-project-overlay/libc/BUILD.bazel (+8)
  • (modified) utils/bazel/llvm-project-overlay/libc/test/src/__support/BUILD.bazel (+8)
  • (modified) utils/bazel/llvm-project-overlay/libc/test/src/__support/FPUtil/BUILD.bazel (+1)
diff --git a/libc/src/__support/integer_literals.h b/libc/src/__support/integer_literals.h
new file mode 100644
index 00000000000000..c588cf9a63986d
--- /dev/null
+++ b/libc/src/__support/integer_literals.h
@@ -0,0 +1,166 @@
+//===-- User literal for unsigned integers ----------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+// This set of user defined literals allows uniform constructions of constants
+// up to 256 bits and also help with unit tests (EXPECT_EQ requires the same
+// type for LHS and RHS).
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_INTEGER_LITERALS_H
+#define LLVM_LIBC_SRC___SUPPORT_INTEGER_LITERALS_H
+
+#include "src/__support/UInt128.h"           // UInt128
+#include "src/__support/macros/attributes.h" // LIBC_INLINE
+#include <limits.h>                          // CHAR_BIT
+#include <stddef.h>                          // size_t
+#include <stdint.h>                          // __uintxx_t
+
+namespace LIBC_NAMESPACE {
+
+LIBC_INLINE constexpr __uint8_t operator""_u8(unsigned long long value) {
+  return value;
+}
+
+LIBC_INLINE constexpr __uint16_t operator""_u16(unsigned long long value) {
+  return value;
+}
+
+LIBC_INLINE constexpr __uint32_t operator""_u32(unsigned long long value) {
+  return value;
+}
+
+LIBC_INLINE constexpr __uint64_t operator""_u64(unsigned long long value) {
+  return value;
+}
+
+namespace internal {
+
+// Creates a T by reading digits from an array.
+template <typename T>
+LIBC_INLINE constexpr T accumulate(int base, const uint8_t *digits,
+                                   size_t size) {
+  T value{};
+  for (; size; ++digits, --size) {
+    value *= base;
+    value += *digits;
+  }
+  return value;
+}
+
+// A static buffer to hold the digits for a T.
+template <typename T, int base> struct DigitBuffer {
+  static_assert(base == 2 || base == 10 || base == 16);
+  // Base 2: one char provides exactly one bit.
+  // Base 10: one char provides between three and four bits.
+  // Base 16: one char provides exactly four bits.
+  LIBC_INLINE_VAR static constexpr size_t BITS_PER_DIGIT = base == 2    ? 1
+                                                           : base == 10 ? 3
+                                                           : base == 16 ? 4
+                                                                        : 0;
+  LIBC_INLINE_VAR static constexpr size_t MAX_DIGITS =
+      sizeof(T) * CHAR_BIT / BITS_PER_DIGIT;
+
+  uint8_t digits[MAX_DIGITS] = {};
+  size_t size = 0;
+
+  constexpr DigitBuffer(const char *str) {
+    for (; *str != '\0'; ++str)
+      push(*str);
+  }
+
+  // Returns the digit for a particular character.
+  // Returns 255 if the character is invalid.
+  LIBC_INLINE static constexpr uint8_t get_digit_value(const char c) {
+    const auto to_lower = [](char c) { return c | 32; };
+    const auto is_digit = [](char c) { return c >= '0' && c <= '9'; };
+    const auto is_lower = [](char c) { return 'a' <= c && c <= 'z'; };
+    const auto is_upper = [](char c) { return 'A' <= c && c <= 'Z'; };
+    const auto is_alpha = [&](char c) { return is_lower(c) || is_upper(c); };
+    if (is_digit(c))
+      return c - '0';
+    if (base > 10 && is_alpha(c))
+      return to_lower(c) - 'a' + 10;
+    return 255;
+  }
+
+  // Adds a single character to this buffer.
+  LIBC_INLINE constexpr void push(char c) {
+    if (c == '\'')
+      return; // ' is valid but not taken into account.
+    const uint8_t value = get_digit_value(c);
+    if (value == 255 || size >= MAX_DIGITS)
+      __builtin_unreachable(); // invalid or too many characters.
+    digits[size] = value;
+    ++size;
+  }
+};
+
+// Generic implementation for native types (including __uint128_t or ExtInt
+// where available).
+template <typename T> struct Parser {
+  template <int base> LIBC_INLINE static constexpr T parse(const char *str) {
+    const DigitBuffer<T, base> buffer(str);
+    return accumulate<T>(base, buffer.digits, buffer.size);
+  }
+};
+
+// Specialization for cpp::UInt<N>.
+// Because this code runs at compile time we try to make it as efficient as
+// possible. For binary and hexadecimal formats we read digits by chunks of 64
+// bits and produce the BigInt internal representation direcly. For decimal
+// numbers we go the slow path and use BigInt arithmetic.
+template <size_t N> struct Parser<LIBC_NAMESPACE::cpp::UInt<N>> {
+  using UIntT = cpp::UInt<N>;
+  template <int base> static constexpr UIntT parse(const char *str) {
+    const DigitBuffer<UIntT, base> buffer(str);
+    if constexpr (base == 10) {
+      // Slow path, we sum and multiply BigInt for each digit.
+      return accumulate<UIntT>(base, buffer.digits, buffer.size);
+    } else {
+      // Fast path, we consume blocks of uint64_t and creates the BigInt's
+      // internal representation directly.
+      using U64ArrayT = cpp::array<uint64_t, UIntT::WORDCOUNT>;
+      U64ArrayT array;
+      size_t size = buffer.size;
+      const uint8_t *digit_ptr = buffer.digits + size;
+      for (size_t i = 0; i < array.size(); ++i) {
+        constexpr size_t U64_DIGITS = DigitBuffer<uint64_t, base>::MAX_DIGITS;
+        const size_t chunk = size > U64_DIGITS ? U64_DIGITS : size;
+        digit_ptr -= chunk;
+        size -= chunk;
+        array[i] = accumulate<uint64_t>(base, digit_ptr, chunk);
+      }
+      return UIntT(array);
+    }
+  }
+};
+
+// Detects the base of the number and dispatches to the right implementation.
+template <typename T>
+LIBC_INLINE constexpr T parse_with_prefix(const char *ptr) {
+  using P = Parser<T>;
+  if (ptr[0] == '0' && ptr[1] == 'x')
+    return P::template parse<16>(ptr + 2);
+  else if (ptr[0] == '0' && ptr[1] == 'b')
+    return P::template parse<2>(ptr + 2);
+  else
+    return P::template parse<10>(ptr);
+}
+
+} // namespace internal
+
+LIBC_INLINE constexpr UInt128 operator""_u128(const char *x) {
+  return internal::parse_with_prefix<UInt128>(x);
+}
+
+LIBC_INLINE constexpr auto operator""_u256(const char *x) {
+  return internal::parse_with_prefix<cpp::UInt<256>>(x);
+}
+
+} // namespace LIBC_NAMESPACE
+
+#endif // LLVM_LIBC_SRC___SUPPORT_INTEGER_LITERALS_H
diff --git a/libc/test/src/__support/FPUtil/fpbits_test.cpp b/libc/test/src/__support/FPUtil/fpbits_test.cpp
index 4504a4f0cfcc7d..312ef276442e95 100644
--- a/libc/test/src/__support/FPUtil/fpbits_test.cpp
+++ b/libc/test/src/__support/FPUtil/fpbits_test.cpp
@@ -8,6 +8,7 @@
 
 #include "src/__support/FPUtil/FPBits.h"
 #include "src/__support/FPUtil/fpbits_str.h"
+#include "src/__support/integer_literals.h"
 #include "test/UnitTest/Test.h"
 
 using LIBC_NAMESPACE::fputil::FPBits;
@@ -15,37 +16,42 @@ using LIBC_NAMESPACE::fputil::FPType;
 using LIBC_NAMESPACE::fputil::Sign;
 using LIBC_NAMESPACE::fputil::internal::FPRep;
 
+using LIBC_NAMESPACE::operator""_u16;
+using LIBC_NAMESPACE::operator""_u32;
+using LIBC_NAMESPACE::operator""_u64;
+using LIBC_NAMESPACE::operator""_u128;
+
 TEST(LlvmLibcFPBitsTest, FPType_IEEE754_Binary16) {
   using Rep = FPRep<FPType::IEEE754_Binary16>;
   using u16 = typename Rep::StorageType;
 
-  EXPECT_EQ(u16(0b0'00000'0000000000), u16(Rep::zero()));
-  EXPECT_EQ(u16(0b0'01111'0000000000), u16(Rep::one()));
-  EXPECT_EQ(u16(0b0'00000'0000000001), u16(Rep::min_subnormal()));
-  EXPECT_EQ(u16(0b0'00000'1111111111), u16(Rep::max_subnormal()));
-  EXPECT_EQ(u16(0b0'00001'0000000000), u16(Rep::min_normal()));
-  EXPECT_EQ(u16(0b0'11110'1111111111), u16(Rep::max_normal()));
-  EXPECT_EQ(u16(0b0'11111'0000000000), u16(Rep::inf()));
-  EXPECT_EQ(u16(0b0'11111'0100000000), u16(Rep::signaling_nan()));
-  EXPECT_EQ(u16(0b0'11111'1000000000), u16(Rep::quiet_nan()));
+  EXPECT_EQ(0b0'00000'0000000000_u16, u16(Rep::zero()));
+  EXPECT_EQ(0b0'01111'0000000000_u16, u16(Rep::one()));
+  EXPECT_EQ(0b0'00000'0000000001_u16, u16(Rep::min_subnormal()));
+  EXPECT_EQ(0b0'00000'1111111111_u16, u16(Rep::max_subnormal()));
+  EXPECT_EQ(0b0'00001'0000000000_u16, u16(Rep::min_normal()));
+  EXPECT_EQ(0b0'11110'1111111111_u16, u16(Rep::max_normal()));
+  EXPECT_EQ(0b0'11111'0000000000_u16, u16(Rep::inf()));
+  EXPECT_EQ(0b0'11111'0100000000_u16, u16(Rep::signaling_nan()));
+  EXPECT_EQ(0b0'11111'1000000000_u16, u16(Rep::quiet_nan()));
 }
 
 TEST(LlvmLibcFPBitsTest, FPType_IEEE754_Binary32) {
   using Rep = FPRep<FPType::IEEE754_Binary32>;
   using u32 = typename Rep::StorageType;
 
-  EXPECT_EQ(u32(0b0'00000000'00000000000000000000000), u32(Rep::zero()));
-  EXPECT_EQ(u32(0b0'01111111'00000000000000000000000), u32(Rep::one()));
-  EXPECT_EQ(u32(0b0'00000000'00000000000000000000001),
+  EXPECT_EQ(0b0'00000000'00000000000000000000000_u32, u32(Rep::zero()));
+  EXPECT_EQ(0b0'01111111'00000000000000000000000_u32, u32(Rep::one()));
+  EXPECT_EQ(0b0'00000000'00000000000000000000001_u32,
             u32(Rep::min_subnormal()));
-  EXPECT_EQ(u32(0b0'00000000'11111111111111111111111),
+  EXPECT_EQ(0b0'00000000'11111111111111111111111_u32,
             u32(Rep::max_subnormal()));
-  EXPECT_EQ(u32(0b0'00000001'00000000000000000000000), u32(Rep::min_normal()));
-  EXPECT_EQ(u32(0b0'11111110'11111111111111111111111), u32(Rep::max_normal()));
-  EXPECT_EQ(u32(0b0'11111111'00000000000000000000000), u32(Rep::inf()));
-  EXPECT_EQ(u32(0b0'11111111'01000000000000000000000),
+  EXPECT_EQ(0b0'00000001'00000000000000000000000_u32, u32(Rep::min_normal()));
+  EXPECT_EQ(0b0'11111110'11111111111111111111111_u32, u32(Rep::max_normal()));
+  EXPECT_EQ(0b0'11111111'00000000000000000000000_u32, u32(Rep::inf()));
+  EXPECT_EQ(0b0'11111111'01000000000000000000000_u32,
             u32(Rep::signaling_nan()));
-  EXPECT_EQ(u32(0b0'11111111'10000000000000000000000), u32(Rep::quiet_nan()));
+  EXPECT_EQ(0b0'11111111'10000000000000000000000_u32, u32(Rep::quiet_nan()));
 }
 
 TEST(LlvmLibcFPBitsTest, FPType_IEEE754_Binary64) {
@@ -53,80 +59,63 @@ TEST(LlvmLibcFPBitsTest, FPType_IEEE754_Binary64) {
   using u64 = typename Rep::StorageType;
 
   EXPECT_EQ(
-      u64(0b0'00000000000'0000000000000000000000000000000000000000000000000000),
+      0b0'00000000000'0000000000000000000000000000000000000000000000000000_u64,
       u64(Rep::zero()));
   EXPECT_EQ(
-      u64(0b0'01111111111'0000000000000000000000000000000000000000000000000000),
+      0b0'01111111111'0000000000000000000000000000000000000000000000000000_u64,
       u64(Rep::one()));
   EXPECT_EQ(
-      u64(0b0'00000000000'0000000000000000000000000000000000000000000000000001),
+      0b0'00000000000'0000000000000000000000000000000000000000000000000001_u64,
       u64(Rep::min_subnormal()));
   EXPECT_EQ(
-      u64(0b0'00000000000'1111111111111111111111111111111111111111111111111111),
+      0b0'00000000000'1111111111111111111111111111111111111111111111111111_u64,
       u64(Rep::max_subnormal()));
   EXPECT_EQ(
-      u64(0b0'00000000001'0000000000000000000000000000000000000000000000000000),
+      0b0'00000000001'0000000000000000000000000000000000000000000000000000_u64,
       u64(Rep::min_normal()));
   EXPECT_EQ(
-      u64(0b0'11111111110'1111111111111111111111111111111111111111111111111111),
+      0b0'11111111110'1111111111111111111111111111111111111111111111111111_u64,
       u64(Rep::max_normal()));
   EXPECT_EQ(
-      u64(0b0'11111111111'0000000000000000000000000000000000000000000000000000),
+      0b0'11111111111'0000000000000000000000000000000000000000000000000000_u64,
       u64(Rep::inf()));
   EXPECT_EQ(
-      u64(0b0'11111111111'0100000000000000000000000000000000000000000000000000),
+      0b0'11111111111'0100000000000000000000000000000000000000000000000000_u64,
       u64(Rep::signaling_nan()));
   EXPECT_EQ(
-      u64(0b0'11111111111'1000000000000000000000000000000000000000000000000000),
+      0b0'11111111111'1000000000000000000000000000000000000000000000000000_u64,
       u64(Rep::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({lo, hi});
-#endif
-}
-
 TEST(LlvmLibcFPBitsTest, FPType_IEEE754_Binary128) {
   using Rep = FPRep<FPType::IEEE754_Binary128>;
 
   EXPECT_EQ(
-      u128(0b0'000000000000000'000000000000000000000000000000000000000000000000,
-           0b0000000000000000000000000000000000000000000000000000000000000000),
+      0b0'000000000000000'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000_u128,
       UInt128(Rep::zero()));
   EXPECT_EQ(
-      u128(0b0'011111111111111'000000000000000000000000000000000000000000000000,
-           0b0000000000000000000000000000000000000000000000000000000000000000),
+      0b0'011111111111111'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000_u128,
       UInt128(Rep::one()));
   EXPECT_EQ(
-      u128(0b0'000000000000000'000000000000000000000000000000000000000000000000,
-           0b0000000000000000000000000000000000000000000000000000000000000001),
+      0b0'000000000000000'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001_u128,
       UInt128(Rep::min_subnormal()));
   EXPECT_EQ(
-      u128(0b0'000000000000000'111111111111111111111111111111111111111111111111,
-           0b1111111111111111111111111111111111111111111111111111111111111111),
+      0b0'000000000000000'1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111_u128,
       UInt128(Rep::max_subnormal()));
   EXPECT_EQ(
-      u128(0b0'000000000000001'000000000000000000000000000000000000000000000000,
-           0b0000000000000000000000000000000000000000000000000000000000000000),
+      0b0'000000000000001'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000_u128,
       UInt128(Rep::min_normal()));
   EXPECT_EQ(
-      u128(0b0'111111111111110'111111111111111111111111111111111111111111111111,
-           0b1111111111111111111111111111111111111111111111111111111111111111),
+      0b0'111111111111110'1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111_u128,
       UInt128(Rep::max_normal()));
   EXPECT_EQ(
-      u128(0b0'111111111111111'000000000000000000000000000000000000000000000000,
-           0b0000000000000000000000000000000000000000000000000000000000000000),
+      0b0'111111111111111'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000_u128,
       UInt128(Rep::inf()));
   EXPECT_EQ(
-      u128(0b0'111111111111111'010000000000000000000000000000000000000000000000,
-           0b0000000000000000000000000000000000000000000000000000000000000000),
+      0b0'111111111111111'0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000_u128,
       UInt128(Rep::signaling_nan()));
   EXPECT_EQ(
-      u128(0b0'111111111111111'100000000000000000000000000000000000000000000000,
-           0b0000000000000000000000000000000000000000000000000000000000000000),
+      0b0'111111111111111'1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000_u128,
       UInt128(Rep::quiet_nan()));
 }
 
@@ -134,89 +123,73 @@ TEST(LlvmLibcFPBitsTest, FPType_X86_Binary80) {
   using Rep = FPRep<FPType::X86_Binary80>;
 
   EXPECT_EQ(
-      u128(0b0'000000000000000,
-           0b0000000000000000000000000000000000000000000000000000000000000000),
+      0b0'0000000000000000000000000000000000000000000000000000000000000000000000000000000_u128,
       UInt128(Rep::zero()));
   EXPECT_EQ(
-      u128(0b0'011111111111111,
-           0b1000000000000000000000000000000000000000000000000000000000000000),
+      0b0'0111111111111111000000000000000000000000000000000000000000000000000000000000000_u128,
       UInt128(Rep::one()));
   EXPECT_EQ(
-      u128(0b0'000000000000000,
-           0b0000000000000000000000000000000000000000000000000000000000000001),
+      0b0'0000000000000000000000000000000000000000000000000000000000000000000000000000001_u128,
       UInt128(Rep::min_subnormal()));
   EXPECT_EQ(
-      u128(0b0'000000000000000,
-           0b0111111111111111111111111111111111111111111111111111111111111111),
+      0b0'0000000000000000111111111111111111111111111111111111111111111111111111111111111_u128,
       UInt128(Rep::max_subnormal()));
   EXPECT_EQ(
-      u128(0b0'000000000000001,
-           0b1000000000000000000000000000000000000000000000000000000000000000),
+      0b0'0000000000000011000000000000000000000000000000000000000000000000000000000000000_u128,
       UInt128(Rep::min_normal()));
   EXPECT_EQ(
-      u128(0b0'111111111111110,
-           0b1111111111111111111111111111111111111111111111111111111111111111),
+      0b0'1111111111111101111111111111111111111111111111111111111111111111111111111111111_u128,
       UInt128(Rep::max_normal()));
   EXPECT_EQ(
-      u128(0b0'111111111111111,
-           0b1000000000000000000000000000000000000000000000000000000000000000),
+      0b0'1111111111111111000000000000000000000000000000000000000000000000000000000000000_u128,
       UInt128(Rep::inf()));
   EXPECT_EQ(
-      u128(0b0'111111111111111,
-           0b1010000000000000000000000000000000000000000000000000000000000000),
+      0b0'1111111111111111010000000000000000000000000000000000000000000000000000000000000_u128,
       UInt128(Rep::signaling_nan()));
   EXPECT_EQ(
-      u128(0b0'111111111111111,
-           0b1100000000000000000000000000000000000000000000000000000000000000),
+      0b0'1111111111111111100000000000000000000000000000000000000000000000000000000000000_u128,
       UInt128(Rep::quiet_nan()));
 }
 
 TEST(LlvmLibcFPBitsTest, FPType_X86_Binary80_IsNan) {
   using Rep = FPRep<FPType::X86_Binary80>;
 
-  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 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));
+  EXPECT_TRUE( // NAN : Pseudo-Infinity
+      Rep(0b0'111111111111111'0000000000000000000000000000000000000000000000000000000000000000_u128)
+          .is_nan());
+  EXPECT_TRUE( // NAN : Pseudo Not a Number
+      Rep(0b0'111111111111111'0000000000000000000000000000000000000000000000000000000000000001_u128)
+          .is_nan());
+...
[truncated]

@gchatelet
Copy link
Contributor Author

@lntue @michaelrj-google I think this could be useful for testing purposes as we need to deal with the impedance mismatch between C++ types, extensions (i.e., __uint128_t) and BigInt types. Do you think it bears its weight?

Ideally the functions should be consteval but we're not C++20 yet.
For now I think it should be used in tests only. I'm not sure how much it'd increase compile time when we have a lot of them.

return; // ' is valid but not taken into account.
const uint8_t value = get_digit_value(c);
if (value == 255 || size >= MAX_DIGITS)
__builtin_unreachable(); // invalid or too many characters.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shall we assert here instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These user defined literals are used to build constants so they should run at compile time exclusively. Since LLVM libc is C++17 we don't have ways to enforce it through consteval but that's the idea nonetheless. If the function is called in a constant context it will force the compile time evaluation of the function.
The nice property of __builtin_unreachable is that it is not executable by the compiler and so the error is pretty clear : https://godbolt.org/z/PT3j1cKc7

If we go with assert, we will only catch the issue when compiling in debug mode.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about static_assert?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So unfortunately static_assert can only access constant expressions so that would fail as well
https://godbolt.org/z/ojWbvs3f4

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, I see what's going on; it's a neat trick. Consider adding a comment above for future travelers to better recognize the trick (and that this will result in an explicit failure in constexpr evaluation context.

@gchatelet gchatelet marked this pull request as draft February 9, 2024 17:05
@gchatelet gchatelet marked this pull request as ready for review February 12, 2024 10:33
Copy link
Member

@nickdesaulniers nickdesaulniers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you forsee usage of these literals outside of libc/test/src/__support/FPUtil/fpbits_test.cpp? It seems like a fair amount of complexity added just to avoid typing out static_cast<>() in libc/test/src/__support/FPUtil/fpbits_test.cpp. libc/test/src/__support/FPUtil/fpbits_test.cpp is already inconsistent of its use of static_cast vs constructor calls.

The literal syntax doesn't save us much typing over just using the constructors:

static_cast<uint16_t>(1)
u16(1)
1_u16

I do like the idea of these suffixes (they remind me of rust, which I think has these), but if the goal is just to simplify libc/test/src/__support/FPUtil/fpbits_test.cpp then I think we can just consistently use the constructor functions rather than static_cast consistently throughout libc/test/src/__support/FPUtil/fpbits_test.cpp without adding support for custom literals.

return; // ' is valid but not taken into account.
const uint8_t value = get_digit_value(c);
if (value == 255 || size >= MAX_DIGITS)
__builtin_unreachable(); // invalid or too many characters.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about static_assert?

libc/src/__support/integer_literals.h Show resolved Hide resolved
@gchatelet
Copy link
Contributor Author

Do you forsee usage of these literals outside of libc/test/src/__support/FPUtil/fpbits_test.cpp? It seems like a fair amount of complexity added just to avoid typing out static_cast<>() in libc/test/src/__support/FPUtil/fpbits_test.cpp. libc/test/src/__support/FPUtil/fpbits_test.cpp is already inconsistent of its use of static_cast vs constructor calls.

The literal syntax doesn't save us much typing over just using the constructors:

static_cast<uint16_t>(1)
u16(1)
1_u16

I do like the idea of these suffixes (they remind me of rust, which I think has these), but if the goal is just to simplify libc/test/src/__support/FPUtil/fpbits_test.cpp then I think we can just consistently use the constructor functions rather than static_cast consistently throughout libc/test/src/__support/FPUtil/fpbits_test.cpp without adding support for custom literals.

Thx for challenging the usefulness of this patch. I think I missed quite a few important pieces of information as I presented it solely for testing purposes.

I initially started working on this because @michaelrj-google rightfully mentioned in #80709 (comment) that I should merge Number and DyadicFloat. One issue though is that DyadicFloat currently always uses BigInt<Bits, false> as its mantissa type where Number uses different types and most notably UInt128 which may or may not be BigInt<128, false> (it can be __uint128_t where available). I think that allowing the use of __uint128_t in DyadicFloat would be a win as the compiler can better optimize the code compared to our custom BigInt implementation. As of today, all uses of DyadicFloat (to the notable exception of get_table_positive_df and tests) are for size 128 so using a more efficient implementation sounds profitable to me.

Now there are a bunch of places where we rely on constants of type DyadicFloat<128> (look for MType in some of these files)

These constants are initialized with uint64_t[2] via this BigInt constructor.
e.g., MType({0xea56d62b82d30a2d, 0x935d8dddaaa8ac16})

Now, if DyadicFloat<128> is implemented with UInt128 (aka __uint128_t or BigInt<128, false>) we'd need a uniform way to create values of these types. User defined literals look like a convenient way to do this:
0x935d8dddaaa8ac16ea56d62b82d30a2d_u128 vs MType({0xea56d62b82d30a2d, 0x935d8dddaaa8ac16})

So indeed there is a bit more to it than just simplifying a few test files (although it also helps in that regard).

Please let me know what you think.

@michaelrj-google
Copy link
Contributor

this seems like a useful tool given the difficulties initializing uint128. Currently in the str_to_long_double_test.cpp float128 tests, the uint128 values are initialized from two uint64s and some shifts.

@lntue
Copy link
Contributor

lntue commented Feb 13, 2024

+1 on simplifying DyadicFloat and UInt initializations.

Copy link
Member

@nickdesaulniers nickdesaulniers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, yeah sounds useful in multiple places then. Consider adding more context to the commit description.

return; // ' is valid but not taken into account.
const uint8_t value = get_digit_value(c);
if (value == 255 || size >= MAX_DIGITS)
__builtin_unreachable(); // invalid or too many characters.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, I see what's going on; it's a neat trick. Consider adding a comment above for future travelers to better recognize the trick (and that this will result in an explicit failure in constexpr evaluation context.

@gchatelet gchatelet changed the title [libc] Add user defined literals to ease testing [libc] Add user defined literals to initialize BigInt and __uint128_t constants Feb 14, 2024
@gchatelet gchatelet changed the title [libc] Add user defined literals to initialize BigInt and __uint128_t constants [libc] Add user defined literals to initialize BigInt and __uint128_t constants Feb 14, 2024
@gchatelet gchatelet merged commit 0323235 into llvm:main Feb 14, 2024
4 checks passed
@gchatelet gchatelet deleted the integer_literals branch February 14, 2024 13:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants