summary refs log tree commit diff
path: root/stdlib
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2022-12-19 18:56:54 +0100
committerFlorian Weimer <fweimer@redhat.com>2022-12-19 18:56:54 +0100
commite88b9f0e5cc50cab57a299dc7efe1a4eb385161d (patch)
tree2b733d221cc4247e16aef46150c2fc8153ad6db4 /stdlib
parent46378560e056300623364669de2405a7182b064f (diff)
downloadglibc-e88b9f0e5cc50cab57a299dc7efe1a4eb385161d.tar.gz
glibc-e88b9f0e5cc50cab57a299dc7efe1a4eb385161d.tar.xz
glibc-e88b9f0e5cc50cab57a299dc7efe1a4eb385161d.zip
stdio-common: Convert vfprintf and related functions to buffers
vfprintf is entangled with vfwprintf (of course), __printf_fp,
__printf_fphex, __vstrfmon_l_internal, and the strfrom family of
functions.  The latter use the internal snprintf functionality,
so vsnprintf is converted as well.

The simples conversion is __printf_fphex, followed by
__vstrfmon_l_internal and __printf_fp, and finally
__vfprintf_internal and __vfwprintf_internal.  __vsnprintf_internal
and strfrom* are mostly consuming the new interfaces, so they
are comparatively simple.

__printf_fp is a public symbol, so the FILE *-based interface
had to preserved.

The __printf_fp rewrite does not change the actual binary-to-decimal
conversion algorithm, and digits are still not emitted directly to
the target buffer.  However, the staging buffer now uses bytes
instead of wide characters, and one buffer copy is eliminated.

The changes are at least performance-neutral in my testing.
Floating point printing and snprintf improved measurably, so that
this Lua script

  for i=1,5000000 do
      print(i, i * math.pi)
  end

runs about 5% faster for me.  To preserve fprintf performance for
a simple "%d" format, this commit has some logic changes under
LABEL (unsigned_number) to avoid additional function calls.  There
are certainly some very easy performance improvements here: binary,
octal and hexadecimal formatting can easily avoid the temporary work
buffer (the number of digits can be computed ahead-of-time using one
of the __builtin_clz* built-ins). Decimal formatting can use a
specialized version of _itoa_word for base 10.

The existing (inconsistent) width handling between strfmon and printf
is preserved here.  __print_fp_buffer_1 would have to use
__translated_number_width to achieve ISO conformance for printf.

Test expectations in libio/tst-vtables-common.c are adjusted because
the internal staging buffer merges all virtual function calls into
one.

In general, stack buffer usage is greatly reduced, particularly for
unbuffered input streams.  __printf_fp can still use a large buffer
in binary128 mode for %g, though.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
Diffstat (limited to 'stdlib')
-rw-r--r--stdlib/strfmon_l.c196
-rw-r--r--stdlib/strfrom-skeleton.c38
2 files changed, 83 insertions, 151 deletions
diff --git a/stdlib/strfmon_l.c b/stdlib/strfmon_l.c
index d9b22088c7..6dc36e07cc 100644
--- a/stdlib/strfmon_l.c
+++ b/stdlib/strfmon_l.c
@@ -29,33 +29,8 @@
 #include <string.h>
 #include "../locale/localeinfo.h"
 #include <bits/floatn.h>
-
-
-#define out_char(Ch)							      \
-  do {									      \
-    if (dest >= s + maxsize - 1)					      \
-      {									      \
-	__set_errno (E2BIG);						      \
-	va_end (ap);							      \
-	return -1;							      \
-      }									      \
-    *dest++ = (Ch);							      \
-  } while (0)
-
-#define out_string(String)						      \
-  do {									      \
-    const char *_s = (String);						      \
-    while (*_s)								      \
-      out_char (*_s++);							      \
-  } while (0)
-
-#define out_nstring(String, N)						      \
-  do {									      \
-    int _n = (N);							      \
-    const char *_s = (String);						      \
-    while (_n-- > 0)							      \
-      out_char (*_s++);							      \
-  } while (0)
+#include <stdio-common/grouping_iterator.h>
+#include <printf_buffer.h>
 
 #define to_digit(Ch) ((Ch) - '0')
 
@@ -75,21 +50,15 @@
    some information in the LC_MONETARY category which should be used,
    too.  Some of the information contradicts the information which can
    be specified in format string.  */
-ssize_t
-__vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
-		       const char *format, va_list ap, unsigned int flags)
+static void
+__vstrfmon_l_buffer (struct __printf_buffer *buf, locale_t loc,
+		     const char *fmt, va_list ap, unsigned int flags)
 {
   struct __locale_data *current = loc->__locales[LC_MONETARY];
-  _IO_strfile f;
   struct printf_info info;
-  char *dest;			/* Pointer so copy the output.  */
-  const char *fmt;		/* Pointer that walks through format.  */
-
-  dest = s;
-  fmt = format;
 
   /* Loop through the format-string.  */
-  while (*fmt != '\0')
+  while (*fmt != '\0' && !__printf_buffer_has_failed (buf))
     {
       /* The floating-point value to output.  */
       union
@@ -122,11 +91,9 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
       int other_cs_precedes;
       const char *sign_string;
       const char *other_sign_string;
-      int done;
       const char *currency_symbol;
       size_t currency_symbol_len;
       long int width;
-      char *startp;
       const void *ptr;
       char space_char;
 
@@ -134,14 +101,14 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
 	 specification.  */
       if (*fmt != '%')
 	{
-	  out_char (*fmt++);
+	  __printf_buffer_putc (buf, *fmt++);
 	  continue;
 	}
 
       /* "%%" means a single '%' character.  */
       if (fmt[1] == '%')
 	{
-	  out_char (*++fmt);
+	  __printf_buffer_putc (buf, *++fmt);
 	  ++fmt;
 	  continue;
 	}
@@ -171,7 +138,8 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
 		{
 		  /* Premature EOS.  */
 		  __set_errno (EINVAL);
-		  return -1;
+		  __printf_buffer_mark_failed (buf);
+		  return;
 		}
 	      continue;
 	    case '^':			/* Don't group digits.  */
@@ -181,7 +149,8 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
 	      if (n_sign_posn != -2)
 		{
 		  __set_errno (EINVAL);
-		  return -1;
+		  __printf_buffer_mark_failed (buf);
+		  return;
 		}
 	      p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
 	      n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
@@ -190,7 +159,8 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
 	      if (n_sign_posn != -2)
 		{
 		  __set_errno (EINVAL);
-		  return -1;
+		  __printf_buffer_mark_failed (buf);
+		  return;
 		}
 	      p_sign_posn = 0;
 	      n_sign_posn = 0;
@@ -220,19 +190,12 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
 		  || (width == LONG_MAX && val > LONG_MAX % 10))
 		{
 		  __set_errno (E2BIG);
-		  return -1;
+		  __printf_buffer_mark_failed (buf);
+		  return;
 		}
 
 	      width = width * 10 + val;
 	    }
-
-	  /* If we don't have enough room for the demanded width we
-	     can stop now and return an error.  */
-	  if (width >= maxsize - (dest - s))
-	    {
-	      __set_errno (E2BIG);
-	      return -1;
-	    }
 	}
 
       /* Recognize left precision.  */
@@ -241,7 +204,8 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
 	  if (!isdigit (*++fmt))
 	    {
 	      __set_errno (EINVAL);
-	      return -1;
+	      __printf_buffer_mark_failed (buf);
+	      return;
 	    }
 	  left_prec = to_digit (*fmt);
 
@@ -258,7 +222,8 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
 	  if (!isdigit (*++fmt))
 	    {
 	      __set_errno (EINVAL);
-	      return -1;
+	      __printf_buffer_mark_failed (buf);
+	      return;
 	    }
 	  right_prec = to_digit (*fmt);
 
@@ -306,7 +271,8 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
 	  break;
 	default:		/* Any unrecognized format is an error.  */
 	  __set_errno (EINVAL);
-	  return -1;
+	  __printf_buffer_mark_failed (buf);
+	  return;
 	}
 
       /* If not specified by the format string now find the values for
@@ -327,8 +293,11 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
       /* If we have to print the digits grouped determine how many
 	 extra characters this means.  */
       if (group && left_prec != -1)
-	left_prec += __guess_grouping (left_prec,
-				       _NL_CURRENT (LC_MONETARY, MON_GROUPING));
+	{
+	  struct grouping_iterator it;
+	  __grouping_iterator_init (&it, LC_MONETARY, loc, left_prec);
+	  left_prec += it.separators;
+	}
 
       /* Now it's time to get the value.  */
       if (is_long_double == 1)
@@ -482,57 +451,46 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
 #define left_paren '('
 #define right_paren ')'
 
-      startp = dest;		/* Remember start so we can compute length.  */
+      char *startp = buf->write_ptr;
 
-      while (left_pad-- > 0)
-	out_char (' ');
+      __printf_buffer_pad (buf, ' ', left_pad);
 
       if (sign_posn == 0 && is_negative)
-	out_char (left_paren);
+	__printf_buffer_putc (buf, left_paren);
 
       if (cs_precedes)
 	{
 	  if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
 	      && sign_posn != 5)
 	    {
-	      out_string (sign_string);
+	      __printf_buffer_puts (buf, sign_string);
 	      if (sep_by_space == 2)
-		out_char (' ');
+		__printf_buffer_putc (buf, ' ');
 	    }
 
 	  if (print_curr_symbol)
-	    out_string (currency_symbol);
+	    __printf_buffer_puts (buf, currency_symbol);
 
 	  if (sign_posn == 4)
 	    {
 	      if (print_curr_symbol && sep_by_space == 2)
-		out_char (space_char);
-	      out_string (sign_string);
+		__printf_buffer_putc (buf, space_char);
+	      __printf_buffer_puts (buf, sign_string);
 	      if (sep_by_space == 1)
 		/* POSIX.2 and SUS are not clear on this case, but C99
 		   says a space follows the adjacent-symbol-and-sign */
-		out_char (' ');
+		__printf_buffer_putc (buf, ' ');
 	    }
 	  else
 	    if (print_curr_symbol && sep_by_space == 1)
-	      out_char (space_char);
+	      __printf_buffer_putc (buf, space_char);
 	}
       else
 	if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
 	    && sign_posn != 4 && sign_posn != 5)
-	  out_string (sign_string);
+	  __printf_buffer_puts (buf, sign_string);
 
       /* Print the number.  */
-#ifdef _IO_MTSAFE_IO
-      f._sbf._f._lock = NULL;
-#endif
-      _IO_init_internal (&f._sbf._f, 0);
-      _IO_JUMPS (&f._sbf) = &_IO_str_jumps;
-      _IO_str_init_static_internal (&f, dest, (s + maxsize) - dest, dest);
-      /* We clear the last available byte so we can find out whether
-	 the numeric representation is too long.  */
-      s[maxsize - 1] = '\0';
-
       memset (&info, '\0', sizeof (info));
       info.prec = right_prec;
       info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
@@ -544,25 +502,17 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
       info.extra = 1;		/* This means use values from LC_MONETARY.  */
 
       ptr = &fpnum;
-      done = __printf_fp_l (&f._sbf._f, loc, &info, &ptr);
-      if (done < 0)
-	return -1;
-
-      if (s[maxsize - 1] != '\0')
-	{
-	  __set_errno (E2BIG);
-	  return -1;
-	}
-
-      dest += done;
+      __printf_fp_l_buffer (buf, loc, &info, &ptr);
+      if (__printf_buffer_has_failed (buf))
+	return;
 
       if (!cs_precedes)
 	{
 	  if (sign_posn == 3)
 	    {
 	      if (sep_by_space == 1)
-		out_char (' ');
-	      out_string (sign_string);
+		__printf_buffer_putc (buf, ' ');
+	      __printf_buffer_puts (buf, sign_string);
 	    }
 
 	  if (print_curr_symbol)
@@ -572,55 +522,61 @@ __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
 		  || (sign_posn == 2 && sep_by_space == 1)
 		  || (sign_posn == 1 && sep_by_space == 1)
 		  || (sign_posn == 0 && sep_by_space == 1))
-		out_char (space_char);
-	      out_nstring (currency_symbol, currency_symbol_len);
+		__printf_buffer_putc (buf, space_char);
+	      __printf_buffer_write (buf, currency_symbol,
+				       __strnlen (currency_symbol,
+						  currency_symbol_len));
 	    }
 
 	  if (sign_posn == 4)
 	    {
 	      if (sep_by_space == 2)
-		out_char (' ');
-	      out_string (sign_string);
+		__printf_buffer_putc (buf, ' ');
+	      __printf_buffer_puts (buf, sign_string);
 	    }
 	}
 
       if (sign_posn == 2)
 	{
 	  if (sep_by_space == 2)
-	    out_char (' ');
-	  out_string (sign_string);
+	    __printf_buffer_putc (buf, ' ');
+	  __printf_buffer_puts (buf, sign_string);
 	}
 
       if (sign_posn == 0 && is_negative)
-	out_char (right_paren);
+	__printf_buffer_putc (buf, right_paren);
 
       /* Now test whether the output width is filled.  */
-      if (dest - startp < width)
+      if (buf->write_ptr - startp < width)
 	{
-	  if (left)
-	    /* We simply have to fill using spaces.  */
-	    do
-	      out_char (' ');
-	    while (dest - startp < width);
-	  else
+	  size_t pad_width = width - (buf->write_ptr - startp);
+	  __printf_buffer_pad (buf, ' ', pad_width);
+	  if (__printf_buffer_has_failed (buf))
+	    /* Implies length check.  */
+	    return;
+	  /* Left padding is already in the correct position.
+	     Otherwise move the field contents in place.  */
+	  if (!left)
 	    {
-	      long int dist = width - (dest - startp);
-	      for (char *cp = dest - 1; cp >= startp; --cp)
-		cp[dist] = cp[0];
-
-	      dest += dist;
-
-	      do
-		startp[--dist] = ' ';
-	      while (dist > 0);
+	      memmove (startp + pad_width, startp, buf->write_ptr - startp);
+	      memset (startp, ' ', pad_width);
 	    }
 	}
     }
+}
 
-  /* Terminate the string.  */
-  *dest = '\0';
-
-  return dest - s;
+ssize_t
+__vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
+		       const char *format, va_list ap, unsigned int flags)
+{
+  struct __printf_buffer buf;
+  __printf_buffer_init (&buf, s, maxsize, __printf_buffer_mode_strfmon);
+  __vstrfmon_l_buffer (&buf, loc, format, ap, flags);
+  __printf_buffer_putc (&buf, '\0'); /* Terminate the string.  */
+  if (__printf_buffer_has_failed (&buf))
+    return -1;
+  else
+    return buf.write_ptr - buf.write_base - 1; /* Exclude NUL byte.  */
 }
 
 ssize_t
diff --git a/stdlib/strfrom-skeleton.c b/stdlib/strfrom-skeleton.c
index 36e9adcad5..810eb315d0 100644
--- a/stdlib/strfrom-skeleton.c
+++ b/stdlib/strfrom-skeleton.c
@@ -28,6 +28,7 @@
 #include <string.h>
 #include <locale/localeinfo.h>
 #include <fix-float-double-convert-nan.h>
+#include <printf_buffer.h>
 
 #define UCHAR_T char
 #define L_(Str) Str
@@ -37,12 +38,7 @@
 int
 STRFROM (char *dest, size_t size, const char *format, FLOAT f)
 {
-  _IO_strnfile sfile;
-#ifdef _IO_MTSAFE_IO
-  sfile.f._sbf._f._lock = NULL;
-#endif
-
-  int done;
+  struct __printf_buffer_snprintf buf;
 
   /* Single-precision values need to be stored in a double type, because
      __printf_fp_l and __printf_fphex do not accept the float type.  */
@@ -106,23 +102,8 @@ STRFROM (char *dest, size_t size, const char *format, FLOAT f)
       abort ();
     }
 
-  /* The following code to prepare the virtual file has been adapted from the
-     function __vsnprintf_internal from libio.  */
-
-  if (size == 0)
-    {
-    /* When size is zero, nothing is written and dest may be a null pointer.
-       This is specified for snprintf in ISO/IEC 9899:2011, Section 7.21.6.5,
-       in the second paragraph.  Thus, if size is zero, prepare to use the
-       overflow buffer right from the start.  */
-      dest = sfile.overflow_buf;
-      size = sizeof (sfile.overflow_buf);
-    }
-
-  /* Prepare the virtual string file.  */
-  _IO_no_init (&sfile.f._sbf._f, _IO_USER_LOCK, -1, NULL, NULL);
-  _IO_JUMPS (&sfile.f._sbf) = &_IO_strn_jumps;
-  _IO_str_init_static_internal (&sfile.f, dest, size - 1, dest);
+  /* Prepare the string buffer.  */
+  __printf_buffer_snprintf_init (&buf, dest, size);
 
   /* Prepare the format specification for printf_fp.  */
   memset (&info, '\0', sizeof (info));
@@ -144,13 +125,8 @@ STRFROM (char *dest, size_t size, const char *format, FLOAT f)
   info.spec = specifier;
 
   if (info.spec != 'a' && info.spec != 'A')
-    done = __printf_fp_l (&sfile.f._sbf._f, _NL_CURRENT_LOCALE, &info, &fpptr);
+    __printf_fp_l_buffer (&buf.base, _NL_CURRENT_LOCALE, &info, &fpptr);
   else
-    done = __printf_fphex (&sfile.f._sbf._f, &info, &fpptr);
-
-  /* Terminate the string.  */
-  if (sfile.f._sbf._f._IO_buf_base != sfile.overflow_buf)
-    *sfile.f._sbf._f._IO_write_ptr = '\0';
-
-  return done;
+    __printf_fphex_l_buffer (&buf.base, _NL_CURRENT_LOCALE, &info, &fpptr);
+  return __printf_buffer_snprintf_done (&buf);
 }