[libcxx-commits] [libcxx] [libc++] Optimize to_chars integral overloads for base 2 and 16 (PR #210659)

Nikolas Klauser via libcxx-commits libcxx-commits at lists.llvm.org
Mon Jul 20 05:57:32 PDT 2026


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

>From 1e1f53b4adc43cd47ff8267712bebdceea1df8ba Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Sat, 18 Jul 2026 13:03:43 +0200
Subject: [PATCH] [libc++] Optimize to_chars integral overloads

---
 libcxx/include/__charconv/to_chars_integral.h | 90 +++++++++++++++++++
 libcxx/test/support/charconv_test_helpers.h   |  2 -
 2 files changed, 90 insertions(+), 2 deletions(-)

diff --git a/libcxx/include/__charconv/to_chars_integral.h b/libcxx/include/__charconv/to_chars_integral.h
index 6d425139260b6..c53374badaef9 100644
--- a/libcxx/include/__charconv/to_chars_integral.h
+++ b/libcxx/include/__charconv/to_chars_integral.h
@@ -11,6 +11,7 @@
 #define _LIBCPP___CHARCONV_TO_CHARS_INTEGRAL_H
 
 #include <__algorithm/copy_n.h>
+#include <__algorithm/simd_utils.h>
 #include <__assert>
 #include <__bit/countl.h>
 #include <__charconv/tables.h>
@@ -22,12 +23,14 @@
 #include <__system_error/errc.h>
 #include <__type_traits/enable_if.h>
 #include <__type_traits/integral_constant.h>
+#include <__type_traits/is_constant_evaluated.h>
 #include <__type_traits/is_integral.h>
 #include <__type_traits/is_same.h>
 #include <__type_traits/is_signed.h>
 #include <__type_traits/make_32_64_or_128_bit.h>
 #include <__type_traits/make_unsigned.h>
 #include <__utility/unreachable.h>
+#include <array>
 #include <cstdint>
 #include <limits>
 
@@ -107,6 +110,29 @@ __to_chars_integral(char* __first, char* __last, _Tp __value, int __base) {
 
 namespace __itoa {
 
+template <size_t _Duplicate, size_t _IndexCount>
+constexpr array<size_t, _IndexCount * _Duplicate> __build_indices() {
+  array<size_t, _IndexCount * _Duplicate> __ret;
+  for (size_t __i = 0; __i != _IndexCount; ++__i) {
+    for (size_t __k = 0; __k != _Duplicate; ++__k)
+      __ret[__i * _Duplicate + __k] = _IndexCount - __i - 1;
+  }
+  return __ret;
+}
+
+_LIBCPP_DIAGNOSTIC_PUSH
+// TODO: remove this once all supported compilers diagnose functions correctly
+_LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wpsabi")
+// This is marked `always_inline` because it interacts with simd vectors
+template <size_t _Duplicate, size_t _VecSize>
+[[__gnu__::__always_inline__]] auto __duplicate_vector_entries(__simd_vector<char, _VecSize> __vals) {
+  static constexpr auto __indices = __itoa::__build_indices<_Duplicate, _VecSize>();
+  return [&]<size_t... _Indices> [[__gnu__::__always_inline__]] (index_sequence<_Indices...>) {
+    return __builtin_shufflevector(__vals, __vals, __indices[_Indices]...);
+  }(make_index_sequence<_Duplicate * _VecSize>());
+}
+_LIBCPP_DIAGNOSTIC_POP
+
 template <unsigned _Base>
 struct _LIBCPP_HIDDEN __integral;
 
@@ -122,6 +148,35 @@ struct _LIBCPP_HIDDEN __integral<2> {
   template <typename _Tp>
   _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI static __to_chars_result
   __to_chars(char* __first, char* __last, _Tp __value) {
+#if _LIBCPP_VECTORIZE_ALGORITHMS && __has_builtin(__builtin_masked_store)
+    if (!__libcpp_is_constant_evaluated() && __last - __first >= numeric_limits<_Tp>::digits) {
+      // Move the to-be-converted bits into the high bits, so that they are printed at the start.
+      auto __char_count = __width(__value);
+      auto __shift      = std::__countl_zero(__value | 1);
+      __value <<= __shift;
+
+      // Move the value into a vector and chop it up into its constituent bytes
+      auto __chopped    = __simd_vector<char, sizeof(_Tp)>(__simd_vector<_Tp, 1>(__value));
+
+      // Duplicate values so we can extract the appropriate bits in multiple positions
+      auto __characters = __itoa::__duplicate_vector_entries<8>(__chopped);
+
+      // This is marked `always_inline` because it interacts with simd vectors
+      auto __shifts     = []<size_t... _Indices> [[__gnu__::__always_inline__]] (index_sequence<_Indices...>) {
+        return __simd_vector<char, 8 * sizeof(_Tp)>{(7 - _Indices % 8)...};
+      }(make_index_sequence<8 * sizeof(_Tp)>());
+
+      // Check the appropriate bit for the position and set the character to 0 or 1 depending on whether it's set.
+      __characters = (__characters & __shifts) == 0 ? '0' : '1';
+
+      // Store all the characters, no matter whether they're part of the value or not. This is only safe if the buffer
+      // we've been given is large enough.
+      // TODO: Generate a mask on platforms which have native masked stores and use this code path unconditionally.
+      __builtin_masked_store(__simd_vector<bool, 8 * sizeof(_Tp)>(true), __characters, __first);
+      return {__first + __char_count, errc(0)};
+    }
+#endif
+
     ptrdiff_t __cap = __last - __first;
     int __n         = __width(__value);
     if (__n > __cap)
@@ -192,6 +247,41 @@ struct _LIBCPP_HIDDEN __integral<16> {
   template <typename _Tp>
   _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI static __to_chars_result
   __to_chars(char* __first, char* __last, _Tp __value) {
+#if _LIBCPP_VECTORIZE_ALGORITHMS && __has_builtin(__builtin_masked_store)
+    if (!__libcpp_is_constant_evaluated() && __last - __first >= (numeric_limits<_Tp>::digits / 4)) {
+      // Lambdas are marked as `always_inline` because they return vectors, which aren't ABI-stable
+
+      // Move the to-be-converted bits into the high bits, so that they are printed at the start. Note that the shift is
+      // 4-bit aligned, so that the value of the characters doesn't change.
+      auto __char_count = __width(__value);
+      auto __shift      = std::__countl_zero(__value | 1) & ~3;
+      __value <<= __shift;
+
+      // Move the value into a vector and chop it up into its constituent bytes
+      auto __chopped    = __simd_vector<char, sizeof(_Tp)>(__simd_vector<_Tp, 1>(__value));
+
+      // Duplicate values so we can extract the appropriate bits in multiple positions
+      auto __characters = __itoa::__duplicate_vector_entries<2>(__chopped);
+
+      // This is marked `always_inline` because it interacts with simd vectors
+      auto __shifts     = []<size_t... _Indices> [[__gnu__::__always_inline__]] (index_sequence<_Indices...>) {
+        return __simd_vector<char, 2 * sizeof(_Tp)>{(_Indices % 2 == 0 ? 4 : 0)...};
+      }(make_index_sequence<2 * sizeof(_Tp)>());
+
+      // Extract the bits we want for a given offset
+      __characters = (__characters >> __shifts) & 15;
+
+      // Convert the value into the hexadecimal character
+      __characters = (__characters >= 10 ? char('a' - 10) : '0') + __characters;
+
+      // Store all the characters, no matter whether they're part of the value or not. This is only safe if the buffer
+      // we've been given is large enough.
+      // TODO: Generate a mask on platforms which have native masked stores and use this code path unconditionally.
+      __builtin_masked_store(__simd_vector<bool, 2 * sizeof(_Tp)>(true), __characters, __first);
+      return {__first + __char_count, errc(0)};
+    }
+#endif
+
     ptrdiff_t __cap = __last - __first;
     int __n         = __width(__value);
     if (__n > __cap)
diff --git a/libcxx/test/support/charconv_test_helpers.h b/libcxx/test/support/charconv_test_helpers.h
index fcae09478457b..f6b60a2c8fe4d 100644
--- a/libcxx/test/support/charconv_test_helpers.h
+++ b/libcxx/test/support/charconv_test_helpers.h
@@ -113,8 +113,6 @@ struct to_chars_test_base
         std::iota(buf, buf + sizeof(buf), static_cast<unsigned char>(1));
         r = std::to_chars(buf, buf + sizeof(buf), v, args...);
         assert(r.ec == std::errc{});
-        for (std::size_t i = r.ptr - buf; i < sizeof(buf); ++i)
-            assert(static_cast<unsigned char>(buf[i]) == i + 1);
         *r.ptr = '\0';
 
 #ifndef TEST_HAS_NO_INT128



More information about the libcxx-commits mailing list