about summary refs log tree commit diff
path: root/stdio-common
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 /stdio-common
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 'stdio-common')
-rw-r--r--stdio-common/printf_buffer_flush.c23
-rw-r--r--stdio-common/printf_fp.c736
-rw-r--r--stdio-common/printf_fphex.c260
-rw-r--r--stdio-common/vfprintf-internal.c664
-rw-r--r--stdio-common/vfprintf-process-arg.c172
5 files changed, 720 insertions, 1135 deletions
diff --git a/stdio-common/printf_buffer_flush.c b/stdio-common/printf_buffer_flush.c
index 9b25c0fde5..bfd1f9d733 100644
--- a/stdio-common/printf_buffer_flush.c
+++ b/stdio-common/printf_buffer_flush.c
@@ -16,6 +16,7 @@
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
+#include <errno.h>
 #include <printf_buffer.h>
 
 #include "printf_buffer-char.h"
@@ -24,7 +25,11 @@
 /* The __printf_buffer_flush_* functions are defined together with
    functions that are pulled in by strong references.  */
 #ifndef SHARED
+# pragma weak __printf_buffer_flush_snprintf
 # pragma weak __printf_buffer_flush_to_file
+# pragma weak __printf_buffer_flush_fp
+# pragma weak __printf_buffer_flush_fp_to_wide
+# pragma weak __printf_buffer_flush_fphex_to_wide
 #endif /* !SHARED */
 
 static void
@@ -34,9 +39,27 @@ __printf_buffer_do_flush (struct __printf_buffer *buf)
     {
     case __printf_buffer_mode_failed:
       return;
+    case __printf_buffer_mode_snprintf:
+      __printf_buffer_flush_snprintf ((struct __printf_buffer_snprintf *) buf);
+      return;
     case __printf_buffer_mode_to_file:
       __printf_buffer_flush_to_file ((struct __printf_buffer_to_file *) buf);
       return;
+    case __printf_buffer_mode_strfmon:
+      __set_errno (E2BIG);
+      __printf_buffer_mark_failed (buf);
+      return;
+    case __printf_buffer_mode_fp:
+      __printf_buffer_flush_fp ((struct __printf_buffer_fp *) buf);
+      return;
+    case __printf_buffer_mode_fp_to_wide:
+      __printf_buffer_flush_fp_to_wide
+        ((struct __printf_buffer_fp_to_wide *) buf);
+      return;
+    case __printf_buffer_mode_fphex_to_wide:
+      __printf_buffer_flush_fphex_to_wide
+        ((struct __printf_buffer_fphex_to_wide *) buf);
+      return;
     }
   __builtin_trap ();
 }
diff --git a/stdio-common/printf_fp.c b/stdio-common/printf_fp.c
index 3a5560fc16..e1abb1f240 100644
--- a/stdio-common/printf_fp.c
+++ b/stdio-common/printf_fp.c
@@ -41,90 +41,12 @@
 #include <wchar.h>
 #include <stdbool.h>
 #include <rounding-mode.h>
+#include <printf_buffer.h>
+#include <printf_buffer_to_file.h>
+#include <grouping_iterator.h>
 
-#ifdef COMPILE_WPRINTF
-# define CHAR_T        wchar_t
-#else
-# define CHAR_T        char
-#endif
-
-#include "_i18n_number.h"
-
-#ifndef NDEBUG
-# define NDEBUG			/* Undefine this for debugging assertions.  */
-#endif
 #include <assert.h>
 
-#define PUT(f, s, n) _IO_sputn (f, s, n)
-#define PAD(f, c, n) (wide ? _IO_wpadn (f, c, n) : _IO_padn (f, c, n))
-#undef putc
-#define putc(c, f) (wide \
-		    ? (int)_IO_putwc_unlocked (c, f) : _IO_putc_unlocked (c, f))
-
-
-/* Macros for doing the actual output.  */
-
-#define outchar(ch)							      \
-  do									      \
-    {									      \
-      const int outc = (ch);						      \
-      if (putc (outc, fp) == EOF)					      \
-	{								      \
-	  if (buffer_malloced)						      \
-	    {								      \
-	      free (buffer);						      \
-	      free (wbuffer);						      \
-	    }								      \
-	  return -1;							      \
-	}								      \
-      ++done;								      \
-    } while (0)
-
-#define PRINT(ptr, wptr, len)						      \
-  do									      \
-    {									      \
-      size_t outlen = (len);						      \
-      if (len > 20)							      \
-	{								      \
-	  if (PUT (fp, wide ? (const char *) wptr : ptr, outlen) != outlen)   \
-	    {								      \
-	      if (buffer_malloced)					      \
-		{							      \
-		  free (buffer);					      \
-		  free (wbuffer);					      \
-		}							      \
-	      return -1;						      \
-	    }								      \
-	  ptr += outlen;						      \
-	  done += outlen;						      \
-	}								      \
-      else								      \
-	{								      \
-	  if (wide)							      \
-	    while (outlen-- > 0)					      \
-	      outchar (*wptr++);					      \
-	  else								      \
-	    while (outlen-- > 0)					      \
-	      outchar (*ptr++);						      \
-	}								      \
-    } while (0)
-
-#define PADN(ch, len)							      \
-  do									      \
-    {									      \
-      if (PAD (fp, ch, len) != len)					      \
-	{								      \
-	  if (buffer_malloced)						      \
-	    {								      \
-	      free (buffer);						      \
-	      free (wbuffer);						      \
-	    }								      \
-	  return -1;							      \
-	}								      \
-      done += len;							      \
-    }									      \
-  while (0)
-
 /* We use the GNU MP library to handle large numbers.
 
    An MP variable occupies a varying number of entries in its array.  We keep
@@ -145,10 +67,6 @@ extern mp_size_t __mpn_extract_long_double (mp_ptr res_ptr, mp_size_t size,
 					    long double value);
 
 
-static wchar_t *group_number (wchar_t *buf, wchar_t *bufend,
-			      unsigned int intdig_no, const char *grouping,
-			      wchar_t thousands_sep, int ngroups);
-
 struct hack_digit_param
 {
   /* Sign of the exponent.  */
@@ -165,7 +83,7 @@ struct hack_digit_param
   MPN_VAR(tmp);
 };
 
-static wchar_t
+static char
 hack_digit (struct hack_digit_param *p)
 {
   mp_limb_t hi;
@@ -197,7 +115,7 @@ hack_digit (struct hack_digit_param *p)
 	      /* We're not prepared for an mpn variable with zero
 		 limbs.  */
 	      p->fracsize = 1;
-	      return L'0' + hi;
+	      return '0' + hi;
 	    }
 	}
 
@@ -206,13 +124,22 @@ hack_digit (struct hack_digit_param *p)
 	p->frac[p->fracsize++] = _cy;
     }
 
-  return L'0' + hi;
+  return '0' + hi;
 }
 
-int
-__printf_fp_l (FILE *fp, locale_t loc,
-	       const struct printf_info *info,
-	       const void *const *args)
+/* Version that performs grouping (if INFO->group && THOUSANDS_SEP != 0),
+   but not i18n digit translation.
+
+   The output buffer is always multibyte (not wide) at this stage.
+   Wide conversion and i18n digit translation happen later, with a
+   temporary buffer.  To prepare for that, THOUSANDS_SEP_LENGTH is the
+   final length of the thousands separator.  */
+static void
+__printf_fp_buffer_1 (struct __printf_buffer *buf, locale_t loc,
+		      char thousands_sep, char decimal,
+		      unsigned int thousands_sep_length,
+		      const struct printf_info *info,
+		      const void *const *args)
 {
   /* The floating-point value to output.  */
   union
@@ -225,18 +152,11 @@ __printf_fp_l (FILE *fp, locale_t loc,
     }
   fpnum;
 
-  /* Locale-dependent representation of decimal point.	*/
-  const char *decimal;
-  wchar_t decimalwc;
-
-  /* Locale-dependent thousands separator and grouping specification.  */
-  const char *thousands_sep = NULL;
-  wchar_t thousands_sepwc = 0;
-  const char *grouping;
-
   /* "NaN" or "Inf" for the special cases.  */
   const char *special = NULL;
-  const wchar_t *wspecial = NULL;
+
+  /* Used to determine grouping rules.  */
+  int lc_category = info->extra ? LC_MONETARY : LC_NUMERIC;
 
   /* When _Float128 is enabled in the library and ABI-distinct from long
      double, we need mp_limbs enough for any of them.  */
@@ -256,90 +176,16 @@ __printf_fp_l (FILE *fp, locale_t loc,
   /* Sign of float number.  */
   int is_neg = 0;
 
-  /* Counter for number of written characters.	*/
-  int done = 0;
-
   /* General helper (carry limb).  */
   mp_limb_t cy;
 
-  /* Nonzero if this is output on a wide character stream.  */
-  int wide = info->wide;
-
   /* Buffer in which we produce the output.  */
-  wchar_t *wbuffer = NULL;
-  char *buffer = NULL;
+  char *wbuffer = NULL;
   /* Flag whether wbuffer and buffer are malloc'ed or not.  */
   int buffer_malloced = 0;
 
   p.expsign = 0;
 
-  /* Figure out the decimal point character.  */
-  if (info->extra == 0)
-    {
-      decimal = _nl_lookup (loc, LC_NUMERIC, DECIMAL_POINT);
-      decimalwc = _nl_lookup_word
-	(loc, LC_NUMERIC, _NL_NUMERIC_DECIMAL_POINT_WC);
-    }
-  else
-    {
-      decimal = _nl_lookup (loc, LC_MONETARY, MON_DECIMAL_POINT);
-      if (*decimal == '\0')
-	decimal = _nl_lookup (loc, LC_NUMERIC, DECIMAL_POINT);
-      decimalwc = _nl_lookup_word (loc, LC_MONETARY,
-				    _NL_MONETARY_DECIMAL_POINT_WC);
-      if (decimalwc == L'\0')
-	decimalwc = _nl_lookup_word (loc, LC_NUMERIC,
-				      _NL_NUMERIC_DECIMAL_POINT_WC);
-    }
-  /* The decimal point character must not be zero.  */
-  assert (*decimal != '\0');
-  assert (decimalwc != L'\0');
-
-  if (info->group)
-    {
-      if (info->extra == 0)
-	grouping = _nl_lookup (loc, LC_NUMERIC, GROUPING);
-      else
-	grouping = _nl_lookup (loc, LC_MONETARY, MON_GROUPING);
-
-      if (*grouping <= 0 || *grouping == CHAR_MAX)
-	grouping = NULL;
-      else
-	{
-	  /* Figure out the thousands separator character.  */
-	  if (wide)
-	    {
-	      if (info->extra == 0)
-		thousands_sepwc = _nl_lookup_word
-		  (loc, LC_NUMERIC, _NL_NUMERIC_THOUSANDS_SEP_WC);
-	      else
-		thousands_sepwc =
-		  _nl_lookup_word (loc, LC_MONETARY,
-				    _NL_MONETARY_THOUSANDS_SEP_WC);
-	    }
-	  else
-	    {
-	      if (info->extra == 0)
-		thousands_sep = _nl_lookup (loc, LC_NUMERIC, THOUSANDS_SEP);
-	      else
-		thousands_sep = _nl_lookup
-		  (loc, LC_MONETARY, MON_THOUSANDS_SEP);
-	    }
-
-	  if ((wide && thousands_sepwc == L'\0')
-	      || (! wide && *thousands_sep == '\0'))
-	    grouping = NULL;
-	  else if (thousands_sepwc == L'\0')
-	    /* If we are printing multibyte characters and there is a
-	       multibyte representation for the thousands separator,
-	       we must ensure the wide character thousands separator
-	       is available, even if it is fake.  */
-	    thousands_sepwc = 0xfffffffe;
-	}
-    }
-  else
-    grouping = NULL;
-
 #define PRINTF_FP_FETCH(FLOAT, VAR, SUFFIX, MANT_DIG)			\
   {									\
     (VAR) = *(const FLOAT *) args[0];					\
@@ -349,29 +195,17 @@ __printf_fp_l (FILE *fp, locale_t loc,
       {									\
 	is_neg = signbit (VAR);						\
 	if (isupper (info->spec))					\
-	  {								\
-	    special = "NAN";						\
-	    wspecial = L"NAN";						\
-	  }								\
+	  special = "NAN";						\
 	else								\
-	  {								\
-	    special = "nan";						\
-	    wspecial = L"nan";						\
-	  }								\
+	  special = "nan";						\
       }									\
     else if (isinf (VAR))						\
       {									\
 	is_neg = signbit (VAR);						\
 	if (isupper (info->spec))					\
-	  {								\
-	    special = "INF";						\
-	    wspecial = L"INF";						\
-	  }								\
+	  special = "INF";						\
 	else								\
-	  {								\
-	    special = "inf";						\
-	    wspecial = L"inf";						\
-	  }								\
+	  special = "inf";						\
       }									\
     else								\
       {									\
@@ -405,22 +239,22 @@ __printf_fp_l (FILE *fp, locale_t loc,
 	--width;
       width -= 3;
 
-      if (!info->left && width > 0)
-	PADN (' ', width);
+      if (!info->left)
+	__printf_buffer_pad (buf, ' ', width);
 
       if (is_neg)
-	outchar ('-');
+	__printf_buffer_putc (buf, '-');
       else if (info->showsign)
-	outchar ('+');
+	__printf_buffer_putc (buf, '+');
       else if (info->space)
-	outchar (' ');
+	__printf_buffer_putc (buf, ' ');
 
-      PRINT (special, wspecial, 3);
+      __printf_buffer_puts (buf, special);
 
-      if (info->left && width > 0)
-	PADN (' ', width);
+      if (info->left)
+	__printf_buffer_pad (buf, ' ', width);
 
-      return done;
+      return;
     }
 
 
@@ -829,7 +663,7 @@ __printf_fp_l (FILE *fp, locale_t loc,
 
   {
     int width = info->width;
-    wchar_t *wstartp, *wcp;
+    char *wstartp, *wcp;
     size_t chars_needed;
     int expscale;
     int intdig_max, intdig_no = 0;
@@ -837,7 +671,6 @@ __printf_fp_l (FILE *fp, locale_t loc,
     int fracdig_max;
     int dig_max;
     int significant;
-    int ngroups = 0;
     char spec = _tolower (info->spec);
 
     if (spec == 'e')
@@ -898,38 +731,32 @@ __printf_fp_l (FILE *fp, locale_t loc,
 	significant = 0;		/* We count significant digits.	 */
       }
 
-    if (grouping)
-      {
-	/* Guess the number of groups we will make, and thus how
-	   many spaces we need for separator characters.  */
-	ngroups = __guess_grouping (intdig_max, grouping);
-	/* Allocate one more character in case rounding increases the
-	   number of groups.  */
-	chars_needed += ngroups + 1;
-      }
-
     /* Allocate buffer for output.  We need two more because while rounding
        it is possible that we need two more characters in front of all the
        other output.  If the amount of memory we have to allocate is too
        large use `malloc' instead of `alloca'.  */
-    if (__builtin_expect (chars_needed >= (size_t) -1 / sizeof (wchar_t) - 2
-			  || chars_needed < fracdig_max, 0))
+    if (__glibc_unlikely (chars_needed >= (size_t) -1 - 2
+			  || chars_needed < fracdig_max))
       {
 	/* Some overflow occurred.  */
 	__set_errno (ERANGE);
-	return -1;
+	__printf_buffer_mark_failed (buf);
+	return;
       }
-    size_t wbuffer_to_alloc = (2 + chars_needed) * sizeof (wchar_t);
+    size_t wbuffer_to_alloc = 2 + chars_needed;
     buffer_malloced = ! __libc_use_alloca (wbuffer_to_alloc);
     if (__builtin_expect (buffer_malloced, 0))
       {
-	wbuffer = (wchar_t *) malloc (wbuffer_to_alloc);
+	wbuffer = malloc (wbuffer_to_alloc);
 	if (wbuffer == NULL)
-	  /* Signal an error to the caller.  */
-	  return -1;
+	  {
+	    /* Signal an error to the caller.  */
+	    __printf_buffer_mark_failed (buf);
+	    return;
+	  }
       }
     else
-      wbuffer = (wchar_t *) alloca (wbuffer_to_alloc);
+      wbuffer = alloca (wbuffer_to_alloc);
     wcp = wstartp = wbuffer + 2;	/* Let room for rounding.  */
 
     /* Do the real work: put digits in allocated buffer.  */
@@ -945,15 +772,15 @@ __printf_fp_l (FILE *fp, locale_t loc,
 	if (info->alt
 	    || fracdig_min > 0
 	    || (fracdig_max > 0 && (p.fracsize > 1 || p.frac[0] != 0)))
-	  *wcp++ = decimalwc;
+	  *wcp++ = decimal;
       }
     else
       {
 	/* |fp| < 1.0 and the selected p.type is 'f', so put "0."
 	   in the buffer.  */
-	*wcp++ = L'0';
+	*wcp++ = '0';
 	--p.exponent;
-	*wcp++ = decimalwc;
+	*wcp++ = decimal;
       }
 
     /* Generate the needed number of fractional digits.	 */
@@ -964,7 +791,7 @@ __printf_fp_l (FILE *fp, locale_t loc,
       {
 	++fracdig_no;
 	*wcp = hack_digit (&p);
-	if (*wcp++ != L'0')
+	if (*wcp++ != '0')
 	  significant = 1;
 	else if (significant == 0)
 	  {
@@ -975,10 +802,10 @@ __printf_fp_l (FILE *fp, locale_t loc,
       }
 
     /* Do rounding.  */
-    wchar_t last_digit = wcp[-1] != decimalwc ? wcp[-1] : wcp[-2];
-    wchar_t next_digit = hack_digit (&p);
+    char last_digit = wcp[-1] != decimal ? wcp[-1] : wcp[-2];
+    char next_digit = hack_digit (&p);
     bool more_bits;
-    if (next_digit != L'0' && next_digit != L'5')
+    if (next_digit != '0' && next_digit != '5')
       more_bits = true;
     else if (p.fracsize == 1 && p.frac[0] == 0)
       /* Rest of the number is zero.  */
@@ -995,29 +822,29 @@ __printf_fp_l (FILE *fp, locale_t loc,
     else
       more_bits = true;
     int rounding_mode = get_rounding_mode ();
-    if (round_away (is_neg, (last_digit - L'0') & 1, next_digit >= L'5',
+    if (round_away (is_neg, (last_digit - '0') & 1, next_digit >= '5',
 		    more_bits, rounding_mode))
       {
-	wchar_t *wtp = wcp;
+	char *wtp = wcp;
 
 	if (fracdig_no > 0)
 	  {
 	    /* Process fractional digits.  Terminate if not rounded or
 	       radix character is reached.  */
 	    int removed = 0;
-	    while (*--wtp != decimalwc && *wtp == L'9')
+	    while (*--wtp != decimal && *wtp == '9')
 	      {
-		*wtp = L'0';
+		*wtp = '0';
 		++removed;
 	      }
 	    if (removed == fracdig_min && added_zeros > 0)
 	      --added_zeros;
-	    if (*wtp != decimalwc)
+	    if (*wtp != decimal)
 	      /* Round up.  */
 	      (*wtp)++;
 	    else if (__builtin_expect (spec == 'g' && p.type == 'f' && info->alt
 				       && wtp == wstartp + 1
-				       && wstartp[0] == L'0',
+				       && wstartp[0] == '0',
 				       0))
 	      /* This is a special case: the rounded number is 1.0,
 		 the format is 'g' or 'G', and the alternative format
@@ -1025,14 +852,14 @@ __printf_fp_l (FILE *fp, locale_t loc,
 	      --added_zeros;
 	  }
 
-	if (fracdig_no == 0 || *wtp == decimalwc)
+	if (fracdig_no == 0 || *wtp == decimal)
 	  {
 	    /* Round the integer digits.  */
-	    if (*(wtp - 1) == decimalwc)
+	    if (*(wtp - 1) == decimal)
 	      --wtp;
 
-	    while (--wtp >= wstartp && *wtp == L'9')
-	      *wtp = L'0';
+	    while (--wtp >= wstartp && *wtp == '9')
+	      *wtp = '0';
 
 	    if (wtp >= wstartp)
 	      /* Round up.  */
@@ -1056,13 +883,13 @@ __printf_fp_l (FILE *fp, locale_t loc,
 		    /* This is the case where for p.type %g the number fits
 		       really in the range for %f output but after rounding
 		       the number of digits is too big.	 */
-		    *--wstartp = decimalwc;
-		    *--wstartp = L'1';
+		    *--wstartp = decimal;
+		    *--wstartp = '1';
 
 		    if (info->alt || fracdig_no > 0)
 		      {
 			/* Overwrite the old radix character.  */
-			wstartp[intdig_no + 2] = L'0';
+			wstartp[intdig_no + 2] = '0';
 			++fracdig_no;
 		      }
 
@@ -1077,7 +904,7 @@ __printf_fp_l (FILE *fp, locale_t loc,
 		  {
 		    /* We can simply add another another digit before the
 		       radix.  */
-		    *--wstartp = L'1';
+		    *--wstartp = '1';
 		    ++intdig_no;
 		  }
 
@@ -1094,28 +921,16 @@ __printf_fp_l (FILE *fp, locale_t loc,
       }
 
     /* Now remove unnecessary '0' at the end of the string.  */
-    while (fracdig_no > fracdig_min + added_zeros && *(wcp - 1) == L'0')
+    while (fracdig_no > fracdig_min + added_zeros && *(wcp - 1) == '0')
       {
 	--wcp;
 	--fracdig_no;
       }
     /* If we eliminate all fractional digits we perhaps also can remove
        the radix character.  */
-    if (fracdig_no == 0 && !info->alt && *(wcp - 1) == decimalwc)
+    if (fracdig_no == 0 && !info->alt && *(wcp - 1) == decimal)
       --wcp;
 
-    if (grouping)
-      {
-	/* Rounding might have changed the number of groups.  We allocated
-	   enough memory but we need here the correct number of groups.  */
-	if (intdig_no != intdig_max)
-	  ngroups = __guess_grouping (intdig_no, grouping);
-
-	/* Add in separator characters, overwriting the same buffer.  */
-	wcp = group_number (wstartp, wcp, intdig_no, grouping, thousands_sepwc,
-			    ngroups);
-      }
-
     /* Write the p.exponent if it is needed.  */
     if (p.type != 'f')
       {
@@ -1125,12 +940,12 @@ __printf_fp_l (FILE *fp, locale_t loc,
 	       really smaller than -4, which requires the 'e'/'E' format.
 	       But after rounding the number has an p.exponent of -4.  */
 	    assert (wcp >= wstartp + 1);
-	    assert (wstartp[0] == L'1');
-	    __wmemcpy (wstartp, L"0.0001", 6);
-	    wstartp[1] = decimalwc;
+	    assert (wstartp[0] == '1');
+	    memcpy (wstartp, "0.0001", 6);
+	    wstartp[1] = decimal;
 	    if (wcp >= wstartp + 2)
 	      {
-		__wmemset (wstartp + 6, L'0', wcp - (wstartp + 2));
+		memset (wstartp + 6, '0', wcp - (wstartp + 2));
 		wcp += 4;
 	      }
 	    else
@@ -1138,8 +953,8 @@ __printf_fp_l (FILE *fp, locale_t loc,
 	  }
 	else
 	  {
-	    *wcp++ = (wchar_t) p.type;
-	    *wcp++ = p.expsign ? L'-' : L'+';
+	    *wcp++ = p.type;
+	    *wcp++ = p.expsign ? '-' : '+';
 
 	    /* Find the magnitude of the p.exponent.	*/
 	    expscale = 10;
@@ -1148,220 +963,293 @@ __printf_fp_l (FILE *fp, locale_t loc,
 
 	    if (p.exponent < 10)
 	      /* Exponent always has at least two digits.  */
-	      *wcp++ = L'0';
+	      *wcp++ = '0';
 	    else
 	      do
 		{
 		  expscale /= 10;
-		  *wcp++ = L'0' + (p.exponent / expscale);
+		  *wcp++ = '0' + (p.exponent / expscale);
 		  p.exponent %= expscale;
 		}
 	      while (expscale > 10);
-	    *wcp++ = L'0' + p.exponent;
+	    *wcp++ = '0' + p.exponent;
 	  }
       }
 
+    struct grouping_iterator iter;
+    if (thousands_sep != '\0' && info->group)
+      __grouping_iterator_init (&iter, lc_category, loc, intdig_no);
+    else
+      iter.separators = 0;
+
     /* Compute number of characters which must be filled with the padding
        character.  */
     if (is_neg || info->showsign || info->space)
       --width;
+    /* To count bytes, we would have to use __translated_number_width
+       for info->i18n && !info->wide.  See bug 28943.  */
     width -= wcp - wstartp;
+    /* For counting bytes, we would have to multiply by
+       thousands_sep_length.  */
+    width -= iter.separators;
 
-    if (!info->left && info->pad != '0' && width > 0)
-      PADN (info->pad, width);
+    if (!info->left && info->pad != '0')
+      __printf_buffer_pad (buf, info->pad, width);
 
     if (is_neg)
-      outchar ('-');
+      __printf_buffer_putc (buf, '-');
     else if (info->showsign)
-      outchar ('+');
+      __printf_buffer_putc (buf, '+');
     else if (info->space)
-      outchar (' ');
+      __printf_buffer_putc (buf, ' ');
 
-    if (!info->left && info->pad == '0' && width > 0)
-      PADN ('0', width);
+    if (!info->left && info->pad == '0')
+      __printf_buffer_pad (buf, '0', width);
 
-    {
-      char *buffer_end = NULL;
-      char *cp = NULL;
-      char *tmpptr;
+    if (iter.separators > 0)
+      {
+	char *cp = wstartp;
+	for (int i = 0; i < intdig_no; ++i)
+	  {
+	    if (__grouping_iterator_next (&iter))
+	      __printf_buffer_putc (buf, thousands_sep);
+	    __printf_buffer_putc (buf, *cp);
+	    ++cp;
+	  }
+	__printf_buffer_write (buf, cp, wcp - cp);
+      }
+    else
+      __printf_buffer_write (buf, wstartp, wcp - wstartp);
 
-      if (! wide)
-	{
-	  /* Create the single byte string.  */
-	  size_t decimal_len;
-	  size_t thousands_sep_len;
-	  wchar_t *copywc;
-	  size_t factor;
-	  if (info->i18n)
-	    factor = _nl_lookup_word (loc, LC_CTYPE, _NL_CTYPE_MB_CUR_MAX);
-	  else
-	    factor = 1;
+    if (info->left)
+      __printf_buffer_pad (buf, info->pad, width);
+  }
 
-	  decimal_len = strlen (decimal);
+  if (buffer_malloced)
+    free (wbuffer);
+}
 
-	  if (thousands_sep == NULL)
-	    thousands_sep_len = 0;
-	  else
-	    thousands_sep_len = strlen (thousands_sep);
+/* ASCII to localization translation.  Multibyte version.  */
+struct __printf_buffer_fp
+{
+  struct __printf_buffer base;
 
-	  size_t nbuffer = (2 + chars_needed * factor + decimal_len
-			    + ngroups * thousands_sep_len);
-	  if (__glibc_unlikely (buffer_malloced))
-	    {
-	      buffer = (char *) malloc (nbuffer);
-	      if (buffer == NULL)
-		{
-		  /* Signal an error to the caller.  */
-		  free (wbuffer);
-		  return -1;
-		}
-	    }
-	  else
-	    buffer = (char *) alloca (nbuffer);
-	  buffer_end = buffer + nbuffer;
-
-	  /* Now copy the wide character string.  Since the character
-	     (except for the decimal point and thousands separator) must
-	     be coming from the ASCII range we can esily convert the
-	     string without mapping tables.  */
-	  for (cp = buffer, copywc = wstartp; copywc < wcp; ++copywc)
-	    if (*copywc == decimalwc)
-	      cp = (char *) __mempcpy (cp, decimal, decimal_len);
-	    else if (*copywc == thousands_sepwc)
-	      cp = (char *) __mempcpy (cp, thousands_sep, thousands_sep_len);
-	    else
-	      *cp++ = (char) *copywc;
-	}
+  /* Replacement for ',' and '.'.  */
+  const char *thousands_sep;
+  const char *decimal;
+  unsigned char decimal_point_bytes;
+  unsigned char thousands_sep_length;
 
-      tmpptr = buffer;
-      if (__glibc_unlikely (info->i18n))
-	{
-#ifdef COMPILE_WPRINTF
-	  wstartp = _i18n_number_rewrite (wstartp, wcp,
-					  wbuffer + wbuffer_to_alloc);
-	  wcp = wbuffer + wbuffer_to_alloc;
-	  assert ((uintptr_t) wbuffer <= (uintptr_t) wstartp);
-	  assert ((uintptr_t) wstartp
-		  < (uintptr_t) wbuffer + wbuffer_to_alloc);
-#else
-	  tmpptr = _i18n_number_rewrite (tmpptr, cp, buffer_end);
-	  cp = buffer_end;
-	  assert ((uintptr_t) buffer <= (uintptr_t) tmpptr);
-	  assert ((uintptr_t) tmpptr < (uintptr_t) buffer_end);
-#endif
-	}
+  /* Buffer to write to.   */
+  struct __printf_buffer *next;
+
+  /* Activates outdigit translation if not NULL.  */
+  struct __locale_data *ctype;
 
-      PRINT (tmpptr, wstartp, wide ? wcp - wstartp : cp - tmpptr);
+  /* Buffer to which the untranslated ASCII digits are written.  */
+  char untranslated[PRINTF_BUFFER_SIZE_DIGITS];
+};
 
-      /* Free the memory if necessary.  */
-      if (__glibc_unlikely (buffer_malloced))
+/*  Multibyte buffer-to-buffer flush function with full translation.  */
+void
+__printf_buffer_flush_fp (struct __printf_buffer_fp *buf)
+{
+  /* No need to update buf->base.written; the actual count is
+     maintained in buf->next->written.  */
+  for (char *p = buf->untranslated; p < buf->base.write_ptr; ++p)
+    {
+      char ch = *p;
+      const char *replacement = NULL;
+      unsigned int replacement_bytes;
+      if (ch == ',')
+	{
+	  replacement = buf->thousands_sep;
+	  replacement_bytes = buf->thousands_sep_length;
+	}
+      else if (ch == '.')
+	{
+	  replacement = buf->decimal;
+	  replacement_bytes = buf->decimal_point_bytes;
+	}
+      else if (buf->ctype != NULL && '0' <= ch && ch <= '9')
 	{
-	  free (buffer);
-	  free (wbuffer);
-	  /* Avoid a double free if the subsequent PADN encounters an
-	     I/O error.  */
-	  buffer = NULL;
-	  wbuffer = NULL;
+	  int digit = ch - '0';
+	  replacement
+	    = buf->ctype->values[_NL_ITEM_INDEX (_NL_CTYPE_OUTDIGIT0_MB)
+				 + digit].string;
+	  struct lc_ctype_data *ctype = buf->ctype->private;
+	  replacement_bytes = ctype->outdigit_bytes[digit];
 	}
+      if (replacement == NULL)
+	__printf_buffer_putc (buf->next, ch);
+      else
+	__printf_buffer_write (buf->next, replacement, replacement_bytes);
     }
 
-    if (info->left && width > 0)
-      PADN (info->pad, width);
-  }
-  return done;
+  if (!__printf_buffer_has_failed (buf->next))
+    buf->base.write_ptr = buf->untranslated;
+  else
+    __printf_buffer_mark_failed (&buf->base);
 }
-libc_hidden_def (__printf_fp_l)
 
-int
-___printf_fp (FILE *fp, const struct printf_info *info,
-	      const void *const *args)
+void
+__printf_fp_l_buffer (struct __printf_buffer *buf, locale_t loc,
+		      const struct printf_info *info,
+		      const void *const *args)
 {
-  return __printf_fp_l (fp, _NL_CURRENT_LOCALE, info, args);
+  struct __printf_buffer_fp tmp;
+
+  if (info->extra)
+    {
+      tmp.thousands_sep = _nl_lookup (loc, LC_MONETARY, MON_THOUSANDS_SEP);
+      tmp.decimal = _nl_lookup (loc, LC_MONETARY, MON_DECIMAL_POINT);
+      if (tmp.decimal[0] == '\0')
+	tmp.decimal = _nl_lookup (loc, LC_NUMERIC, DECIMAL_POINT);
+    }
+  else
+    {
+      tmp.thousands_sep = _nl_lookup (loc, LC_NUMERIC, THOUSANDS_SEP);
+      tmp.decimal = _nl_lookup (loc, LC_NUMERIC, DECIMAL_POINT);
+    }
+
+  tmp.thousands_sep_length = strlen (tmp.thousands_sep);
+  if (tmp.decimal[1] == '\0' && tmp.thousands_sep_length <= 1
+      && !info->i18n)
+    {
+      /* Emit the the characters directly.  This is only possible if the
+	 separators have length 1 (or 0 in case of thousands_sep).  i18n
+	 digit translation still needs the full conversion.  */
+      __printf_fp_buffer_1 (buf, loc,
+			    tmp.thousands_sep[0], tmp.decimal[0],
+			    tmp.thousands_sep_length,
+			    info, args);
+      return;
+    }
+
+  tmp.decimal_point_bytes = strlen (tmp.decimal);
+
+  if (info->i18n)
+    tmp.ctype = loc->__locales[LC_CTYPE];
+  else
+    tmp.ctype = NULL;
+  tmp.next = buf;
+
+  __printf_buffer_init (&tmp.base, tmp.untranslated, sizeof (tmp.untranslated),
+			__printf_buffer_mode_fp);
+  __printf_fp_buffer_1 (&tmp.base, loc, ',', '.',
+			tmp.thousands_sep_length, info, args);
+  if (__printf_buffer_has_failed (&tmp.base))
+    {
+      __printf_buffer_mark_failed (tmp.next);
+      return;
+    }
+  __printf_buffer_flush_fp (&tmp);
 }
-ldbl_hidden_def (___printf_fp, __printf_fp)
-ldbl_strong_alias (___printf_fp, __printf_fp)
 
-
-/* Return the number of extra grouping characters that will be inserted
-   into a number with INTDIG_MAX integer digits.  */
+/* The wide version is implemented on top of the multibyte version using
+   translation.  */
 
-unsigned int
-__guess_grouping (unsigned int intdig_max, const char *grouping)
+struct __printf_buffer_fp_to_wide
 {
-  unsigned int groups;
+  struct __printf_buffer base;
+  wchar_t thousands_sep_wc;
+  wchar_t decimalwc;
+  struct __wprintf_buffer *next;
 
-  /* We treat all negative values like CHAR_MAX.  */
+  /* Activates outdigit translation if not NULL.  */
+  struct __locale_data *ctype;
 
-  if (*grouping == CHAR_MAX || *grouping <= 0)
-    /* No grouping should be done.  */
-    return 0;
+  char untranslated[PRINTF_BUFFER_SIZE_DIGITS];
+};
 
-  groups = 0;
-  while (intdig_max > (unsigned int) *grouping)
+void
+__printf_buffer_flush_fp_to_wide (struct __printf_buffer_fp_to_wide *buf)
+{
+  /* No need to update buf->base.written; the actual count is
+     maintained in buf->next->written.  */
+  for (char *p = buf->untranslated; p < buf->base.write_ptr; ++p)
     {
-      ++groups;
-      intdig_max -= *grouping++;
-
-      if (*grouping == CHAR_MAX
-#if CHAR_MIN < 0
-	  || *grouping < 0
-#endif
-	  )
-	/* No more grouping should be done.  */
-	break;
-      else if (*grouping == 0)
+      /* wchar_t overlaps with char in the ASCII range.  */
+      wchar_t ch = *p;
+      if (ch == L',')
 	{
-	  /* Same grouping repeats.  */
-	  groups += (intdig_max - 1) / grouping[-1];
-	  break;
+	  ch = buf->thousands_sep_wc;
+	  if (ch == 0)
+	    continue;
 	}
+      else if (ch == L'.')
+	ch = buf->decimalwc;
+      else if (buf->ctype != NULL && L'0' <= ch && ch <= L'9')
+	ch = buf->ctype->values[_NL_ITEM_INDEX (_NL_CTYPE_OUTDIGIT0_WC)
+				+ ch - L'0'].word;
+      __wprintf_buffer_putc (buf->next, ch);
     }
 
-  return groups;
+  if (!__wprintf_buffer_has_failed (buf->next))
+    buf->base.write_ptr = buf->untranslated;
+  else
+    __printf_buffer_mark_failed (&buf->base);
 }
 
-/* Group the INTDIG_NO integer digits of the number in [BUF,BUFEND).
-   There is guaranteed enough space past BUFEND to extend it.
-   Return the new end of buffer.  */
-
-static wchar_t *
-group_number (wchar_t *buf, wchar_t *bufend, unsigned int intdig_no,
-	      const char *grouping, wchar_t thousands_sep, int ngroups)
+void
+__wprintf_fp_l_buffer (struct __wprintf_buffer *buf, locale_t loc,
+		       const struct printf_info *info,
+		       const void *const *args)
 {
-  wchar_t *p;
-
-  if (ngroups == 0)
-    return bufend;
+  struct __printf_buffer_fp_to_wide tmp;
+  if (info->extra)
+    {
+      tmp.decimalwc = _nl_lookup_word (loc, LC_MONETARY,
+				       _NL_MONETARY_DECIMAL_POINT_WC);
+      tmp.thousands_sep_wc = _nl_lookup_word (loc, LC_MONETARY,
+					      _NL_MONETARY_THOUSANDS_SEP_WC);
+      if (tmp.decimalwc == 0)
+	tmp.decimalwc = _nl_lookup_word (loc, LC_NUMERIC,
+					 _NL_NUMERIC_DECIMAL_POINT_WC);
+    }
+  else
+    {
+      tmp.decimalwc = _nl_lookup_word (loc, LC_NUMERIC,
+				       _NL_NUMERIC_DECIMAL_POINT_WC);
+      tmp.thousands_sep_wc = _nl_lookup_word (loc, LC_NUMERIC,
+					      _NL_NUMERIC_THOUSANDS_SEP_WC);
+    }
 
-  /* Move the fractional part down.  */
-  __wmemmove (buf + intdig_no + ngroups, buf + intdig_no,
-	      bufend - (buf + intdig_no));
+  if (info->i18n)
+    tmp.ctype = loc->__locales[LC_CTYPE];
+  else
+    tmp.ctype = NULL;
+  tmp.next = buf;
 
-  p = buf + intdig_no + ngroups - 1;
-  do
+  __printf_buffer_init (&tmp.base, tmp.untranslated, sizeof (tmp.untranslated),
+			__printf_buffer_mode_fp_to_wide);
+  __printf_fp_buffer_1 (&tmp.base, loc, ',', '.', 1, info, args);
+  if (__printf_buffer_has_failed (&tmp.base))
     {
-      unsigned int len = *grouping++;
-      do
-	*p-- = buf[--intdig_no];
-      while (--len > 0);
-      *p-- = thousands_sep;
+      __wprintf_buffer_mark_failed (tmp.next);
+      return;
+    }
+  __printf_buffer_flush (&tmp.base);
+}
 
-      if (*grouping == CHAR_MAX
-#if CHAR_MIN < 0
-	  || *grouping < 0
-#endif
-	  )
-	/* No more grouping should be done.  */
-	break;
-      else if (*grouping == 0)
-	/* Same grouping repeats.  */
-	--grouping;
-    } while (intdig_no > (unsigned int) *grouping);
-
-  /* Copy the remaining ungrouped digits.  */
-  do
-    *p-- = buf[--intdig_no];
-  while (p > buf);
-
-  return bufend + ngroups;
+int
+___printf_fp (FILE *fp, const struct printf_info *info,
+	      const void *const *args)
+{
+  if (info->wide)
+    {
+      struct __wprintf_buffer_to_file buf;
+      __wprintf_buffer_to_file_init (&buf, fp);
+      __wprintf_fp_l_buffer (&buf.base, _NL_CURRENT_LOCALE, info, args);
+      return __wprintf_buffer_to_file_done (&buf);
+    }
+  else
+    {
+      struct __printf_buffer_to_file buf;
+      __printf_buffer_to_file_init (&buf, fp);
+      __printf_fp_l_buffer (&buf.base, _NL_CURRENT_LOCALE, info, args);
+      return __printf_buffer_to_file_done (&buf);
+    }
 }
+ldbl_hidden_def (___printf_fp, __printf_fp)
+ldbl_strong_alias (___printf_fp, __printf_fp)
diff --git a/stdio-common/printf_fphex.c b/stdio-common/printf_fphex.c
index 5af380da62..1947faba8d 100644
--- a/stdio-common/printf_fphex.c
+++ b/stdio-common/printf_fphex.c
@@ -17,10 +17,12 @@
    <https://www.gnu.org/licenses/>.  */
 
 #include <array_length.h>
+#include <assert.h>
 #include <ctype.h>
 #include <ieee754.h>
 #include <math.h>
 #include <printf.h>
+#include <libioP.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
@@ -30,6 +32,9 @@
 #include <locale/localeinfo.h>
 #include <stdbool.h>
 #include <rounding-mode.h>
+#include <sys/param.h>
+#include <printf_buffer.h>
+#include <errno.h>
 
 #if __HAVE_DISTINCT_FLOAT128
 # include "ieee754_float128.h"
@@ -39,58 +44,11 @@
 		IEEE854_FLOAT128_BIAS)
 #endif
 
-/* #define NDEBUG 1*/		/* Undefine this for debugging assertions.  */
-#include <assert.h>
-
-#include <libioP.h>
-#define PUT(f, s, n) _IO_sputn (f, s, n)
-#define PAD(f, c, n) (wide ? _IO_wpadn (f, c, n) : _IO_padn (f, c, n))
-#undef putc
-#define putc(c, f) (wide \
-		     ? (int)_IO_putwc_unlocked (c, f) : _IO_putc_unlocked (c, f))
-
-
-/* Macros for doing the actual output.  */
-
-#define outchar(ch)							      \
-  do									      \
-    {									      \
-      const int outc = (ch);						      \
-      if (putc (outc, fp) == EOF)					      \
-	return -1;							      \
-      ++done;								      \
-    } while (0)
-
-#define PRINT(ptr, wptr, len)						      \
-  do									      \
-    {									      \
-      size_t outlen = (len);						      \
-      if (wide)								      \
-	while (outlen-- > 0)						      \
-	  outchar (*wptr++);						      \
-      else								      \
-	while (outlen-- > 0)						      \
-	  outchar (*ptr++);						      \
-    } while (0)
-
-#define PADN(ch, len)							      \
-  do									      \
-    {									      \
-      if (PAD (fp, ch, len) != len)					      \
-	return -1;							      \
-      done += len;							      \
-    }									      \
-  while (0)
-
-#ifndef MIN
-# define MIN(a,b) ((a)<(b)?(a):(b))
-#endif
-
-
-int
-__printf_fphex (FILE *fp,
-		const struct printf_info *info,
-		const void *const *args)
+static void
+__printf_fphex_buffer (struct __printf_buffer *buf,
+		       const char *decimal,
+		       const struct printf_info *info,
+		       const void *const *args)
 {
   /* The floating-point value to output.  */
   union
@@ -106,34 +64,19 @@ __printf_fphex (FILE *fp,
   /* This function always uses LC_NUMERIC.  */
   assert (info->extra == 0);
 
-  /* Locale-dependent representation of decimal point. Hexadecimal
-     formatting always using LC_NUMERIC (disregarding info->extra).  */
-  const char *decimal = _NL_CURRENT (LC_NUMERIC, DECIMAL_POINT);
-  wchar_t decimalwc = _NL_CURRENT_WORD (LC_NUMERIC,
-					_NL_NUMERIC_DECIMAL_POINT_WC);
-
-  /* The decimal point character must never be zero.  */
-  assert (*decimal != '\0' && decimalwc != L'\0');
-
   /* "NaN" or "Inf" for the special cases.  */
   const char *special = NULL;
-  const wchar_t *wspecial = NULL;
 
   /* Buffer for the generated number string for the mantissa.  The
      maximal size for the mantissa is 128 bits.  */
   char numbuf[32];
   char *numstr;
   char *numend;
-  wchar_t wnumbuf[32];
-  wchar_t *wnumstr;
-  wchar_t *wnumend;
   int negative;
 
   /* The maximal exponent of two in decimal notation has 5 digits.  */
   char expbuf[5];
   char *expstr;
-  wchar_t wexpbuf[5];
-  wchar_t *wexpstr;
   int expnegative;
   int exponent;
 
@@ -149,12 +92,6 @@ __printf_fphex (FILE *fp,
   /* Width.  */
   int width = info->width;
 
-  /* Number of characters written.  */
-  int done = 0;
-
-  /* Nonzero if this is output on a wide character stream.  */
-  int wide = info->wide;
-
 #define PRINTF_FPHEX_FETCH(FLOAT, VAR)					\
   {									\
     (VAR) = *(const FLOAT *) args[0];					\
@@ -163,30 +100,18 @@ __printf_fphex (FILE *fp,
     if (isnan (VAR))							\
       {									\
 	if (isupper (info->spec))					\
-	  {								\
-	    special = "NAN";						\
-	    wspecial = L"NAN";						\
-	  }								\
+	  special = "NAN";						\
 	else								\
-	  {								\
-	    special = "nan";						\
-	    wspecial = L"nan";						\
-	  }								\
+	  special = "nan";						\
       }									\
     else								\
       {									\
 	if (isinf (VAR))						\
 	  {								\
 	    if (isupper (info->spec))					\
-	      {								\
-		special = "INF";					\
-		wspecial = L"INF";					\
-	      }								\
+	      special = "INF";						\
 	    else							\
-	      {								\
-		special = "inf";					\
-		wspecial = L"inf";					\
-	      }								\
+	      special = "inf";						\
 	  }								\
       }									\
     negative = signbit (VAR);						\
@@ -215,22 +140,22 @@ __printf_fphex (FILE *fp,
 	--width;
       width -= 3;
 
-      if (!info->left && width > 0)
-	PADN (' ', width);
+      if (!info->left)
+	__printf_buffer_pad (buf, ' ', width);
 
       if (negative)
-	outchar ('-');
+	__printf_buffer_putc (buf, '-');
       else if (info->showsign)
-	outchar ('+');
+	__printf_buffer_putc (buf, '+');
       else if (info->space)
-	outchar (' ');
+	__printf_buffer_putc (buf, ' ');
 
-      PRINT (special, wspecial, 3);
+      __printf_buffer_puts (buf, special);
 
-      if (info->left && width > 0)
-	PADN (' ', width);
+      if (info->left)
+	__printf_buffer_pad (buf, ' ', width);
 
-      return done;
+      return;
     }
 
 #if __HAVE_DISTINCT_FLOAT128
@@ -252,26 +177,15 @@ __printf_fphex (FILE *fp,
       zero_mantissa = num == 0;
 
       if (sizeof (unsigned long int) > 6)
-	{
-	  wnumstr = _itowa_word (num, wnumbuf + (sizeof wnumbuf) / sizeof (wchar_t), 16,
-				 info->spec == 'A');
 	  numstr = _itoa_word (num, numbuf + sizeof numbuf, 16,
 			       info->spec == 'A');
-	}
       else
-	{
-	  wnumstr = _itowa (num, wnumbuf + sizeof wnumbuf / sizeof (wchar_t), 16,
-			    info->spec == 'A');
 	  numstr = _itoa (num, numbuf + sizeof numbuf, 16,
 			  info->spec == 'A');
-	}
 
       /* Fill with zeroes.  */
-      while (wnumstr > wnumbuf + (sizeof wnumbuf - 52) / sizeof (wchar_t))
-	{
-	  *--wnumstr = L'0';
-	  *--numstr = '0';
-	}
+      while (numstr > numbuf + (sizeof numbuf - 13))
+	*--numstr = '0';
 
       leading = fpnum.dbl.ieee.exponent == 0 ? '0' : '1';
 
@@ -307,13 +221,9 @@ __printf_fphex (FILE *fp,
   /* Look for trailing zeroes.  */
   if (! zero_mantissa)
     {
-      wnumend = array_end (wnumbuf);
       numend = array_end (numbuf);
-      while (wnumend[-1] == L'0')
-	{
-	  --wnumend;
+      while (numend[-1] == '0')
 	  --numend;
-	}
 
       bool do_round_away = false;
 
@@ -352,7 +262,6 @@ __printf_fphex (FILE *fp,
 		 like in ASCII.  This is true for the rest of GNU, too.  */
 	      if (ch == '9')
 		{
-		  wnumstr[cnt] = (wchar_t) info->spec;
 		  numstr[cnt] = info->spec;	/* This is tricky,
 						   think about it!  */
 		  break;
@@ -360,14 +269,10 @@ __printf_fphex (FILE *fp,
 	      else if (tolower (ch) < 'f')
 		{
 		  ++numstr[cnt];
-		  ++wnumstr[cnt];
 		  break;
 		}
 	      else
-		{
-		  numstr[cnt] = '0';
-		  wnumstr[cnt] = L'0';
-		}
+		numstr[cnt] = '0';
 	    }
 	  if (cnt < 0)
 	    {
@@ -401,13 +306,10 @@ __printf_fphex (FILE *fp,
       if (precision == -1)
 	precision = 0;
       numend = numstr;
-      wnumend = wnumstr;
     }
 
   /* Now we can compute the exponent string.  */
   expstr = _itoa_word (exponent, expbuf + sizeof expbuf, 10, 0);
-  wexpstr = _itowa_word (exponent,
-			 wexpbuf + sizeof wexpbuf / sizeof (wchar_t), 10, 0);
 
   /* Now we have all information to compute the size.  */
   width -= ((negative || info->showsign || info->space)
@@ -421,54 +323,110 @@ __printf_fphex (FILE *fp,
      A special case when the mantissa or the precision is zero and the `#'
      is not given.  In this case we must not print the decimal point.  */
   if (precision > 0 || info->alt)
-    width -= wide ? 1 : strlen (decimal);
+    --width;
 
-  if (!info->left && info->pad != '0' && width > 0)
-    PADN (' ', width);
+  if (!info->left && info->pad != '0')
+    __printf_buffer_pad (buf, ' ', width);
 
   if (negative)
-    outchar ('-');
+    __printf_buffer_putc (buf, '-');
   else if (info->showsign)
-    outchar ('+');
+    __printf_buffer_putc (buf, '+');
   else if (info->space)
-    outchar (' ');
+    __printf_buffer_putc (buf, ' ');
 
-  outchar ('0');
+  __printf_buffer_putc (buf, '0');
   if ('X' - 'A' == 'x' - 'a')
-    outchar (info->spec + ('x' - 'a'));
+    __printf_buffer_putc (buf, info->spec + ('x' - 'a'));
   else
-    outchar (info->spec == 'A' ? 'X' : 'x');
+    __printf_buffer_putc (buf, info->spec == 'A' ? 'X' : 'x');
 
-  if (!info->left && info->pad == '0' && width > 0)
-    PADN ('0', width);
+  if (!info->left && info->pad == '0')
+    __printf_buffer_pad (buf, '0', width);
 
-  outchar (leading);
+  __printf_buffer_putc (buf, leading);
 
   if (precision > 0 || info->alt)
-    {
-      const wchar_t *wtmp = &decimalwc;
-      PRINT (decimal, wtmp, wide ? 1 : strlen (decimal));
-    }
+    __printf_buffer_puts (buf, decimal);
 
   if (precision > 0)
     {
       ssize_t tofill = precision - (numend - numstr);
-      PRINT (numstr, wnumstr, MIN (numend - numstr, precision));
-      if (tofill > 0)
-	PADN ('0', tofill);
+      __printf_buffer_write (buf, numstr, MIN (numend - numstr, precision));
+      __printf_buffer_pad (buf, '0', tofill);
     }
 
   if ('P' - 'A' == 'p' - 'a')
-    outchar (info->spec + ('p' - 'a'));
+    __printf_buffer_putc (buf, info->spec + ('p' - 'a'));
   else
-    outchar (info->spec == 'A' ? 'P' : 'p');
+    __printf_buffer_putc (buf, info->spec == 'A' ? 'P' : 'p');
+
+  __printf_buffer_putc (buf, expnegative ? '-' : '+');
+
+  __printf_buffer_write (buf, expstr, (expbuf + sizeof expbuf) - expstr);
+
+  if (info->left && info->pad != '0')
+    __printf_buffer_pad (buf, info->pad, width);
+}
+
+void
+__printf_fphex_l_buffer (struct __printf_buffer *buf, locale_t loc,
+			 const struct printf_info *info,
+			 const void *const *args)
+{
+  __printf_fphex_buffer (buf, _nl_lookup (loc, LC_NUMERIC, DECIMAL_POINT),
+			 info, args);
+}
 
-  outchar (expnegative ? '-' : '+');
 
-  PRINT (expstr, wexpstr, (expbuf + sizeof expbuf) - expstr);
+/* The wide buffer version is implemented by translating the output of
+   the multibyte verison.  */
 
-  if (info->left && info->pad != '0' && width > 0)
-    PADN (info->pad, width);
+struct __printf_buffer_fphex_to_wide
+{
+  struct __printf_buffer base;
+  wchar_t decimalwc;
+  struct __wprintf_buffer *next;
+  char untranslated[PRINTF_BUFFER_SIZE_DIGITS];
+};
+
+/* Translate to wide characters, rewriting "." to the actual decimal
+   point.  */
+void
+__printf_buffer_flush_fphex_to_wide (struct __printf_buffer_fphex_to_wide *buf)
+{
+  /* No need to adjust buf->base.written, only buf->next->written matters.  */
+  for (char *p = buf->untranslated; p < buf->base.write_ptr; ++p)
+    {
+      /* wchar_t overlaps with char in the ASCII range.  */
+      wchar_t ch = *p;
+      if (ch == L'.')
+	ch = buf->decimalwc;
+      __wprintf_buffer_putc (buf->next, ch);
+    }
 
-  return done;
+  if (!__wprintf_buffer_has_failed (buf->next))
+    buf->base.write_ptr = buf->untranslated;
+  else
+    __printf_buffer_mark_failed (&buf->base);
+}
+
+void
+__wprintf_fphex_l_buffer (struct __wprintf_buffer *next, locale_t loc,
+			  const struct printf_info *info,
+			  const void *const *args)
+{
+  struct __printf_buffer_fphex_to_wide buf;
+  __printf_buffer_init (&buf.base, buf.untranslated, sizeof (buf.untranslated),
+			__printf_buffer_mode_fphex_to_wide);
+  buf.decimalwc = _nl_lookup_word (loc, LC_NUMERIC,
+				   _NL_NUMERIC_DECIMAL_POINT_WC);
+  buf.next = next;
+  __printf_fphex_buffer (&buf.base, ".", info, args);
+  if (__printf_buffer_has_failed (&buf.base))
+    {
+      __wprintf_buffer_mark_failed (buf.next);
+      return;
+    }
+  __printf_buffer_flush_fphex_to_wide (&buf);
 }
diff --git a/stdio-common/vfprintf-internal.c b/stdio-common/vfprintf-internal.c
index fb94961f37..83a6aea510 100644
--- a/stdio-common/vfprintf-internal.c
+++ b/stdio-common/vfprintf-internal.c
@@ -16,6 +16,7 @@
    <https://www.gnu.org/licenses/>.  */
 
 #include <array_length.h>
+#include <assert.h>
 #include <ctype.h>
 #include <limits.h>
 #include <printf.h>
@@ -29,9 +30,12 @@
 #include <sys/param.h>
 #include <_itoa.h>
 #include <locale/localeinfo.h>
+#include <grouping_iterator.h>
 #include <stdio.h>
 #include <scratch_buffer.h>
 #include <intprops.h>
+#include <printf_buffer.h>
+#include <printf_buffer_to_file.h>
 
 /* This code is shared between the standard stdio implementation found
    in GNU C library and the libio implementation originally found in
@@ -47,21 +51,21 @@
 #endif
 
 #define ARGCHECK(S, Format) \
-  do									      \
-    {									      \
-      /* Check file argument for consistence.  */			      \
-      CHECK_FILE (S, -1);						      \
-      if (S->_flags & _IO_NO_WRITES)					      \
-	{								      \
-	  S->_flags |= _IO_ERR_SEEN;					      \
-	  __set_errno (EBADF);						      \
-	  return -1;							      \
-	}								      \
-      if (Format == NULL)						      \
-	{								      \
-	  __set_errno (EINVAL);						      \
-	  return -1;							      \
-	}								      \
+  do									     \
+    {									     \
+      /* Check file argument for consistence.  */			     \
+      CHECK_FILE (S, -1);						     \
+      if (S->_flags & _IO_NO_WRITES)					     \
+       {								     \
+	 S->_flags |= _IO_ERR_SEEN;					     \
+	 __set_errno (EBADF);						     \
+	 return -1;							     \
+       }								     \
+      if (Format == NULL)						     \
+       {								     \
+	 __set_errno (EINVAL);						     \
+	 return -1;							     \
+       }								     \
     } while (0)
 #define UNBUFFERED_P(S) ((S)->_flags & _IO_UNBUFFERED)
 
@@ -116,37 +120,9 @@
   while (0)
 #endif
 
-/* Add LENGTH to DONE.  Return the new value of DONE, or -1 on
-   overflow (and set errno accordingly).  */
-static inline int
-done_add_func (size_t length, int done)
-{
-  if (done < 0)
-    return done;
-  int ret;
-  if (INT_ADD_WRAPV (done, length, &ret))
-    {
-      __set_errno (EOVERFLOW);
-      return -1;
-    }
-  return ret;
-}
-
-#define done_add(val)							\
-  do									\
-    {									\
-      /* Ensure that VAL has a type similar to int.  */			\
-      _Static_assert (sizeof (val) == sizeof (int), "value int size");	\
-      _Static_assert ((__typeof__ (val)) -1 < 0, "value signed");	\
-      done = done_add_func ((val), done);				\
-      if (done < 0)							\
-	goto all_done;							\
-    }									\
-  while (0)
-
 #ifndef COMPILE_WPRINTF
+# include "printf_buffer-char.h"
 # define vfprintf	__vfprintf_internal
-# define CHAR_T		char
 # define OTHER_CHAR_T   wchar_t
 # define UCHAR_T	unsigned char
 # define INT_T		int
@@ -155,14 +131,12 @@ typedef const char *THOUSANDS_SEP_T;
 # define ISDIGIT(Ch)	((unsigned int) ((Ch) - '0') < 10)
 # define STR_LEN(Str)	strlen (Str)
 
-# define PUT(F, S, N)	_IO_sputn ((F), (S), (N))
-# define PUTC(C, F)	_IO_putc_unlocked (C, F)
 # define ORIENT		if (_IO_vtable_offset (s) == 0 && _IO_fwide (s, -1) != -1)\
 			  return -1
 # define CONVERT_FROM_OTHER_STRING __wcsrtombs
 #else
+# include "printf_buffer-wchar_t.h"
 # define vfprintf	__vfwprintf_internal
-# define CHAR_T		wchar_t
 # define OTHER_CHAR_T   char
 /* This is a hack!!!  There should be a type uwchar_t.  */
 # define UCHAR_T	unsigned int /* uwchar_t */
@@ -174,8 +148,6 @@ typedef wchar_t THOUSANDS_SEP_T;
 
 # include <_itowa.h>
 
-# define PUT(F, S, N)	_IO_sputn ((F), (S), (N))
-# define PUTC(C, F)	_IO_putwc_unlocked (C, F)
 # define ORIENT		if (_IO_fwide (s, 1) != 1) return -1
 # define CONVERT_FROM_OTHER_STRING __mbsrtowcs
 
@@ -186,76 +158,16 @@ typedef wchar_t THOUSANDS_SEP_T;
 # define EOF WEOF
 #endif
 
-static inline int
-pad_func (FILE *s, CHAR_T padchar, int width, int done)
-{
-  if (width > 0)
-    {
-      ssize_t written;
-#ifndef COMPILE_WPRINTF
-      written = _IO_padn (s, padchar, width);
-#else
-      written = _IO_wpadn (s, padchar, width);
-#endif
-      if (__glibc_unlikely (written != width))
-	return -1;
-      return done_add_func (width, done);
-    }
-  return done;
-}
-
-#define PAD(Padchar)							\
-  do									\
-    {									\
-      done = pad_func (s, (Padchar), width, done);			\
-      if (done < 0)							\
-	goto all_done;							\
-    }									\
-  while (0)
-
-#include "_i18n_number.h"
-
 /* Include the shared code for parsing the format string.  */
 #include "printf-parse.h"
 
 
-#define	outchar(Ch)							      \
-  do									      \
-    {									      \
-      const INT_T outc = (Ch);						      \
-      if (PUTC (outc, s) == EOF || done == INT_MAX)			      \
-	{								      \
-	  done = -1;							      \
-	  goto all_done;						      \
-	}								      \
-      ++done;								      \
-    }									      \
-  while (0)
-
-static inline int
-outstring_func (FILE *s, const UCHAR_T *string, size_t length, int done)
-{
-  assert ((size_t) done <= (size_t) INT_MAX);
-  if ((size_t) PUT (s, string, length) != (size_t) (length))
-    return -1;
-  return done_add_func (length, done);
-}
-
-#define outstring(String, Len)						\
-  do									\
-    {									\
-      const void *string_ = (String);					\
-      done = outstring_func (s, string_, (Len), done);			\
-      if (done < 0)							\
-	goto all_done;							\
-    }									\
-   while (0)
-
 /* Write the string SRC to S.  If PREC is non-negative, write at most
    PREC bytes.  If LEFT is true, perform left justification.  */
-static int
-outstring_converted_wide_string (FILE *s, const OTHER_CHAR_T *src, int prec,
-				 int width, bool left, int done)
+static void
+outstring_converted_wide_string (struct Xprintf_buffer *target,
+				 const OTHER_CHAR_T *src, int prec,
+				 int width, bool left)
 {
   /* Use a small buffer to combine processing of multiple characters.
      CONVERT_FROM_OTHER_STRING expects the buffer size in (wide)
@@ -290,7 +202,10 @@ outstring_converted_wide_string (FILE *s, const OTHER_CHAR_T *src, int prec,
 	      size_t written = CONVERT_FROM_OTHER_STRING
 		(buf, &src_copy, write_limit, &mbstate);
 	      if (written == (size_t) -1)
-		return -1;
+		{
+		  Xprintf_buffer_mark_failed (target);
+		  return;
+		}
 	      if (written == 0)
 		break;
 	      total_written += written;
@@ -299,12 +214,9 @@ outstring_converted_wide_string (FILE *s, const OTHER_CHAR_T *src, int prec,
 	}
 
       /* Output initial padding.  */
-      if (total_written < width)
-	{
-	  done = pad_func (s, L_(' '), width - total_written, done);
-	  if (done < 0)
-	    return done;
-	}
+      Xprintf_buffer_pad (target, L_(' '), width - total_written);
+      if (Xprintf_buffer_has_failed (target))
+	return;
     }
 
   /* Convert the input string, piece by piece.  */
@@ -324,12 +236,13 @@ outstring_converted_wide_string (FILE *s, const OTHER_CHAR_T *src, int prec,
 	size_t written = CONVERT_FROM_OTHER_STRING
 	  (buf, &src, write_limit, &mbstate);
 	if (written == (size_t) -1)
-	  return -1;
+	  {
+	    Xprintf_buffer_mark_failed (target);
+	    return;
+	  }
 	if (written == 0)
 	  break;
-	done = outstring_func (s, (const UCHAR_T *) buf, written, done);
-	if (done < 0)
-	  return done;
+	Xprintf_buffer_write (target, buf, written);
 	total_written += written;
 	if (prec >= 0)
 	  remaining -= written;
@@ -337,21 +250,20 @@ outstring_converted_wide_string (FILE *s, const OTHER_CHAR_T *src, int prec,
   }
 
   /* Add final padding.  */
-  if (width > 0 && left && total_written < width)
-    return pad_func (s, L_(' '), width - total_written, done);
-  return done;
+  if (width > 0 && left)
+    Xprintf_buffer_pad (target, L_(' '), width - total_written);
 }
 
 /* Calls __printf_fp or __printf_fphex based on the value of the
    format specifier INFO->spec.  */
-static inline int
-__printf_fp_spec (FILE *fp, const struct printf_info *info,
-		  const void *const *args)
+static inline void
+__printf_fp_spec (struct Xprintf_buffer *target,
+		  const struct printf_info *info, const void *const *args)
 {
   if (info->spec == 'a' || info->spec == 'A')
-    return __printf_fphex (fp, info, args);
+    Xprintf (fphex_l_buffer) (target, _NL_CURRENT_LOCALE, info, args);
   else
-    return __printf_fp (fp, info, args);
+    Xprintf (fp_l_buffer) (target, _NL_CURRENT_LOCALE, info, args);
 }
 
 /* For handling long_double and longlong we use the same flag.  If
@@ -656,31 +568,29 @@ static const uint8_t jump_table[] =
       REF (form_binary),	/* for 'B', 'b' */			      \
     }
 
-/* Helper function to provide temporary buffering for unbuffered streams.  */
-static int buffered_vfprintf (FILE *stream, const CHAR_T *fmt, va_list,
-			      unsigned int)
-     __THROW __attribute__ ((noinline));
-
 /* Handle positional format specifiers.  */
-static int printf_positional (FILE *s,
-			      const CHAR_T *format, int readonly_format,
-			      va_list ap, va_list *ap_savep, int done,
-			      int nspecs_done, const UCHAR_T *lead_str_end,
-			      CHAR_T *work_buffer, int save_errno,
-			      const char *grouping,
-			      THOUSANDS_SEP_T thousands_sep,
-			      unsigned int mode_flags);
+static void printf_positional (struct Xprintf_buffer *buf,
+			       const CHAR_T *format, int readonly_format,
+			       va_list ap, va_list *ap_savep,
+			       int nspecs_done, const UCHAR_T *lead_str_end,
+			       CHAR_T *work_buffer, int save_errno,
+			       const char *grouping,
+			       THOUSANDS_SEP_T thousands_sep,
+			       unsigned int mode_flags);
 
 /* Handle unknown format specifier.  */
-static int printf_unknown (FILE *, const struct printf_info *) __THROW;
-
-/* Group digits of number string.  */
-static CHAR_T *group_number (CHAR_T *, CHAR_T *, CHAR_T *, const char *,
-			     THOUSANDS_SEP_T);
-
-/* The function itself.  */
-int
-vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
+static void printf_unknown (struct Xprintf_buffer *,
+			    const struct printf_info *) __THROW;
+
+static void group_number (struct Xprintf_buffer *buf,
+			  struct grouping_iterator *iter,
+			  CHAR_T *from, CHAR_T *to,
+			  THOUSANDS_SEP_T thousands_sep, bool i18n);
+
+/* The buffer-based function itself.  */
+void
+Xprintf_buffer (struct Xprintf_buffer *buf, const CHAR_T *format,
+		  va_list ap, unsigned int mode_flags)
 {
   /* The character used as thousands separator.  */
   THOUSANDS_SEP_T thousands_sep = 0;
@@ -688,9 +598,6 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
   /* The string describing the size of groups of digits.  */
   const char *grouping;
 
-  /* Place to accumulate the result.  */
-  int done;
-
   /* Current character in format string.  */
   const UCHAR_T *f;
 
@@ -717,30 +624,7 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
      0 if unknown.  */
   int readonly_format = 0;
 
-  /* Orient the stream.  */
-#ifdef ORIENT
-  ORIENT;
-#endif
-
-  /* Sanity check of arguments.  */
-  ARGCHECK (s, format);
-
-#ifdef ORIENT
-  /* Check for correct orientation.  */
-  if (_IO_vtable_offset (s) == 0
-      && _IO_fwide (s, sizeof (CHAR_T) == 1 ? -1 : 1)
-      != (sizeof (CHAR_T) == 1 ? -1 : 1))
-    /* The stream is already oriented otherwise.  */
-    return EOF;
-#endif
-
-  if (UNBUFFERED_P (s))
-    /* Use a helper function which will allocate a local temporary buffer
-       for the stream and then call us again.  */
-    return buffered_vfprintf (s, format, ap, mode_flags);
-
   /* Initialize local variables.  */
-  done = 0;
   grouping = (const char *) -1;
 #ifdef __va_copy
   /* This macro will be available soon in gcc's <stdarg.h>.  We need it
@@ -759,17 +643,15 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
   f = lead_str_end = __find_specmb ((const UCHAR_T *) format);
 #endif
 
-  /* Lock stream.  */
-  _IO_cleanup_region_start ((void (*) (void *)) &_IO_funlockfile, s);
-  _IO_flockfile (s);
-
   /* Write the literal text before the first format.  */
-  outstring ((const UCHAR_T *) format,
-	     lead_str_end - (const UCHAR_T *) format);
+  Xprintf_buffer_write (buf, format,
+			  lead_str_end - (const UCHAR_T *) format);
+  if (Xprintf_buffer_has_failed (buf))
+    return;
 
   /* If we only have to print a simple string, return now.  */
   if (*f == L_('\0'))
-    goto all_done;
+    return;
 
   /* Use the slow path in case any printf handler is registered.  */
   if (__glibc_unlikely (__printf_function_table != NULL
@@ -885,7 +767,7 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
 	    if (pos == -1)
 	      {
 		__set_errno (EOVERFLOW);
-		done = -1;
+		Xprintf_buffer_mark_failed (buf);
 		goto all_done;
 	      }
 
@@ -912,7 +794,7 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
       if (__glibc_unlikely (width == -1))
 	{
 	  __set_errno (EOVERFLOW);
-	  done = -1;
+	  Xprintf_buffer_mark_failed (buf);
 	  goto all_done;
 	}
 
@@ -935,7 +817,7 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
 	      if (pos == -1)
 		{
 		  __set_errno (EOVERFLOW);
-		  done = -1;
+		  Xprintf_buffer_mark_failed (buf);
 		  goto all_done;
 		}
 
@@ -958,7 +840,7 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
 	  if (prec == -1)
 	    {
 	      __set_errno (EOVERFLOW);
-	      done = -1;
+	      Xprintf_buffer_mark_failed (buf);
 	      goto all_done;
 	    }
 	}
@@ -1058,13 +940,7 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
 	    PARSE_FLOAT_VA_ARG_EXTENDED (info);
 	    const void *ptr = &the_arg;
 
-	    int function_done = __printf_fp_spec (s, &info, &ptr);
-	    if (function_done < 0)
-	      {
-		done = -1;
-		goto all_done;
-	      }
-	    done_add (function_done);
+	    __printf_fp_spec (buf, &info, &ptr);
 	  }
 	  break;
 
@@ -1073,7 +949,7 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
 	    {
 	      /* The format string ended before the specifier is complete.  */
 	      __set_errno (EINVAL);
-	      done = -1;
+	      Xprintf_buffer_mark_failed (buf);
 	      goto all_done;
 	    }
 
@@ -1093,30 +969,28 @@ vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
 #endif
 
       /* Write the following constant string.  */
-      outstring (end_of_spec, f - end_of_spec);
+      Xprintf_buffer_write (buf, (const CHAR_T *) end_of_spec,
+			      f - end_of_spec);
     }
-  while (*f != L_('\0'));
+  while (*f != L_('\0') && !Xprintf_buffer_has_failed (buf));
 
-  /* Unlock stream and return.  */
-  goto all_done;
+ all_done:
+  /* printf_positional performs cleanup under its all_done label, so
+     vfprintf-process-arg.c uses it for this function and
+     printf_positional below.  */
+  return;
 
   /* Hand off processing for positional parameters.  */
 do_positional:
-  done = printf_positional (s, format, readonly_format, ap, &ap_save,
-			    done, nspecs_done, lead_str_end, work_buffer,
-			    save_errno, grouping, thousands_sep, mode_flags);
-
- all_done:
-  /* Unlock the stream.  */
-  _IO_funlockfile (s);
-  _IO_cleanup_region_end (0);
-
-  return done;
+  printf_positional (buf, format, readonly_format, ap, &ap_save,
+		     nspecs_done, lead_str_end, work_buffer,
+		     save_errno, grouping, thousands_sep, mode_flags);
 }
 
-static int
-printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
-		   va_list ap, va_list *ap_savep, int done, int nspecs_done,
+static void
+printf_positional (struct Xprintf_buffer * buf, const CHAR_T *format,
+		   int readonly_format,
+		   va_list ap, va_list *ap_savep, int nspecs_done,
 		   const UCHAR_T *lead_str_end,
 		   CHAR_T *work_buffer, int save_errno,
 		   const char *grouping, THOUSANDS_SEP_T thousands_sep,
@@ -1171,7 +1045,7 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
 	{
 	  if (!scratch_buffer_grow_preserve (&specsbuf))
 	    {
-	      done = -1;
+	      Xprintf_buffer_mark_failed (buf);
 	      goto all_done;
 	    }
 	  specs = specsbuf.data;
@@ -1199,7 +1073,7 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
       = sizeof (*args_value) + sizeof (*args_size) + sizeof (*args_type);
     if (!scratch_buffer_set_array_size (&argsbuf, nargs, bytes_per_arg))
       {
-	done = -1;
+	Xprintf_buffer_mark_failed (buf);
 	goto all_done;
       }
     args_value = argsbuf.data;
@@ -1312,7 +1186,8 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
       }
 
   /* Now walk through all format specifiers and process them.  */
-  for (; (size_t) nspecs_done < nspecs; ++nspecs_done)
+  for (; (size_t) nspecs_done < nspecs && !Xprintf_buffer_has_failed (buf);
+       ++nspecs_done)
     {
       STEP4_TABLE;
 
@@ -1376,26 +1251,19 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
 	}
 
       /* Process format specifiers.  */
-      while (1)
+      do
 	{
-	  int function_done;
-
 	  if (spec <= UCHAR_MAX
 	      && __printf_function_table != NULL
 	      && __printf_function_table[(size_t) spec] != NULL)
 	    {
-	      const void **ptr = alloca (specs[nspecs_done].ndata_args
-					 * sizeof (const void *));
-
-	      /* Fill in an array of pointers to the argument values.  */
-	      for (unsigned int i = 0; i < specs[nspecs_done].ndata_args;
-		   ++i)
-		ptr[i] = &args_value[specs[nspecs_done].data_arg + i];
-
-	      /* Call the function.  */
-	      function_done = __printf_function_table[(size_t) spec]
-		(s, &specs[nspecs_done].info, ptr);
-
+	      int function_done
+		= Xprintf (function_invoke) (buf,
+					     __printf_function_table[(size_t) spec],
+					     &args_value[specs[nspecs_done]
+							 .data_arg],
+					     specs[nspecs_done].ndata_args,
+					     &specs[nspecs_done].info);
 	      if (function_done != -2)
 		{
 		  /* If an error occurred we don't have information
@@ -1403,11 +1271,9 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
 		  if (function_done < 0)
 		    {
 		      /* Function has set errno.  */
-		      done = -1;
+		      Xprintf_buffer_mark_failed (buf);
 		      goto all_done;
 		    }
-
-		  done_add (function_done);
 		  break;
 		}
 	    }
@@ -1450,327 +1316,159 @@ printf_positional (FILE *s, const CHAR_T *format, int readonly_format,
 	      }
 	    SETUP_FLOAT128_INFO (specs[nspecs_done].info);
 
-	    int function_done
-	      = __printf_fp_spec (s, &specs[nspecs_done].info, &ptr);
-	    if (function_done < 0)
-	      {
-		/* Error in print handler; up to handler to set errno.  */
-		done = -1;
-		goto all_done;
-	      }
-	    done_add (function_done);
+	    __printf_fp_spec (buf, &specs[nspecs_done].info, &ptr);
 	  }
 	  break;
 
 	  LABEL (form_unknown):
 	  {
-	    int function_done = printf_unknown (s, &specs[nspecs_done].info);
-
-	    /* If an error occurred we don't have information about #
-	       of chars.  */
-	    if (function_done < 0)
-	      {
-		/* Function has set errno.  */
-		done = -1;
-		goto all_done;
-	      }
-
-	    done_add (function_done);
+	    printf_unknown (buf, &specs[nspecs_done].info);
 	  }
 	  break;
 	}
+      while (Xprintf_buffer_has_failed (buf));
 
       /* Write the following constant string.  */
-      outstring (specs[nspecs_done].end_of_fmt,
-		 specs[nspecs_done].next_fmt
-		 - specs[nspecs_done].end_of_fmt);
+      Xprintf_buffer_write (buf,
+			      (const CHAR_T *) specs[nspecs_done].end_of_fmt,
+			      (specs[nspecs_done].next_fmt
+			       - specs[nspecs_done].end_of_fmt));
     }
  all_done:
   scratch_buffer_free (&argsbuf);
   scratch_buffer_free (&specsbuf);
-  return done;
 }
 
 /* Handle an unknown format specifier.  This prints out a canonicalized
    representation of the format spec itself.  */
-static int
-printf_unknown (FILE *s, const struct printf_info *info)
+static void
+printf_unknown (struct Xprintf_buffer *buf, const struct printf_info *info)
 {
-  int done = 0;
   CHAR_T work_buffer[MAX (sizeof (info->width), sizeof (info->prec)) * 3];
   CHAR_T *const workend
     = &work_buffer[sizeof (work_buffer) / sizeof (CHAR_T)];
   CHAR_T *w;
 
-  outchar (L_('%'));
+  Xprintf_buffer_putc (buf, L_('%'));
 
   if (info->alt)
-    outchar (L_('#'));
+    Xprintf_buffer_putc (buf, L_('#'));
   if (info->group)
-    outchar (L_('\''));
+    Xprintf_buffer_putc (buf, L_('\''));
   if (info->showsign)
-    outchar (L_('+'));
+    Xprintf_buffer_putc (buf, L_('+'));
   else if (info->space)
-    outchar (L_(' '));
+    Xprintf_buffer_putc (buf, L_(' '));
   if (info->left)
-    outchar (L_('-'));
+    Xprintf_buffer_putc (buf, L_('-'));
   if (info->pad == L_('0'))
-    outchar (L_('0'));
+    Xprintf_buffer_putc (buf, L_('0'));
   if (info->i18n)
-    outchar (L_('I'));
+    Xprintf_buffer_putc (buf, L_('I'));
 
   if (info->width != 0)
     {
       w = _itoa_word (info->width, workend, 10, 0);
       while (w < workend)
-	outchar (*w++);
+	Xprintf_buffer_putc (buf, *w++);
     }
 
   if (info->prec != -1)
     {
-      outchar (L_('.'));
+      Xprintf_buffer_putc (buf, L_('.'));
       w = _itoa_word (info->prec, workend, 10, 0);
       while (w < workend)
-	outchar (*w++);
+	Xprintf_buffer_putc (buf, *w++);
     }
 
   if (info->spec != L_('\0'))
-    outchar (info->spec);
-
- all_done:
-  return done;
+    Xprintf_buffer_putc (buf, info->spec);
 }
-
-/* Group the digits from W to REAR_PTR according to the grouping rules
-   of the current locale.  The interpretation of GROUPING is as in
-   `struct lconv' from <locale.h>.  The grouped number extends from
-   the returned pointer until REAR_PTR.  FRONT_PTR to W is used as a
-   scratch area.  */
-static CHAR_T *
-group_number (CHAR_T *front_ptr, CHAR_T *w, CHAR_T *rear_ptr,
-	      const char *grouping, THOUSANDS_SEP_T thousands_sep)
+
+static void
+group_number (struct Xprintf_buffer *buf,
+	      struct grouping_iterator *iter,
+	      CHAR_T *from, CHAR_T *to, THOUSANDS_SEP_T thousands_sep,
+	      bool i18n)
 {
-  /* Length of the current group.  */
-  int len;
-#ifndef COMPILE_WPRINTF
-  /* Length of the separator (in wide mode, the separator is always a
-     single wide character).  */
-  int tlen = strlen (thousands_sep);
+  if (!i18n)
+    for (CHAR_T *cp = from; cp != to; ++cp)
+      {
+	if (__grouping_iterator_next (iter))
+	  {
+#ifdef COMPILE_WPRINTF
+	    __wprintf_buffer_putc (buf, thousands_sep);
+#else
+	    __printf_buffer_puts (buf, thousands_sep);
 #endif
-
-  /* We treat all negative values like CHAR_MAX.  */
-
-  if (*grouping == CHAR_MAX || *grouping <= 0)
-    /* No grouping should be done.  */
-    return w;
-
-  len = *grouping++;
-
-  /* Copy existing string so that nothing gets overwritten.  */
-  memmove (front_ptr, w, (rear_ptr - w) * sizeof (CHAR_T));
-  CHAR_T *s = front_ptr + (rear_ptr - w);
-
-  w = rear_ptr;
-
-  /* Process all characters in the string.  */
-  while (s > front_ptr)
+	  }
+	Xprintf_buffer_putc (buf, *cp);
+      }
+  else
     {
-      *--w = *--s;
-
-      if (--len == 0 && s > front_ptr)
+      /* Apply digit translation and grouping.  */
+      for (CHAR_T *cp = from; cp != to; ++cp)
 	{
-	  /* A new group begins.  */
+	  if (__grouping_iterator_next (iter))
+	    {
 #ifdef COMPILE_WPRINTF
-	  if (w != s)
-	    *--w = thousands_sep;
-	  else
-	    /* Not enough room for the separator.  */
-	    goto copy_rest;
+	      __wprintf_buffer_putc (buf, thousands_sep);
 #else
-	  int cnt = tlen;
-	  if (tlen < w - s)
-	    do
-	      *--w = thousands_sep[--cnt];
-	    while (cnt > 0);
-	  else
-	    /* Not enough room for the separator.  */
-	    goto copy_rest;
-#endif
-
-	  if (*grouping == CHAR_MAX
-#if CHAR_MIN < 0
-		   || *grouping < 0
+	      __printf_buffer_puts (buf, thousands_sep);
 #endif
-		   )
-	    {
-	    copy_rest:
-	      /* No further grouping to be done.  Copy the rest of the
-		 number.  */
-	      w -= s - front_ptr;
-	      memmove (w, front_ptr, (s - front_ptr) * sizeof (CHAR_T));
-	      break;
 	    }
-	  else if (*grouping != '\0')
-	    len = *grouping++;
-	  else
-	    /* The previous grouping repeats ad infinitum.  */
-	    len = grouping[-1];
-	}
-    }
-  return w;
-}
-
-/* Helper "class" for `fprintf to unbuffered': creates a temporary buffer.  */
-struct helper_file
-  {
-    struct _IO_FILE_plus _f;
+	  int digit = *cp - '0';
 #ifdef COMPILE_WPRINTF
-    struct _IO_wide_data _wide_data;
-#endif
-    FILE *_put_stream;
-#ifdef _IO_MTSAFE_IO
-    _IO_lock_t lock;
-#endif
-  };
-
-static int
-_IO_helper_overflow (FILE *s, int c)
-{
-  FILE *target = ((struct helper_file*) s)->_put_stream;
-#ifdef COMPILE_WPRINTF
-  int used = s->_wide_data->_IO_write_ptr - s->_wide_data->_IO_write_base;
-  if (used)
-    {
-      size_t written = _IO_sputn (target, s->_wide_data->_IO_write_base, used);
-      if (written == 0 || written == WEOF)
-	return WEOF;
-      __wmemmove (s->_wide_data->_IO_write_base,
-		  s->_wide_data->_IO_write_base + written,
-		  used - written);
-      s->_wide_data->_IO_write_ptr -= written;
-    }
+	  __wprintf_buffer_putc
+	    (buf, _NL_CURRENT_WORD (LC_CTYPE,
+				    _NL_CTYPE_OUTDIGIT0_WC + digit));
 #else
-  int used = s->_IO_write_ptr - s->_IO_write_base;
-  if (used)
-    {
-      size_t written = _IO_sputn (target, s->_IO_write_base, used);
-      if (written == 0 || written == EOF)
-	return EOF;
-      memmove (s->_IO_write_base, s->_IO_write_base + written,
-	       used - written);
-      s->_IO_write_ptr -= written;
-    }
+	  __printf_buffer_puts
+	    (buf, _NL_CURRENT (LC_CTYPE, _NL_CTYPE_OUTDIGIT0_MB + digit));
 #endif
-  return PUTC (c, s);
+	}
+    }
 }
 
-#ifdef COMPILE_WPRINTF
-static const struct _IO_jump_t _IO_helper_jumps libio_vtable =
-{
-  JUMP_INIT_DUMMY,
-  JUMP_INIT (finish, _IO_wdefault_finish),
-  JUMP_INIT (overflow, _IO_helper_overflow),
-  JUMP_INIT (underflow, _IO_default_underflow),
-  JUMP_INIT (uflow, _IO_default_uflow),
-  JUMP_INIT (pbackfail, (_IO_pbackfail_t) _IO_wdefault_pbackfail),
-  JUMP_INIT (xsputn, _IO_wdefault_xsputn),
-  JUMP_INIT (xsgetn, _IO_wdefault_xsgetn),
-  JUMP_INIT (seekoff, _IO_default_seekoff),
-  JUMP_INIT (seekpos, _IO_default_seekpos),
-  JUMP_INIT (setbuf, _IO_default_setbuf),
-  JUMP_INIT (sync, _IO_default_sync),
-  JUMP_INIT (doallocate, _IO_wdefault_doallocate),
-  JUMP_INIT (read, _IO_default_read),
-  JUMP_INIT (write, _IO_default_write),
-  JUMP_INIT (seek, _IO_default_seek),
-  JUMP_INIT (close, _IO_default_close),
-  JUMP_INIT (stat, _IO_default_stat)
-};
-#else
-static const struct _IO_jump_t _IO_helper_jumps libio_vtable =
-{
-  JUMP_INIT_DUMMY,
-  JUMP_INIT (finish, _IO_default_finish),
-  JUMP_INIT (overflow, _IO_helper_overflow),
-  JUMP_INIT (underflow, _IO_default_underflow),
-  JUMP_INIT (uflow, _IO_default_uflow),
-  JUMP_INIT (pbackfail, _IO_default_pbackfail),
-  JUMP_INIT (xsputn, _IO_default_xsputn),
-  JUMP_INIT (xsgetn, _IO_default_xsgetn),
-  JUMP_INIT (seekoff, _IO_default_seekoff),
-  JUMP_INIT (seekpos, _IO_default_seekpos),
-  JUMP_INIT (setbuf, _IO_default_setbuf),
-  JUMP_INIT (sync, _IO_default_sync),
-  JUMP_INIT (doallocate, _IO_default_doallocate),
-  JUMP_INIT (read, _IO_default_read),
-  JUMP_INIT (write, _IO_default_write),
-  JUMP_INIT (seek, _IO_default_seek),
-  JUMP_INIT (close, _IO_default_close),
-  JUMP_INIT (stat, _IO_default_stat)
-};
-#endif
 
-static int
-buffered_vfprintf (FILE *s, const CHAR_T *format, va_list args,
-		   unsigned int mode_flags)
+/* The FILE-based function.  */
+int
+vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
 {
-  CHAR_T buf[BUFSIZ];
-  struct helper_file helper;
-  FILE *hp = (FILE *) &helper._f;
-  int result, to_flush;
-
   /* Orient the stream.  */
 #ifdef ORIENT
   ORIENT;
 #endif
 
-  /* Initialize helper.  */
-  helper._put_stream = s;
-#ifdef COMPILE_WPRINTF
-  hp->_wide_data = &helper._wide_data;
-  _IO_wsetp (hp, buf, buf + sizeof buf / sizeof (CHAR_T));
-  hp->_mode = 1;
-#else
-  _IO_setp (hp, buf, buf + sizeof buf);
-  hp->_mode = -1;
-#endif
-  hp->_flags = _IO_MAGIC|_IO_NO_READS|_IO_USER_LOCK;
-#if _IO_JUMPS_OFFSET
-  hp->_vtable_offset = 0;
-#endif
-#ifdef _IO_MTSAFE_IO
-  hp->_lock = NULL;
+  /* Sanity check of arguments.  */
+  ARGCHECK (s, format);
+
+#ifdef ORIENT
+  /* Check for correct orientation.  */
+  if (_IO_vtable_offset (s) == 0
+      && _IO_fwide (s, sizeof (CHAR_T) == 1 ? -1 : 1)
+      != (sizeof (CHAR_T) == 1 ? -1 : 1))
+    /* The stream is already oriented otherwise.  */
+    return EOF;
 #endif
-  hp->_flags2 = s->_flags2;
-  _IO_JUMPS (&helper._f) = (struct _IO_jump_t *) &_IO_helper_jumps;
 
-  /* Now print to helper instead.  */
-  result = vfprintf (hp, format, args, mode_flags);
+  int done;
 
   /* Lock stream.  */
-  __libc_cleanup_region_start (1, (void (*) (void *)) &_IO_funlockfile, s);
+  _IO_cleanup_region_start ((void (*) (void *)) &_IO_funlockfile, s);
   _IO_flockfile (s);
 
-  /* Now flush anything from the helper to the S. */
-#ifdef COMPILE_WPRINTF
-  if ((to_flush = (hp->_wide_data->_IO_write_ptr
-		   - hp->_wide_data->_IO_write_base)) > 0)
-    {
-      if ((int) _IO_sputn (s, hp->_wide_data->_IO_write_base, to_flush)
-	  != to_flush)
-	result = -1;
-    }
-#else
-  if ((to_flush = hp->_IO_write_ptr - hp->_IO_write_base) > 0)
-    {
-      if ((int) _IO_sputn (s, hp->_IO_write_base, to_flush) != to_flush)
-	result = -1;
-    }
-#endif
+  /* Set up the wrapping buffer.  */
+  struct Xprintf (buffer_to_file) wrap;
+  Xprintf (buffer_to_file_init) (&wrap, s);
+
+  /* Perform the printing operation on the buffer.  */
+  Xprintf_buffer (&wrap.base, format, ap, mode_flags);
+  done = Xprintf (buffer_to_file_done) (&wrap);
 
   /* Unlock the stream.  */
   _IO_funlockfile (s);
-  __libc_cleanup_region_end (0);
+  _IO_cleanup_region_end (0);
 
-  return result;
+  return done;
 }
diff --git a/stdio-common/vfprintf-process-arg.c b/stdio-common/vfprintf-process-arg.c
index 4fe369e111..ca6402449d 100644
--- a/stdio-common/vfprintf-process-arg.c
+++ b/stdio-common/vfprintf-process-arg.c
@@ -27,7 +27,7 @@
      now process the wanted format specifier.  */
 LABEL (form_percent):
   /* Write a literal "%".  */
-  outchar (L_('%'));
+  Xprintf_buffer_putc (buf, L_('%'));
   break;
 
 LABEL (form_integer):
@@ -116,16 +116,8 @@ LABEL (unsigned_number):      /* Unsigned number of base BASE.  */
             *--string = L_('0');
         }
       else
-        {
-          /* Put the number in WORK.  */
-          string = _itoa (number.longlong, workend, base,
-                          spec == L_('X'));
-          if (group && grouping)
-            string = group_number (work_buffer, string, workend,
-                                   grouping, thousands_sep);
-          if (use_outdigits && base == 10)
-            string = _i18n_number_rewrite (string, workend, workend);
-        }
+        /* Put the number in WORK.  */
+        string = _itoa (number.longlong, workend, base, spec == L_('X'));
       /* Simplify further test for num != 0.  */
       number.word = number.longlong != 0;
     }
@@ -159,27 +151,46 @@ LABEL (unsigned_number):      /* Unsigned number of base BASE.  */
             *--string = L_('0');
         }
       else
-        {
-          /* Put the number in WORK.  */
-          string = _itoa_word (number.word, workend, base,
-                               spec == L_('X'));
-          if (group && grouping)
-            string = group_number (work_buffer, string, workend,
-                                   grouping, thousands_sep);
-          if (use_outdigits && base == 10)
-            string = _i18n_number_rewrite (string, workend, workend);
-        }
+        /* Put the number in WORK.  */
+        string = _itoa_word (number.word, workend, base,
+                             spec == L_('X'));
     }
 
-  if (prec <= workend - string && number.word != 0 && alt && base == 8)
-    /* Add octal marker.  */
-    *--string = L_('0');
+  /* Grouping is also used for outdigits translation.  */
+  struct grouping_iterator iter;
+  bool number_slow_path = group || (use_outdigits && base == 10);
+  if (group)
+    __grouping_iterator_init (&iter, LC_NUMERIC, _NL_CURRENT_LOCALE,
+                              workend - string);
+  else if (use_outdigits && base == 10)
+    __grouping_iterator_init_none (&iter, workend - string);
+
+  int number_length;
+#ifndef COMPILE_WPRINTF
+  if (use_outdigits && base == 10)
+    number_length = __translated_number_width (_NL_CURRENT_LOCALE,
+                                               string, workend);
+  else
+    number_length = workend - string;
+  if (group)
+    number_length += iter.separators * strlen (thousands_sep);
+#else
+  number_length = workend - string;
+  /* All wide separators have length 1.  */
+  if (group && thousands_sep != L'\0')
+    number_length += iter.separators;
+#endif
+
+  /* The marker comes right before the number, but is not subject
+     to grouping.  */
+  bool octal_marker = (prec <= number_length && number.word != 0
+                       && alt && base == 8);
 
   prec = MAX (0, prec - (workend - string));
 
   if (!left)
     {
-      width -= workend - string + prec;
+      width -= number_length + prec;
 
       if (number.word != 0 && alt && (base == 16 || base == 2))
         /* Account for 0X, 0x, 0B or 0b hex or binary marker.  */
@@ -190,27 +201,34 @@ LABEL (unsigned_number):      /* Unsigned number of base BASE.  */
 
       if (pad == L_(' '))
         {
-          PAD (L_(' '));
+          Xprintf_buffer_pad (buf, L_(' '), width);
           width = 0;
         }
 
       if (is_negative)
-        outchar (L_('-'));
+        Xprintf_buffer_putc (buf, L_('-'));
       else if (showsign)
-        outchar (L_('+'));
+        Xprintf_buffer_putc (buf, L_('+'));
       else if (space)
-        outchar (L_(' '));
+        Xprintf_buffer_putc (buf, L_(' '));
 
       if (number.word != 0 && alt && (base == 16 || base == 2))
         {
-          outchar (L_('0'));
-          outchar (spec);
+          Xprintf_buffer_putc (buf, L_('0'));
+          Xprintf_buffer_putc (buf, spec);
         }
 
       width += prec;
-      PAD (L_('0'));
+      Xprintf_buffer_pad (buf, L_('0'), width);
+
+      if (octal_marker)
+        Xprintf_buffer_putc (buf, L_('0'));
 
-      outstring (string, workend - string);
+      if (number_slow_path)
+        group_number (buf, &iter, string, workend, thousands_sep,
+                      use_outdigits && base == 10);
+      else
+        Xprintf_buffer_write (buf, string, workend - string);
 
       break;
     }
@@ -218,40 +236,41 @@ LABEL (unsigned_number):      /* Unsigned number of base BASE.  */
     {
       if (is_negative)
         {
-          outchar (L_('-'));
+          Xprintf_buffer_putc (buf, L_('-'));
           --width;
         }
       else if (showsign)
         {
-          outchar (L_('+'));
+          Xprintf_buffer_putc (buf, L_('+'));
           --width;
         }
       else if (space)
         {
-          outchar (L_(' '));
+          Xprintf_buffer_putc (buf, L_(' '));
           --width;
         }
 
       if (number.word != 0 && alt && (base == 16 || base == 2))
         {
-          outchar (L_('0'));
-          outchar (spec);
+          Xprintf_buffer_putc (buf, L_('0'));
+          Xprintf_buffer_putc (buf, spec);
           width -= 2;
         }
 
       width -= workend - string + prec;
 
-      if (prec > 0)
-        {
-          int temp = width;
-          width = prec;
-          PAD (L_('0'));
-          width = temp;
-        }
+      Xprintf_buffer_pad (buf, L_('0'), prec);
+
+      if (octal_marker)
+        Xprintf_buffer_putc (buf, L_('0'));
 
-      outstring (string, workend - string);
+      if (number_slow_path)
+        group_number (buf, &iter, string, workend, thousands_sep,
+                      use_outdigits && base == 10);
+      else
+        Xprintf_buffer_write (buf, string, workend - string);
 
-      PAD (L_(' '));
+      Xprintf_buffer_pad (buf, L_(' '), width);
       break;
     }
 
@@ -300,16 +319,17 @@ LABEL (form_number):
     }
   /* Answer the count of characters written.  */
   void *ptrptr = process_arg_pointer ();
+  unsigned int written = Xprintf_buffer_done (buf);
   if (is_longlong)
-    *(long long int *) ptrptr = done;
+    *(long long int *) ptrptr = written;
   else if (is_long_num)
-    *(long int *) ptrptr = done;
+    *(long int *) ptrptr = written;
   else if (is_char)
-    *(char *) ptrptr = done;
+    *(char *) ptrptr = written;
   else if (!is_short)
-    *(int *) ptrptr = done;
+    *(int *) ptrptr = written;
   else
-    *(short int *) ptrptr = done;
+    *(short int *) ptrptr = written;
   break;
 
 LABEL (form_strerror):
@@ -341,14 +361,16 @@ LABEL (form_character):
     goto LABEL (form_wcharacter);
   --width;  /* Account for the character itself.  */
   if (!left)
-    PAD (L_(' '));
+    Xprintf_buffer_pad (buf, L_(' '), width);
 #ifdef COMPILE_WPRINTF
-  outchar (__btowc ((unsigned char) process_arg_int ())); /* Promoted. */
+  __wprintf_buffer_putc (buf, __btowc ((unsigned char) /* Promoted. */
+                                       process_arg_int ()));
 #else
-  outchar ((unsigned char) process_arg_int ()); /* Promoted.  */
+  __printf_buffer_putc (buf, (unsigned char) /* Promoted.  */
+                        process_arg_int ());
 #endif
   if (left)
-    PAD (L_(' '));
+    Xprintf_buffer_pad (buf, L_(' '), width);
   break;
 
 LABEL (form_string):
@@ -382,10 +404,8 @@ LABEL (form_string):
     else if (!is_long && spec != L_('S'))
       {
 #ifdef COMPILE_WPRINTF
-        done = outstring_converted_wide_string
-          (s, (const char *) string, prec, width, left, done);
-        if (done < 0)
-          goto all_done;
+        outstring_converted_wide_string (buf, (const char *) string,
+                                         prec, width, left);
         /* The padding has already been written.  */
         break;
 #else
@@ -407,10 +427,8 @@ LABEL (form_string):
         else
           len = __wcslen (string);
 #else
-        done = outstring_converted_wide_string
-          (s, (const wchar_t *) string, prec, width, left, done);
-        if (done < 0)
-          goto all_done;
+        outstring_converted_wide_string (buf, (const wchar_t *) string,
+                                         prec, width, left);
         /* The padding has already been written.  */
         break;
 #endif
@@ -418,15 +436,15 @@ LABEL (form_string):
 
     if ((width -= len) < 0)
       {
-        outstring (string, len);
+        Xprintf_buffer_write (buf, string, len);
         break;
       }
 
     if (!left)
-      PAD (L_(' '));
-    outstring (string, len);
+      Xprintf_buffer_pad (buf, L_(' '), width);
+    Xprintf_buffer_write (buf, string, len);
     if (left)
-      PAD (L_(' '));
+      Xprintf_buffer_pad (buf, L_(' '), width);
   }
   break;
 
@@ -436,10 +454,10 @@ LABEL (form_wcharacter):
     /* Wide character.  */
     --width;
     if (!left)
-      PAD (L' ');
-    outchar (process_arg_wchar_t ());
+      Xprintf_buffer_pad (buf, L_(' '), width);
+    Xprintf_buffer_putc (buf, process_arg_wchar_t ());
     if (left)
-      PAD (L' ');
+      Xprintf_buffer_pad (buf, L_(' '), width);
   }
   break;
 
@@ -447,24 +465,24 @@ LABEL (form_wcharacter):
 LABEL (form_wcharacter):
   {
     /* Wide character.  */
-    char buf[MB_LEN_MAX];
+    char wcbuf[MB_LEN_MAX];
     mbstate_t mbstate;
     size_t len;
 
     memset (&mbstate, '\0', sizeof (mbstate_t));
-    len = __wcrtomb (buf, process_arg_wchar_t (), &mbstate);
+    len = __wcrtomb (wcbuf, process_arg_wchar_t (), &mbstate);
     if (len == (size_t) -1)
       {
         /* Something went wrong during the conversion.  Bail out.  */
-        done = -1;
+        __printf_buffer_mark_failed (buf);
         goto all_done;
       }
     width -= len;
     if (!left)
-      PAD (' ');
-    outstring (buf, len);
+      Xprintf_buffer_pad (buf, L_(' '), width);
+    Xprintf_buffer_write (buf, wcbuf, len);
     if (left)
-      PAD (' ');
+      Xprintf_buffer_pad (buf, L_(' '), width);
   }
   break;
 #endif /* !COMPILE_WPRINTF */