diff --git a/libcxx/benchmarks/to_chars.bench.cpp b/libcxx/benchmarks/to_chars.bench.cpp new file mode 100644 --- /dev/null +++ b/libcxx/benchmarks/to_chars.bench.cpp @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// 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 +// +//===----------------------------------------------------------------------===// + +#include +#include +#include + +#include "benchmark/benchmark.h" +#include "test_macros.h" + +static const std::array input = [] { + std::mt19937 generator; + std::uniform_int_distribution distribution(0, std::numeric_limits::max()); + std::array result; + std::generate_n(result.begin(), result.size(), [&] { return distribution(generator); }); + return result; +}(); + +static void BM_to_chars_good(benchmark::State& state) { + char buffer[128]; + int base = state.range(0); + while (state.KeepRunningBatch(input.size())) + for (auto value : input) + benchmark::DoNotOptimize(std::to_chars(buffer, &buffer[128], value, base)); +} +BENCHMARK(BM_to_chars_good)->DenseRange(2, 36, 1); + +static void BM_to_chars_bad(benchmark::State& state) { + char buffer[128]; + int base = state.range(0); + struct sample { + unsigned size; + unsigned value; + }; + std::array data; + // Assume the failure occurs, on average, halfway during the conversion. + std::transform(input.begin(), input.end(), data.begin(), [&](unsigned value) { + std::to_chars_result result = std::to_chars(buffer, &buffer[128], value, base); + return sample{unsigned((result.ptr - buffer) / 2), value}; + }); + + while (state.KeepRunningBatch(data.size())) + for (auto element : data) + benchmark::DoNotOptimize(std::to_chars(buffer, &buffer[element.size], element.value, base)); +} +BENCHMARK(BM_to_chars_bad)->DenseRange(2, 36, 1); + +int main(int argc, char** argv) { + benchmark::Initialize(&argc, argv); + if (benchmark::ReportUnrecognizedArguments(argc, argv)) + return 1; + + benchmark::RunSpecifiedBenchmarks(); +} diff --git a/libcxx/include/charconv b/libcxx/include/charconv --- a/libcxx/include/charconv +++ b/libcxx/include/charconv @@ -78,6 +78,7 @@ #include <__errc> #include // for log2f #include +#include // for _LIBCPP_UNREACHABLE #include #include #include @@ -400,33 +401,54 @@ return __to_chars_integral(__first, __last, __x, __base, false_type()); } +template +_LIBCPP_AVAILABILITY_TO_CHARS _LIBCPP_INLINE_VISIBILITY int __to_chars_integral_width(_Tp __value, unsigned __base) { + _LIBCPP_ASSERT(__value >= 0, "The function requires a non-negative value."); + + unsigned __pow_2 = __base * __base; + unsigned __pow_3 = __pow_2 * __base; + unsigned __pow_4 = __pow_3 * __base; + + int __r = 0; + while (true) { + if (__value < __base) + return __r + 1; + if (__value < __pow_2) + return __r + 2; + if (__value < __pow_3) + return __r + 3; + if (__value < __pow_4) + return __r + 4; + + __value /= __pow_4; + __r += 4; + } + + _LIBCPP_UNREACHABLE(); +} + template _LIBCPP_AVAILABILITY_TO_CHARS inline _LIBCPP_INLINE_VISIBILITY to_chars_result __to_chars_integral(char* __first, char* __last, _Tp __value, int __base, false_type) { - if (__base == 10) - return __to_chars_itoa(__first, __last, __value, false_type()); - - auto __p = __last; - while (__p != __first) - { - auto __c = __value % __base; - __value /= __base; - *--__p = "0123456789abcdefghijklmnopqrstuvwxyz"[__c]; - if (__value == 0) - break; - } - - auto __len = __last - __p; - if (__value != 0 || !__len) - return {__last, errc::value_too_large}; - else - { - _VSTD::memmove(__first, __p, __len); - return {__first + __len, {}}; - } + if (__base == 10) + return __to_chars_itoa(__first, __last, __value, false_type()); + + ptrdiff_t __diff = __last - __first; + int __width = __to_chars_integral_width(__value, __base); + if (__width > __diff) + return {__last, errc::value_too_large}; + + __last = __first + __width; + char* __p = __last; + do { + unsigned __c = __value % __base; + __value /= __base; + *--__p = "0123456789abcdefghijklmnopqrstuvwxyz"[__c]; + } while (__value != 0); + return {__last, errc(0)}; } template ::value, int>::type = 0> diff --git a/libcxx/test/support/charconv_test_helpers.h b/libcxx/test/support/charconv_test_helpers.h --- a/libcxx/test/support/charconv_test_helpers.h +++ b/libcxx/test/support/charconv_test_helpers.h @@ -9,6 +9,7 @@ #ifndef SUPPORT_CHARCONV_TEST_HELPERS_H #define SUPPORT_CHARCONV_TEST_HELPERS_H +#include #include #include #include @@ -105,8 +106,12 @@ using std::to_chars; std::to_chars_result r; + // Poison the buffer for testing whether a successful std::to_chars + // doesn't modify data beyond r.ptr. + std::fill(buf, buf + sizeof(buf), '~'); r = to_chars(buf, buf + sizeof(buf), v, args...); assert(r.ec == std::errc{}); + assert(std::all_of(r.ptr, buf + sizeof(buf), [](char c) { return c == '~'; })); *r.ptr = '\0'; auto a = fromchars(buf, r.ptr, args...);