[libcxx-commits] [libcxx] [libc++] Avoid heap allocation for default-precision double formatting (PR #212502)

Adib Pathan via libcxx-commits libcxx-commits at lists.llvm.org
Sat Aug 1 05:01:10 PDT 2026


https://github.com/adxb-pxthxn updated https://github.com/llvm/llvm-project/pull/212502

>From 54cd8d83c3e742eea2dc9289e0467a5ea0578cb3 Mon Sep 17 00:00:00 2001
From: Adib <adibpathan280 at gmail.com>
Date: Sat, 1 Aug 2026 21:30:52 +0930
Subject: [PATCH] [libc++] Format into stack buffer before allocating for
 floats

Keep unspecified precision equivalent to to_chars without a precision
argument. Try the stack buffer first and grow only if to_chars reports
insufficient space. Fixes #211909.
---
 .../__format/formatter_floating_point.h       | 126 +++++++++++++-----
 .../formatter.floating_point.pass.cpp         |  10 ++
 2 files changed, 100 insertions(+), 36 deletions(-)

diff --git a/libcxx/include/__format/formatter_floating_point.h b/libcxx/include/__format/formatter_floating_point.h
index f4de8b927651a..ed00baf0d8129 100644
--- a/libcxx/include/__format/formatter_floating_point.h
+++ b/libcxx/include/__format/formatter_floating_point.h
@@ -56,27 +56,6 @@ _LIBCPP_BEGIN_NAMESPACE_STD
 
 namespace __formatter {
 
-template <floating_point _Tp>
-_LIBCPP_HIDE_FROM_ABI char* __to_buffer(char* __first, char* __last, _Tp __value) {
-  to_chars_result __r = std::to_chars(__first, __last, __value);
-  _LIBCPP_ASSERT_INTERNAL(__r.ec == errc(0), "Internal buffer too small");
-  return __r.ptr;
-}
-
-template <floating_point _Tp>
-_LIBCPP_HIDE_FROM_ABI char* __to_buffer(char* __first, char* __last, _Tp __value, chars_format __fmt) {
-  to_chars_result __r = std::to_chars(__first, __last, __value, __fmt);
-  _LIBCPP_ASSERT_INTERNAL(__r.ec == errc(0), "Internal buffer too small");
-  return __r.ptr;
-}
-
-template <floating_point _Tp>
-_LIBCPP_HIDE_FROM_ABI char* __to_buffer(char* __first, char* __last, _Tp __value, chars_format __fmt, int __precision) {
-  to_chars_result __r = std::to_chars(__first, __last, __value, __fmt, __precision);
-  _LIBCPP_ASSERT_INTERNAL(__r.ec == errc(0), "Internal buffer too small");
-  return __r.ptr;
-}
-
 // https://en.cppreference.com/w/cpp/language/types#cite_note-1
 // float             min subnormal: +/-0x1p-149   max: +/- 3.402,823,4 10^38
 // double            min subnormal: +/-0x1p-1074  max  +/- 1.797,693,134,862,315,7 10^308
@@ -139,7 +118,10 @@ struct __traits<double> {
 /// Helper class to store the conversion buffer.
 ///
 /// Depending on the maximum size required for a value, the buffer is allocated
-/// on the stack or the heap.
+/// on the stack or the heap. When precision is unspecified the formatting is
+/// equivalent to calling \c to_chars without a precision argument; in that case
+/// the stack buffer is used first and the buffer grows only if formatting
+/// fails due to insufficient space.
 template <floating_point _Fp>
 class __float_buffer {
   using _Traits _LIBCPP_NODEBUG = __traits<_Fp>;
@@ -153,8 +135,7 @@ class __float_buffer {
   // When supporting long doubles the __max_integral part becomes 4932 which
   // may be too much for some platforms. For these cases a better estimate is
   // required.
-  explicit _LIBCPP_HIDE_FROM_ABI __float_buffer(int __precision)
-      : __precision_(__precision != -1 ? __precision : _Traits::__max_fractional) {
+  explicit _LIBCPP_HIDE_FROM_ABI __float_buffer(int __precision) : __precision_(__precision) {
     // When the precision is larger than _Traits::__max_fractional the digits in
     // the range (_Traits::__max_fractional, precision] will contain the value
     // zero. There's no need to request to_chars to write these zeros:
@@ -169,6 +150,15 @@ class __float_buffer {
       __precision_          = _Traits::__max_fractional;
     }
 
+    // Unspecified precision means to_chars without a precision argument. That
+    // output fits in the stack buffer for float/double; allocate only if
+    // formatting later reports the buffer is too small.
+    if (__precision < 0) {
+      __size_  = _Traits::__stack_buffer_size;
+      __begin_ = __buffer_;
+      return;
+    }
+
     __size_ = __formatter::__float_buffer_size<_Fp>(__precision_);
     if (__size_ > _Traits::__stack_buffer_size)
       // The allocated buffer's contents don't need initialization.
@@ -192,6 +182,21 @@ class __float_buffer {
   _LIBCPP_HIDE_FROM_ABI void __remove_trailing_zeros() { __num_trailing_zeros_ = 0; }
   _LIBCPP_HIDE_FROM_ABI void __add_trailing_zeros(int __zeros) { __num_trailing_zeros_ += __zeros; }
 
+  /// Grows the buffer to \a __new_size, preserving characters in
+  /// [\c begin(), \a __first). Returns the corresponding pointer into the new
+  /// buffer.
+  _LIBCPP_HIDE_FROM_ABI char* __grow(size_t __new_size, char* __first) {
+    _LIBCPP_ASSERT_INTERNAL(__new_size > __size_, "the buffer should grow");
+    ptrdiff_t __offset    = __first - __begin_;
+    char* __new_begin     = allocator<char>{}.allocate(__new_size);
+    std::copy_n(__begin_, __offset, __new_begin);
+    if (__size_ > _Traits::__stack_buffer_size)
+      allocator<char>{}.deallocate(__begin_, __size_);
+    __begin_ = __new_begin;
+    __size_  = __new_size;
+    return __begin_ + __offset;
+  }
+
 private:
   int __precision_;
   int __num_trailing_zeros_{0};
@@ -200,6 +205,52 @@ class __float_buffer {
   char __buffer_[_Traits::__stack_buffer_size];
 };
 
+template <floating_point _Fp, floating_point _Tp>
+_LIBCPP_HIDE_FROM_ABI char* __to_buffer(__float_buffer<_Fp>& __buffer, char*& __first, _Tp __value) {
+  to_chars_result __r = std::to_chars(__first, __buffer.end(), __value);
+  if (__r.ec == errc::value_too_large) [[unlikely]] {
+    size_t __target = __formatter::__float_buffer_size<_Fp>(__traits<_Fp>::__max_fractional);
+    _LIBCPP_ASSERT_INTERNAL(__target > static_cast<size_t>(__buffer.end() - __buffer.begin()),
+                            "Internal buffer size estimate too small");
+    __first = __buffer.__grow(__target, __first);
+    __r     = std::to_chars(__first, __buffer.end(), __value);
+  }
+  _LIBCPP_ASSERT_INTERNAL(__r.ec == errc(0), "Internal buffer too small");
+  return __r.ptr;
+}
+
+template <floating_point _Fp, floating_point _Tp>
+_LIBCPP_HIDE_FROM_ABI char*
+__to_buffer(__float_buffer<_Fp>& __buffer, char*& __first, _Tp __value, chars_format __fmt) {
+  to_chars_result __r = std::to_chars(__first, __buffer.end(), __value, __fmt);
+  if (__r.ec == errc::value_too_large) [[unlikely]] {
+    size_t __target = __formatter::__float_buffer_size<_Fp>(__traits<_Fp>::__max_fractional);
+    _LIBCPP_ASSERT_INTERNAL(__target > static_cast<size_t>(__buffer.end() - __buffer.begin()),
+                            "Internal buffer size estimate too small");
+    __first = __buffer.__grow(__target, __first);
+    __r     = std::to_chars(__first, __buffer.end(), __value, __fmt);
+  }
+  _LIBCPP_ASSERT_INTERNAL(__r.ec == errc(0), "Internal buffer too small");
+  return __r.ptr;
+}
+
+template <floating_point _Fp, floating_point _Tp>
+_LIBCPP_HIDE_FROM_ABI char*
+__to_buffer(__float_buffer<_Fp>& __buffer, char*& __first, _Tp __value, chars_format __fmt, int __precision) {
+  to_chars_result __r = std::to_chars(__first, __buffer.end(), __value, __fmt, __precision);
+  if (__r.ec == errc::value_too_large) [[unlikely]] {
+    int __size_precision = __precision > __traits<_Fp>::__max_fractional ? __traits<_Fp>::__max_fractional
+                                                                         : __precision;
+    size_t __target      = __formatter::__float_buffer_size<_Fp>(__size_precision);
+    _LIBCPP_ASSERT_INTERNAL(__target > static_cast<size_t>(__buffer.end() - __buffer.begin()),
+                            "Internal buffer size estimate too small");
+    __first = __buffer.__grow(__target, __first);
+    __r     = std::to_chars(__first, __buffer.end(), __value, __fmt, __precision);
+  }
+  _LIBCPP_ASSERT_INTERNAL(__r.ec == errc(0), "Internal buffer too small");
+  return __r.ptr;
+}
+
 struct __float_result {
   /// Points at the beginning of the integral part in the buffer.
   ///
@@ -236,10 +287,10 @@ constexpr inline _LIBCPP_HIDE_FROM_ABI char* __find_exponent(char* __first, char
 
 template <class _Fp, class _Tp>
 _LIBCPP_HIDE_FROM_ABI __float_result
-__format_buffer_default(const __float_buffer<_Fp>& __buffer, _Tp __value, char* __integral) {
+__format_buffer_default(__float_buffer<_Fp>& __buffer, _Tp __value, char* __integral) {
   __float_result __result;
   __result.__integral = __integral;
-  __result.__last     = __formatter::__to_buffer(__integral, __buffer.end(), __value);
+  __result.__last     = __formatter::__to_buffer(__buffer, __result.__integral, __value);
 
   __result.__exponent = __formatter::__find_exponent(__result.__integral, __result.__last);
 
@@ -265,13 +316,14 @@ __format_buffer_default(const __float_buffer<_Fp>& __buffer, _Tp __value, char*
 
 template <class _Fp, class _Tp>
 _LIBCPP_HIDE_FROM_ABI __float_result __format_buffer_hexadecimal_lower_case(
-    const __float_buffer<_Fp>& __buffer, _Tp __value, int __precision, char* __integral) {
+    __float_buffer<_Fp>& __buffer, _Tp __value, int __precision, char* __integral) {
   __float_result __result;
   __result.__integral = __integral;
   if (__precision == -1)
-    __result.__last = __formatter::__to_buffer(__integral, __buffer.end(), __value, chars_format::hex);
+    __result.__last = __formatter::__to_buffer(__buffer, __result.__integral, __value, chars_format::hex);
   else
-    __result.__last = __formatter::__to_buffer(__integral, __buffer.end(), __value, chars_format::hex, __precision);
+    __result.__last =
+        __formatter::__to_buffer(__buffer, __result.__integral, __value, chars_format::hex, __precision);
 
   // H = one or more hex-digits
   // S = sign
@@ -317,7 +369,7 @@ _LIBCPP_HIDE_FROM_ABI __float_result __format_buffer_hexadecimal_lower_case(
 
 template <class _Fp, class _Tp>
 _LIBCPP_HIDE_FROM_ABI __float_result __format_buffer_hexadecimal_upper_case(
-    const __float_buffer<_Fp>& __buffer, _Tp __value, int __precision, char* __integral) {
+    __float_buffer<_Fp>& __buffer, _Tp __value, int __precision, char* __integral) {
   __float_result __result =
       __formatter::__format_buffer_hexadecimal_lower_case(__buffer, __value, __precision, __integral);
   std::transform(__result.__integral, __result.__exponent, __result.__integral, __hex_to_upper);
@@ -327,11 +379,11 @@ _LIBCPP_HIDE_FROM_ABI __float_result __format_buffer_hexadecimal_upper_case(
 
 template <class _Fp, class _Tp>
 _LIBCPP_HIDE_FROM_ABI __float_result __format_buffer_scientific_lower_case(
-    const __float_buffer<_Fp>& __buffer, _Tp __value, int __precision, char* __integral) {
+    __float_buffer<_Fp>& __buffer, _Tp __value, int __precision, char* __integral) {
   __float_result __result;
   __result.__integral = __integral;
   __result.__last =
-      __formatter::__to_buffer(__integral, __buffer.end(), __value, chars_format::scientific, __precision);
+      __formatter::__to_buffer(__buffer, __result.__integral, __value, chars_format::scientific, __precision);
 
   char* __first = __integral + 1;
   _LIBCPP_ASSERT_INTERNAL(__first != __result.__last, "No exponent present");
@@ -354,7 +406,7 @@ _LIBCPP_HIDE_FROM_ABI __float_result __format_buffer_scientific_lower_case(
 
 template <class _Fp, class _Tp>
 _LIBCPP_HIDE_FROM_ABI __float_result __format_buffer_scientific_upper_case(
-    const __float_buffer<_Fp>& __buffer, _Tp __value, int __precision, char* __integral) {
+    __float_buffer<_Fp>& __buffer, _Tp __value, int __precision, char* __integral) {
   __float_result __result =
       __formatter::__format_buffer_scientific_lower_case(__buffer, __value, __precision, __integral);
   *__result.__exponent = 'E';
@@ -363,10 +415,11 @@ _LIBCPP_HIDE_FROM_ABI __float_result __format_buffer_scientific_upper_case(
 
 template <class _Fp, class _Tp>
 _LIBCPP_HIDE_FROM_ABI __float_result
-__format_buffer_fixed(const __float_buffer<_Fp>& __buffer, _Tp __value, int __precision, char* __integral) {
+__format_buffer_fixed(__float_buffer<_Fp>& __buffer, _Tp __value, int __precision, char* __integral) {
   __float_result __result;
   __result.__integral = __integral;
-  __result.__last     = __formatter::__to_buffer(__integral, __buffer.end(), __value, chars_format::fixed, __precision);
+  __result.__last =
+      __formatter::__to_buffer(__buffer, __result.__integral, __value, chars_format::fixed, __precision);
 
   // When there's no precision there's no radix point.
   // Else the radix point is placed at __precision + 1 from the end.
@@ -391,7 +444,8 @@ __format_buffer_general_lower_case(__float_buffer<_Fp>& __buffer, _Tp __value, i
 
   __float_result __result;
   __result.__integral = __integral;
-  __result.__last = __formatter::__to_buffer(__integral, __buffer.end(), __value, chars_format::general, __precision);
+  __result.__last =
+      __formatter::__to_buffer(__buffer, __result.__integral, __value, chars_format::general, __precision);
 
   char* __first = __integral + 1;
   if (__first == __result.__last) {
diff --git a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.floating_point.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.floating_point.pass.cpp
index 3ad84577b90d2..fb410591ab7c5 100644
--- a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.floating_point.pass.cpp
+++ b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.floating_point.pass.cpp
@@ -42,6 +42,7 @@
 #include "test_format_context.h"
 #include "test_macros.h"
 #include "make_string.h"
+#include "count_new.h"
 
 #define STR(S) MAKE_STRING(CharT, S)
 
@@ -562,11 +563,20 @@ void test_all_float_types() {
   test_float_type<long double, CharT>();
 }
 
+void test_default_precision_double_does_not_allocate() {
+  globalMemCounter.reset();
+  DisableAllocationGuard g;
+  std::string result = std::format("{}", 1.23);
+  g.release();
+  assert(result == "1.23");
+}
+
 int main(int, char**) {
   test_all_float_types<char>();
 #ifndef TEST_HAS_NO_WIDE_CHARACTERS
   test_all_float_types<wchar_t>();
 #endif
+  test_default_precision_double_does_not_allocate();
 
   return 0;
 }



More information about the libcxx-commits mailing list