[libcxx] [llvm] [libcxx] Speed up xsgetn for always_noconv (PR #212010)

via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 11 02:07:26 PDT 2026


https://github.com/aokblast updated https://github.com/llvm/llvm-project/pull/212010

>From 3fe8ab44b769ed1a0c79f21a2643485ada4780dd Mon Sep 17 00:00:00 2001
From: ShengYi Hung <aokblast at FreeBSD.org>
Date: Sat, 25 Jul 2026 18:42:41 +0800
Subject: [PATCH 1/5] [libcxx] Speed up xsgetn for always_noconv

In the always_noconv path, optimize character reads in the same way as
xsgetn.

Unlike fread, getwc has different semantics, so the same optimization
cannot be applied to wide characters. Therefore, only optimize the char
path.
---
 libcxx/src/std_stream.h | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/libcxx/src/std_stream.h b/libcxx/src/std_stream.h
index 4b9d3a34b2441..214acc9e88b70 100644
--- a/libcxx/src/std_stream.h
+++ b/libcxx/src/std_stream.h
@@ -45,6 +45,7 @@ class _LIBCPP_HIDDEN __stdinbuf : public basic_streambuf<_CharT, char_traits<_Ch
 protected:
   virtual int_type underflow();
   virtual int_type uflow();
+  virtual streamsize xsgetn(char_type* __s, streamsize __n);
   virtual int_type pbackfail(int_type __c = traits_type::eof());
   virtual void imbue(const locale& __loc);
 
@@ -199,6 +200,28 @@ typename __stdinbuf<_CharT>::int_type __stdinbuf<_CharT>::__getchar(bool __consu
   return traits_type::to_int_type(__1buf);
 }
 
+template <class _CharT>
+streamsize __stdinbuf<_CharT>::xsgetn(char_type* __s, streamsize __n) {
+  if constexpr (is_same<_CharT, char>::value) {
+    if (__always_noconv_) {
+      streamsize __i = 0;
+      if (__i < __n && __last_consumed_is_next_) {
+        __s[__i++]               = traits_type::to_char_type(__last_consumed_);
+        __last_consumed_         = traits_type::eof();
+        __last_consumed_is_next_ = false;
+      }
+      if (__i < __n) {
+        size_t __nread = fread(__s + __i, 1, static_cast<size_t>(__n - __i), __file_);
+        if (__nread > 0)
+          __last_consumed_ = traits_type::to_int_type(__s[__i + __nread - 1]);
+        __i += static_cast<streamsize>(__nread);
+      }
+      return __i;
+    }
+  }
+  return basic_streambuf<char_type, traits_type>::xsgetn(__s, __n);
+}
+
 template <class _CharT>
 typename __stdinbuf<_CharT>::int_type __stdinbuf<_CharT>::pbackfail(int_type __c) {
   if (traits_type::eq_int_type(__c, traits_type::eof())) {

>From 43c6ea10b15e75c91dccd822534b969846c73d80 Mon Sep 17 00:00:00 2001
From: ShengYi Hung <aokblast at FreeBSD.org>
Date: Sun, 26 Jul 2026 00:40:18 +0800
Subject: [PATCH 2/5] benchmark fix

---
 third-party/benchmark/src/sysinfo.cc | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/third-party/benchmark/src/sysinfo.cc b/third-party/benchmark/src/sysinfo.cc
index 3977772bfede4..c622eeb142dc7 100644
--- a/third-party/benchmark/src/sysinfo.cc
+++ b/third-party/benchmark/src/sysinfo.cc
@@ -143,7 +143,7 @@ struct ValueUnion {
   }
 
   template <class T, int N>
-  std::array<T, N> GetAsArray() {
+  BENCHMARK_MAYBE_UNUSED std::array<T, N> GetAsArray() {
     const int arr_size = sizeof(T) * N;
     BM_CHECK_LE(arr_size, size);
     std::array<T, N> arr;
@@ -204,7 +204,8 @@ bool GetSysctl(std::string const& name, Tp* out) {
 }
 
 template <class Tp, size_t N>
-bool GetSysctl(std::string const& name, std::array<Tp, N>* out) {
+BENCHMARK_MAYBE_UNUSED bool GetSysctl(std::string const& name,
+                                      std::array<Tp, N>* out) {
   auto buff = GetSysctlImp(name);
   if (!buff) return false;
   *out = buff.GetAsArray<Tp, N>();

>From f3aa86fcbe217124d7c2377aa88c5c49e1be2318 Mon Sep 17 00:00:00 2001
From: ShengYi Hung <aokblast at FreeBSD.org>
Date: Mon, 3 Aug 2026 16:32:54 +0900
Subject: [PATCH 3/5] fixup! [libcxx] Speed up xsgetn for always_noconv

---
 .../cin-read-stdio-sync.sh.cpp                | 93 +++++++++++++++++++
 1 file changed, 93 insertions(+)
 create mode 100644 libcxx/test/libcxx/input.output/iostream.objects/cin-read-stdio-sync.sh.cpp

diff --git a/libcxx/test/libcxx/input.output/iostream.objects/cin-read-stdio-sync.sh.cpp b/libcxx/test/libcxx/input.output/iostream.objects/cin-read-stdio-sync.sh.cpp
new file mode 100644
index 0000000000000..d3b8f5485abd4
--- /dev/null
+++ b/libcxx/test/libcxx/input.output/iostream.objects/cin-read-stdio-sync.sh.cpp
@@ -0,0 +1,93 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// <iostream>
+
+// istream cin;
+
+// std::cin is backed by __stdinbuf, which reads from the C stdin FILE so that
+// C++ and C input can be interleaved (std::ios_base::sync_with_stdio). Its
+// xsgetn() takes a bulk fread() fast path for the no-conversion case, while
+// single-character operations go through __getchar()/getc(). This test checks
+// that the two paths observe the same stream and agree on the putback
+// bookkeeping (__last_consumed_). Some of the putback expectations
+// (e.g. sungetc() failing after re-consuming a putback character) are
+// libc++-specific, which is why this test lives under test/libcxx.
+
+// RUN: %{build}
+// RUN: echo -n ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 > %t.input
+// RUN: %{exec} %t.exe < %t.input
+
+#include <cassert>
+#include <cstdio>
+#include <cstring>
+#include <iostream>
+
+int main(int, char**) {
+  typedef std::char_traits<char> Traits;
+  char buf[16];
+
+  // C read first: bulk reads must continue where stdio left off.
+  int c = std::getchar();
+  assert(c == 'A');
+
+  // A character pushed back with ungetc() onto the FILE must be the first
+  // byte a bulk read delivers.
+  assert(std::ungetc('a', stdin) == 'a');
+  std::cin.read(buf, 4);
+  assert(std::cin.gcount() == 4);
+  assert(std::memcmp(buf, "aBCD", 4) == 0);
+
+  // peek() (underflow, which pushes the byte back with ungetc()) followed by
+  // a bulk read must not lose or duplicate the peeked byte.
+  assert(std::cin.peek() == 'E');
+  std::cin.read(buf, 3);
+  assert(std::cin.gcount() == 3);
+  assert(std::memcmp(buf, "EFG", 3) == 0);
+
+  // sungetc() after a bulk read: the last character delivered by the bulk
+  // read must be the one made available again.
+  assert(std::cin.rdbuf()->sungetc() == 'G');
+  std::cin.read(buf, 2);
+  assert(std::cin.gcount() == 2);
+  assert(std::memcmp(buf, "GH", 2) == 0);
+
+  // putback() of an arbitrary character, then a bulk read: the pending
+  // character comes first, the rest comes from the stream.
+  assert(std::cin.putback('h').good());
+  std::cin.read(buf, 2);
+  assert(std::cin.gcount() == 2);
+  assert(std::memcmp(buf, "hI", 2) == 0);
+
+  // A bulk read that delivers ONLY a pending putback character must behave
+  // like __getchar() re-consuming it: sungetc() afterwards fails. This
+  // matches the single-character path, which forgets __last_consumed_ when
+  // returning a putback character.
+  assert(std::cin.putback('q').good());
+  std::cin.read(buf, 1);
+  assert(std::cin.gcount() == 1);
+  assert(buf[0] == 'q');
+  assert(std::cin.rdbuf()->sungetc() == Traits::eof());
+
+  // Back to C stdio: it must see the byte right after what C++ consumed.
+  c = std::getchar();
+  assert(c == 'J');
+
+  // Single-character get() still works after bulk reads.
+  assert(std::cin.get() == 'K');
+
+  // Read to EOF: 'L'..'Z' and '0'..'9' remain (25 characters). The first
+  // read is satisfied in full, the second is short and hits EOF.
+  std::cin.read(buf, sizeof(buf));
+  assert(std::cin.gcount() == 16);
+  std::cin.read(buf, sizeof(buf));
+  assert(std::cin.gcount() == 9);
+  assert(std::cin.eof());
+
+  return 0;
+}

>From b0b88aa9f1129cb84a999b01a08ce2abd53817d6 Mon Sep 17 00:00:00 2001
From: ShengYi Hung <aokblast at FreeBSD.org>
Date: Fri, 7 Aug 2026 16:20:44 +0900
Subject: [PATCH 4/5] fixup! [libcxx] Speed up xsgetn for always_noconv

---
 .../iostream.objects/cin-read-stdio-sync.sh.cpp          | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/libcxx/test/libcxx/input.output/iostream.objects/cin-read-stdio-sync.sh.cpp b/libcxx/test/libcxx/input.output/iostream.objects/cin-read-stdio-sync.sh.cpp
index d3b8f5485abd4..6ac88a782ec6e 100644
--- a/libcxx/test/libcxx/input.output/iostream.objects/cin-read-stdio-sync.sh.cpp
+++ b/libcxx/test/libcxx/input.output/iostream.objects/cin-read-stdio-sync.sh.cpp
@@ -6,6 +6,15 @@
 //
 //===----------------------------------------------------------------------===//
 
+// QEMU does not detect EOF, when reading from stdin
+// "echo -n" suppresses any characters after the output and so the test hangs.
+// https://gitlab.com/qemu-project/qemu/-/issues/1963
+// UNSUPPORTED: LIBCXX-PICOLIBC-FIXME
+
+// This test hangs on Android devices that lack shell_v2, which was added in
+// Android N (API 24).
+// UNSUPPORTED: LIBCXX-ANDROID-FIXME && android-device-api={{2[1-3]}}
+
 // <iostream>
 
 // istream cin;

>From 517a960e4ef75874cf60e6f10b02331004430f67 Mon Sep 17 00:00:00 2001
From: ShengYi Hung <aokblast at FreeBSD.org>
Date: Tue, 11 Aug 2026 16:44:48 +0800
Subject: [PATCH 5/5] fixup! [llvm-strings] Use small buffer instead of reading
 whole file

---
 libcxx/src/std_stream.h | 28 +++++++++++++---------------
 1 file changed, 13 insertions(+), 15 deletions(-)

diff --git a/libcxx/src/std_stream.h b/libcxx/src/std_stream.h
index 214acc9e88b70..bdb15d267b47c 100644
--- a/libcxx/src/std_stream.h
+++ b/libcxx/src/std_stream.h
@@ -202,22 +202,20 @@ typename __stdinbuf<_CharT>::int_type __stdinbuf<_CharT>::__getchar(bool __consu
 
 template <class _CharT>
 streamsize __stdinbuf<_CharT>::xsgetn(char_type* __s, streamsize __n) {
-  if constexpr (is_same<_CharT, char>::value) {
-    if (__always_noconv_) {
-      streamsize __i = 0;
-      if (__i < __n && __last_consumed_is_next_) {
-        __s[__i++]               = traits_type::to_char_type(__last_consumed_);
-        __last_consumed_         = traits_type::eof();
-        __last_consumed_is_next_ = false;
-      }
-      if (__i < __n) {
-        size_t __nread = fread(__s + __i, 1, static_cast<size_t>(__n - __i), __file_);
-        if (__nread > 0)
-          __last_consumed_ = traits_type::to_int_type(__s[__i + __nread - 1]);
-        __i += static_cast<streamsize>(__nread);
-      }
-      return __i;
+  if (__always_noconv_ && !__is_win32api_wide_char) {
+    streamsize __i = 0;
+    if (__n > 0 && __last_consumed_is_next_) {
+      __s[__i++]               = traits_type::to_char_type(__last_consumed_);
+      __last_consumed_         = traits_type::eof();
+      __last_consumed_is_next_ = false;
+    }
+    if (__n > __i) {
+      size_t __nread = fread(__s + __i, 1, static_cast<size_t>(__n - __i), __file_);
+      if (__nread > 0)
+        __last_consumed_ = traits_type::to_int_type(__s[__i + __nread - 1]);
+      __i += static_cast<streamsize>(__nread);
     }
+    return __i;
   }
   return basic_streambuf<char_type, traits_type>::xsgetn(__s, __n);
 }



More information about the llvm-commits mailing list