diff --git a/libcxx/include/__format/format_functions.h b/libcxx/include/__format/format_functions.h index 5feaf7e5a064a..98e2d97718831 100644 --- a/libcxx/include/__format/format_functions.h +++ b/libcxx/include/__format/format_functions.h @@ -76,6 +76,9 @@ template } # endif +template _OutIt> +_LIBCPP_ALWAYS_INLINE _LIBCPP_HIDE_FROM_ABI _OutIt vformat_to(_OutIt __out_it, string_view __fmt, format_args __args); + namespace __format { /// Helper class parse and handle argument. @@ -147,6 +150,40 @@ struct _LIBCPP_TEMPLATE_VIS __compile_time_basic_format_context { size_t __size_; }; +template +union __any_std_formatter { +# define _LIBCCP_IMPLICIT_UNION(__type, __member_name) \ + constexpr __any_std_formatter(formatter<__type, _CharT> __formatter) : __member_name(__formatter) {} \ + explicit constexpr operator formatter<__type, _CharT>() const { return __member_name; } \ + formatter<__type, _CharT> __member_name + + _LIBCCP_IMPLICIT_UNION(bool, __b); + _LIBCCP_IMPLICIT_UNION(_CharT, __c); + + _LIBCCP_IMPLICIT_UNION(int, __i); + _LIBCCP_IMPLICIT_UNION(long long, __ll); +# if _LIBCPP_HAS_INT128 + _LIBCCP_IMPLICIT_UNION(__int128_t, __i128); +# endif + + _LIBCCP_IMPLICIT_UNION(unsigned, __u); + _LIBCCP_IMPLICIT_UNION(unsigned long long, __ull); +# if _LIBCPP_HAS_INT128 + _LIBCCP_IMPLICIT_UNION(__uint128_t, __u128); +# endif + + _LIBCCP_IMPLICIT_UNION(float, __f); + _LIBCCP_IMPLICIT_UNION(double, __d); + _LIBCCP_IMPLICIT_UNION(long double, __ld); + + _LIBCCP_IMPLICIT_UNION(const _CharT*, __cs); + _LIBCCP_IMPLICIT_UNION(basic_string_view<_CharT>, __sv); + + _LIBCCP_IMPLICIT_UNION(const void*, __p); + +# undef _LIBCCP_IMPLICIT_UNION +}; + // [format.string.std]/8 // If { arg-idopt } is used in a width or precision, the value of the // corresponding formatting argument is used in its place. If the @@ -156,7 +193,7 @@ struct _LIBCPP_TEMPLATE_VIS __compile_time_basic_format_context { // // _HasPrecision does the formatter have a precision? template -_LIBCPP_HIDE_FROM_ABI constexpr void __compile_time_validate_argument( +_LIBCPP_HIDE_FROM_ABI constexpr __any_std_formatter<_CharT> __compile_time_validate_argument( basic_format_parse_context<_CharT>& __parse_ctx, __compile_time_basic_format_context<_CharT>& __ctx) { auto __validate_type = [](__arg_t __type) { // LWG3720 originally allowed "signed or unsigned integer types", however @@ -185,11 +222,13 @@ _LIBCPP_HIDE_FROM_ABI constexpr void __compile_time_validate_argument( if constexpr (_HasPrecision) if (__formatter.__parser_.__precision_as_arg_) __validate_type(__ctx.arg(__formatter.__parser_.__precision_)); + + return __formatter; } // This function is not user facing, so it can directly use the non-standard types of the "variant". template -_LIBCPP_HIDE_FROM_ABI constexpr void __compile_time_visit_format_arg( +_LIBCPP_HIDE_FROM_ABI constexpr __any_std_formatter<_CharT> __compile_time_visit_format_arg( basic_format_parse_context<_CharT>& __parse_ctx, __compile_time_basic_format_context<_CharT>& __ctx, __arg_t __type) { @@ -210,7 +249,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr void __compile_time_visit_format_arg( # else std::__throw_format_error("Invalid argument"); # endif - return; + break; case __arg_t::__unsigned: return __format::__compile_time_validate_argument<_CharT, unsigned>(__parse_ctx, __ctx); case __arg_t::__unsigned_long_long: @@ -221,7 +260,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr void __compile_time_visit_format_arg( # else std::__throw_format_error("Invalid argument"); # endif - return; + break; case __arg_t::__float: return __format::__compile_time_validate_argument<_CharT, float, true>(__parse_ctx, __ctx); case __arg_t::__double: @@ -368,6 +407,67 @@ struct _LIBCPP_TEMPLATE_VIS basic_format_string { consteval basic_format_string(const _Tp& __str) : __str_{__str} { __format::__vformat_to(basic_format_parse_context<_CharT>{__str_, sizeof...(_Args)}, _Context{__types_.data(), __handles_.data(), sizeof...(_Args)}); + + basic_format_parse_context<_CharT> __parse_ctx{__str_, sizeof...(_Args)}; + _Context __ctx{__types_.data(), __handles_.data(), sizeof...(_Args)}; + + __state_ = [&] { + auto __begin = __str_.begin(); + auto __end = __str_.end(); + auto __element_begin = __str_.begin(); + + while (__begin != __end) { + switch (*__begin) { + case _CharT('{'): + if (__begin + 1 == __end) + std::__throw_format_error("The format string terminates at a '{'"); + + if (*(__begin + 1) == _CharT('{')) { + if (!__add_curly_brace(__element_begin, __begin, __element_type::__curly_brace_open)) + return __element_state::__parsing_partial; + + } else { + if (__element_begin != __begin) { + // Add the parsed string before the replaacemet + if (!__add_string(__element_begin, __begin)) + return __element_state::__parsing_partial; + } + + __format::__parse_number_result __r = __process_arg_id(__begin + 1, __end, __parse_ctx); + __format::__arg_t __type = __ctx.arg(__r.__value); + if (__type == __format::__arg_t::__none) + std::__throw_format_error("The argument index value is too large for the number of arguments supplied"); + else if (__type == __format::__arg_t::__handle) + // + // TODO ADD Handle support + // + return __element_state::__do_not_use; + else { + if (!__add_std_formatter(__element_begin, __begin, __end, __r.__value, __type, __parse_ctx, __ctx)) + return __element_state::__parsing_partial; + } + } + break; + + case _CharT('}'): + if (__begin + 1 == __end || *(__begin + 1) != _CharT('}')) + std::__throw_format_error("The format string contains an invalid escape sequence"); + + if (!__add_curly_brace(__element_begin, __begin, __element_type::__curly_brace_close)) + return __element_state::__parsing_partial; + break; + + default: + ++__begin; + } + } + // Handle last element. This should always be a string element, else there is a logic errror. + if (__element_count_ == 0 || __element_begin != __end) { + if (!__add_string(__element_begin, __begin)) + return __element_state::__parsing_partial; + } + return __element_state::__parsing_completed; + }(); } _LIBCPP_HIDE_FROM_ABI constexpr basic_string_view<_CharT> get() const noexcept { return __str_; } @@ -375,6 +475,26 @@ struct _LIBCPP_TEMPLATE_VIS basic_format_string { _LIBCPP_HIDE_FROM_ABI basic_format_string(__runtime_format_string<_CharT> __s) noexcept : __str_(__s.__str_) {} # endif + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI string __vformat(format_args __args) { + __format::__allocating_buffer __buffer; + switch (__state_) { + case __element_state::__parsing_partial: +# ifdef _LIBCPP_DEBUG_BASIC_FORMAT_STRING_CACHE + __buffer.__copy(std::string_view{"PART:"}); +# endif + [[fallthrough]]; + case __element_state::__do_not_use: + std::vformat_to(__buffer.__make_output_iterator(), __str_, __args); + break; + + case __element_state::__parsing_completed: + __format(__buffer, __args); + break; + } + + return string{__buffer.__view()}; + } + private: basic_string_view<_CharT> __str_; @@ -391,6 +511,264 @@ struct _LIBCPP_TEMPLATE_VIS basic_format_string { return __handle; }()...}; + + using __iterator = basic_string_view<_CharT>::iterator; + + enum class __element_state { + // The __elements_ should not be used. + // + // This is intened to be removed once everything works + __do_not_use, + + // The entire __str_ has been parsed and stored in __elements_ + __parsing_completed, + // The input __str_ has been partially parsed and stored in __elements_. + // + // This happens when the __str_ needed more entries than available in __elemenst_, + // so the output can first process __elements_ and then do run-time parsing for + // the remaining part of __str_. + // + // + // TODO test autotic numbering keeps the right elements + // + __parsing_partial, + + }; + + __element_state __state_; + + enum class __element_type { + // This is a formatter for one of the non-handle types. + // + // All these formatters use the same state for parsing this state is stored in the array. + // This means at run-time there is no need to parse this replacement-field. + __formatter_std, + // A handle formatter + // + // This replacement-field is validated at compile-time, but its state is not stored. + // This means it needs to be parsed again at run-time. + __formatter_handle, + + // This part of the format-string contains a wstring. + // + // This can be copied to the output verbatim. + __wstring, + // This part of the format-string contains a string with valid UTF-8. + // + // This can be copied to the output verbatim. + __string_valid_utf8, + // This part of the format-string contains a string with invalid UTF-8. + // + // Since UTF-8 is self syncing the end of the string can be determined + // + // The distiction between wstring, string (|in)valid UTF-8 is needed for + // std::string. When the string contains invalid Unicode the recommended + // practice is to diagnose this. std::print only takes `char` as input + // character type. So when using std::print the algorithm can use this + // information to diagnose things. (Diagnose means using Unicode + // replacement characters.) + __string_invalid_utf8, + // Found {{ in the input, unescape to {. + __curly_brace_open, + // Found }} in the input, unescape to }. + // + // Note it would be possible to use __char and then store '{' or '}' in + // the __parser_state_'s fill field. However that feels clumsy and there + // are only 2 escape characters. + __curly_brace_close, + // The field is empty. + // + // The number of fields is hard-coded so this is a sentinal value. + // Once an element is empty, all remaining elements are empty too. + __empty, + }; + + struct __element_formatter { + uint32_t __id = 0; + __format::__any_std_formatter<_CharT> __formatter; + }; + + struct __element { + __element_type __type; + union { + __element_formatter __formatter; + basic_string_view<_CharT> __string; + int __dummy; + } __data_ = {.__dummy = 0}; + }; + + size_t __element_count_ = 0; + + // The elements found in __str_ + static constexpr size_t __n_elements_ = 32; + array<__element, __n_elements_> __elements_ = [] { + array<__element, __n_elements_> __result; + for (size_t __i = 0; __i < __result.size(); ++__i) { + __result[__i] = {__element_type::__empty}; + } + return __result; + }(); + + // TODO add UTF-8 validation to this function. + [[nodiscard]] consteval bool __add_string(__iterator __begin, __iterator __end) { + if (__element_count_ == __elements_.size()) + return false; + + __elements_[__element_count_++] = { + same_as<_CharT, char> ? __element_type::__string_valid_utf8 : __element_type::__wstring, + {.__string = {__begin, __end}}}; + + return true; + } + + // Adds an escaped curly brace, with an optional string prefix, to the parsed elements. + // + // When the input is like + // abc{{ + // ^ ^ ^ + // 1 2 3 + // + // 1. input value of __element_begin + // 2. input value of __begin + // 3. output value of __element_begin and __begin + // + // pre __begin + 1 == __begin + 2 == ('{' || '}') + // pre __type = *(__begin + 1) == '{' ? __curly_brace_open + // : *(__begin + 1) == '}' ? __curly_brace_close + // : pre-condition failure + // + // __begin and __element_begin may point to the same element, in that case + // there is no parsed string. For example, "{}{{" or "{{" at the start of the + // input. + // + // When there is a string prefix the curly brace is stored in the string. + // This saves 1 entry. + [[nodiscard]] consteval bool + __add_curly_brace(__iterator& __element_begin, __iterator& __begin, __element_type __type) { + if (__element_begin == __begin) { + // No string before. + if (__element_count_ == __elements_.size()) + return false; + __elements_[__element_count_++] = {__type}; + } else { + // Merge with string. + if (!__add_string(__element_begin, __begin + 1)) + return false; + } + + __begin += 2; + __element_begin = __begin; + return true; + } + + [[nodiscard]] consteval bool __add_std_formatter( + __iterator& __element_begin, + __iterator& __begin, + __iterator __end, + uint32_t __arg_id, + __format::__arg_t __type, + basic_format_parse_context<_CharT>& __parse_ctx, + _Context& __ctx) { + if (__element_count_ == __elements_.size()) + return false; + + __format::__any_std_formatter<_CharT> __formatter = __compile_time_visit_format_arg(__parse_ctx, __ctx, __type); + __begin = __parse_ctx.begin(); + if (__begin == __end || *__begin != _CharT('}')) + std::__throw_format_error("The replacement field misses a terminating '}'"); + + ++__begin; + __element_begin = __begin; + + __elements_[__element_count_++] = {__element_type::__formatter_std, {.__formatter{__arg_id, __formatter}} + + }; + + return true; + } + + [[nodiscard]] consteval __format::__parse_number_result<__iterator> + __process_arg_id(__iterator __begin, __iterator __end, basic_format_parse_context<_CharT>& __parse_ctx) { + __format::__parse_number_result __result = __format::__parse_arg_id(__begin, __end, __parse_ctx); + if (__result.__last == __end) + std::__throw_format_error("The argument index should end with a ':' or a '}'"); + + // + // Note we always need to parse to get the proper default parser settings, + // this differs from where we copy pasted the data + // + // bool __parse = *__result.__last == _CharT(':'); + switch (*__result.__last) { + case _CharT(':'): + // The arg-id has a format-specifier, advance the input to the format-spec. + __parse_ctx.advance_to(__result.__last + 1); + break; + case _CharT('}'): + // The arg-id has no format-specifier. + __parse_ctx.advance_to(__result.__last); + break; + default: + std::__throw_format_error("The argument index should end with a ':' or a '}'"); + } + + return __result; + } + + void __format(__format::__allocating_buffer& __buffer, format_args __args) { +# ifdef _LIBCPP_DEBUG_BASIC_FORMAT_STRING_CACHE + __buffer.__copy(std::string_view{"FULL:"}); +# endif + auto __ctx = std::__format_context_create(__buffer.__make_output_iterator(), __args); + + for (auto& __element : __elements_) { + if (__element.__type == __element_type::__empty) + break; + + switch (__element.__type) { + case __element_type::__formatter_std: + { + std::__visit_format_arg( + [&](auto __arg) { + if constexpr (same_as) + std::__throw_format_error("The argument index value is too large for the number of arguments supplied"); + else if constexpr (same_as::handle>) + std::__throw_format_error("Not implemented"); + else { + // TODO __formatter.__formatter looks odd, maybe the first should + // be __replacement_field. + const auto& __formatter = + static_cast>(__element.__data_.__formatter.__formatter); + + __ctx.advance_to(__formatter.format(__arg, __ctx)); + } + }, + __ctx.arg(__element.__data_.__formatter.__id)); + + } break; + + case __element_type::__formatter_handle: + break; + case __element_type::__string_invalid_utf8: + if (false) { + break; + } + [[fallthrough]]; + case __element_type::__string_valid_utf8: + [[fallthrough]]; + case __element_type::__wstring: + __buffer.__copy(__element.__data_.__string); + break; + case __element_type::__curly_brace_open: + __buffer.push_back(_CharT('{')); + break; + case __element_type::__curly_brace_close: + __buffer.push_back(_CharT('}')); + break; + case __element_type::__empty: + break; + } + } + } }; template @@ -471,7 +849,7 @@ vformat(wstring_view __fmt, wformat_args __args) { template [[nodiscard]] _LIBCPP_ALWAYS_INLINE _LIBCPP_HIDE_FROM_ABI string format(format_string<_Args...> __fmt, _Args&&... __args) { - return std::vformat(__fmt.get(), std::make_format_args(__args...)); + return __fmt.__vformat(std::make_format_args(__args...)); } # if _LIBCPP_HAS_WIDE_CHARACTERS diff --git a/libcxx/test/benchmarks/format/parsing.bench.cpp b/libcxx/test/benchmarks/format/parsing.bench.cpp new file mode 100644 index 0000000000000..c31778efcc8c0 --- /dev/null +++ b/libcxx/test/benchmarks/format/parsing.bench.cpp @@ -0,0 +1,355 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 + +// This benchmark does formatting but the main goal is to test the parsing. +// Calling std::format without formatting args may me somewhat unrealistc, +// however it is realistic for std::print. Unfortunately std::print is hard +// to benchmark since the writing to the terminal is not what is inteded to +// be measured. + +#include +#include +#include +#include +#include + +#include "benchmark/benchmark.h" + +// No prefix + +static void BM_empty(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format(""); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_empty); + +static void BM_curly_open(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("{{"); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_curly_open); + +static void BM_curly_close(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("}}"); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_curly_close); + +static void BM_pipe(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("||"); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_pipe); + +static void BM_int(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("{}", 42); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_int); + +static void BM_int_formatted(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("{0:-^#6x}", 42); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_int_formatted); + +static void BM_3_ints(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("{},{},{}", 42, 0, 99); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_3_ints); + +static void BM_3_ints_formatted(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("{0:-^#6x},{0:-^#6x},{0:-^#6x}", 42); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_3_ints_formatted); + +// Prefix 5 + +static void BM_prefix_5_and_empty(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'"); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_5_and_empty); + +static void BM_prefix_5_and_curly_open(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'{{"); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_5_and_curly_open); + +static void BM_prefix_5_and_curly_close(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'}}"); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_5_and_curly_close); + +static void BM_prefix_5_and_pipe(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'||"); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_5_and_pipe); + +static void BM_prefix_5_and_int(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'{}", 42); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_5_and_int); + +static void BM_prefix_5_and_int_formatted(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'{0:-^#6x}", 42); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_5_and_int_formatted); + +static void BM_prefix_5_and_3_ints(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'{},{},{}", 42, 0, 99); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_5_and_3_ints); + +static void BM_prefix_5_and_3_ints_formatted(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'{0:-^#6x},{0:-^#6x},{0:-^#6x}", 42); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_5_and_3_ints_formatted); + +// Prefix 10 + +static void BM_prefix_10_and_empty(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'"); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_10_and_empty); + +static void BM_prefix_10_and_curly_open(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'{{"); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_10_and_curly_open); + +static void BM_prefix_10_and_curly_close(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'}}"); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_10_and_curly_close); + +static void BM_prefix_10_and_pipe(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'||"); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_10_and_pipe); + +static void BM_prefix_10_and_int(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'{}", 42); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_10_and_int); + +static void BM_prefix_10_and_int_formatted(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'{0:-^#6x}", 42); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_10_and_int_formatted); + +static void BM_prefix_10_and_3_ints(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'{},{},{}", 42, 0, 99); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_10_and_3_ints); + +static void BM_prefix_10_and_3_ints_formatted(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'{0:-^#6x},{0:-^#6x},{0:-^#6x}", 42); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_10_and_3_ints_formatted); + +// Prefix 20 + +static void BM_prefix_20_and_empty(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'aaaa'aaaa'"); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_20_and_empty); + +static void BM_prefix_20_and_curly_open(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'aaaa'aaaa'{{"); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_20_and_curly_open); + +static void BM_prefix_20_and_curly_close(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'aaaa'aaaa'}}"); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_20_and_curly_close); + +static void BM_prefix_20_and_pipe(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'aaaa'aaaa'||"); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_20_and_pipe); + +static void BM_prefix_20_and_int(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'aaaa'aaaa'{}", 42); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_20_and_int); + +static void BM_prefix_20_and_int_formatted(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'aaaa'aaaa'{0:-^#6x}", 42); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_20_and_int_formatted); + +static void BM_prefix_20_and_3_ints(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'aaaa'aaaa'{},{},{}", 42, 0, 99); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_20_and_3_ints); + +static void BM_prefix_20_and_3_ints_formatted(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'aaaa'aaaa'{0:-^#6x},{0:-^#6x},{0:-^#6x}", 42); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_20_and_3_ints_formatted); + +// Prefix 40 + +static void BM_prefix_40_and_empty(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'"); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_40_and_empty); + +static void BM_prefix_40_and_curly_open(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'{{"); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_40_and_curly_open); + +static void BM_prefix_40_and_curly_close(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'}}"); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_40_and_curly_close); + +static void BM_prefix_40_and_pipe(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'||"); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_40_and_pipe); + +static void BM_prefix_40_and_int(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'{}", 42); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_40_and_int); + +static void BM_prefix_40_and_int_formatted(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'{0:-^#6x}", 42); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_40_and_int_formatted); + +static void BM_prefix_40_and_3_ints(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'{},{},{}", 42, 0, 99); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_40_and_3_ints); + +static void BM_prefix_40_and_3_ints_formatted(benchmark::State& state) { + for (auto _ : state) { + auto s = std::format("aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'aaaa'{0:-^#6x},{0:-^#6x},{0:-^#6x}", 42); + benchmark::DoNotOptimize(s); + } +} +BENCHMARK(BM_prefix_40_and_3_ints_formatted); + +BENCHMARK_MAIN(); diff --git a/libcxx/test/std/utilities/format/format.functions/cache.pass.cpp b/libcxx/test/std/utilities/format/format.functions/cache.pass.cpp new file mode 100644 index 0000000000000..a1d8ced4ae289 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.functions/cache.pass.cpp @@ -0,0 +1,97 @@ +//===----------------------------------------------------------------------===// +// 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 +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME + + +// TODO FMT This test should not require std::to_chars(floating-point) +// XFAIL: availability-fp_to_chars-missing + +// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_DEBUG_BASIC_FORMAT_STRING_CACHE + +// + + +// This is a test for the new caching mechanism. + +#include +#include +#include + +#include "make_string.h" +#include "test_format_string.h" + +#include +#include + +#define SV(S) MAKE_STRING_VIEW(CharT, S) +template < class CharT, class... Args> +void check(std::basic_string_view expected, test_format_string fmt, Args&&... args) { + std::basic_string out = std::format(fmt, std::forward(args)...); +#ifndef TEST_HAS_NO_LOCALIZATION + if constexpr (std::same_as) + if (out != expected) + std::cerr << "\nFormat string " << fmt.get() << "\nExpected output " << expected << "\nActual output " << out + << '\n'; +#endif + assert(out == expected); +}; + +template +struct String { + std::basic_string s; +}; + +template +struct std::formatter, CharT> : std::formatter> { + template + typename FormatContext::iterator format(const String& str, FormatContext& ctx) const { + return std::formatter>::format(std::basic_string_view{str.s}, ctx); + } +}; + +template +static void test() { + check(SV("FULL:{"), SV("{{")); + check(SV("FULL:foo{"), SV("foo{{")); + check(SV("FULL:foo{{"), SV("foo{{{{")); + + check(SV("FULL:}"), SV("}}")); + check(SV("FULL:foo}"), SV("foo}}")); + check(SV("FULL:foo}}"), SV("foo}}}}")); + + check(SV("FULL:foo{}"), SV("foo{{}}")); + check(SV("FULL:foo{{}}"), SV("foo{{{{}}}}")); + check(SV("FULL:foo}{"), SV("foo}}{{")); + check(SV("FULL:foo}}{{"), SV("foo}}}}{{{{")); + + check(SV("FULL:ZZZ"), SV("ZZZ")); + check(SV("FULL:Z{Z"), SV("Z{{Z")); + check(SV("FULL:Z}Z"), SV("Z}}Z")); + check(SV("FULL:Z{Z{"), SV("Z{{Z{{")); + check(SV("FULL:Z}Z{Z"), SV("Z}}Z{{Z")); + + check(SV("FULL:ZZZ"), SV("{}"), SV("ZZZ")); + check(SV("FULL:42"), SV("{}"), 42); + check(SV("FULL:true"), SV("{}"), true); + + check(SV("FULL:ZZZ"), SV("{:}"), SV("ZZZ")); + check(SV("FULL:0x0042"), SV("{:#06x}"), 0x42); + check(SV("FULL:0x0042=answer"), SV("{:#06x}={}"), 0x42, SV("answer")); + +// check(SV("FULL:hello world"), SV("{} world"), String{"hello"}); +// check(SV("FULL:hello world"), SV("{0:} world"), String{"hello"}); + + // TODO TEST WITH ARG EATER +} + +int main(int, char**) { + test(); + + return 0; +}