[libcxx-commits] [libcxx] [libc++] Add basic constant folding for std::format (PR #107197)

Nikolas Klauser via libcxx-commits libcxx-commits at lists.llvm.org
Mon Feb 3 11:23:49 PST 2025


https://github.com/philnik777 updated https://github.com/llvm/llvm-project/pull/107197

>From 0db3132b818ba1fd1f8d5b5d684e5170d31375df Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Tue, 3 Sep 2024 19:39:17 +0200
Subject: [PATCH] [libc++] Add basic constant folding for std::format

---
 libcxx/include/__config                       |  8 ++++
 libcxx/include/__format/format_functions.h    | 41 +++++++++++++++++++
 .../test/benchmarks/format/format.bench.cpp   |  9 ++++
 3 files changed, 58 insertions(+)

diff --git a/libcxx/include/__config b/libcxx/include/__config
index 1c6dd8f36c32f2..a7c857aec2e9bb 100644
--- a/libcxx/include/__config
+++ b/libcxx/include/__config
@@ -556,6 +556,13 @@ typedef __char32_t char32_t;
 #    define _LIBCPP_CLANG_DIAGNOSTIC_IGNORED_CXX23_EXTENSION _LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wc++2b-extensions")
 #  endif
 
+#  if __has_warning("-Wc++23-lambda-attributes")
+#    define _LIBCPP_CLANG_DIANGOSTIC_INGORED_CXX23_LAMBDA_ATTRIBUTES                                                   \
+      _LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wc++23-lambda-attributes")
+#  else
+#    define _LIBCPP_CLANG_DIANGOSTIC_INGORED_CXX23_LAMBDA_ATTRIBUTES
+#  endif
+
 // Clang modules take a significant compile time hit when pushing and popping diagnostics.
 // Since all the headers are marked as system headers in the modulemap, we can simply disable this
 // pushing and popping when building with clang modules.
@@ -567,6 +574,7 @@ typedef __char32_t char32_t;
       _LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wc++17-extensions")                                                           \
       _LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wc++20-extensions")                                                           \
       _LIBCPP_CLANG_DIAGNOSTIC_IGNORED_CXX23_EXTENSION                                                                 \
+      _LIBCPP_CLANG_DIANGOSTIC_INGORED_CXX23_LAMBDA_ATTRIBUTES                                                         \
       _LIBCPP_GCC_DIAGNOSTIC_IGNORED("-Wc++14-extensions")                                                             \
       _LIBCPP_GCC_DIAGNOSTIC_IGNORED("-Wc++17-extensions")                                                             \
       _LIBCPP_GCC_DIAGNOSTIC_IGNORED("-Wc++20-extensions")                                                             \
diff --git a/libcxx/include/__format/format_functions.h b/libcxx/include/__format/format_functions.h
index 5feaf7e5a064ad..fd71f53b51c1e6 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>
@@ -447,10 +449,46 @@ 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.
+template <class _CharT>
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI optional<basic_string<_CharT>> __try_constant_folding_format(
+    basic_string_view<_CharT> __fmt,
+    basic_format_args<basic_format_context<back_insert_iterator<__format::__output_buffer<_CharT>>, _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(
+          []<class _Tp>(_Tp&& __argument) -> optional<basic_string<_CharT>> {
+            if constexpr (is_same_v<remove_cvref_t<_Tp>, basic_string_view<_CharT>>) {
+              return basic_string<_CharT>{__argument};
+            } else {
+              return nullopt;
+            }
+          },
+          __arg);
+    }
+  }
+
+  return nullopt;
+}
+
 // 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 <class = void>
 [[nodiscard]] _LIBCPP_ALWAYS_INLINE inline _LIBCPP_HIDE_FROM_ABI string vformat(string_view __fmt, format_args __args) {
+  auto __result = std::__try_constant_folding_format(__fmt, __args);
+  if (__result.has_value())
+    return *__result;
   __format::__allocating_buffer<char> __buffer;
   std::vformat_to(__buffer.__make_output_iterator(), __fmt, __args);
   return string{__buffer.__view()};
@@ -462,6 +500,9 @@ template <class = void>
 template <class = void>
 [[nodiscard]] _LIBCPP_ALWAYS_INLINE inline _LIBCPP_HIDE_FROM_ABI wstring
 vformat(wstring_view __fmt, wformat_args __args) {
+  auto __result = std::__try_constant_folding_format(__fmt, __args);
+  if (__result.has_value())
+    return *__result;
   __format::__allocating_buffer<wchar_t> __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 267ef229506685..044cad6d09e40d 100644
--- a/libcxx/test/benchmarks/format/format.bench.cpp
+++ b/libcxx/test/benchmarks/format/format.bench.cpp
@@ -35,4 +35,13 @@ BENCHMARK(BM_format_string<char>)->RangeMultiplier(2)->Range(1, 1 << 20);
 BENCHMARK(BM_format_string<wchar_t>)->RangeMultiplier(2)->Range(1, 1 << 20);
 #endif
 
+template <class CharT>
+static void BM_string_without_formatting(benchmark::State& state) {
+  for (auto _ : state) {
+    benchmark::DoNotOptimize(std::format(CSTR("Hello, World!")));
+  }
+}
+BENCHMARK(BM_string_without_formatting<char>);
+BENCHMARK(BM_string_without_formatting<wchar_t>);
+
 BENCHMARK_MAIN();



More information about the libcxx-commits mailing list