diff --git a/libcxx/include/__format/format_functions.h b/libcxx/include/__format/format_functions.h index 74fec9f2761e0..873265bc17c24 100644 --- a/libcxx/include/__format/format_functions.h +++ b/libcxx/include/__format/format_functions.h @@ -11,6 +11,8 @@ #define _LIBCPP___FORMAT_FORMAT_FUNCTIONS #include <__algorithm/clamp.h> +#include <__algorithm/ranges_find_first_of.h> +#include <__chrono/statically_widen.h> #include <__concepts/convertible_to.h> #include <__concepts/same_as.h> #include <__config> @@ -36,6 +38,7 @@ #include <__iterator/iterator_traits.h> // iter_value_t #include <__variant/monostate.h> #include +#include #include #include @@ -447,10 +450,47 @@ format_to(_OutIt __out_it, wformat_string<_Args...> __fmt, _Args&&... __args) { } # endif +// Try constant folding the format string instead of going through the whole formatting machinery. If there is no +// constant folding no extra code should be emitted (with optimizations enabled) and the function returns nullopt. When +// constant folding is successful, the formatting is performed and the resulting string is returned. +namespace __format { +template +[[nodiscard]] _LIBCPP_HIDE_FROM_ABI optional> __try_constant_folding( + basic_string_view<_CharT> __fmt, + basic_format_args>, _CharT>> __args) { + // Fold strings not containing '{' or '}' to just return the string + if (bool __is_identity = [&] [[__gnu__::__pure__]] // Make sure the compiler knows this call can be eliminated + { return std::ranges::find_first_of(__fmt, array{'{', '}'}) == __fmt.end(); }(); + __builtin_constant_p(__is_identity) && __is_identity) + return basic_string<_CharT>{__fmt}; + + // Fold '{}' to the appropriate conversion function + if (auto __only_first_arg = __fmt == _LIBCPP_STATICALLY_WIDEN(_CharT, "{}"); + __builtin_constant_p(__only_first_arg) && __only_first_arg) { + if (auto __arg = __args.get(0); __builtin_constant_p(__arg.__type_)) { + return std::__visit_format_arg( + [](_Tp&& __argument) -> optional> { + if constexpr (is_same_v, basic_string_view<_CharT>>) { + return basic_string<_CharT>{__argument}; + } else { + return nullopt; + } + }, + __arg); + } + } + + return nullopt; +} +} // namespace __format + // TODO FMT This needs to be a template or std::to_chars(floating-point) availability markup // fires too eagerly, see http://llvm.org/PR61563. template [[nodiscard]] _LIBCPP_ALWAYS_INLINE inline _LIBCPP_HIDE_FROM_ABI string vformat(string_view __fmt, format_args __args) { + auto __result = __format::__try_constant_folding(__fmt, __args); + if (__result.has_value()) + return *std::move(__result); __format::__allocating_buffer __buffer; std::vformat_to(__buffer.__make_output_iterator(), __fmt, __args); return string{__buffer.__view()}; @@ -462,6 +502,9 @@ template template [[nodiscard]] _LIBCPP_ALWAYS_INLINE inline _LIBCPP_HIDE_FROM_ABI wstring vformat(wstring_view __fmt, wformat_args __args) { + auto __result = __format::__try_constant_folding(__fmt, __args); + if (__result.has_value()) + return *std::move(__result); __format::__allocating_buffer __buffer; std::vformat_to(__buffer.__make_output_iterator(), __fmt, __args); return wstring{__buffer.__view()}; diff --git a/libcxx/test/benchmarks/format/format.bench.cpp b/libcxx/test/benchmarks/format/format.bench.cpp index 267ef22950668..65caac747cbab 100644 --- a/libcxx/test/benchmarks/format/format.bench.cpp +++ b/libcxx/test/benchmarks/format/format.bench.cpp @@ -35,4 +35,15 @@ BENCHMARK(BM_format_string)->RangeMultiplier(2)->Range(1, 1 << 20); BENCHMARK(BM_format_string)->RangeMultiplier(2)->Range(1, 1 << 20); #endif +template +static void BM_string_without_formatting(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(std::format(CSTR("Hello, World!"))); + } +} +BENCHMARK(BM_string_without_formatting); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS +BENCHMARK(BM_string_without_formatting); +#endif + BENCHMARK_MAIN();