diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -133,6 +133,7 @@ __config __debug __errc + __format/buffer.h __format/format_arg.h __format/format_args.h __format/format_context.h diff --git a/libcxx/include/__format/buffer.h b/libcxx/include/__format/buffer.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__format/buffer.h @@ -0,0 +1,215 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP___FORMAT_BUFFER_H +#define _LIBCPP___FORMAT_BUFFER_H + +#include <__algorithm/copy_n.h> +#include <__algorithm/unwrap_iter.h> +#include <__config> +#include <__debug> +#include <__format/formatter.h> // for __char_type TODO FMT Move the concept? +#include <__iterator/back_insert_iterator.h> +#include <__iterator/concepts.h> +#include <__iterator/iterator_traits.h> +#include <__iterator/wrap_iter.h> +#include <__utility/move.h> +#include +#include +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +#pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 17 + +// TODO FMT Remove this once we require compilers with proper C++20 support. +// If the compiler has no concepts support, the format header will be disabled. +// Without concepts support enable_if needs to be used and that too much effort +// to support compilers with partial C++20 support. +#if !defined(_LIBCPP_HAS_NO_CONCEPTS) + +namespace __format { + +/// A "buffer" that handles writing to the proper iterator. +/// +/// This helper used together with the @ref back_insert_iterator to offer +/// type-erasure for the formatting functions. Reducing the number to template +/// instantiations. +template <__formatter::__char_type _CharT> +class _LIBCPP_TEMPLATE_VIS __output_buffer { +public: + using value_type = _CharT; + + template + _LIBCPP_HIDE_FROM_ABI explicit __output_buffer(_CharT* __ptr, + size_t __capacity, _Tp* __obj) + : __ptr_(__ptr), __capacity_(__capacity), + __flush_([](_CharT* __ptr, size_t __size, void* __o) { + static_cast<_Tp*>(__o)->flush(__ptr, __size); + }), + __obj_(__obj) {} + + _LIBCPP_HIDE_FROM_ABI void reset(_CharT* __ptr, size_t __capacity) { + __ptr_ = __ptr; + __capacity_ = __capacity; + } + + _LIBCPP_HIDE_FROM_ABI auto make_output_iterator() { + 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; + + // Profiling showed flushing after adding is more efficient than flushing + // when entering the function. + if (__size_ == __capacity_) + flush(); + } + + _LIBCPP_HIDE_FROM_ABI void flush() { + __flush_(__ptr_, __size_, __obj_); + __size_ = 0; + } + +private: + _CharT* __ptr_; + size_t __capacity_; + size_t __size_{0}; + void (*__flush_)(_CharT*, size_t, void*); + void* __obj_; +}; + +/// A storage using an internal buffer. +/// +/// This storage is used when writing a single element to the output iterator +/// is expensive. +template <__formatter::__char_type _CharT> +class _LIBCPP_TEMPLATE_VIS __internal_storage { +public: + _LIBCPP_HIDE_FROM_ABI _CharT* begin() { return __buffer_; } + _LIBCPP_HIDE_FROM_ABI size_t capacity() { return __buffer_size_; } + +private: + static constexpr size_t __buffer_size_ = 256 / sizeof(_CharT); + _CharT __buffer_[__buffer_size_]; +}; + +/// A storage writing directly to the storage. +/// +/// This requires the storage to be a contiguous buffer of \a _CharT. +/// Since the output is directly written to the underlying storage this class +/// is just an empty class. +template <__formatter::__char_type _CharT> +class _LIBCPP_TEMPLATE_VIS __direct_storage {}; + +template +concept __enable_direct_output = __formatter::__char_type<_CharT> && + (same_as<_OutIt, _CharT*> +#if _LIBCPP_DEBUG_LEVEL < 2 + || same_as<_OutIt, __wrap_iter<_CharT*>> +#endif + ); + +/// Write policy for directly writing to the underlying output. +template +class _LIBCPP_TEMPLATE_VIS __writer_direct { +public: + _LIBCPP_HIDE_FROM_ABI explicit __writer_direct(_OutIt __out_it) + : __out_it_(__out_it) {} + + _LIBCPP_HIDE_FROM_ABI auto out() { return __out_it_; } + + _LIBCPP_HIDE_FROM_ABI void flush(_CharT*, size_t __size) { + // _OutIt can be a __wrap_iter. Therefore the original iterator + // is adjusted. + __out_it_ += __size; + } + +private: + _OutIt __out_it_; +}; + +/// Write policy for copying the buffer to the output. +template +class _LIBCPP_TEMPLATE_VIS __writer_iterator { +public: + _LIBCPP_HIDE_FROM_ABI explicit __writer_iterator(_OutIt __out_it) + : __out_it_{_VSTD::move(__out_it)} {} + + _LIBCPP_HIDE_FROM_ABI auto out() { return __out_it_; } + + _LIBCPP_HIDE_FROM_ABI void flush(_CharT* __ptr, size_t __size) { + __out_it_ = _VSTD::copy_n(__ptr, __size, _VSTD::move(__out_it_)); + } + +private: + _OutIt __out_it_; +}; + +/// Selects the type of the writer used for the output iterator. +template +class _LIBCPP_TEMPLATE_VIS __writer_selector { +public: + using type = conditional_t<__enable_direct_output<_OutIt, _CharT>, + __writer_direct<_OutIt, _CharT>, + __writer_iterator<_OutIt, _CharT>>; +}; + +/// The generic formatting buffer. +template +requires(output_iterator<_OutIt, const _CharT&>) class _LIBCPP_TEMPLATE_VIS + __format_buffer { + using _Storage = + conditional_t<__enable_direct_output<_OutIt, _CharT>, + __direct_storage<_CharT>, __internal_storage<_CharT>>; + +public: + _LIBCPP_HIDE_FROM_ABI explicit __format_buffer(_OutIt __out_it) requires( + same_as<_Storage, __internal_storage<_CharT>>) + : __output_(__storage_.begin(), __storage_.capacity(), this), + __writer_(_VSTD::move(__out_it)) {} + + _LIBCPP_HIDE_FROM_ABI explicit __format_buffer(_OutIt __out_it) requires( + same_as<_Storage, __direct_storage<_CharT>>) + : __output_(_VSTD::__unwrap_iter(__out_it), size_t(-1), this), + __writer_(_VSTD::move(__out_it)) {} + + _LIBCPP_HIDE_FROM_ABI auto make_output_iterator() { + return __output_.make_output_iterator(); + } + + _LIBCPP_HIDE_FROM_ABI void flush(_CharT* __ptr, size_t __size) { + __writer_.flush(__ptr, __size); + } + + _LIBCPP_HIDE_FROM_ABI _OutIt out() && { + __output_.flush(); + return _VSTD::move(__writer_).out(); + } + +private: + [[no_unique_address]] _Storage __storage_; + __output_buffer<_CharT> __output_; + typename __writer_selector<_OutIt, _CharT>::type __writer_; +}; +} // namespace __format + +#endif // !defined(_LIBCPP_HAS_NO_CONCEPTS) +#endif //_LIBCPP_STD_VER > 17 + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___FORMAT_BUFFER_H diff --git a/libcxx/include/__format/format_context.h b/libcxx/include/__format/format_context.h --- a/libcxx/include/__format/format_context.h +++ b/libcxx/include/__format/format_context.h @@ -12,12 +12,12 @@ #include <__availability> #include <__config> +#include <__format/buffer.h> #include <__format/format_args.h> #include <__format/format_fwd.h> +#include <__iterator/back_insert_iterator.h> #include <__iterator/concepts.h> #include -#include -#include #ifndef _LIBCPP_HAS_NO_LOCALIZATION #include @@ -70,16 +70,12 @@ } #endif -// TODO FMT Implement [format.context]/4 -// [Note 1: For a given type charT, implementations are encouraged to provide a -// single instantiation of basic_format_context for appending to -// basic_string, vector, or any other container with contiguous -// storage by wrapping those in temporary objects with a uniform interface -// (such as a span) and polymorphic reallocation. - end note] - -using format_context = basic_format_context, char>; +using format_context = + basic_format_context>, + char>; #ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS -using wformat_context = basic_format_context, wchar_t>; +using wformat_context = basic_format_context< + back_insert_iterator<__format::__output_buffer>, wchar_t>; #endif template diff --git a/libcxx/include/format b/libcxx/include/format --- a/libcxx/include/format +++ b/libcxx/include/format @@ -266,6 +266,7 @@ #include <__config> #include <__debug> +#include <__format/buffer.h> #include <__format/format_arg.h> #include <__format/format_args.h> #include <__format/format_context.h> @@ -506,11 +507,12 @@ basic_format_parse_context{__fmt, __args.__size()}, _VSTD::__format_context_create(_VSTD::move(__out_it), __args)); else { - basic_string<_CharT> __str; + __format::__format_buffer<_OutIt, _CharT> __buffer{_VSTD::move(__out_it)}; _VSTD::__format::__vformat_to( basic_format_parse_context{__fmt, __args.__size()}, - _VSTD::__format_context_create(_VSTD::back_inserter(__str), __args)); - return _VSTD::copy_n(__str.begin(), __str.size(), _VSTD::move(__out_it)); + _VSTD::__format_context_create(__buffer.make_output_iterator(), + __args)); + return _VSTD::move(__buffer).out(); } } @@ -637,12 +639,12 @@ _VSTD::__format_context_create(_VSTD::move(__out_it), __args, _VSTD::move(__loc))); else { - basic_string<_CharT> __str; + __format::__format_buffer<_OutIt, _CharT> __buffer{_VSTD::move(__out_it)}; _VSTD::__format::__vformat_to( basic_format_parse_context{__fmt, __args.__size()}, - _VSTD::__format_context_create(_VSTD::back_inserter(__str), __args, - _VSTD::move(__loc))); - return _VSTD::copy_n(__str.begin(), __str.size(), _VSTD::move(__out_it)); + _VSTD::__format_context_create(__buffer.make_output_iterator(), + __args, _VSTD::move(__loc))); + return _VSTD::move(__buffer).out(); } } diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -439,8 +439,9 @@ export * module __format { - module format_arg { private header "__format/format_arg.h" } - module format_args { private header "__format/format_args.h" } + module buffer { private header "__format/buffer.h" } + module format_arg { private header "__format/format_arg.h" } + module format_args { private header "__format/format_args.h" } module format_context { private header "__format/format_context.h" export optional diff --git a/libcxx/test/libcxx/diagnostics/detail.headers/format/buffer.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/format/buffer.module.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/diagnostics/detail.headers/format/buffer.module.verify.cpp @@ -0,0 +1,15 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: modules-build + +// WARNING: This test was generated by 'generate_private_header_tests.py' +// and should not be edited manually. + +// expected-error@*:* {{use of private header from outside its module: '__format/buffer.h'}} +#include <__format/buffer.h> diff --git a/libcxx/test/std/utilities/format/format.formatter/format.context/types.compile.pass.cpp b/libcxx/test/libcxx/utilities/format/format.formatter/format.context/types.compile.pass.cpp rename from libcxx/test/std/utilities/format/format.formatter/format.context/types.compile.pass.cpp rename to libcxx/test/libcxx/utilities/format/format.formatter/format.context/types.compile.pass.cpp --- a/libcxx/test/std/utilities/format/format.formatter/format.context/types.compile.pass.cpp +++ b/libcxx/test/libcxx/utilities/format/format.formatter/format.context/types.compile.pass.cpp @@ -25,7 +25,6 @@ // using wformat_context = basic_format_context; #include -#include #include #include @@ -98,26 +97,24 @@ } constexpr void test() { - test>, char>(); + test>, char>(); #ifndef TEST_HAS_NO_WIDE_CHARACTERS - test>, wchar_t>(); + test>, + wchar_t>(); #endif - test>, char8_t>(); - test>, char16_t>(); - test>, char32_t>(); } -static_assert(std::is_same_v< - std::format_context, - std::basic_format_context< - std::back_insert_iterator>, char>>); +static_assert( + std::is_same_v< + std::format_context, + std::basic_format_context< + std::back_insert_iterator>, + char>>); #ifndef TEST_HAS_NO_WIDE_CHARACTERS static_assert( std::is_same_v< std::wformat_context, std::basic_format_context< - std::back_insert_iterator>, wchar_t>>); + std::back_insert_iterator< std::__format::__output_buffer>, + wchar_t>>); #endif - -// Required for MSVC internal test runner compatibility. -int main(int, char**) { return 0; }