[libc-commits] [libc] dd82a72 - [libc] Float80 Emulation in LLVM libc (#214447)

via libc-commits libc-commits at lists.llvm.org
Mon Aug 24 20:28:35 PDT 2026


Author: Zorojuro
Date: 2026-08-25T08:58:27+05:30
New Revision: dd82a72885c82891fce73dbca5af8b6d9db2af34

URL: https://github.com/llvm/llvm-project/commit/dd82a72885c82891fce73dbca5af8b6d9db2af34
DIFF: https://github.com/llvm/llvm-project/commit/dd82a72885c82891fce73dbca5af8b6d9db2af34.diff

LOG: [libc] Float80 Emulation in LLVM libc (#214447)

Continued from https://github.com/llvm/llvm-project/pull/211350

---------

Co-authored-by: OverMighty <its.overmighty at gmail.com>

Added: 
    libc/src/__support/FPUtil/float80.h
    libc/test/src/__support/FPUtil/float80_test.cpp

Modified: 
    libc/src/__support/CPP/type_traits/is_floating_point.h
    libc/src/__support/FPUtil/CMakeLists.txt
    libc/src/__support/FPUtil/FPBits.h
    libc/src/__support/FPUtil/cast.h
    libc/src/__support/FPUtil/dyadic_float.h
    libc/src/__support/macros/properties/types.h
    libc/test/src/__support/FPUtil/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/libc/src/__support/CPP/type_traits/is_floating_point.h b/libc/src/__support/CPP/type_traits/is_floating_point.h
index b8c3bf05b4ebb..4a5984e38a8cf 100644
--- a/libc/src/__support/CPP/type_traits/is_floating_point.h
+++ b/libc/src/__support/CPP/type_traits/is_floating_point.h
@@ -44,7 +44,10 @@ template <typename T> struct is_floating_point {
                               bfloat16
 
                               ,
-                              fputil::Float128>();
+                              fputil::Float128
+
+                              ,
+                              fputil::Float80>();
 };
 template <typename T>
 LIBC_INLINE_VAR constexpr bool is_floating_point_v =

diff  --git a/libc/src/__support/FPUtil/CMakeLists.txt b/libc/src/__support/FPUtil/CMakeLists.txt
index 4673ef794c80b..96fbb6d80a669 100644
--- a/libc/src/__support/FPUtil/CMakeLists.txt
+++ b/libc/src/__support/FPUtil/CMakeLists.txt
@@ -313,4 +313,22 @@ add_header_library(
     libc.src.__support.uint128
 )
 
+add_header_library(
+  float80
+  HDRS
+    float80.h
+  DEPENDS
+    .cast
+    .comparison_operations
+    .dyadic_float
+    libc.hdr.stdint_proxy
+    libc.src.__support.CPP.type_traits
+    libc.src.__support.FPUtil.generic.add_sub
+    libc.src.__support.FPUtil.generic.div
+    libc.src.__support.FPUtil.generic.mul
+    libc.src.__support.macros.attributes
+    libc.src.__support.macros.config
+    libc.src.__support.uint128
+)
+
 add_subdirectory(generic)

diff  --git a/libc/src/__support/FPUtil/FPBits.h b/libc/src/__support/FPUtil/FPBits.h
index 0a7aee7ae232e..6127dede03f4a 100644
--- a/libc/src/__support/FPUtil/FPBits.h
+++ b/libc/src/__support/FPUtil/FPBits.h
@@ -816,6 +816,8 @@ template <typename T> LIBC_INLINE static constexpr FPType get_fp_type() {
     return FPType::BFloat16;
   else if constexpr (cpp::is_same_v<UnqualT, Float128>)
     return FPType::IEEE754_Binary128;
+  else if constexpr (cpp::is_same_v<UnqualT, Float80>)
+    return FPType::X86_Binary80;
   else
     static_assert(cpp::always_false<UnqualT>, "Unsupported type");
 }

diff  --git a/libc/src/__support/FPUtil/cast.h b/libc/src/__support/FPUtil/cast.h
index 4fc5ea0893ebe..2a9bfd5a9f62e 100644
--- a/libc/src/__support/FPUtil/cast.h
+++ b/libc/src/__support/FPUtil/cast.h
@@ -33,7 +33,9 @@ cast(InType x) {
     if constexpr (cpp::is_same_v<OutType, bfloat16> ||
                   cpp::is_same_v<InType, bfloat16> ||
                   cpp::is_same_v<OutType, Float128> ||
-                  cpp::is_same_v<InType, Float128>
+                  cpp::is_same_v<InType, Float128> ||
+                  cpp::is_same_v<OutType, Float80> ||
+                  cpp::is_same_v<InType, Float80>
 #if defined(LIBC_TYPES_HAS_FLOAT16) && !defined(__LIBC_USE_FLOAT16_CONVERSION)
                   || cpp::is_same_v<OutType, float16> ||
                   cpp::is_same_v<InType, float16>

diff  --git a/libc/src/__support/FPUtil/dyadic_float.h b/libc/src/__support/FPUtil/dyadic_float.h
index 2effcfcb4a34d..e32906e3cf9cf 100644
--- a/libc/src/__support/FPUtil/dyadic_float.h
+++ b/libc/src/__support/FPUtil/dyadic_float.h
@@ -256,10 +256,12 @@ template <size_t Bits> struct DyadicFloat {
             static_cast<StorageType>(unbiased_exp + FPBits::EXP_BIAS);
       }
 
-      MantissaType round_mask = MantissaType(1) << (extra_fraction_len - 1);
-      round = (mantissa & round_mask) != 0;
-      MantissaType sticky_mask = round_mask - 1;
-      sticky = (mantissa & sticky_mask) != 0;
+      if (extra_fraction_len > 0) {
+        MantissaType round_mask = MantissaType(1) << (extra_fraction_len - 1);
+        round = (mantissa & round_mask) != 0;
+        MantissaType sticky_mask = round_mask - 1;
+        sticky = (mantissa & sticky_mask) != 0;
+      }
 
       out_mantissa = static_cast<StorageType>(mantissa >> extra_fraction_len);
     }
@@ -438,7 +440,8 @@ template <size_t Bits> struct DyadicFloat {
                                             (FPBits<T>::FRACTION_LEN < Bits),
                                         void>>
   LIBC_INLINE LIBC_CONSTEXPR_DEFAULT T as() const {
-    if constexpr (cpp::is_same_v<T, bfloat16> || cpp::is_same_v<T, Float128>
+    if constexpr (cpp::is_same_v<T, bfloat16> || cpp::is_same_v<T, Float128> ||
+                  cpp::is_same_v<T, Float80>
 #if defined(LIBC_TYPES_HAS_FLOAT16) && !defined(__LIBC_USE_FLOAT16_CONVERSION)
                   || cpp::is_same_v<T, float16>
 #endif

diff  --git a/libc/src/__support/FPUtil/float80.h b/libc/src/__support/FPUtil/float80.h
new file mode 100644
index 0000000000000..4529fe396a6dc
--- /dev/null
+++ b/libc/src/__support/FPUtil/float80.h
@@ -0,0 +1,103 @@
+//===-- Definition for Float80 data type ------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_FPUTIL_FLOAT80_H
+#define LLVM_LIBC_SRC___SUPPORT_FPUTIL_FLOAT80_H
+
+#include "hdr/stdint_proxy.h"
+#include "src/__support/CPP/type_traits.h"
+#include "src/__support/FPUtil/cast.h"
+#include "src/__support/FPUtil/comparison_operations.h"
+#include "src/__support/FPUtil/dyadic_float.h"
+#include "src/__support/FPUtil/generic/add_sub.h"
+#include "src/__support/FPUtil/generic/div.h"
+#include "src/__support/FPUtil/generic/mul.h"
+#include "src/__support/macros/attributes.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/uint128.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace fputil {
+
+struct Float80 {
+  UInt128 bits;
+
+  LIBC_INLINE Float80() = default;
+  LIBC_INLINE constexpr Float80(const Float80 &) = default;
+  LIBC_INLINE constexpr Float80(Float80 &&) = default;
+  LIBC_INLINE constexpr Float80 &operator=(const Float80 &) = default;
+  LIBC_INLINE constexpr Float80 &operator=(Float80 &&) = default;
+
+  // Floating point type and integer type
+  template <typename T>
+  LIBC_INLINE constexpr explicit Float80(T value) : bits(0U) {
+    if constexpr (cpp::is_floating_point_v<T>) {
+      bits = fputil::cast<Float80>(value).bits;
+    } else if constexpr (cpp::is_integral_v<T>) {
+      Sign sign = Sign::POS;
+      auto unsigned_value = static_cast<cpp::make_unsigned_t<T>>(value);
+
+      if constexpr (cpp::is_signed_v<T>) {
+        if (value < 0) {
+          sign = Sign::NEG;
+          unsigned_value = -unsigned_value;
+        }
+      }
+
+      fputil::DyadicFloat<FPBits<Float80>::STORAGE_LEN> xd(sign, 0,
+                                                           unsigned_value);
+      bits = xd.template as<Float80, /*ShouldSignalExceptions=*/true>().bits;
+
+    } else if constexpr (cpp::is_convertible_v<T, Float80>) {
+      bits = value.operator Float80().bits;
+    } else {
+      bits = fputil::cast<Float80>(static_cast<float>(value)).bits;
+    }
+  }
+
+  template <typename T, cpp::enable_if_t<cpp::is_floating_point_v<T> &&
+                                             !cpp::is_same_v<T, Float80>,
+                                         int> = 0>
+  LIBC_INLINE LIBC_CONSTEXPR_DEFAULT operator T() const {
+    return fputil::cast<T>(*this);
+  }
+
+  template <typename T, cpp::enable_if_t<cpp::is_integral_v<T>, int> = 0>
+  LIBC_INLINE constexpr explicit operator T() const {
+    constexpr T MIN_T = cpp::numeric_limits<T>::min();
+    constexpr T MAX_T = cpp::numeric_limits<T>::max();
+    FPBits<Float80> x_bits(*this);
+    // Raise FE_INVALID for inf and NaN
+    if (x_bits.is_inf_or_nan()) {
+      raise_except_if_required(FE_INVALID);
+      return x_bits.is_neg() ? MIN_T : MAX_T;
+    }
+    int exponent = x_bits.get_explicit_exponent();
+    constexpr int EXPONENT_LIMIT = cpp::numeric_limits<T>::digits;
+    if (exponent > EXPONENT_LIMIT) {
+      raise_except_if_required(FE_INVALID);
+      return x_bits.is_neg() ? MIN_T : MAX_T;
+    } else if (exponent == EXPONENT_LIMIT) {
+      if (x_bits.is_pos() || x_bits.get_mantissa() != 0) {
+        raise_except_if_required(FE_INVALID);
+        return x_bits.is_neg() ? MIN_T : MAX_T;
+      }
+    }
+
+    int x_bits_exp = exponent - FPBits<Float80>::FRACTION_LEN;
+    // sign * 2^(exp-bias) * mantissa
+    DyadicFloat<FPBits<Float80>::STORAGE_LEN> xd(
+        x_bits.sign(), x_bits_exp, x_bits.get_explicit_mantissa());
+    return static_cast<T>(xd.as_mantissa_type());
+  }
+};
+
+} // namespace fputil
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_FPUTIL_Float80_H

diff  --git a/libc/src/__support/macros/properties/types.h b/libc/src/__support/macros/properties/types.h
index ec42b2ef033a6..aad4c41005c45 100644
--- a/libc/src/__support/macros/properties/types.h
+++ b/libc/src/__support/macros/properties/types.h
@@ -93,6 +93,16 @@ struct Float128;
 // TODO: Commented till we modify all required functions to support emulated
 // Float128.
 
+// -- Emulated float80 support ------------------------------------------------
+
+namespace LIBC_NAMESPACE_DECL {
+namespace fputil {
+struct Float80;
+}
+} // namespace LIBC_NAMESPACE_DECL
+
+using float80 = LIBC_NAMESPACE::fputil::Float80;
+
 // -- bfloat16 support ---------------------------------------------------------
 
 namespace LIBC_NAMESPACE_DECL {

diff  --git a/libc/test/src/__support/FPUtil/CMakeLists.txt b/libc/test/src/__support/FPUtil/CMakeLists.txt
index 87fd10aaacd90..b5d9ca2331697 100644
--- a/libc/test/src/__support/FPUtil/CMakeLists.txt
+++ b/libc/test/src/__support/FPUtil/CMakeLists.txt
@@ -52,6 +52,18 @@ add_fp_unittest(
     libc.src.__support.macros.properties.types
 )
 
+add_fp_unittest(
+  float80_test
+  SUITE
+    libc-fputil-tests
+  SRCS
+    float80_test.cpp
+  DEPENDS
+    libc.hdr.limits_macros
+    libc.src.__support.FPUtil.fenv_impl
+    libc.src.__support.FPUtil.float80
+)
+
 # TODO: Temporally disable bfloat16 test until MPCommon target is updated
 # https://github.com/llvm/llvm-project/pull/149678
 if(LLVM_LIBC_FULL_BUILD)

diff  --git a/libc/test/src/__support/FPUtil/float80_test.cpp b/libc/test/src/__support/FPUtil/float80_test.cpp
new file mode 100644
index 0000000000000..72c1155f8c6d3
--- /dev/null
+++ b/libc/test/src/__support/FPUtil/float80_test.cpp
@@ -0,0 +1,112 @@
+//===-- Unittests for Float80 emulated type -------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "hdr/limits_macros.h"
+#include "src/__support/FPUtil/FEnvImpl.h"
+#include "src/__support/FPUtil/float80.h"
+#include "test/UnitTest/FPMatcher.h"
+#include "test/UnitTest/Test.h"
+
+using LIBC_NAMESPACE::Sign;
+using LIBC_NAMESPACE::fputil::Float80;
+using FPBits = LIBC_NAMESPACE::fputil::FPBits<Float80>;
+
+TEST(LlvmLibcFloat80Test, IntegerConversion) {
+  // Float80 to Integer conversion test
+  ASSERT_EQ(static_cast<int>(Float80(0.0f)), 0);
+  ASSERT_EQ(static_cast<int>(Float80(1.0f)), 1);
+  ASSERT_EQ(static_cast<long long>(Float80(1000000000.0)),
+            static_cast<long long>(1000000000));
+  ASSERT_EQ(static_cast<unsigned>(Float80(7.0f)), 7U);
+  ASSERT_EQ(static_cast<int>(Float80(1.9f)), 1);
+
+  // Border values
+  LIBC_NAMESPACE::fputil::clear_except(FE_ALL_EXCEPT);
+  ASSERT_EQ(static_cast<int>(Float80(INT_MAX)), INT_MAX);
+  ASSERT_EQ(static_cast<long long>(Float80(LLONG_MAX)), LLONG_MAX);
+  ASSERT_EQ(static_cast<unsigned>(Float80(UINT_MAX)), UINT_MAX);
+  EXPECT_EQ(LIBC_NAMESPACE::fputil::test_except(FE_INVALID), 0);
+
+  // FP exceptions
+  LIBC_NAMESPACE::fputil::clear_except(FE_ALL_EXCEPT);
+  ASSERT_EQ(static_cast<int>(FPBits::quiet_nan().get_val()), INT_MAX);
+  EXPECT_FP_EXCEPTION(FE_INVALID);
+
+  LIBC_NAMESPACE::fputil::clear_except(FE_ALL_EXCEPT);
+  ASSERT_EQ(static_cast<int>(FPBits::inf().get_val()), INT_MAX);
+  EXPECT_FP_EXCEPTION(FE_INVALID);
+
+  // Extreme values
+  LIBC_NAMESPACE::fputil::clear_except(FE_ALL_EXCEPT);
+  ASSERT_EQ(static_cast<int>(Float80(1e300)), INT_MAX);
+  EXPECT_FP_EXCEPTION(FE_INVALID);
+
+  LIBC_NAMESPACE::fputil::clear_except(FE_ALL_EXCEPT);
+  ASSERT_EQ(static_cast<int>(FPBits::inf(Sign::POS).get_val()), INT_MAX);
+  EXPECT_FP_EXCEPTION(FE_INVALID);
+
+  // Small values
+  LIBC_NAMESPACE::fputil::clear_except(FE_ALL_EXCEPT);
+  ASSERT_EQ(static_cast<int>(Float80(1e-300)), 0);
+  ASSERT_EQ(static_cast<int>(Float80(0.5)), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::fputil::test_except(FE_INVALID), 0);
+}
+
+#ifdef LIBC_TYPES_LONG_DOUBLE_IS_X86_FLOAT80
+TEST(LlvmLibcFloat80Test, randomTest) {
+  using FPBitsL = LIBC_NAMESPACE::fputil::FPBits<long double>;
+
+  const FPBitsL::StorageType EDGE_CASES[] = {
+      FPBitsL::zero(Sign::POS).uintval(),
+      FPBitsL::zero(Sign::NEG).uintval(),
+      FPBitsL::inf(Sign::POS).uintval(),
+      FPBitsL::inf(Sign::NEG).uintval(),
+      FPBitsL::quiet_nan().uintval(),
+      FPBitsL::min_subnormal(Sign::POS).uintval(),
+      FPBitsL::min_subnormal(Sign::NEG).uintval(),
+      FPBitsL::max_subnormal(Sign::POS).uintval(),
+      FPBitsL::max_subnormal(Sign::NEG).uintval(),
+      FPBitsL::min_normal(Sign::POS).uintval(),
+      FPBitsL::min_normal(Sign::NEG).uintval(),
+      FPBitsL::max_normal(Sign::POS).uintval(),
+      FPBitsL::max_normal(Sign::NEG).uintval(),
+      FPBitsL::one(Sign::POS).uintval(),
+      FPBitsL::one(Sign::NEG).uintval(),
+  };
+
+  for (FPBitsL::StorageType bits : EDGE_CASES) {
+    long double native = FPBitsL(bits).get_val();
+
+    Float80 f80_temp = LIBC_NAMESPACE::fputil::cast<Float80>(native);
+    EXPECT_EQ(FPBits(f80_temp).uintval(), bits);
+
+    long double ld_temp =
+        LIBC_NAMESPACE::fputil::cast<long double>(FPBits(bits).get_val());
+    EXPECT_EQ(FPBitsL(ld_temp).uintval(), bits);
+  }
+}
+
+#endif // LIBC_TYPES_LONG_DOUBLE_IS_X86_FLOAT80
+
+TEST(LlvmLibcFloat80Test, FromIntegralTypes) {
+  // Integer to Float80 conversion test
+  ASSERT_EQ(FPBits(Float80(42)).uintval(), FPBits(Float80(42.0f)).uintval());
+  ASSERT_EQ(FPBits(Float80(0)).uintval(), FPBits(Float80(0.0f)).uintval());
+  ASSERT_EQ(FPBits(Float80(7U)).uintval(), FPBits(Float80(7.0f)).uintval());
+  ASSERT_EQ(FPBits(Float80(123456789LL)).uintval(),
+            FPBits(Float80(123456789.0)).uintval());
+
+  // 2147483648.0 or 2^31 is out of bound in signed and not in unsigned
+  LIBC_NAMESPACE::fputil::clear_except(FE_ALL_EXCEPT);
+  ASSERT_EQ(static_cast<int>(Float80(2147483648.0)), INT_MAX);
+  EXPECT_FP_EXCEPTION(FE_INVALID);
+
+  LIBC_NAMESPACE::fputil::clear_except(FE_ALL_EXCEPT);
+  ASSERT_EQ(static_cast<unsigned>(Float80(2147483648.0)), 2147483648U);
+  EXPECT_EQ(LIBC_NAMESPACE::fputil::test_except(FE_INVALID), 0);
+}


        


More information about the libc-commits mailing list