[RFC,1/1] newlib: strtold: Import strtorQ for 128-bit long double support

Message ID 20250922171137.3137186-2-fadli@adacore.com
State New
Headers
Series strold: Support for 128-bit long double |

Commit Message

Zakaria Fadli Sept. 22, 2025, 5:11 p.m. UTC
  The implementation of strtorQ is imported from FreeBSD's gdtoa library (By David
M. Gay) with some adaptations to fit with newlib.

`strtorQ.c` enables `strtold` to perform correct parsing on targets where long
double uses the IEEE754 binary128 format (113-bit mantissa), such as AArch64.
Without this patch, strtold would wrongly fallback to `strtorx` which will
parse into a 80-bit long double and give invalid result.

* libc/stdlib/strtorQ.c: New file, adapted from FreeBSD gdtoa.
* libc/stdlib/strtold.c (_strtold_impl): New helper selecting strtorx
  for 80-bit and strtorQ for 128-bit long double.
  (_strtold_r, strtold_l, strtold): Use _strtold_impl.
* libc/stdlib/mprec.h (_strtorQ_l): Declare.
* libc/stdlib/Makefile.inc (libc_a_SOURCES): Add strtorQ.c.
* newlib/Makefile.in: Regenerate with automake

Signed-off-by: Zakaria Fadli <fadli@adacore.com>
---
 newlib/Makefile.in              |  19 +++++
 newlib/libc/stdlib/Makefile.inc |   1 +
 newlib/libc/stdlib/mprec.h      |   2 +
 newlib/libc/stdlib/strtold.c    |  56 +++++++-------
 newlib/libc/stdlib/strtorQ.c    | 129 ++++++++++++++++++++++++++++++++
 5 files changed, 178 insertions(+), 29 deletions(-)
 create mode 100644 newlib/libc/stdlib/strtorQ.c
  

Patch

diff --git a/newlib/Makefile.in b/newlib/Makefile.in
index 9a32646ab..c1515407b 100644
--- a/newlib/Makefile.in
+++ b/newlib/Makefile.in
@@ -125,6 +125,7 @@  check_PROGRAMS =
 @HAVE_LONG_DOUBLE_TRUE@am__append_6 = \
 @HAVE_LONG_DOUBLE_TRUE@	libc/stdlib/strtodg.c \
 @HAVE_LONG_DOUBLE_TRUE@	libc/stdlib/strtold.c \
+@HAVE_LONG_DOUBLE_TRUE@	libc/stdlib/strtorQ.c \
 @HAVE_LONG_DOUBLE_TRUE@	libc/stdlib/strtorx.c \
 @HAVE_LONG_DOUBLE_TRUE@	libc/stdlib/wcstold.c
 
@@ -1045,6 +1046,7 @@  am__dirstamp = $(am__leading_dot)dirstamp
 @HAVE_LONG_DOUBLE_TRUE@am__objects_2 =  \
 @HAVE_LONG_DOUBLE_TRUE@	libc/stdlib/libc_a-strtodg.$(OBJEXT) \
 @HAVE_LONG_DOUBLE_TRUE@	libc/stdlib/libc_a-strtold.$(OBJEXT) \
+@HAVE_LONG_DOUBLE_TRUE@	libc/stdlib/libc_a-strtorQ.$(OBJEXT) \
 @HAVE_LONG_DOUBLE_TRUE@	libc/stdlib/libc_a-strtorx.$(OBJEXT) \
 @HAVE_LONG_DOUBLE_TRUE@	libc/stdlib/libc_a-wcstold.$(OBJEXT)
 am__objects_3 = libc/stdlib/libc_a-a64l.$(OBJEXT) \
@@ -5611,6 +5613,8 @@  libc/stdlib/libc_a-strtodg.$(OBJEXT): libc/stdlib/$(am__dirstamp) \
 	libc/stdlib/$(DEPDIR)/$(am__dirstamp)
 libc/stdlib/libc_a-strtold.$(OBJEXT): libc/stdlib/$(am__dirstamp) \
 	libc/stdlib/$(DEPDIR)/$(am__dirstamp)
+libc/stdlib/libc_a-strtorQ.$(OBJEXT): libc/stdlib/$(am__dirstamp) \
+	libc/stdlib/$(DEPDIR)/$(am__dirstamp)
 libc/stdlib/libc_a-strtorx.$(OBJEXT): libc/stdlib/$(am__dirstamp) \
 	libc/stdlib/$(DEPDIR)/$(am__dirstamp)
 libc/stdlib/libc_a-wcstold.$(OBJEXT): libc/stdlib/$(am__dirstamp) \
@@ -13652,6 +13656,7 @@  distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@libc/stdlib/$(DEPDIR)/libc_a-strtold.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@libc/stdlib/$(DEPDIR)/libc_a-strtoll.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@libc/stdlib/$(DEPDIR)/libc_a-strtoll_r.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@libc/stdlib/$(DEPDIR)/libc_a-strtorQ.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@libc/stdlib/$(DEPDIR)/libc_a-strtorx.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@libc/stdlib/$(DEPDIR)/libc_a-strtoul.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@libc/stdlib/$(DEPDIR)/libc_a-strtoull.Po@am__quote@
@@ -21559,6 +21564,20 @@  libc/stdlib/libc_a-strtold.obj: libc/stdlib/strtold.c
 @AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
 @am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libc_a_CPPFLAGS) $(CPPFLAGS) $(libc_a_CFLAGS) $(CFLAGS) -c -o libc/stdlib/libc_a-strtold.obj `if test -f 'libc/stdlib/strtold.c'; then $(CYGPATH_W) 'libc/stdlib/strtold.c'; else $(CYGPATH_W) '$(srcdir)/libc/stdlib/strtold.c'; fi`
 
+libc/stdlib/libc_a-strtorQ.o: libc/stdlib/strtorQ.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libc_a_CPPFLAGS) $(CPPFLAGS) $(libc_a_CFLAGS) $(CFLAGS) -MT libc/stdlib/libc_a-strtorQ.o -MD -MP -MF libc/stdlib/$(DEPDIR)/libc_a-strtorQ.Tpo -c -o libc/stdlib/libc_a-strtorQ.o `test -f 'libc/stdlib/strtorQ.c' || echo '$(srcdir)/'`libc/stdlib/strtorQ.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) libc/stdlib/$(DEPDIR)/libc_a-strtorQ.Tpo libc/stdlib/$(DEPDIR)/libc_a-strtorQ.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='libc/stdlib/strtorQ.c' object='libc/stdlib/libc_a-strtorQ.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libc_a_CPPFLAGS) $(CPPFLAGS) $(libc_a_CFLAGS) $(CFLAGS) -c -o libc/stdlib/libc_a-strtorQ.o `test -f 'libc/stdlib/strtorQ.c' || echo '$(srcdir)/'`libc/stdlib/strtorQ.c
+
+libc/stdlib/libc_a-strtorQ.obj: libc/stdlib/strtorQ.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libc_a_CPPFLAGS) $(CPPFLAGS) $(libc_a_CFLAGS) $(CFLAGS) -MT libc/stdlib/libc_a-strtorQ.obj -MD -MP -MF libc/stdlib/$(DEPDIR)/libc_a-strtorQ.Tpo -c -o libc/stdlib/libc_a-strtorQ.obj `if test -f 'libc/stdlib/strtorQ.c'; then $(CYGPATH_W) 'libc/stdlib/strtorQ.c'; else $(CYGPATH_W) '$(srcdir)/libc/stdlib/strtorQ.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) libc/stdlib/$(DEPDIR)/libc_a-strtorQ.Tpo libc/stdlib/$(DEPDIR)/libc_a-strtorQ.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='libc/stdlib/strtorQ.c' object='libc/stdlib/libc_a-strtorQ.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libc_a_CPPFLAGS) $(CPPFLAGS) $(libc_a_CFLAGS) $(CFLAGS) -c -o libc/stdlib/libc_a-strtorQ.obj `if test -f 'libc/stdlib/strtorQ.c'; then $(CYGPATH_W) 'libc/stdlib/strtorQ.c'; else $(CYGPATH_W) '$(srcdir)/libc/stdlib/strtorQ.c'; fi`
+
 libc/stdlib/libc_a-strtorx.o: libc/stdlib/strtorx.c
 @am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libc_a_CPPFLAGS) $(CPPFLAGS) $(libc_a_CFLAGS) $(CFLAGS) -MT libc/stdlib/libc_a-strtorx.o -MD -MP -MF libc/stdlib/$(DEPDIR)/libc_a-strtorx.Tpo -c -o libc/stdlib/libc_a-strtorx.o `test -f 'libc/stdlib/strtorx.c' || echo '$(srcdir)/'`libc/stdlib/strtorx.c
 @am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) libc/stdlib/$(DEPDIR)/libc_a-strtorx.Tpo libc/stdlib/$(DEPDIR)/libc_a-strtorx.Po
diff --git a/newlib/libc/stdlib/Makefile.inc b/newlib/libc/stdlib/Makefile.inc
index 9812add76..6510cdfdd 100644
--- a/newlib/libc/stdlib/Makefile.inc
+++ b/newlib/libc/stdlib/Makefile.inc
@@ -83,6 +83,7 @@  if HAVE_LONG_DOUBLE
 libc_a_SOURCES += \
 	%D%/strtodg.c \
 	%D%/strtold.c \
+	%D%/strtorQ.c \
 	%D%/strtorx.c \
 	%D%/wcstold.c
 endif # HAVE_LONG_DOUBLE
diff --git a/newlib/libc/stdlib/mprec.h b/newlib/libc/stdlib/mprec.h
index 83de932c2..c2d0a0ac3 100644
--- a/newlib/libc/stdlib/mprec.h
+++ b/newlib/libc/stdlib/mprec.h
@@ -385,6 +385,8 @@  double		_strtod_l (struct _reent *ptr, const char *__restrict s00,
 #if defined (_HAVE_LONG_DOUBLE) && !defined (_LDBL_EQ_DBL)
 int		_strtorx_l (struct _reent *, const char *, char **, int,
 			    void *, locale_t);
+int		_strtorQ_l (struct _reent *, const char *, char **, int,
+			    void *, locale_t);
 int		_strtodg_l (struct _reent *p, const char *s00, char **se,
 			    struct FPI *fpi, Long *exp, __ULong *bits,
 			    locale_t);
diff --git a/newlib/libc/stdlib/strtold.c b/newlib/libc/stdlib/strtold.c
index 6bd1c2cbb..57cc38e30 100644
--- a/newlib/libc/stdlib/strtold.c
+++ b/newlib/libc/stdlib/strtold.c
@@ -58,47 +58,45 @@  __flt_rounds(void)
 #define FLT_ROUNDS 0
 #endif
 
-long double
-_strtold_r (struct _reent *ptr, const char *__restrict s00,
-	    char **__restrict se)
-{
-#ifdef _LDBL_EQ_DBL
-  /* On platforms where long double is as wide as double.  */
-  return _strtod_l (ptr, s00, se, __get_current_locale ());
-#else
+/*
+ * The core implementation for the strtold family of functions, handling
+ * different long double formats.
+ */
+static long double
+_strtold_impl(struct _reent *ptr, const char *__restrict s00,
+              char **__restrict se, locale_t loc) {
+#if defined(_LDBL_EQ_DBL)
+  /* On platforms where long double is as wide as double. */
+  return _strtod_l(ptr, s00, se, loc);
+#elif LDBL_MANT_DIG == 64 /* For 80 bit long doubles */
   long double result;
-
-  _strtorx_l (ptr, s00, se, FLT_ROUNDS, &result, __get_current_locale ());
+  _strtorx_l(ptr, s00, se, FLT_ROUNDS, &result, loc);
   return result;
+#elif LDBL_MANT_DIG == 113 /* For 128bit long doubles */
+  long double result;
+  _strtorQ_l(ptr, s00, se, FLT_ROUNDS, &result, loc);
+  return result;
+#else
+#warning "strtold not implemented for this long double format"
+  return (long double)_strtod_l(ptr, s00, se, loc);
 #endif
 }
 
 long double
-strtold_l (const char *__restrict s00, char **__restrict se, locale_t loc)
-{
-#ifdef _LDBL_EQ_DBL
-  /* On platforms where long double is as wide as double.  */
-  return _strtod_l (_REENT, s00, se, loc);
-#else
-  long double result;
+_strtold_r(struct _reent *ptr, const char *__restrict s00,
+           char **__restrict se) {
+  return _strtold_impl(ptr, s00, se, __get_current_locale());
+}
 
-  _strtorx_l (_REENT, s00, se, FLT_ROUNDS, &result, loc);
-  return result;
-#endif
+long double
+strtold_l(const char *__restrict s00, char **__restrict se, locale_t loc) {
+  return _strtold_impl(_REENT, s00, se, loc);
 }
 
 long double
 strtold (const char *__restrict s00, char **__restrict se)
 {
-#ifdef _LDBL_EQ_DBL
-  /* On platforms where long double is as wide as double.  */
-  return _strtod_l (_REENT, s00, se, __get_current_locale ());
-#else
-  long double result;
-
-  _strtorx_l (_REENT, s00, se, FLT_ROUNDS, &result, __get_current_locale ());
-  return result;
-#endif
+  return _strtold_impl(_REENT, s00, se, __get_current_locale());
 }
 
 #endif /* _HAVE_LONG_DOUBLE */
diff --git a/newlib/libc/stdlib/strtorQ.c b/newlib/libc/stdlib/strtorQ.c
new file mode 100644
index 000000000..b3205e3b7
--- /dev/null
+++ b/newlib/libc/stdlib/strtorQ.c
@@ -0,0 +1,129 @@ 
+/****************************************************************
+
+The author of this software is David M. Gay.
+
+Copyright (C) 1998, 2000 by Lucent Technologies
+All Rights Reserved
+
+Permission to use, copy, modify, and distribute this software and
+its documentation for any purpose and without fee is hereby
+granted, provided that the above copyright notice appear in all
+copies and that both that the copyright notice and this
+permission notice and warranty disclaimer appear in supporting
+documentation, and that the name of Lucent or any of its entities
+not be used in advertising or publicity pertaining to
+distribution of the software without specific, written prior
+permission.
+
+LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
+SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
+IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
+
+****************************************************************/
+
+/* Please send bug reports to David M. Gay (dmg at acm dot org,
+ * with " at " changed at "@" and " dot " changed to ".").	*/
+
+#include <_ansi.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include "mprec.h"
+#include "gdtoa.h"
+
+#if defined (_HAVE_LONG_DOUBLE) && !defined (_LDBL_EQ_DBL)
+
+/* one or the other of IEEE_MC68k or IEEE_8087 should be #defined */
+
+#ifdef IEEE_MC68k
+#define _0 0
+#define _1 1
+#define _2 2
+#define _3 3
+#endif
+#ifdef IEEE_8087
+#define _0 3
+#define _1 2
+#define _2 1
+#define _3 0
+#endif
+
+ void
+#ifdef KR_headers
+ULtoQ(L, bits, exp, k) ULong *L; ULong *bits; Long exp; int k;
+#else
+ULtoQ(ULong *L, ULong *bits, Long exp, int k)
+#endif
+{
+	switch(k & STRTOG_Retmask) {
+	  case STRTOG_NoNumber:
+	  case STRTOG_Zero:
+		L[0] = L[1] = L[2] = L[3] = 0;
+		break;
+
+	  case STRTOG_Normal:
+		L[_3] = bits[0];
+		L[_2] = bits[1];
+		L[_1] = bits[2];
+		L[_0] = (bits[3] & ~0x10000) | ((exp + 0x3fff + 112) << 16);
+		break;
+
+	  case STRTOG_NaNbits:
+		L[_3] = bits[0];
+		L[_2] = bits[1];
+		L[_1] = bits[2];
+		L[_0] = (bits[3] & ~0x10000)
+		    | (((exp + 0x3fff + 112) << 16) | (1 << 15));
+		break;
+
+	  case STRTOG_Denormal:
+		L[_3] = bits[0];
+		L[_2] = bits[1];
+		L[_1] = bits[2];
+		L[_0] = bits[3];
+		break;
+
+	  case STRTOG_Infinite:
+		L[_0] = 0x7fff0000;
+		L[_1] = L[_2] = L[_3] = 0;
+		break;
+
+	  case STRTOG_NaN:
+		*((long double*)L) = __builtin_nanl ("");
+	  }
+	if (k & STRTOG_Neg)
+		L[_0] |= 0x80000000L;
+	}
+
+ int
+#ifdef KR_headers
+_strtorQ_l(s, sp, rounding, L, locale) struct _reent *p; CONST char *s;
+char **sp; int rounding; void *L; locale_t locale;
+#else
+_strtorQ_l(struct _reent *p, CONST char *s, char **sp,
+           int rounding, void *L, locale_t locale)
+#endif
+{
+	static FPI fpi0 = { 113, 1-16383-113+1, 32766-16383-113+1, 1, SI };
+	FPI *fpi, fpi1;
+	ULong bits[4];
+	Long exp;
+	int k;
+
+	fpi = &fpi0;
+	if (rounding != FPI_Round_near) {
+		fpi1 = fpi0;
+		fpi1.rounding = rounding;
+		fpi = &fpi1;
+		}
+	k = _strtodg_l(p, s, sp, fpi, &exp, bits, locale);
+	ULtoQ((ULong*)L, bits, exp, k);
+	return k;
+	}
+
+#endif /* _HAVE_LONG_DOUBLE && !_LDBL_EQ_DBL */