[libcxx-commits] [libcxx] [libc++] Optimize standard streams with sync_with_stdio(false) (PR #209161)

Nikolas Klauser via libcxx-commits libcxx-commits at lists.llvm.org
Mon Jul 13 05:30:04 PDT 2026


https://github.com/philnik777 created https://github.com/llvm/llvm-project/pull/209161

Fixes #21566


>From fed852da6cb208b549915a4f0aa1fd57fe4295bd Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Mon, 13 Jul 2026 14:28:02 +0200
Subject: [PATCH] [libc++] Optimize standard streams with
 sync_with_stdio(false)

Fixes #21566
---
 libcxx/include/fstream                        |  9 +-
 libcxx/src/ios.cpp                            |  7 --
 libcxx/src/iostream.cpp                       | 86 +++++++++++++++----
 .../cin.sync_with_stdio.sh.cpp                | 37 ++++++++
 .../clog.sync_with_stdio.sh.cpp               | 29 +++++++
 .../cout.sync_with_stdio.sh.cpp               | 27 ++++++
 .../wcin.sync_with_stdio.sh.cpp               | 37 ++++++++
 .../wclog.sync_with_stdio.sh.cpp              | 29 +++++++
 .../wcout.sync_with_stdio.sh.cpp              | 27 ++++++
 9 files changed, 260 insertions(+), 28 deletions(-)
 create mode 100644 libcxx/test/std/input.output/iostream.objects/narrow.stream.objects/cin.sync_with_stdio.sh.cpp
 create mode 100644 libcxx/test/std/input.output/iostream.objects/narrow.stream.objects/clog.sync_with_stdio.sh.cpp
 create mode 100644 libcxx/test/std/input.output/iostream.objects/narrow.stream.objects/cout.sync_with_stdio.sh.cpp
 create mode 100644 libcxx/test/std/input.output/iostream.objects/wide.stream.objects/wcin.sync_with_stdio.sh.cpp
 create mode 100644 libcxx/test/std/input.output/iostream.objects/wide.stream.objects/wclog.sync_with_stdio.sh.cpp
 create mode 100644 libcxx/test/std/input.output/iostream.objects/wide.stream.objects/wcout.sync_with_stdio.sh.cpp

diff --git a/libcxx/include/fstream b/libcxx/include/fstream
index 7b84bf6609086..23803909ff802 100644
--- a/libcxx/include/fstream
+++ b/libcxx/include/fstream
@@ -391,10 +391,12 @@ private:
 
   _LIBCPP_EXPORTED_FROM_ABI friend FILE* __get_ostream_file(ostream&);
 
+public:
   // There are multiple (__)open function, they use different C-API open
   // function. After that call these functions behave the same. This function
   // does that part and determines the final return value.
-  _LIBCPP_HIDE_FROM_ABI basic_filebuf* __do_open(FILE* __file, ios_base::openmode __mode) {
+  // This is also used for the various iostreams in sync_with_stdio(false) mode
+  _LIBCPP_HIDE_FROM_ABI basic_filebuf* __adopt_file(FILE* __file, ios_base::openmode __mode) {
     __file_ = __file;
     if (!__file_)
       return nullptr;
@@ -417,6 +419,7 @@ private:
     return this;
   }
 
+private:
   // If the file is already open, switch to unbuffered mode. Otherwise, record
   // the request to use unbuffered mode so that we use that mode when we
   // eventually open the file.
@@ -742,7 +745,7 @@ basic_filebuf<_CharT, _Traits>* basic_filebuf<_CharT, _Traits>::open(const char*
   if (!__mdstr)
     return nullptr;
 
-  return __do_open(std::fopen(__s, __mdstr), __mode);
+  return __adopt_file(std::fopen(__s, __mdstr), __mode);
 }
 
 template <class _CharT, class _Traits>
@@ -753,7 +756,7 @@ inline basic_filebuf<_CharT, _Traits>* basic_filebuf<_CharT, _Traits>::__open(in
   if (!__mdstr)
     return nullptr;
 
-  return __do_open(fdopen(__fd, __mdstr), __mode);
+  return __adopt_file(fdopen(__fd, __mdstr), __mode);
 }
 
 #    if _LIBCPP_HAS_OPEN_WITH_WCHAR
diff --git a/libcxx/src/ios.cpp b/libcxx/src/ios.cpp
index 2e049098740dc..0b728db5b76f9 100644
--- a/libcxx/src/ios.cpp
+++ b/libcxx/src/ios.cpp
@@ -366,13 +366,6 @@ void ios_base::__set_failbit_and_consider_rethrow() {
 #endif // _LIBCPP_HAS_EXCEPTIONS
 }
 
-bool ios_base::sync_with_stdio(bool sync) {
-  static bool previous_state = true;
-  bool r                     = previous_state;
-  previous_state             = sync;
-  return r;
-}
-
 _LIBCPP_END_EXPLICIT_ABI_ANNOTATIONS
 _LIBCPP_END_NAMESPACE_STD
 
diff --git a/libcxx/src/iostream.cpp b/libcxx/src/iostream.cpp
index 0393982361456..14835efa7a2e9 100644
--- a/libcxx/src/iostream.cpp
+++ b/libcxx/src/iostream.cpp
@@ -10,6 +10,7 @@
 
 #include <__memory/construct_at.h>
 #include <__ostream/basic_ostream.h>
+#include <fstream>
 #include <istream>
 
 #define ABI_NAMESPACE_STR _LIBCPP_TOSTRING(_LIBCPP_ABI_NAMESPACE)
@@ -34,21 +35,44 @@ union stream {
   StreamT value;
 };
 
-template <class StreamT, class BufferT>
+template <class StreamT, class SyncBufT, class UnsyncBufT>
 union stream_data {
   constexpr stream_data() {}
   constexpr ~stream_data() {}
   struct {
-    BufferT buffer;
+    union {
+      SyncBufT sync_buffer;
+      UnsyncBufT unsync_buffer;
+    };
     mbstate_t mb;
   };
 };
 
-template <class StreamT, class BufferT>
-void init_stream(FILE* stdstream, stream<StreamT>& stream, stream_data<StreamT, BufferT>& data) {
+template <class StreamT, class SyncBufT, class UnsyncBufT>
+void init_stream(FILE* stdstream, stream<StreamT>& stream, stream_data<StreamT, SyncBufT, UnsyncBufT>& data) {
   data.mb = {};
-  std::construct_at(&data.buffer, stdstream, &data.mb);
-  std::construct_at(&stream.value, &data.buffer);
+  std::construct_at(&data.sync_buffer, stdstream, &data.mb);
+  std::construct_at(&stream.value, &data.sync_buffer);
+}
+
+template <class StreamT, class SyncBufT, class UnsyncBufT>
+void switch_to_sync_stream(FILE* stdstream, stream<StreamT>& stream, stream_data<StreamT, SyncBufT, UnsyncBufT>& data) {
+  data.unsync_buffer.__adopt_file(nullptr, {}); // reset the file, so that basic_filebuf doesn't close standard streams
+  std::destroy_at(&data.unsync_buffer);
+  data.mb = {};
+  std::construct_at(&data.sync_buffer, stdstream, &data.mb);
+  stream.value.rdbuf(&data.sync_buffer);
+}
+
+template <class StreamT, class SyncBufT, class UnsyncBufT>
+void switch_to_unsync_stream(FILE* stdstream,
+                             stream<StreamT>& stream,
+                             stream_data<StreamT, SyncBufT, UnsyncBufT>& data,
+                             ios_base::openmode mode) {
+  std::destroy_at(&data.sync_buffer);
+  std::construct_at(&data.unsync_buffer);
+  data.unsync_buffer.__adopt_file(stdstream, mode);
+  stream.value.rdbuf(&data.unsync_buffer);
 }
 
 #define CHAR_MANGLING_char "D"
@@ -62,29 +86,29 @@ void init_stream(FILE* stdstream, stream<StreamT>& stream, stream_data<StreamT,
 #endif
 
 #ifdef _LIBCPP_ABI_MICROSOFT
-#  define STREAM(StreamT, BufferT, CharT, var)                                                                         \
-    STRING_DATA_CONSTINIT stream_data<StreamT<CharT>, BufferT<CharT>> var##_data;                                      \
+#  define STREAM(StreamT, SyncBufT, UnsyncBufT, CharT, var)                                                            \
+    STRING_DATA_CONSTINIT stream_data<StreamT<CharT>, SyncBufT<CharT>, UnsyncBufT<CharT>> var##_data;                  \
     _LIBCPP_EXPORTED_FROM_ABI STRING_DATA_CONSTINIT stream<StreamT<CharT>> var __asm__(                                \
         "?" #var "@" ABI_NAMESPACE_STR "@std@@3V?$" #StreamT                                                           \
         "@" CHAR_MANGLING(CharT) "U?$char_traits@" CHAR_MANGLING(CharT) "@" ABI_NAMESPACE_STR "@std@@@12 at A")
 #else
-#  define STREAM(StreamT, BufferT, CharT, var)                                                                         \
-    STRING_DATA_CONSTINIT stream_data<StreamT<CharT>, BufferT<CharT>> var##_data;                                      \
+#  define STREAM(StreamT, SyncBufT, UnsyncBufT, CharT, var)                                                            \
+    STRING_DATA_CONSTINIT stream_data<StreamT<CharT>, SyncBufT<CharT>, UnsyncBufT<CharT>> var##_data;                  \
     _LIBCPP_EXPORTED_FROM_ABI STRING_DATA_CONSTINIT stream<StreamT<CharT>> var
 #endif
 
 // These definitions and the declarations in <iostream> technically cause ODR violations, since they have different
 // types (stream_data and {i,o}stream respectively). This means that <iostream> should never be included in this TU.
 
-STREAM(basic_istream, __stdinbuf, char, cin);
-STREAM(basic_ostream, __stdoutbuf, char, cout);
-STREAM(basic_ostream, __stdoutbuf, char, cerr);
-STREAM(basic_ostream, __stdoutbuf, char, clog);
+STREAM(basic_istream, __stdinbuf, basic_filebuf, char, cin);
+STREAM(basic_ostream, __stdoutbuf, basic_filebuf, char, cout);
+STREAM(basic_ostream, __stdoutbuf, basic_filebuf, char, cerr);
+STREAM(basic_ostream, __stdoutbuf, basic_filebuf, char, clog);
 #if _LIBCPP_HAS_WIDE_CHARACTERS
-STREAM(basic_istream, __stdinbuf, wchar_t, wcin);
-STREAM(basic_ostream, __stdoutbuf, wchar_t, wcout);
-STREAM(basic_ostream, __stdoutbuf, wchar_t, wcerr);
-STREAM(basic_ostream, __stdoutbuf, wchar_t, wclog);
+STREAM(basic_istream, __stdinbuf, basic_filebuf, wchar_t, wcin);
+STREAM(basic_ostream, __stdoutbuf, basic_filebuf, wchar_t, wcout);
+STREAM(basic_ostream, __stdoutbuf, basic_filebuf, wchar_t, wcerr);
+STREAM(basic_ostream, __stdoutbuf, basic_filebuf, wchar_t, wclog);
 #endif // _LIBCPP_HAS_WIDE_CHARACTERS
 
 // Pretend we're inside a system header so the compiler doesn't flag the use of the init_priority
@@ -155,5 +179,31 @@ ios_base::Init::Init() {
 
 ios_base::Init::~Init() {}
 
+bool ios_base::sync_with_stdio(bool sync) {
+  static bool previous_state = true;
+  bool r                     = previous_state;
+
+  if (sync != previous_state) {
+    if (sync) {
+      switch_to_sync_stream(stdin, cin, cin_data);
+      switch_to_sync_stream(stdout, cout, cout_data);
+      switch_to_sync_stream(stderr, clog, clog_data);
+      switch_to_sync_stream(stdin, wcin, wcin_data);
+      switch_to_sync_stream(stdout, wcout, wcout_data);
+      switch_to_sync_stream(stderr, wclog, wclog_data);
+    } else {
+      switch_to_unsync_stream(stdin, cin, cin_data, ios::in);
+      switch_to_unsync_stream(stdout, cout, cout_data, ios::out);
+      switch_to_unsync_stream(stderr, clog, clog_data, ios::out);
+      switch_to_unsync_stream(stdin, wcin, wcin_data, ios::in);
+      switch_to_unsync_stream(stdout, wcout, wcout_data, ios::out);
+      switch_to_unsync_stream(stderr, wclog, wclog_data, ios::out);
+    }
+  }
+
+  previous_state = sync;
+  return r;
+}
+
 _LIBCPP_END_EXPLICIT_ABI_ANNOTATIONS
 _LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/test/std/input.output/iostream.objects/narrow.stream.objects/cin.sync_with_stdio.sh.cpp b/libcxx/test/std/input.output/iostream.objects/narrow.stream.objects/cin.sync_with_stdio.sh.cpp
new file mode 100644
index 0000000000000..052356449c5ab
--- /dev/null
+++ b/libcxx/test/std/input.output/iostream.objects/narrow.stream.objects/cin.sync_with_stdio.sh.cpp
@@ -0,0 +1,37 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// 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;
+
+// RUN: %{build}
+// RUN: echo -n 1234 > %t.input
+// RUN: %{exec} %t.exe < %t.input
+
+#include <cassert>
+#include <iostream>
+
+int main(int, char**) {
+  assert(std::ios::sync_with_stdio(false));
+  assert(!std::ios::sync_with_stdio(true));
+  assert(std::ios::sync_with_stdio(false));
+  int i;
+  std::cin >> i;
+  assert(i == 1234);
+  return 0;
+}
diff --git a/libcxx/test/std/input.output/iostream.objects/narrow.stream.objects/clog.sync_with_stdio.sh.cpp b/libcxx/test/std/input.output/iostream.objects/narrow.stream.objects/clog.sync_with_stdio.sh.cpp
new file mode 100644
index 0000000000000..ab30dee5f695d
--- /dev/null
+++ b/libcxx/test/std/input.output/iostream.objects/narrow.stream.objects/clog.sync_with_stdio.sh.cpp
@@ -0,0 +1,29 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// XFAIL: LIBCXX-PICOLIBC-FIXME
+
+// <iostream>
+
+// ostream clog;
+
+// RUN: %{build}
+// RUN: %{exec} %t.exe 2> %t.actual
+// RUN: echo -n 1234 > %t.expected
+// RUN: diff %t.expected %t.actual
+
+#include <cassert>
+#include <iostream>
+
+int main(int, char**) {
+  assert(std::ios::sync_with_stdio(false));
+  assert(!std::ios::sync_with_stdio(true));
+  assert(std::ios::sync_with_stdio(false));
+  std::clog << "1234";
+  return 0;
+}
diff --git a/libcxx/test/std/input.output/iostream.objects/narrow.stream.objects/cout.sync_with_stdio.sh.cpp b/libcxx/test/std/input.output/iostream.objects/narrow.stream.objects/cout.sync_with_stdio.sh.cpp
new file mode 100644
index 0000000000000..3a94964f09223
--- /dev/null
+++ b/libcxx/test/std/input.output/iostream.objects/narrow.stream.objects/cout.sync_with_stdio.sh.cpp
@@ -0,0 +1,27 @@
+//===----------------------------------------------------------------------===//
+//
+// 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>
+
+// ostream cout;
+
+// RUN: %{build}
+// RUN: %{exec} %t.exe > %t.actual
+// RUN: echo -n 1234 > %t.expected
+// RUN: diff %t.expected %t.actual
+
+#include <cassert>
+#include <iostream>
+
+int main(int, char**) {
+  assert(std::ios::sync_with_stdio(false));
+  assert(!std::ios::sync_with_stdio(true));
+  assert(std::ios::sync_with_stdio(false));
+  std::cout << "1234";
+  return 0;
+}
diff --git a/libcxx/test/std/input.output/iostream.objects/wide.stream.objects/wcin.sync_with_stdio.sh.cpp b/libcxx/test/std/input.output/iostream.objects/wide.stream.objects/wcin.sync_with_stdio.sh.cpp
new file mode 100644
index 0000000000000..3e388982785a2
--- /dev/null
+++ b/libcxx/test/std/input.output/iostream.objects/wide.stream.objects/wcin.sync_with_stdio.sh.cpp
@@ -0,0 +1,37 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// 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;
+
+// RUN: %{build}
+// RUN: echo -n 1234 > %t.input
+// RUN: %{exec} %t.exe < %t.input
+
+#include <cassert>
+#include <iostream>
+
+int main(int, char**) {
+  assert(std::ios::sync_with_stdio(false));
+  assert(!std::ios::sync_with_stdio(true));
+  assert(std::ios::sync_with_stdio(false));
+  int i;
+  std::wcin >> i;
+  assert(i == 1234);
+  return 0;
+}
diff --git a/libcxx/test/std/input.output/iostream.objects/wide.stream.objects/wclog.sync_with_stdio.sh.cpp b/libcxx/test/std/input.output/iostream.objects/wide.stream.objects/wclog.sync_with_stdio.sh.cpp
new file mode 100644
index 0000000000000..30f908546b23d
--- /dev/null
+++ b/libcxx/test/std/input.output/iostream.objects/wide.stream.objects/wclog.sync_with_stdio.sh.cpp
@@ -0,0 +1,29 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// XFAIL: LIBCXX-PICOLIBC-FIXME
+
+// <iostream>
+
+// ostream clog;
+
+// RUN: %{build}
+// RUN: %{exec} %t.exe 2> %t.actual
+// RUN: echo -n 1234 > %t.expected
+// RUN: diff %t.expected %t.actual
+
+#include <cassert>
+#include <iostream>
+
+int main(int, char**) {
+  assert(std::ios::sync_with_stdio(false));
+  assert(!std::ios::sync_with_stdio(true));
+  assert(std::ios::sync_with_stdio(false));
+  std::wclog << "1234";
+  return 0;
+}
diff --git a/libcxx/test/std/input.output/iostream.objects/wide.stream.objects/wcout.sync_with_stdio.sh.cpp b/libcxx/test/std/input.output/iostream.objects/wide.stream.objects/wcout.sync_with_stdio.sh.cpp
new file mode 100644
index 0000000000000..d3821d3d9ad8d
--- /dev/null
+++ b/libcxx/test/std/input.output/iostream.objects/wide.stream.objects/wcout.sync_with_stdio.sh.cpp
@@ -0,0 +1,27 @@
+//===----------------------------------------------------------------------===//
+//
+// 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>
+
+// ostream cout;
+
+// RUN: %{build}
+// RUN: %{exec} %t.exe > %t.actual
+// RUN: echo -n 1234 > %t.expected
+// RUN: diff %t.expected %t.actual
+
+#include <cassert>
+#include <iostream>
+
+int main(int, char**) {
+  assert(std::ios::sync_with_stdio(false));
+  assert(!std::ios::sync_with_stdio(true));
+  assert(std::ios::sync_with_stdio(false));
+  std::wcout << "1234";
+  return 0;
+}



More information about the libcxx-commits mailing list