From: Jonathan Wakely <jwakely@redhat.com>
To: Jakub Jelinek <jakub@redhat.com>
Cc: Patrick Palka <ppalka@redhat.com>,
gcc-patches@gcc.gnu.org, libstdc++@gcc.gnu.org
Subject: Re: [PATCH] libstdc++: Add _Float128 to_chars/from_chars support for x86, ia64 and ppc64le with glibc
Date: Mon, 7 Nov 2022 13:45:04 +0000 [thread overview]
Message-ID: <CACb0b4nNR5vqBe_H5nWv+JFhzveD8Jjypyrqruse-ko5yUn3Yw@mail.gmail.com> (raw)
In-Reply-To: <Y2I3pr1Eyn120h1C@tucnak>
On Wed, 2 Nov 2022 at 09:26, Jakub Jelinek <jakub@redhat.com> wrote:
>
> Hi!
>
> The following patch adds std::{to,from}_chars support for std::float128_t
> on glibc 2.26+ for {i?86,x86_64,ia64,powerpc64le}-linux.
> When long double is already IEEE quad, previous changes already handle
> it by using long double overloads in _Float128 overloads.
> The powerpc64le case (with explicit or implicit -mabi=ibmlongdouble)
> is handled by using the __float128/__ieee128 entrypoints which are
> already in the library and used for -mabi=ieeelongdouble.
> For i?86, x86_64 and ia64 this patch adds new library entrypoints,
> mostly by enabling the code that was already there for powerpc64le-linux.
> Those use __float128 or __ieee128, the patch uses _Float128 for the
> exported overloads and internally as template parameter. While
> powerpc64le-linux uses __sprintfieee128 and __strtoieee128,
> for _Float128 the patch uses the glibc 2.26 strfromf128 and strtof128
> APIs. So that one can build gcc against older glibc and then compile
> user programs on newer glibc, the patch uses weak references unless
> gcc is compiled against glibc 2.26+. strfromf128 unfortunately can't
> handle %.0Lf and %.*Le, %.*Lf, %.*Lg format strings sprintf/__sprintfieee128
> use, we need to remove the L from those and replace * with actually
> directly printing the precision into the format string (i.e. it can
> handle %.0f and %.27f (floating point type is implied from the function
> name)).
Wow, nice work juggling all the pieces here.
> Unlike the std::{,b}float16_t support, this one actually exports APIs
> with std::float128_t aka _Float128 in the mangled name, because no
> standard format is superset of it. On the other side, e.g. on i?86/x86_64
> it doesn't have restrictions like for _Float16/__bf16 which ISAs need
> to be enabled in order to use it.
>
> The denorm_min case in the testcase is temporarily commented out because
> of the ERANGE subnormal issue Patrick posted patch for.
>
> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
OK, thanks!
>
> 2022-11-02 Jakub Jelinek <jakub@redhat.com>
>
> * include/std/charconv (from_chars, to_chars): Add _Float128
> overfloads if _GLIBCXX_HAVE_FLOAT128_MATH is defined.
> * config/abi/pre/gnu.ver (GLIBCXX_3.4.31): Export
> _ZSt8to_charsPcS_DF128_, _ZSt8to_charsPcS_DF128_St12chars_format,
> _ZSt8to_charsPcS_DF128_St12chars_formati and
> _ZSt10from_charsPKcS0_RDF128_St12chars_format.
> * src/c++17/floating_from_chars.cc (USE_STRTOF128_FOR_FROM_CHARS):
> Define if needed.
> (__strtof128): Declare.
> (from_chars_impl): Handle _Float128.
> (from_chars): New _Float128 overload if USE_STRTOF128_FOR_FROM_CHARS
> is define.
> * src/c++17/floating_to_chars.cc (__strfromf128): Declare.
> (FLOAT128_TO_CHARS): Define even when _Float128 is supported and
> wider than long double.
> (F128_type): Use _Float128 for that case.
> (floating_type_traits): Specialize for F128_type rather than
> __float128.
> (sprintf_ld): Add length argument. Handle _Float128.
> (__floating_to_chars_shortest, __floating_to_chars_precision):
> Pass length to sprintf_ld.
> (to_chars): Add _Float128 overloads for the F128_type being
> _Float128 cases.
> * testsuite/20_util/to_chars/float128_c++23.cc: New test.
>
> --- libstdc++-v3/include/std/charconv.jj 2022-10-31 22:20:39.475072806 +0100
> +++ libstdc++-v3/include/std/charconv 2022-11-01 16:48:50.693196228 +0100
> @@ -736,6 +736,27 @@ namespace __detail
> __value = __val;
> return __res;
> }
> +#elif defined(__STDCPP_FLOAT128_T__) && defined(_GLIBCXX_HAVE_FLOAT128_MATH)
> +#ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT
> + __extension__ from_chars_result
> + from_chars(const char* __first, const char* __last, __ieee128& __value,
> + chars_format __fmt = chars_format::general) noexcept;
> +
> + inline from_chars_result
> + from_chars(const char* __first, const char* __last, _Float128& __value,
> + chars_format __fmt = chars_format::general) noexcept
> + {
> + __extension__ __ieee128 __val;
> + from_chars_result __res = from_chars(__first, __last, __val, __fmt);
> + if (__res.ec == errc{})
> + __value = __val;
> + return __res;
> + }
> +#else
> + from_chars_result
> + from_chars(const char* __first, const char* __last, _Float128& __value,
> + chars_format __fmt = chars_format::general) noexcept;
> +#endif
> #endif
>
> #if defined(__STDCPP_BFLOAT16_T__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32) \
> @@ -851,6 +872,46 @@ namespace __detail
> return to_chars(__first, __last, static_cast<long double>(__value), __fmt,
> __precision);
> }
> +#elif defined(__STDCPP_FLOAT128_T__) && defined(_GLIBCXX_HAVE_FLOAT128_MATH)
> +#ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT
> + __extension__ to_chars_result
> + to_chars(char* __first, char* __last, __float128 __value) noexcept;
> + __extension__ to_chars_result
> + to_chars(char* __first, char* __last, __float128 __value,
> + chars_format __fmt) noexcept;
> + __extension__ to_chars_result
> + to_chars(char* __first, char* __last, __float128 __value,
> + chars_format __fmt, int __precision) noexcept;
> +
> + inline to_chars_result
> + to_chars(char* __first, char* __last, _Float128 __value) noexcept
> + {
> + __extension__
> + return to_chars(__first, __last, static_cast<__float128>(__value));
> + }
> + inline to_chars_result
> + to_chars(char* __first, char* __last, _Float128 __value,
> + chars_format __fmt) noexcept
> + {
> + __extension__
> + return to_chars(__first, __last, static_cast<__float128>(__value), __fmt);
> + }
> + inline to_chars_result
> + to_chars(char* __first, char* __last, _Float128 __value,
> + chars_format __fmt, int __precision) noexcept
> + {
> + __extension__
> + return to_chars(__first, __last, static_cast<__float128>(__value), __fmt,
> + __precision);
> + }
> +#else
> + to_chars_result to_chars(char* __first, char* __last, _Float128 __value)
> + noexcept;
> + to_chars_result to_chars(char* __first, char* __last, _Float128 __value,
> + chars_format __fmt) noexcept;
> + to_chars_result to_chars(char* __first, char* __last, _Float128 __value,
> + chars_format __fmt, int __precision) noexcept;
> +#endif
> #endif
>
> #if defined(__STDCPP_BFLOAT16_T__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32)
> --- libstdc++-v3/config/abi/pre/gnu.ver.jj 2022-10-31 22:20:39.475072806 +0100
> +++ libstdc++-v3/config/abi/pre/gnu.ver 2022-11-01 17:00:57.682346445 +0100
> @@ -2450,6 +2450,10 @@ GLIBCXX_3.4.31 {
> _ZSt21__to_chars_bfloat16_tPcS_fSt12chars_format;
> _ZSt22__from_chars_float16_tPKcS0_RfSt12chars_format;
> _ZSt23__from_chars_bfloat16_tPKcS0_RfSt12chars_format;
> + _ZSt8to_charsPcS_DF128_;
> + _ZSt8to_charsPcS_DF128_St12chars_format;
> + _ZSt8to_charsPcS_DF128_St12chars_formati;
> + _ZSt10from_charsPKcS0_RDF128_St12chars_format;
> } GLIBCXX_3.4.30;
>
> # Symbols in the support library (libsupc++) have their own tag.
> --- libstdc++-v3/src/c++17/floating_from_chars.cc.jj 2022-10-31 22:20:39.476072793 +0100
> +++ libstdc++-v3/src/c++17/floating_from_chars.cc 2022-11-01 16:59:23.999615740 +0100
> @@ -59,6 +59,14 @@
> #endif
> // strtold for __ieee128
> extern "C" __ieee128 __strtoieee128(const char*, char**);
> +#elif __FLT128_MANT_DIG__ == 113 && __LDBL_MANT_DIG__ != 113 \
> + && defined(__GLIBC_PREREQ)
> +#define USE_STRTOF128_FOR_FROM_CHARS 1
> +extern "C" _Float128 __strtof128(const char*, char**)
> +#ifndef _GLIBCXX_HAVE_FLOAT128_MATH
> + __attribute__((__weak__))
> +#endif
> + __asm ("strtof128");
> #endif
>
> #if _GLIBCXX_FLOAT_IS_IEEE_BINARY32 && _GLIBCXX_DOUBLE_IS_IEEE_BINARY64 \
> @@ -618,6 +626,16 @@ namespace
> # ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT
> else if constexpr (is_same_v<T, __ieee128>)
> tmpval = __strtoieee128(str, &endptr);
> +# elif defined(USE_STRTOF128_FOR_FROM_CHARS)
> + else if constexpr (is_same_v<T, _Float128>)
> + {
> +#ifndef _GLIBCXX_HAVE_FLOAT128_MATH
> + if (&__strtof128 == nullptr)
> + tmpval = _Float128(std::strtold(str, &endptr);
> + else
> +#endif
> + tmpval = __strtof128(str, &endptr);
> + }
> # endif
> #else
> tmpval = std::strtod(str, &endptr);
> @@ -1232,6 +1250,14 @@ from_chars(const char* first, const char
> chars_format fmt) noexcept
> {
> // fast_float doesn't support IEEE binary128 format, but we can use strtold.
> + return from_chars_strtod(first, last, value, fmt);
> +}
> +#elif defined(USE_STRTOF128_FOR_FROM_CHARS)
> +from_chars_result
> +from_chars(const char* first, const char* last, _Float128& value,
> + chars_format fmt) noexcept
> +{
> + // fast_float doesn't support IEEE binary128 format, but we can use strtold.
> return from_chars_strtod(first, last, value, fmt);
> }
> #endif
> --- libstdc++-v3/src/c++17/floating_to_chars.cc.jj 2022-11-01 12:16:14.352652455 +0100
> +++ libstdc++-v3/src/c++17/floating_to_chars.cc 2022-11-01 16:25:48.330968909 +0100
> @@ -43,6 +43,13 @@
> #endif
> // sprintf for __ieee128
> extern "C" int __sprintfieee128(char*, const char*, ...);
> +#elif __FLT128_MANT_DIG__ == 113 && __LDBL_MANT_DIG__ != 113 \
> + && defined(__GLIBC_PREREQ)
> +extern "C" int __strfromf128(char*, size_t, const char*, _Float128)
> +#ifndef _GLIBCXX_HAVE_FLOAT128_MATH
> + __attribute__((__weak__))
> +#endif
> + __asm ("strfromf128");
> #endif
>
> // This implementation crucially assumes float/double have the
> @@ -77,10 +84,11 @@ extern "C" int __sprintfieee128(char*, c
> #if defined _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT && __FLT128_MANT_DIG__ == 113
> // Define overloads of std::to_chars for __float128.
> # define FLOAT128_TO_CHARS 1
> -#endif
> -
> -#ifdef FLOAT128_TO_CHARS
> using F128_type = __float128;
> +#elif __FLT128_MANT_DIG__ == 113 && __LDBL_MANT_DIG__ != 113 \
> + && defined(__GLIBC_PREREQ)
> +# define FLOAT128_TO_CHARS 1
> +using F128_type = _Float128;
> #else
> using F128_type = void;
> #endif
> @@ -252,7 +260,7 @@ namespace
>
> # ifdef FLOAT128_TO_CHARS
> template<>
> - struct floating_type_traits<__float128> : floating_type_traits_binary128
> + struct floating_type_traits<F128_type> : floating_type_traits_binary128
> { };
> # endif
> #endif
> @@ -1035,7 +1043,8 @@ namespace
> #pragma GCC diagnostic ignored "-Wabi"
> template<typename T, typename... Extra>
> inline int
> - sprintf_ld(char* buffer, const char* format_string, T value, Extra... args)
> + sprintf_ld(char* buffer, size_t length __attribute__((unused)),
> + const char* format_string, T value, Extra... args)
> {
> int len;
>
> @@ -1045,10 +1054,31 @@ namespace
> fesetround(FE_TONEAREST); // We want round-to-nearest behavior.
> #endif
>
> +#ifdef FLOAT128_TO_CHARS
> #ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT
> if constexpr (is_same_v<T, __ieee128>)
> len = __sprintfieee128(buffer, format_string, args..., value);
> else
> +#else
> + if constexpr (is_same_v<T, _Float128>)
> + {
> +#ifndef _GLIBCXX_HAVE_FLOAT128_MATH
> + if (&__strfromf128 == nullptr)
> + len = sprintf(buffer, format_string, args..., (long double)value);
> + else
> +#endif
> + if constexpr (sizeof...(args) == 0)
> + len = __strfromf128(buffer, length, "%.0f", value);
> + else
> + {
> + // strfromf128 unfortunately doesn't allow .*
> + char fmt[3 * sizeof(int) + 6];
> + sprintf(fmt, "%%.%d%c", args..., int(format_string[4]));
> + len = __strfromf128(buffer, length, fmt, value);
> + }
> + }
> + else
> +#endif
> #endif
> len = sprintf(buffer, format_string, args..., value);
>
> @@ -1206,8 +1236,10 @@ template<typename T>
> // can avoid this if we use sprintf to write all but the last
> // digit, and carefully compute and write the last digit
> // ourselves.
> - char buffer[expected_output_length+1];
> - const int output_length = sprintf_ld(buffer, "%.0Lf", value);
> + char buffer[expected_output_length + 1];
> + const int output_length = sprintf_ld(buffer,
> + expected_output_length + 1,
> + "%.0Lf", value);
> __glibcxx_assert(output_length == expected_output_length);
> memcpy(first, buffer, output_length);
> return {first + output_length, errc{}};
> @@ -1397,9 +1429,10 @@ template<typename T>
> __builtin_unreachable();
>
> // Do the sprintf into the local buffer.
> - char buffer[output_length_upper_bound+1];
> + char buffer[output_length_upper_bound + 1];
> int output_length
> - = sprintf_ld(buffer, output_specifier, value, effective_precision);
> + = sprintf_ld(buffer, output_length_upper_bound + 1, output_specifier,
> + value, effective_precision);
> __glibcxx_assert(output_length <= output_length_upper_bound);
>
> if (effective_precision > 0)
> @@ -1799,6 +1832,7 @@ to_chars(char* first, char* last, long d
> }
>
> #ifdef FLOAT128_TO_CHARS
> +#ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT
> to_chars_result
> to_chars(char* first, char* last, __float128 value) noexcept
> {
> @@ -1817,6 +1851,26 @@ to_chars(char* first, char* last, __floa
> {
> return __floating_to_chars_precision(first, last, value, fmt, precision);
> }
> +#else
> +to_chars_result
> +to_chars(char* first, char* last, _Float128 value) noexcept
> +{
> + return __floating_to_chars_shortest(first, last, value, chars_format{});
> +}
> +
> +to_chars_result
> +to_chars(char* first, char* last, _Float128 value, chars_format fmt) noexcept
> +{
> + return __floating_to_chars_shortest(first, last, value, fmt);
> +}
> +
> +to_chars_result
> +to_chars(char* first, char* last, _Float128 value, chars_format fmt,
> + int precision) noexcept
> +{
> + return __floating_to_chars_precision(first, last, value, fmt, precision);
> +}
> +#endif
> #endif
>
> // Entrypoints for 16-bit floats.
> --- libstdc++-v3/testsuite/20_util/to_chars/float128_c++23.cc.jj 2022-11-01 17:04:19.895606130 +0100
> +++ libstdc++-v3/testsuite/20_util/to_chars/float128_c++23.cc 2022-11-01 20:47:15.254989646 +0100
> @@ -0,0 +1,105 @@
> +// Copyright (C) 2022 Free Software Foundation, Inc.
> +//
> +// This file is part of the GNU ISO C++ Library. This library is free
> +// software; you can redistribute it and/or modify it under the
> +// terms of the GNU General Public License as published by the
> +// Free Software Foundation; either version 3, or (at your option)
> +// any later version.
> +
> +// This library is distributed in the hope that it will be useful,
> +// but WITHOUT ANY WARRANTY; without even the implied warranty of
> +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> +// GNU General Public License for more details.
> +
> +// You should have received a copy of the GNU General Public License along
> +// with this library; see the file COPYING3. If not see
> +// <http://www.gnu.org/licenses/>.
> +
> +// { dg-options "-std=gnu++2b" }
> +// { dg-do run { target c++23 } }
> +// { dg-require-effective-target ieee_floats }
> +// { dg-require-effective-target size32plus }
> +// { dg-add-options ieee }
> +
> +#include <charconv>
> +#include <stdfloat>
> +#include <limits>
> +#include <numbers>
> +#include <testsuite_hooks.h>
> +
> +#if defined(__STDCPP_FLOAT128_T__) \
> + && (defined(_GLIBCXX_LDOUBLE_IS_IEEE_BINARY128) \
> + || defined(_GLIBCXX_HAVE_FLOAT128_MATH))
> +void
> +test(std::chars_format fmt = std::chars_format{})
> +{
> + std::float128_t tests[] = {
> +// std::numeric_limits<std::float128_t>::denorm_min(),
> + std::numeric_limits<std::float128_t>::min(),
> + 0.0f128,
> + -42.0f128,
> + 1234.5678912345f128,
> + std::numbers::e_v<std::float128_t>,
> + std::numbers::log2e_v<std::float128_t>,
> + std::numbers::log10e_v<std::float128_t>,
> + std::numbers::pi_v<std::float128_t>,
> + std::numbers::inv_pi_v<std::float128_t>,
> + std::numbers::inv_sqrtpi_v<std::float128_t>,
> + std::numbers::ln2_v<std::float128_t>,
> + std::numbers::ln10_v<std::float128_t>,
> + std::numbers::sqrt2_v<std::float128_t>,
> + std::numbers::sqrt3_v<std::float128_t>,
> + std::numbers::inv_sqrt3_v<std::float128_t>,
> + std::numbers::egamma_v<std::float128_t>,
> + std::numbers::phi_v<std::float128_t>,
> + std::numeric_limits<std::float128_t>::max()
> + };
> + char str1[10000], str2[10000];
> + for (auto u : tests)
> + {
> + auto [ptr1, ec1] = std::to_chars(str1, str1 + sizeof(str1), u, fmt);
> + VERIFY( ec1 == std::errc() );
> +// std::cout << i << ' ' << std::string_view (str1, ptr1) << '\n';
> + if (fmt == std::chars_format::fixed)
> + {
> + auto [ptr2, ec2] = std::to_chars(str2, str2 + (ptr1 - str1), u, fmt);
> + VERIFY( ec2 == std::errc() && ptr2 - str2 == ptr1 - str1 );
> + auto [ptr3, ec3] = std::to_chars(str2, str2 + (ptr1 - str1 - 1), u, fmt);
> + VERIFY( ec3 != std::errc() );
> + }
> + std::float128_t v;
> + auto [ptr4, ec4] = std::from_chars(str1, ptr1, v,
> + fmt == std::chars_format{}
> + ? std::chars_format::general : fmt);
> + VERIFY( ec4 == std::errc() && ptr4 == ptr1 );
> + VERIFY( u == v );
> +
> + auto [ptr5, ec5] = std::to_chars(str1, str1 + sizeof(str1), u, fmt, 90);
> + VERIFY( ec5 == std::errc() );
> +// std::cout << i << ' ' << std::string_view (str1, ptr5) << '\n';
> + v = 4.0f128;
> + auto [ptr6, ec6] = std::from_chars(str1, ptr5, v,
> + fmt == std::chars_format{}
> + ? std::chars_format::general : fmt);
> + VERIFY( ec6 == std::errc() && ptr6 == ptr5 );
> + if (fmt == std::chars_format::fixed && u > 0.0f128 && u < 0.000001f128)
> + VERIFY( v == 0.0 );
> + else
> + VERIFY( u == v );
> + }
> +}
> +#endif
> +
> +int
> +main()
> +{
> +#if defined(__STDCPP_FLOAT128_T__) \
> + && (defined(_GLIBCXX_LDOUBLE_IS_IEEE_BINARY128) \
> + || defined(_GLIBCXX_HAVE_FLOAT128_MATH))
> + test();
> + test(std::chars_format::fixed);
> + test(std::chars_format::scientific);
> + test(std::chars_format::general);
> + test(std::chars_format::hex);
> +#endif
> +}
>
> Jakub
>
next prev parent reply other threads:[~2022-11-07 13:45 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-11-02 9:25 Jakub Jelinek
2022-11-07 13:45 ` Jonathan Wakely [this message]
2022-11-07 16:11 ` Joseph Myers
2022-11-07 17:48 ` Jonathan Wakely
2022-11-07 17:52 ` Joseph Myers
2022-11-07 19:13 ` Jakub Jelinek
2022-11-07 22:43 ` Jonathan Wakely
2022-11-08 1:41 ` Joseph Myers
2022-11-08 8:43 ` Jakub Jelinek
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=CACb0b4nNR5vqBe_H5nWv+JFhzveD8Jjypyrqruse-ko5yUn3Yw@mail.gmail.com \
--to=jwakely@redhat.com \
--cc=gcc-patches@gcc.gnu.org \
--cc=jakub@redhat.com \
--cc=libstdc++@gcc.gnu.org \
--cc=ppalka@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).