[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
Tue Jul 21 02:20:42 PDT 2026
https://github.com/philnik777 updated https://github.com/llvm/llvm-project/pull/210659
>From b6f0d385fcc77c1841d7e5d30c940f6ebeae7c33 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 | 94 +++++++++++++++++++
libcxx/test/support/charconv_test_helpers.h | 2 -
2 files changed, 94 insertions(+), 2 deletions(-)
diff --git a/libcxx/include/__charconv/to_chars_integral.h b/libcxx/include/__charconv/to_chars_integral.h
index 6d425139260b6..1b7649ab1638a 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,39 @@ 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
+ _LIBCPP_DIAGNOSTIC_PUSH
+ // TODO: remove this once all supported compilers diagnose functions correctly
+ _LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wpsabi")
+ 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)>());
+ _LIBCPP_DIAGNOSTIC_POP
+
+ // 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 +251,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