[PATCH v6 08/11] libio: Convert __vasprintf_internal to buffers

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Mon Dec 19 16:37:25 GMT 2022



On 16/12/22 17:15, Florian Weimer via Libc-alpha wrote:
> The buffer resizing algorithm is slightly different.  The initial
> buffer is on the stack, and small buffers are directly allocated
> on the heap using the exact required size.  The overhead of the
> additional copy is compensated by the lowered setup cost for buffers
> compared to libio streams.

LGTM, thanks.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  include/printf_buffer.h            |   9 ++
>  libio/vasprintf.c                  | 141 ++++++++++++++++++++---------
>  stdio-common/printf_buffer_flush.c |   4 +
>  3 files changed, 109 insertions(+), 45 deletions(-)
> 
> diff --git a/include/printf_buffer.h b/include/printf_buffer.h
> index cc9f8e5346..c8e22e1e90 100644
> --- a/include/printf_buffer.h
> +++ b/include/printf_buffer.h
> @@ -49,6 +49,7 @@ enum __printf_buffer_mode
>      __printf_buffer_mode_snprintf,
>      __printf_buffer_mode_sprintf_chk,
>      __printf_buffer_mode_to_file,
> +    __printf_buffer_mode_asprintf,
>      __printf_buffer_mode_strfmon,
>      __printf_buffer_mode_fp,         /* For __printf_fp_l_buffer.  */
>      __printf_buffer_mode_fp_to_wide, /* For __wprintf_fp_l_buffer.  */
> @@ -304,6 +305,9 @@ void __printf_buffer_flush_snprintf (struct __printf_buffer_snprintf *)
>  struct __printf_buffer_to_file;
>  void __printf_buffer_flush_to_file (struct __printf_buffer_to_file *)
>    attribute_hidden;
> +struct __printf_buffer_asprintf;
> +void __printf_buffer_flush_asprintf (struct __printf_buffer_asprintf *)
> +  attribute_hidden;
>  struct __printf_buffer_fp;
>  void __printf_buffer_flush_fp (struct __printf_buffer_fp *)
>    attribute_hidden;
> @@ -332,4 +336,9 @@ void __wprintf_buffer_flush_to_file (struct __wprintf_buffer_to_file *)
>  /* Temporary buffer used during floating point digit translation.  */
>  #define PRINTF_BUFFER_SIZE_DIGITS 64
>  
> +/* Size of the initial on-stack buffer for asprintf.  It should be
> +   large enough to copy almost all asprintf usages with just a single
> +   (final, correctly sized) heap allocation.  */
> +#define PRINTF_BUFFER_SIZE_ASPRINTF 200
> +
>  #endif /* PRINTF_BUFFER_H */

Ok.

> diff --git a/libio/vasprintf.c b/libio/vasprintf.c
> index 4430a266c6..4f69cb61f9 100644
> --- a/libio/vasprintf.c
> +++ b/libio/vasprintf.c
> @@ -24,64 +24,115 @@
>     This exception applies to code released by its copyright holders
>     in files containing the exception.  */
>  
> -#include <string.h>
> +#include <array_length.h>
> +#include <errno.h>
> +#include <limits.h>
> +#include <math_ldbl_opt.h>
> +#include <printf.h>
> +#include <stdio.h>
>  #include <stdlib.h>
> -#include <strfile.h>
> +#include <string.h>
> +#include <printf_buffer.h>
> +
> +struct __printf_buffer_asprintf
> +{
> +  /* base.write_base points either to a heap-allocated buffer, or to
> +     the direct array below.  */
> +  struct __printf_buffer base;
> +
> +  /* Initial allocation.  200 should be large enough to copy almost
> +     all asprintf usages with just a single (final, correctly sized)
> +     heap allocation.  */
> +  char direct[PRINTF_BUFFER_SIZE_ASPRINTF];
> +};
> +
> +void
> +__printf_buffer_flush_asprintf (struct __printf_buffer_asprintf *buf)
> +{
> +  size_t current_pos = buf->base.write_ptr - buf->base.write_base;
> +  if (current_pos >= INT_MAX)
> +    {
> +      /* The result is not representable.  No need to continue.  */
> +      __set_errno (EOVERFLOW);
> +      __printf_buffer_mark_failed (&buf->base);
> +      return;
> +    }
> +
> +  size_t current_size = buf->base.write_end - buf->base.write_base;
> +  /* Implement an exponentiation sizing policy.  Keep the size
> +     congruent 8 (mod 16), to account for the footer in glibc
> +     malloc.  */
> +  size_t new_size = ALIGN_UP (current_size + current_size / 2, 16) | 8;
> +  char *new_buffer;
> +  if (buf->base.write_base == buf->direct)
> +    {
> +      new_buffer = malloc (new_size);
> +      if (new_buffer == NULL)
> +	{
> +	  __printf_buffer_mark_failed (&buf->base);
> +	  return;
> +	}
> +      memcpy (new_buffer, buf->direct, current_pos);
> +    }
> +  else
> +    {
> +      new_buffer = realloc (buf->base.write_base, new_size);
> +      if (new_buffer == NULL)
> +	{
> +	  __printf_buffer_mark_failed (&buf->base);
> +	  return;
> +	}
> +    }
> +
> +  /* Set up the new write area.  */
> +  buf->base.write_base = new_buffer;
> +  buf->base.write_ptr = new_buffer + current_pos;
> +  buf->base.write_end = new_buffer + new_size;
> +}
> +
>  
>  int
>  __vasprintf_internal (char **result_ptr, const char *format, va_list args,
>  		      unsigned int mode_flags)
>  {
> -  /* Initial size of the buffer to be used.  Will be doubled each time an
> -     overflow occurs.  */
> -  const size_t init_string_size = 100;
> -  char *string;
> -  _IO_strfile sf;
> -  int ret;
> -  size_t needed;
> -  size_t allocated;
> -  /* No need to clear the memory here (unlike for open_memstream) since
> -     we know we will never seek on the stream.  */
> -  string = (char *) malloc (init_string_size);
> -  if (string == NULL)
> -    return -1;
> -#ifdef _IO_MTSAFE_IO
> -  sf._sbf._f._lock = NULL;
> -#endif
> -  _IO_no_init (&sf._sbf._f, _IO_USER_LOCK, -1, NULL, NULL);
> -  _IO_JUMPS (&sf._sbf) = &_IO_str_jumps;
> -  _IO_str_init_static_internal (&sf, string, init_string_size, string);
> -  sf._sbf._f._flags &= ~_IO_USER_BUF;
> -  sf._s._allocate_buffer_unused = (_IO_alloc_type) malloc;
> -  sf._s._free_buffer_unused = (_IO_free_type) free;
> -  ret = __vfprintf_internal (&sf._sbf._f, format, args, mode_flags);
> -  if (ret < 0)
> +  struct __printf_buffer_asprintf buf;
> +  __printf_buffer_init (&buf.base, buf.direct, array_length (buf.direct),
> +			__printf_buffer_mode_asprintf);
> +
> +  __printf_buffer (&buf.base, format, args, mode_flags);
> +  int done = __printf_buffer_done (&buf.base);
> +  if (done < 0)
>      {
> -      free (sf._sbf._f._IO_buf_base);
> -      return ret;
> +      if (buf.base.write_base != buf.direct)
> +	free (buf.base.write_base);
> +      return done;
> +    }
> +
> +  /* Transfer to the final buffer.  */
> +  char *result;
> +  size_t size = buf.base.write_ptr - buf.base.write_base;
> +  if (buf.base.write_base == buf.direct)
> +    {
> +      result = malloc (size + 1);
> +      if (result == NULL)
> +	return -1;
> +      memcpy (result, buf.direct, size);
>      }
> -  /* Only use realloc if the size we need is of the same (binary)
> -     order of magnitude then the memory we allocated.  */
> -  needed = sf._sbf._f._IO_write_ptr - sf._sbf._f._IO_write_base + 1;
> -  allocated = sf._sbf._f._IO_write_end - sf._sbf._f._IO_write_base;
> -  if ((allocated >> 1) <= needed)
> -    *result_ptr = (char *) realloc (sf._sbf._f._IO_buf_base, needed);
>    else
>      {
> -      *result_ptr = (char *) malloc (needed);
> -      if (*result_ptr != NULL)
> +      result = realloc (buf.base.write_base, size + 1);
> +      if (result == NULL)
>  	{
> -	  memcpy (*result_ptr, sf._sbf._f._IO_buf_base, needed - 1);
> -	  free (sf._sbf._f._IO_buf_base);
> +	  free (buf.base.write_base);
> +	  return -1;
>  	}
> -      else
> -	/* We have no choice, use the buffer we already have.  */
> -	*result_ptr = (char *) realloc (sf._sbf._f._IO_buf_base, needed);
>      }
> -  if (*result_ptr == NULL)
> -    *result_ptr = sf._sbf._f._IO_buf_base;
> -  (*result_ptr)[needed - 1] = '\0';
> -  return ret;
> +
> +  /* Add NUL termination.  */
> +  result[size] = '\0';
> +  *result_ptr = result;
> +
> +  return done;
>  }
>  
>  int

Ok.

> diff --git a/stdio-common/printf_buffer_flush.c b/stdio-common/printf_buffer_flush.c
> index f44c8b887f..14fe1b2df4 100644
> --- a/stdio-common/printf_buffer_flush.c
> +++ b/stdio-common/printf_buffer_flush.c
> @@ -27,6 +27,7 @@
>  #ifndef SHARED
>  # pragma weak __printf_buffer_flush_snprintf
>  # pragma weak __printf_buffer_flush_to_file
> +# pragma weak __printf_buffer_flush_asprintf
>  # pragma weak __printf_buffer_flush_fp
>  # pragma weak __printf_buffer_flush_fp_to_wide
>  # pragma weak __printf_buffer_flush_fphex_to_wide
> @@ -49,6 +50,9 @@ __printf_buffer_do_flush (struct __printf_buffer *buf)
>      case __printf_buffer_mode_to_file:
>        __printf_buffer_flush_to_file ((struct __printf_buffer_to_file *) buf);
>        return;
> +    case __printf_buffer_mode_asprintf:
> +      __printf_buffer_flush_asprintf ((struct __printf_buffer_asprintf *) buf);
> +      return;
>      case __printf_buffer_mode_strfmon:
>        __set_errno (E2BIG);
>        __printf_buffer_mark_failed (buf);

Ok.


More information about the Libc-alpha mailing list