From e88b9f0e5cc50cab57a299dc7efe1a4eb385161d Mon Sep 17 00:00:00 2001 From: Florian Weimer Date: Mon, 19 Dec 2022 18:56:54 +0100 Subject: 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 --- stdlib/strfmon_l.c | 196 ++++++++++++++++++---------------------------- stdlib/strfrom-skeleton.c | 38 ++------- 2 files changed, 83 insertions(+), 151 deletions(-) (limited to 'stdlib') 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 #include "../locale/localeinfo.h" #include - - -#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 +#include #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 #include #include +#include #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); } -- cgit 1.4.1