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,103 @@ flush(); } + /// Copies the input \a __str to the buffer. + /// + /// Since some of the input is generated by to_chars, there needs to be a + /// conversion when \a _CharT is \c 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 usecase 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 __size = __str.size(); + + __flush_on_overflow(__size); + if (__size <= __capacity_) { + _VSTD::copy_n(__str.data(), __size, _VSTD::addressof(__ptr_[__size_])); + __size_ += __size; + 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(__size, __capacity_); + _VSTD::copy_n(__first, __chunk, _VSTD::addressof(__ptr_[__size_])); + __size_ = __chunk; + __first += __chunk; + __size -= __chunk; + flush(); + } while (__size); + } + + /// A \c 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"); + // The file test/std/utilities/format/format.functions/format_tests.h + // should have a test for the fallback algorithm. However the code is only + // used for integral hexadecimal. The floating point code does the + // transformation internally so never calls this code. That means even a + // 128-bit value will fit in capacity, making it not possible to test the + // code path. + // Alternatively only this assertion remains and the fallback code is removed. + _LIBCPP_ASSERT(static_cast(__last - __first) <= __capacity_, "add a test case"); + + size_t __size = static_cast(__last - __first); + __flush_on_overflow(__size); + if (__size <= __capacity_) { + _VSTD::transform(__first, __last, _VSTD::addressof(__ptr_[__size_]), _VSTD::move(__operation)); + __size_ += __size; + 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(__size, __capacity_); + _VSTD::transform(__first, __first + __chunk, _VSTD::addressof(__ptr_[__size_]), __operation); + __size_ = __chunk; + __first += __chunk; + __size -= __chunk; + flush(); + } while (__size); + } + + /// 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 +189,45 @@ 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 (__size <= __capacity_) { + /// // Flush when we really would overflow. + /// if (__size_ + __size >= __capacity_) + /// flush(); + /// ... + /// } + /// \endcode + /// + /// This approach works for all cases but one: + /// A \ref __format_to_n_buffer_base where \ref __enable_direct_output is + /// \c 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 \a __writer_ to its \a __storage_. The \ref __capacity_ of the + /// former depends on the value of \c 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 + /// \a __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 complexer and \ref format_to_n is + /// not the most common use case. Therefore the optimization isn't done. + void __flush_on_overflow(size_t __size) { + if (__size_ + __size >= __capacity_) + flush(); + } }; /// A storage using an internal buffer. @@ -311,23 +448,33 @@ 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)) { + : __output_(_VSTD::__unwrap_iter(__out_it), __n, this), __writer_(_VSTD::move(__out_it)), __n_(__n) { if (__n <= 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. if (__size_ == 0 && __ptr != __storage_.begin()) { __writer_.flush(__ptr, __size); __output_.reset(__storage_.begin(), __storage_.__buffer_size); + } else if (__size_ < __n_) { + // 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), __n_ - __size_); + std::copy_n(__ptr, __s, __writer_.out()); + __writer_.flush(__ptr, __s); + __n_ += __s; } __size_ += __size; @@ -338,6 +485,7 @@ __output_buffer<_CharT> __output_; __writer_direct<_OutIt, _CharT> __writer_; + _Size __n_; _Size __size_{0}; }; 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,16 @@ 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 @@ -15,6 +15,7 @@ #include <__algorithm/fill_n.h> #include <__algorithm/transform.h> #include <__config> +#include <__format/buffer.h> #include <__format/formatter.h> #include <__format/parser_std_format_spec.h> #include <__utility/move.h> @@ -85,6 +86,57 @@ __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> +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 _VSTD::copy_n(__str.data(), __str.size(), _VSTD::move(__out_it)); +} + +template <__formatter::__char_type _CharT, __formatter::__char_type _OutCharT = _CharT> +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> +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> +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 _VSTD::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> +_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 _VSTD::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, @@ -96,22 +148,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(); @@ -132,10 +184,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; } @@ -146,7 +198,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. @@ -154,12 +206,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 @@ -173,28 +223,41 @@ /// 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 @@ -206,12 +269,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. @@ -236,11 +299,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_); } # ifndef _LIBCPP_HAS_NO_UNICODE @@ -253,13 +316,13 @@ _LIBCPP_ASSERT(!__specs.__has_precision(), "use __write_unicode"); // 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)); // Non Unicode part larger than width -> copy the string auto __last = __format_spec::__detail::__estimate_column_width_fast(__str.begin(), __str.end()); ptrdiff_t __size = __last - __str.begin(); if (__size >= __specs.__width_) - return _VSTD::copy(__str.begin(), __str.end(), _VSTD::move(__out_it)); + return __formatter::__copy(__str, _VSTD::move(__out_it)); // Is there a non Unicode part? if (__last != __str.end()) { @@ -268,10 +331,10 @@ __format_spec::__detail::__estimate_column_width(__last, __str.end(), __specs.__width_); __size += __column_width.__width; // Note this new size is used when __size < __specs.__width_ if (__size >= __specs.__width_) - return _VSTD::copy(__str.begin(), __str.end(), _VSTD::move(__out_it)); + return __formatter::__copy(__str, _VSTD::move(__out_it)); } - return __formatter::__write(__str.begin(), __str.end(), _VSTD::move(__out_it), __specs, __size); + return __formatter::__write(__str, _VSTD::move(__out_it), __specs, __size); } # endif @@ -293,7 +356,7 @@ // No non Unicode part, implies __size < __specs.__precision_ -> use normal write operation if (__last == __str.end()) - return __formatter::__write(__str.begin(), __str.end(), _VSTD::move(__out_it), __specs, __str.size()); + return __formatter::__write(__str, _VSTD::move(__out_it), __specs, __str.size()); __format_spec::__detail::__column_width_result __column_width = __format_spec::__detail::__estimate_column_width(__last, __str.end(), __specs.__precision_ - __size); @@ -302,7 +365,7 @@ if (__column_width.__ptr != __str.end()) __str.remove_suffix(__str.end() - __column_width.__ptr); - return __formatter::__write(__str.begin(), __str.end(), _VSTD::move(__out_it), __specs, __size); + return __formatter::__write(__str, _VSTD::move(__out_it), __specs, __size); # else if (__specs.__has_precision()) { @@ -311,7 +374,7 @@ return __formatter::__write(__str.begin(), __str.begin() + __specs.__precision_, _VSTD::move(__out_it), __specs, __specs.__precision_); } - return __formatter::__write(__str.begin(), __str.end(), _VSTD::move(__out_it), __specs, __str.size()); + return __formatter::__write(__str, _VSTD::move(__out_it), __specs, __str.size()); # endif } 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 @@ -2535,6 +2535,67 @@ 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 *** @@ -2650,6 +2711,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