diff --git a/libcxx/include/__format/buffer.h b/libcxx/include/__format/buffer.h --- a/libcxx/include/__format/buffer.h +++ b/libcxx/include/__format/buffer.h @@ -11,8 +11,10 @@ #define _LIBCPP___FORMAT_BUFFER_H #include <__algorithm/copy_n.h> +#include <__algorithm/fill_n.h> #include <__algorithm/max.h> #include <__algorithm/min.h> +#include <__algorithm/transform.h> #include <__algorithm/unwrap_iter.h> #include <__config> #include <__format/enable_insertable.h> @@ -26,6 +28,7 @@ #include <__utility/move.h> #include #include +#include #include #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) @@ -69,8 +72,6 @@ return back_insert_iterator{*this}; } - // TODO FMT It would be nice to have an overload taking a - // basic_string_view<_CharT> and append it directly. _LIBCPP_HIDE_FROM_ABI void push_back(_CharT __c) { __ptr_[__size_++] = __c; @@ -80,6 +81,95 @@ flush(); } + /// Copies the input __str to the buffer. + /// + /// Since some of the input is generated by std::to_chars, there needs to be a + /// conversion when _CharT is wchar_t. + template <__formatter::__char_type _InCharT> + _LIBCPP_HIDE_FROM_ABI void __copy(basic_string_view<_InCharT> __str) { + // When the underlying iterator is a simple iterator the __capacity_ is + // infinite. For a string or container back_inserter it isn't. This means + // adding a large string the the buffer can cause some overhead. In that + // case a better approach could be: + // - flush the buffer + // - container.append(__str.begin(), __str.end()); + // The same holds true for the fill. + // For transform it might be slightly harder, however the use case for + // transform is slightly less common; it converts hexadecimal values to + // upper case. For integral these strings are short. + // TODO FMT Look at the improvements above. + size_t __n = __str.size(); + + __flush_on_overflow(__n); + if (__n <= __capacity_) { + _VSTD::copy_n(__str.data(), __n, _VSTD::addressof(__ptr_[__size_])); + __size_ += __n; + return; + } + + // The output doesn't fit in the internal buffer. + // Copy the data in "__capacity_" sized chunks. + _LIBCPP_ASSERT(__size_ == 0, "the buffer should be flushed by __flush_on_overflow"); + const _InCharT* __first = __str.data(); + do { + size_t __chunk = _VSTD::min(__n, __capacity_); + _VSTD::copy_n(__first, __chunk, _VSTD::addressof(__ptr_[__size_])); + __size_ = __chunk; + __first += __chunk; + __n -= __chunk; + flush(); + } while (__n); + } + + /// A std::transform wrapper. + /// + /// Like @ref __copy it may need to do type conversion. + template <__formatter::__char_type _InCharT, class _UnaryOperation> + _LIBCPP_HIDE_FROM_ABI void __transform(const _InCharT* __first, const _InCharT* __last, _UnaryOperation __operation) { + _LIBCPP_ASSERT(__first <= __last, "not a valid range"); + + size_t __n = static_cast(__last - __first); + __flush_on_overflow(__n); + if (__n <= __capacity_) { + _VSTD::transform(__first, __last, _VSTD::addressof(__ptr_[__size_]), _VSTD::move(__operation)); + __size_ += __n; + return; + } + + // The output doesn't fit in the internal buffer. + // Transform the data in "__capacity_" sized chunks. + _LIBCPP_ASSERT(__size_ == 0, "the buffer should be flushed by __flush_on_overflow"); + do { + size_t __chunk = _VSTD::min(__n, __capacity_); + _VSTD::transform(__first, __first + __chunk, _VSTD::addressof(__ptr_[__size_]), __operation); + __size_ = __chunk; + __first += __chunk; + __n -= __chunk; + flush(); + } while (__n); + } + + /// A \c fill_n wrapper. + _LIBCPP_HIDE_FROM_ABI void __fill(size_t __n, _CharT __value) { + __flush_on_overflow(__n); + if (__n <= __capacity_) { + _VSTD::fill_n(_VSTD::addressof(__ptr_[__size_]), __n, __value); + __size_ += __n; + return; + } + + // The output doesn't fit in the internal buffer. + // Fill the buffer in "__capacity_" sized chunks. + _LIBCPP_ASSERT(__size_ == 0, "the buffer should be flushed by __flush_on_overflow"); + do { + size_t __chunk = _VSTD::min(__n, __capacity_); + _VSTD::fill_n(_VSTD::addressof(__ptr_[__size_]), __chunk, __value); + __size_ = __chunk; + __n -= __chunk; + flush(); + } while (__n); + } + _LIBCPP_HIDE_FROM_ABI void flush() { __flush_(__ptr_, __size_, __obj_); __size_ = 0; @@ -91,6 +181,44 @@ size_t __size_{0}; void (*__flush_)(_CharT*, size_t, void*); void* __obj_; + + /// Flushes the buffer when the output operation would overflow the buffer. + /// + /// A simple approach for the overflow detection would be something along the + /// lines: + /// \code + /// // The internal buffer is large enough. + /// if (__n <= __capacity_) { + /// // Flush when we really would overflow. + /// if (__size_ + __n >= __capacity_) + /// flush(); + /// ... + /// } + /// \endcode + /// + /// This approach works for all cases but one: + /// A __format_to_n_buffer_base where \ref __enable_direct_output is true. + /// In that case the \ref __capacity_ of the buffer changes during the first + /// \ref flush. During that operation the output buffer switches from its + /// __writer_ to its __storage_. The \ref __capacity_ of the former depends + /// on the value of n, of the latter is a fixed size. For example: + /// - a format_to_n call with a 10'000 char buffer, + /// - the buffer is filled with 9'500 chars, + /// - adding 1'000 elements would overflow the buffer so the buffer gets + /// changed and the \ref __capacity_ decreases from 10'000 to + /// __buffer_size (256 at the time of writing). + /// + /// This means that the \ref flush for this class may need to copy a part of + /// the internal buffer to the proper output. In this example there will be + /// 500 characters that need this copy operation. + /// + /// Note it would be more efficient to write 500 chars directly and then swap + /// the buffers. This would make the code more complex and \ref format_to_n is + /// not the most common use case. Therefore the optimization isn't done. + _LIBCPP_HIDE_FROM_ABI void __flush_on_overflow(size_t __n) { + if (__size_ + __n >= __capacity_) + flush(); + } }; /// A storage using an internal buffer. @@ -280,12 +408,12 @@ using _Size = iter_difference_t<_OutIt>; public: - _LIBCPP_HIDE_FROM_ABI explicit __format_to_n_buffer_base(_OutIt __out_it, _Size __n) - : __writer_(_VSTD::move(__out_it)), __n_(_VSTD::max(_Size(0), __n)) {} + _LIBCPP_HIDE_FROM_ABI explicit __format_to_n_buffer_base(_OutIt __out_it, _Size __max_size) + : __writer_(_VSTD::move(__out_it)), __max_size_(_VSTD::max(_Size(0), __max_size)) {} _LIBCPP_HIDE_FROM_ABI void flush(_CharT* __ptr, size_t __size) { - if (_Size(__size_) <= __n_) - __writer_.flush(__ptr, _VSTD::min(_Size(__size), __n_ - __size_)); + if (_Size(__size_) <= __max_size_) + __writer_.flush(__ptr, _VSTD::min(_Size(__size), __max_size_ - __size_)); __size_ += __size; } @@ -294,7 +422,7 @@ __output_buffer<_CharT> __output_{__storage_.begin(), __storage_.__buffer_size, this}; typename __writer_selector<_OutIt, _CharT>::type __writer_; - _Size __n_; + _Size __max_size_; _Size __size_{0}; }; @@ -310,24 +438,35 @@ using _Size = iter_difference_t<_OutIt>; public: - _LIBCPP_HIDE_FROM_ABI explicit __format_to_n_buffer_base(_OutIt __out_it, _Size __n) - : __output_(_VSTD::__unwrap_iter(__out_it), __n, this), __writer_(_VSTD::move(__out_it)) { - if (__n <= 0) [[unlikely]] + _LIBCPP_HIDE_FROM_ABI explicit __format_to_n_buffer_base(_OutIt __out_it, _Size __max_size) + : __output_(_VSTD::__unwrap_iter(__out_it), __max_size, this), + __writer_(_VSTD::move(__out_it)), + __max_size_(__max_size) { + if (__max_size <= 0) [[unlikely]] __output_.reset(__storage_.begin(), __storage_.__buffer_size); } _LIBCPP_HIDE_FROM_ABI void flush(_CharT* __ptr, size_t __size) { - // A flush to the direct writer happens in two occasions: + // A flush to the direct writer happens in the following occasions: // - The format function has written the maximum number of allowed code // units. At this point it's no longer valid to write to this writer. So // switch to the internal storage. This internal storage doesn't need to // be written anywhere so the flush for that storage writes no output. + // - Like above, but the next "mass write" operation would overflow the + // buffer. In that case the buffer is pre-emptively switched. The still + // valid code units will be written separately. // - The format_to_n function is finished. In this case there's no need to // switch the buffer, but for simplicity the buffers are still switched. - // When the __n <= 0 the constructor already switched the buffers. + // When the __max_size <= 0 the constructor already switched the buffers. if (__size_ == 0 && __ptr != __storage_.begin()) { __writer_.flush(__ptr, __size); __output_.reset(__storage_.begin(), __storage_.__buffer_size); + } else if (__size_ < __max_size_) { + // Copies a part of the internal buffer to the output up to n characters. + // See __output_buffer<_CharT>::__flush_on_overflow for more information. + _Size __s = _VSTD::min(_Size(__size), __max_size_ - __size_); + std::copy_n(__ptr, __s, __writer_.out()); + __writer_.flush(__ptr, __s); } __size_ += __size; @@ -338,6 +477,7 @@ __output_buffer<_CharT> __output_; __writer_direct<_OutIt, _CharT> __writer_; + _Size __max_size_; _Size __size_{0}; }; @@ -350,7 +490,8 @@ using _Size = iter_difference_t<_OutIt>; public: - _LIBCPP_HIDE_FROM_ABI explicit __format_to_n_buffer(_OutIt __out_it, _Size __n) : _Base(_VSTD::move(__out_it), __n) {} + _LIBCPP_HIDE_FROM_ABI explicit __format_to_n_buffer(_OutIt __out_it, _Size __max_size) + : _Base(_VSTD::move(__out_it), __max_size) {} _LIBCPP_HIDE_FROM_ABI auto make_output_iterator() { return this->__output_.make_output_iterator(); } _LIBCPP_HIDE_FROM_ABI format_to_n_result<_OutIt> result() && { diff --git a/libcxx/include/__format/formatter_floating_point.h b/libcxx/include/__format/formatter_floating_point.h --- a/libcxx/include/__format/formatter_floating_point.h +++ b/libcxx/include/__format/formatter_floating_point.h @@ -10,9 +10,7 @@ #ifndef _LIBCPP___FORMAT_FORMATTER_FLOATING_POINT_H #define _LIBCPP___FORMAT_FORMATTER_FLOATING_POINT_H -#include <__algorithm/copy.h> #include <__algorithm/copy_n.h> -#include <__algorithm/fill_n.h> #include <__algorithm/find.h> #include <__algorithm/min.h> #include <__algorithm/rotate.h> @@ -528,13 +526,13 @@ // sign and (zero padding or alignment) if (__zero_padding && __first != __buffer.begin()) *__out_it++ = *__buffer.begin(); - __out_it = _VSTD::fill_n(_VSTD::move(__out_it), __padding.__before_, __specs.__fill_); + __out_it = __formatter::__fill(_VSTD::move(__out_it), __padding.__before_, __specs.__fill_); if (!__zero_padding && __first != __buffer.begin()) *__out_it++ = *__buffer.begin(); // integral part if (__grouping.empty()) { - __out_it = _VSTD::copy_n(__first, __digits, _VSTD::move(__out_it)); + __out_it = __formatter::__copy(__first, __digits, _VSTD::move(__out_it)); } else { auto __r = __grouping.rbegin(); auto __e = __grouping.rend() - 1; @@ -546,7 +544,7 @@ // This loop achieves that process by testing the termination condition // midway in the loop. while (true) { - __out_it = _VSTD::copy_n(__first, *__r, _VSTD::move(__out_it)); + __out_it = __formatter::__copy(__first, *__r, _VSTD::move(__out_it)); __first += *__r; if (__r == __e) @@ -560,16 +558,16 @@ // fractional part if (__result.__radix_point != __result.__last) { *__out_it++ = __np.decimal_point(); - __out_it = _VSTD::copy(__result.__radix_point + 1, __result.__exponent, _VSTD::move(__out_it)); - __out_it = _VSTD::fill_n(_VSTD::move(__out_it), __buffer.__num_trailing_zeros(), _CharT('0')); + __out_it = __formatter::__copy(__result.__radix_point + 1, __result.__exponent, _VSTD::move(__out_it)); + __out_it = __formatter::__fill(_VSTD::move(__out_it), __buffer.__num_trailing_zeros(), _CharT('0')); } // exponent if (__result.__exponent != __result.__last) - __out_it = _VSTD::copy(__result.__exponent, __result.__last, _VSTD::move(__out_it)); + __out_it = __formatter::__copy(__result.__exponent, __result.__last, _VSTD::move(__out_it)); // alignment - return _VSTD::fill_n(_VSTD::move(__out_it), __padding.__after_, __specs.__fill_); + return __formatter::__fill(_VSTD::move(__out_it), __padding.__after_, __specs.__fill_); } # endif // _LIBCPP_HAS_NO_LOCALIZATION @@ -651,14 +649,15 @@ if (__size + __num_trailing_zeros >= __specs.__width_) { if (__num_trailing_zeros && __result.__exponent != __result.__last) // Insert trailing zeros before exponent character. - return _VSTD::copy( + return __formatter::__copy( __result.__exponent, __result.__last, - _VSTD::fill_n( - _VSTD::copy(__buffer.begin(), __result.__exponent, __ctx.out()), __num_trailing_zeros, _CharT('0'))); + __formatter::__fill(__formatter::__copy(__buffer.begin(), __result.__exponent, __ctx.out()), + __num_trailing_zeros, + _CharT('0'))); - return _VSTD::fill_n( - _VSTD::copy(__buffer.begin(), __result.__last, __ctx.out()), __num_trailing_zeros, _CharT('0')); + return __formatter::__fill( + __formatter::__copy(__buffer.begin(), __result.__last, __ctx.out()), __num_trailing_zeros, _CharT('0')); } auto __out_it = __ctx.out(); diff --git a/libcxx/include/__format/formatter_integral.h b/libcxx/include/__format/formatter_integral.h --- a/libcxx/include/__format/formatter_integral.h +++ b/libcxx/include/__format/formatter_integral.h @@ -243,7 +243,7 @@ // The zero padding is done like: // - Write [sign][prefix] // - Write data right aligned with '0' as fill character. - __out_it = _VSTD::copy(__begin, __first, _VSTD::move(__out_it)); + __out_it = __formatter::__copy(__begin, __first, _VSTD::move(__out_it)); __specs.__alignment_ = __format_spec::__alignment::__right; __specs.__fill_ = _CharT('0'); int32_t __size = __first - __begin; diff --git a/libcxx/include/__format/formatter_output.h b/libcxx/include/__format/formatter_output.h --- a/libcxx/include/__format/formatter_output.h +++ b/libcxx/include/__format/formatter_output.h @@ -14,10 +14,13 @@ #include <__algorithm/copy_n.h> #include <__algorithm/fill_n.h> #include <__algorithm/transform.h> +#include <__concepts/same_as.h> #include <__config> +#include <__format/buffer.h> #include <__format/formatter.h> #include <__format/parser_std_format_spec.h> #include <__format/unicode.h> +#include <__iterator/back_insert_iterator.h> #include <__utility/move.h> #include <__utility/unreachable.h> #include @@ -86,6 +89,63 @@ __libcpp_unreachable(); } +/// Copy wrapper. +/// +/// This uses a "mass output function" of __format::__output_buffer when possible. +template <__formatter::__char_type _CharT, __formatter::__char_type _OutCharT = _CharT> +_LIBCPP_HIDE_FROM_ABI auto __copy(basic_string_view<_CharT> __str, output_iterator auto __out_it) + -> decltype(__out_it) { + if constexpr (_VSTD::same_as>>) { + __out_it.__get_container()->__copy(__str); + return __out_it; + } else { + return std::copy_n(__str.data(), __str.size(), _VSTD::move(__out_it)); + } +} + +template <__formatter::__char_type _CharT, __formatter::__char_type _OutCharT = _CharT> +_LIBCPP_HIDE_FROM_ABI auto +__copy(const _CharT* __first, const _CharT* __last, output_iterator auto __out_it) + -> decltype(__out_it) { + return __formatter::__copy(basic_string_view{__first, __last}, _VSTD::move(__out_it)); +} + +template <__formatter::__char_type _CharT, __formatter::__char_type _OutCharT = _CharT> +_LIBCPP_HIDE_FROM_ABI auto __copy(const _CharT* __first, size_t __n, output_iterator auto __out_it) + -> decltype(__out_it) { + return __formatter::__copy(basic_string_view{__first, __n}, _VSTD::move(__out_it)); +} + +/// Transform wrapper. +/// +/// This uses a "mass output function" of __format::__output_buffer when possible. +template <__formatter::__char_type _CharT, __formatter::__char_type _OutCharT = _CharT, class _UnaryOperation> +_LIBCPP_HIDE_FROM_ABI auto +__transform(const _CharT* __first, + const _CharT* __last, + output_iterator auto __out_it, + _UnaryOperation __operation) -> decltype(__out_it) { + if constexpr (_VSTD::same_as>>) { + __out_it.__get_container()->__transform(__first, __last, _VSTD::move(__operation)); + return __out_it; + } else { + return std::transform(__first, __last, _VSTD::move(__out_it), __operation); + } +} + +/// Fill wrapper. +/// +/// This uses a "mass output function" of __format::__output_buffer when possible. +template <__formatter::__char_type _CharT, output_iterator _OutIt> +_LIBCPP_HIDE_FROM_ABI _OutIt __fill(_OutIt __out_it, size_t __n, _CharT __value) { + if constexpr (_VSTD::same_as>>) { + __out_it.__get_container()->__fill(__n, __value); + return __out_it; + } else { + return std::fill_n(_VSTD::move(__out_it), __n, __value); + } +} + template _LIBCPP_HIDE_FROM_ABI _OutIt __write_using_decimal_separators(_OutIt __out_it, const char* __begin, const char* __first, const char* __last, string&& __grouping, _CharT __sep, @@ -97,22 +157,22 @@ __padding_size_result __padding = {0, 0}; if (__specs.__alignment_ == __format_spec::__alignment::__zero_padding) { // Write [sign][prefix]. - __out_it = _VSTD::copy(__begin, __first, _VSTD::move(__out_it)); + __out_it = __formatter::__copy(__begin, __first, _VSTD::move(__out_it)); if (__specs.__width_ > __size) { // Write zero padding. __padding.__before_ = __specs.__width_ - __size; - __out_it = _VSTD::fill_n(_VSTD::move(__out_it), __specs.__width_ - __size, _CharT('0')); + __out_it = __formatter::__fill(_VSTD::move(__out_it), __specs.__width_ - __size, _CharT('0')); } } else { if (__specs.__width_ > __size) { // Determine padding and write padding. __padding = __padding_size(__size, __specs.__width_, __specs.__alignment_); - __out_it = _VSTD::fill_n(_VSTD::move(__out_it), __padding.__before_, __specs.__fill_); + __out_it = __formatter::__fill(_VSTD::move(__out_it), __padding.__before_, __specs.__fill_); } // Write [sign][prefix]. - __out_it = _VSTD::copy(__begin, __first, _VSTD::move(__out_it)); + __out_it = __formatter::__copy(__begin, __first, _VSTD::move(__out_it)); } auto __r = __grouping.rbegin(); @@ -133,10 +193,10 @@ while (true) { if (__specs.__std_.__type_ == __format_spec::__type::__hexadecimal_upper_case) { __last = __first + *__r; - __out_it = _VSTD::transform(__first, __last, _VSTD::move(__out_it), __hex_to_upper); + __out_it = __formatter::__transform(__first, __last, _VSTD::move(__out_it), __hex_to_upper); __first = __last; } else { - __out_it = _VSTD::copy_n(__first, *__r, _VSTD::move(__out_it)); + __out_it = __formatter::__copy(__first, *__r, _VSTD::move(__out_it)); __first += *__r; } @@ -147,7 +207,7 @@ *__out_it++ = __sep; } - return _VSTD::fill_n(_VSTD::move(__out_it), __padding.__after_, __specs.__fill_); + return __formatter::__fill(_VSTD::move(__out_it), __padding.__after_, __specs.__fill_); } /// Writes the input to the output with the required padding. @@ -155,12 +215,10 @@ /// Since the output column width is specified the function can be used for /// ASCII and Unicode output. /// -/// \pre [\a __first, \a __last) is a valid range. /// \pre \a __size <= \a __width. Using this function when this pre-condition /// doesn't hold incurs an unwanted overhead. /// -/// \param __first Pointer to the first element to write. -/// \param __last Pointer beyond the last element to write. +/// \param __str The string to write. /// \param __out_it The output iterator to write to. /// \param __specs The parsed formatting specifications. /// \param __size The (estimated) output column width. When the elements @@ -174,31 +232,42 @@ /// conversion, which means the [\a __first, \a __last) always contains elements /// of the type \c char. template -_LIBCPP_HIDE_FROM_ABI auto __write( - const _CharT* __first, - const _CharT* __last, - output_iterator auto __out_it, - __format_spec::__parsed_specifications<_ParserCharT> __specs, - ptrdiff_t __size) -> decltype(__out_it) { - _LIBCPP_ASSERT(__first <= __last, "Not a valid range"); - +_LIBCPP_HIDE_FROM_ABI auto +__write(basic_string_view<_CharT> __str, + output_iterator auto __out_it, + __format_spec::__parsed_specifications<_ParserCharT> __specs, + ptrdiff_t __size) -> decltype(__out_it) { if (__size >= __specs.__width_) - return _VSTD::copy(__first, __last, _VSTD::move(__out_it)); + return __formatter::__copy(__str, _VSTD::move(__out_it)); __padding_size_result __padding = __formatter::__padding_size(__size, __specs.__width_, __specs.__std_.__alignment_); - __out_it = _VSTD::fill_n(_VSTD::move(__out_it), __padding.__before_, __specs.__fill_); - __out_it = _VSTD::copy(__first, __last, _VSTD::move(__out_it)); - return _VSTD::fill_n(_VSTD::move(__out_it), __padding.__after_, __specs.__fill_); + __out_it = __formatter::__fill(_VSTD::move(__out_it), __padding.__before_, __specs.__fill_); + __out_it = __formatter::__copy(__str, _VSTD::move(__out_it)); + return __formatter::__fill(_VSTD::move(__out_it), __padding.__after_, __specs.__fill_); +} + +template +_LIBCPP_HIDE_FROM_ABI auto +__write(const _CharT* __first, + const _CharT* __last, + output_iterator auto __out_it, + __format_spec::__parsed_specifications<_ParserCharT> __specs, + ptrdiff_t __size) -> decltype(__out_it) { + _LIBCPP_ASSERT(__first <= __last, "Not a valid range"); + return __formatter::__write(basic_string_view{__first, __last}, _VSTD::move(__out_it), __specs, __size); } /// \overload /// /// Calls the function above where \a __size = \a __last - \a __first. template -_LIBCPP_HIDE_FROM_ABI auto __write(const _CharT* __first, const _CharT* __last, - output_iterator auto __out_it, - __format_spec::__parsed_specifications<_ParserCharT> __specs) -> decltype(__out_it) { - return __write(__first, __last, _VSTD::move(__out_it), __specs, __last - __first); +_LIBCPP_HIDE_FROM_ABI auto +__write(const _CharT* __first, + const _CharT* __last, + output_iterator auto __out_it, + __format_spec::__parsed_specifications<_ParserCharT> __specs) -> decltype(__out_it) { + _LIBCPP_ASSERT(__first <= __last, "Not a valid range"); + return __formatter::__write(__first, __last, _VSTD::move(__out_it), __specs, __last - __first); } template @@ -210,12 +279,12 @@ ptrdiff_t __size = __last - __first; if (__size >= __specs.__width_) - return _VSTD::transform(__first, __last, _VSTD::move(__out_it), __op); + return __formatter::__transform(__first, __last, _VSTD::move(__out_it), __op); __padding_size_result __padding = __padding_size(__size, __specs.__width_, __specs.__alignment_); - __out_it = _VSTD::fill_n(_VSTD::move(__out_it), __padding.__before_, __specs.__fill_); - __out_it = _VSTD::transform(__first, __last, _VSTD::move(__out_it), __op); - return _VSTD::fill_n(_VSTD::move(__out_it), __padding.__after_, __specs.__fill_); + __out_it = __formatter::__fill(_VSTD::move(__out_it), __padding.__before_, __specs.__fill_); + __out_it = __formatter::__transform(__first, __last, _VSTD::move(__out_it), __op); + return __formatter::__fill(_VSTD::move(__out_it), __padding.__after_, __specs.__fill_); } /// Writes additional zero's for the precision before the exponent. @@ -240,11 +309,11 @@ __padding_size_result __padding = __padding_size(__size + __num_trailing_zeros, __specs.__width_, __specs.__alignment_); - __out_it = _VSTD::fill_n(_VSTD::move(__out_it), __padding.__before_, __specs.__fill_); - __out_it = _VSTD::copy(__first, __exponent, _VSTD::move(__out_it)); - __out_it = _VSTD::fill_n(_VSTD::move(__out_it), __num_trailing_zeros, _CharT('0')); - __out_it = _VSTD::copy(__exponent, __last, _VSTD::move(__out_it)); - return _VSTD::fill_n(_VSTD::move(__out_it), __padding.__after_, __specs.__fill_); + __out_it = __formatter::__fill(_VSTD::move(__out_it), __padding.__before_, __specs.__fill_); + __out_it = __formatter::__copy(__first, __exponent, _VSTD::move(__out_it)); + __out_it = __formatter::__fill(_VSTD::move(__out_it), __num_trailing_zeros, _CharT('0')); + __out_it = __formatter::__copy(__exponent, __last, _VSTD::move(__out_it)); + return __formatter::__fill(_VSTD::move(__out_it), __padding.__after_, __specs.__fill_); } /// Writes a string using format's width estimation algorithm. @@ -262,7 +331,7 @@ // No padding -> copy the string if (!__specs.__has_width()) - return _VSTD::copy(__str.begin(), __str.end(), _VSTD::move(__out_it)); + return __formatter::__copy(__str, _VSTD::move(__out_it)); // Note when the estimated width is larger than size there's no padding. So // there's no reason to get the real size when the estimate is larger than or @@ -270,8 +339,7 @@ size_t __size = __format_spec::__estimate_column_width(__str, __specs.__width_, __format_spec::__column_width_rounding::__up) .__width_; - - return __formatter::__write(__str.begin(), __str.end(), _VSTD::move(__out_it), __specs, __size); + return __formatter::__write(__str, _VSTD::move(__out_it), __specs, __size); } template diff --git a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.unsigned_integral.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.unsigned_integral.pass.cpp --- a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.unsigned_integral.pass.cpp +++ b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.unsigned_integral.pass.cpp @@ -88,6 +88,8 @@ test_termination_condition( STR("340282366920938463463374607431768211455"), STR("}"), A(std::numeric_limits<__uint128_t>::max())); #endif + // Test __formatter::__transform (libc++ specific). + test_termination_condition(STR("FF"), STR("X}"), A(255)); } template diff --git a/libcxx/test/std/utilities/format/format.functions/format_tests.h b/libcxx/test/std/utilities/format/format.functions/format_tests.h --- a/libcxx/test/std/utilities/format/format.functions/format_tests.h +++ b/libcxx/test/std/utilities/format/format.functions/format_tests.h @@ -2557,6 +2557,68 @@ format_test_pointer(check, check_exception); } +/// Tests special buffer functions with a "large" input. +/// +/// This is a test specific for libc++, however the code should behave the same +/// on all implementations. +/// In \c __format::__output_buffer there are some special functions to optimize +/// outputting multiple characters, \c __copy, \c __transform, \c __fill. This +/// test validates whether the functions behave properly when the output size +/// doesn't fit in its internal buffer. +template +void format_test_buffer_optimizations(TestFunction check) { +#ifdef _LIBCPP_VERSION + // Used to validate our test sets are the proper size. + // To test the chunked operations it needs to be larger than the internal + // buffer. Picked a nice looking number. + constexpr int minimum = 3 * std::__format::__internal_storage::__buffer_size; +#else + constexpr int minimum = 1; +#endif + + // Copy + std::basic_string str = STR( + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog." + "The quick brown fox jumps over the lazy dog."); + assert(str.size() > minimum); + check.template operator()<"{}">(std::basic_string_view{str}, str); + + // Fill + std::basic_string fill(minimum, CharT('*')); + check.template operator()<"{:*<{}}">(std::basic_string_view{str + fill}, str, str.size() + minimum); + check.template operator()<"{:*^{}}">( + std::basic_string_view{fill + str + fill}, str, minimum + str.size() + minimum); + check.template operator()<"{:*>{}}">(std::basic_string_view{fill + str}, str, minimum + str.size()); +} + template void format_tests(TestFunction check, ExceptionTest check_exception) { // *** Test escaping *** @@ -2671,6 +2733,9 @@ // *** Test handle formatter argument *** format_test_handle(check, check_exception); + + // *** Test the interal buffer optimizations *** + format_test_buffer_optimizations(check); } #ifndef TEST_HAS_NO_WIDE_CHARACTERS