[libc-commits] [libc] [llvm] [libc] Files now flush on exit (PR #230215)
Michael Jones via libc-commits
libc-commits at lists.llvm.org
Mon Oct 12 11:53:46 PDT 2026
https://github.com/michaelrj-google updated https://github.com/llvm/llvm-project/pull/230215
>From 2b3ffe41b55e66fbe350d769d503341ac1539bad Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Thu, 8 Oct 2026 17:04:06 +0000
Subject: [PATCH 1/3] [libc] Files now flush on exit
The C standard specifies that when the `exit` function is called, all
open files are flushed. This PR refactors fflush to be an internal
function so that it can be called on exit.
Fixes: #219219
Assisted-by: Automated tooling, human reviewed
---
libc/config/config.json | 6 +
libc/src/stdio/CMakeLists.txt | 39 +++++-
libc/src/stdio/baremetal/CMakeLists.txt | 11 +-
libc/src/stdio/baremetal/fflush.cpp | 22 ----
libc/src/stdio/baremetal/fflush_internal.cpp | 34 +++++
libc/src/stdio/fflush.cpp | 36 ++++++
libc/src/stdio/fflush_internal.h | 30 +++++
libc/src/stdio/generic/CMakeLists.txt | 39 +++---
.../{fflush.cpp => fflush_internal.cpp} | 44 +++----
libc/src/stdio/gpu/CMakeLists.txt | 11 +-
.../gpu/{fflush.cpp => fflush_internal.cpp} | 22 +++-
libc/src/stdlib/CMakeLists.txt | 7 ++
libc/src/stdlib/exit.cpp | 15 ++-
libc/test/src/stdlib/CMakeLists.txt | 29 ++++-
libc/test/src/stdlib/exit_test.cpp | 119 +++++++++++++++++-
.../llvm-project-overlay/libc/BUILD.bazel | 27 +++-
.../libc/libc_configure_options.bzl | 3 +
17 files changed, 407 insertions(+), 87 deletions(-)
delete mode 100644 libc/src/stdio/baremetal/fflush.cpp
create mode 100644 libc/src/stdio/baremetal/fflush_internal.cpp
create mode 100644 libc/src/stdio/fflush.cpp
create mode 100644 libc/src/stdio/fflush_internal.h
rename libc/src/stdio/generic/{fflush.cpp => fflush_internal.cpp} (69%)
rename libc/src/stdio/gpu/{fflush.cpp => fflush_internal.cpp} (53%)
diff --git a/libc/config/config.json b/libc/config/config.json
index 604b03c9b5688..6f611d60ff727 100644
--- a/libc/config/config.json
+++ b/libc/config/config.json
@@ -165,6 +165,12 @@
"doc": "Configures sorting algorithm for qsort and qsort_r. Values accepted are LIBC_QSORT_QUICK_SORT, LIBC_QSORT_HEAP_SORT."
}
},
+ "stdlib": {
+ "LIBC_CONF_EXIT_FLUSH_STREAMS": {
+ "value": true,
+ "doc": "Flush all open stdio streams when exit is called, as required by the C standard. Can be disabled in configurations without stdio streams (e.g. baremetal without files) so that exit does not depend on stdio."
+ }
+ },
"setjmp": {
"LIBC_CONF_SETJMP_AARCH64_RESTORE_PLATFORM_REGISTER": {
"value": true,
diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt
index a9dd34a79441e..c4838b77a039a 100644
--- a/libc/src/stdio/CMakeLists.txt
+++ b/libc/src/stdio/CMakeLists.txt
@@ -21,6 +21,11 @@ endfunction()
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
endif()
+
+add_stdio_entrypoint_object(stdin)
+add_stdio_entrypoint_object(stdout)
+add_stdio_entrypoint_object(stderr)
+
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/generic)
if(NOT LLVM_LIBC_FULL_BUILD)
@@ -28,6 +33,36 @@ if(NOT LLVM_LIBC_FULL_BUILD)
libc_add_definition(use_system_file "LIBC_COPT_STDIO_USE_SYSTEM_FILE")
endif()
+if(TARGET libc.src.stdio.${LIBC_TARGET_OS}.fflush_internal)
+ add_object_library(
+ fflush_internal
+ ALIAS
+ DEPENDS
+ .${LIBC_TARGET_OS}.fflush_internal
+ )
+elseif(TARGET libc.src.stdio.generic.fflush_internal)
+ add_object_library(
+ fflush_internal
+ ALIAS
+ DEPENDS
+ .generic.fflush_internal
+ )
+endif()
+
+add_entrypoint_object(
+ fflush
+ SRCS
+ fflush.cpp
+ HDRS
+ fflush.h
+ DEPENDS
+ libc.hdr.stdio_macros
+ libc.hdr.types.FILE
+ libc.src.__support.error_or
+ libc.src.__support.libc_errno
+ .fflush_internal
+)
+
add_entrypoint_object(
flockfile
SRCS
@@ -310,7 +345,6 @@ add_stdio_entrypoint_object(ftell)
add_stdio_entrypoint_object(fseeko)
add_stdio_entrypoint_object(fileno)
add_stdio_entrypoint_object(ftello)
-add_stdio_entrypoint_object(fflush)
add_stdio_entrypoint_object(clearerr)
add_stdio_entrypoint_object(clearerr_unlocked)
add_stdio_entrypoint_object(fopen)
@@ -338,9 +372,6 @@ add_stdio_entrypoint_object(fgets)
add_stdio_entrypoint_object(ungetc)
add_stdio_entrypoint_object(scanf)
add_stdio_entrypoint_object(fscanf)
-add_stdio_entrypoint_object(stdin)
-add_stdio_entrypoint_object(stdout)
-add_stdio_entrypoint_object(stderr)
add_stdio_entrypoint_object(vprintf)
add_stdio_entrypoint_object(vfprintf)
add_stdio_entrypoint_object(vscanf)
diff --git a/libc/src/stdio/baremetal/CMakeLists.txt b/libc/src/stdio/baremetal/CMakeLists.txt
index 844d275240b4d..4741b6d620e36 100644
--- a/libc/src/stdio/baremetal/CMakeLists.txt
+++ b/libc/src/stdio/baremetal/CMakeLists.txt
@@ -151,14 +151,15 @@ add_entrypoint_object(
libc.src.stdio.baremetal.fgetc
)
-add_entrypoint_object(
- fflush
+add_object_library(
+ fflush_internal
SRCS
- fflush.cpp
+ fflush_internal.cpp
HDRS
- ../fflush.h
+ ../fflush_internal.h
DEPENDS
- .file_internal
+ libc.hdr.types.FILE
+ libc.src.__support.error_or
)
add_entrypoint_object(
diff --git a/libc/src/stdio/baremetal/fflush.cpp b/libc/src/stdio/baremetal/fflush.cpp
deleted file mode 100644
index 4a39c25ae11c9..0000000000000
--- a/libc/src/stdio/baremetal/fflush.cpp
+++ /dev/null
@@ -1,22 +0,0 @@
-//===-- Implementation of fflush for baremetal -----------------*- C++ -*-===//
-//
-// 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 "src/stdio/fflush.h"
-
-#include "src/__support/common.h"
-
-namespace LIBC_NAMESPACE_DECL {
-
-// Baremetal uses unbuffered I/O, so there is nothing to flush.
-LLVM_LIBC_FUNCTION(int, fflush, (::FILE * stream)) {
- (void)stream;
- // TODO: Shall we have an embedding API for fflush?
- return 0;
-}
-
-} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/baremetal/fflush_internal.cpp b/libc/src/stdio/baremetal/fflush_internal.cpp
new file mode 100644
index 0000000000000..6f2e578e8cc7f
--- /dev/null
+++ b/libc/src/stdio/baremetal/fflush_internal.cpp
@@ -0,0 +1,34 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Baremetal implementation of the internal flushing helpers.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/stdio/fflush_internal.h"
+
+#include "hdr/types/FILE.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace internal {
+
+// Baremetal uses unbuffered I/O, so there is nothing to flush.
+// TODO: Shall we have an embedding API for fflush?
+
+ErrorOr<int> flush_stream(::FILE *stream) {
+ (void)stream;
+ return 0;
+}
+
+ErrorOr<int> flush_all_streams() { return 0; }
+
+} // namespace internal
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/fflush.cpp b/libc/src/stdio/fflush.cpp
new file mode 100644
index 0000000000000..4ee86b8acb55e
--- /dev/null
+++ b/libc/src/stdio/fflush.cpp
@@ -0,0 +1,36 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of fflush.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/stdio/fflush.h"
+
+#include "hdr/stdio_macros.h"
+#include "hdr/types/FILE.h"
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/stdio/fflush_internal.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, fflush, (::FILE * stream)) {
+ ErrorOr<int> result = stream == nullptr ? internal::flush_all_streams()
+ : internal::flush_stream(stream);
+ if (!result.has_value()) {
+ libc_errno = result.error();
+ return EOF;
+ }
+ return result.value();
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/fflush_internal.h b/libc/src/stdio/fflush_internal.h
new file mode 100644
index 0000000000000..dc6ca6a69c3f4
--- /dev/null
+++ b/libc/src/stdio/fflush_internal.h
@@ -0,0 +1,30 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Internal helpers for flushing streams, shared by fflush and exit.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_STDIO_FFLUSH_INTERNAL_H
+#define LLVM_LIBC_SRC_STDIO_FFLUSH_INTERNAL_H
+
+#include "hdr/types/FILE.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace internal {
+
+ErrorOr<int> flush_stream(::FILE *stream);
+ErrorOr<int> flush_all_streams();
+
+} // namespace internal
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDIO_FFLUSH_INTERNAL_H
diff --git a/libc/src/stdio/generic/CMakeLists.txt b/libc/src/stdio/generic/CMakeLists.txt
index 5f45d15ac8f26..b7df0381b41a8 100644
--- a/libc/src/stdio/generic/CMakeLists.txt
+++ b/libc/src/stdio/generic/CMakeLists.txt
@@ -94,22 +94,29 @@ add_generic_entrypoint_object(
libc.src.__support.File.platform_file
)
-add_generic_entrypoint_object(
- fflush
- SRCS
- fflush.cpp
- HDRS
- ../fflush.h
- DEPENDS
- libc.src.errno.errno
- libc.hdr.types.FILE
- libc.src.__support.File.file
- libc.src.__support.File.platform_file
- libc.src.stdio.stdin
- libc.src.stdio.stdout
- libc.src.stdio.stderr
-)
-
+# since this isn't an entrypoint we need to do these checks manually.
+if(NOT TARGET libc.src.stdio.${LIBC_TARGET_OS}.fflush_internal AND
+ TARGET libc.src.__support.File.file AND
+ TARGET libc.src.__support.File.platform_file AND
+ "libc.src.stdio.stdin" IN_LIST TARGET_LLVMLIBC_ENTRYPOINTS AND
+ "libc.src.stdio.stdout" IN_LIST TARGET_LLVMLIBC_ENTRYPOINTS AND
+ "libc.src.stdio.stderr" IN_LIST TARGET_LLVMLIBC_ENTRYPOINTS)
+ add_object_library(
+ fflush_internal
+ SRCS
+ fflush_internal.cpp
+ HDRS
+ ../fflush_internal.h
+ DEPENDS
+ libc.hdr.types.FILE
+ libc.src.__support.error_or
+ libc.src.__support.File.file
+ libc.src.__support.File.platform_file
+ libc.src.stdio.stdin
+ libc.src.stdio.stdout
+ libc.src.stdio.stderr
+ )
+endif()
add_generic_entrypoint_object(
fseek
diff --git a/libc/src/stdio/generic/fflush.cpp b/libc/src/stdio/generic/fflush_internal.cpp
similarity index 69%
rename from libc/src/stdio/generic/fflush.cpp
rename to libc/src/stdio/generic/fflush_internal.cpp
index 87888770937bf..a7c7ff8b84e0d 100644
--- a/libc/src/stdio/generic/fflush.cpp
+++ b/libc/src/stdio/generic/fflush_internal.cpp
@@ -1,36 +1,37 @@
-//===-- Implementation of fflush ------------------------------------------===//
+//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Generic implementation of the internal flushing helpers.
+///
+//===----------------------------------------------------------------------===//
-#include "src/stdio/fflush.h"
+#include "src/stdio/fflush_internal.h"
+
+#include "hdr/types/FILE.h"
#include "src/__support/File/file.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/config.h"
#include "src/stdio/stderr.h"
#include "src/stdio/stdin.h"
#include "src/stdio/stdout.h"
-#include "hdr/types/FILE.h"
-#include "src/__support/libc_errno.h"
-#include "src/__support/macros/config.h"
-
namespace LIBC_NAMESPACE_DECL {
+namespace internal {
-LLVM_LIBC_FUNCTION(int, fflush, (::FILE * stream)) {
- // If a non-null stream is specified, we only flush that single stream.
- if (stream != nullptr) {
- int result = reinterpret_cast<File *>(stream)->flush();
- if (result != 0) {
- libc_errno = result;
- return EOF;
- }
- return 0;
- }
+ErrorOr<int> flush_stream(::FILE *stream) {
+ int result = reinterpret_cast<File *>(stream)->flush();
+ if (result != 0)
+ return Error(result);
+ return 0;
+}
- // If the stream is null, we flush all open streams as per C and POSIX
- // requirements.
+ErrorOr<int> flush_all_streams() {
int total_error = 0;
// We explicitly flush the standard streams as they may not be part of the
@@ -56,11 +57,10 @@ LLVM_LIBC_FUNCTION(int, fflush, (::FILE * stream)) {
}
File::unlock_list();
- if (total_error != 0) {
- libc_errno = total_error;
- return EOF;
- }
+ if (total_error != 0)
+ return Error(total_error);
return 0;
}
+} // namespace internal
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/gpu/CMakeLists.txt b/libc/src/stdio/gpu/CMakeLists.txt
index b55b6d3ee3cbe..c264f932bb40e 100644
--- a/libc/src/stdio/gpu/CMakeLists.txt
+++ b/libc/src/stdio/gpu/CMakeLists.txt
@@ -100,14 +100,17 @@ add_entrypoint_object(
.gpu_file
)
-add_entrypoint_object(
- fflush
+add_object_library(
+ fflush_internal
SRCS
- fflush.cpp
+ fflush_internal.cpp
HDRS
- ../fflush.h
+ ../fflush_internal.h
DEPENDS
+ libc.hdr.stdint_proxy
libc.hdr.types.FILE
+ libc.src.__support.RPC.rpc_client
+ libc.src.__support.error_or
.gpu_file
)
diff --git a/libc/src/stdio/gpu/fflush.cpp b/libc/src/stdio/gpu/fflush_internal.cpp
similarity index 53%
rename from libc/src/stdio/gpu/fflush.cpp
rename to libc/src/stdio/gpu/fflush_internal.cpp
index 3032554cd11c0..993e5c99e4bab 100644
--- a/libc/src/stdio/gpu/fflush.cpp
+++ b/libc/src/stdio/gpu/fflush_internal.cpp
@@ -1,20 +1,29 @@
-//===-- Implementation of fflush ------------------------------------------===//
+//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
+///
+/// \file
+/// GPU implementation of the internal flushing helpers.
+///
+//===----------------------------------------------------------------------===//
-#include "src/stdio/fflush.h"
+#include "src/stdio/fflush_internal.h"
#include "file.h"
+#include "hdr/stdint_proxy.h"
#include "hdr/types/FILE.h"
-#include "src/__support/common.h"
+#include "src/__support/RPC/rpc_client.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/config.h"
namespace LIBC_NAMESPACE_DECL {
+namespace internal {
-LLVM_LIBC_FUNCTION(int, fflush, (::FILE * stream)) {
+static int flush_on_host(::FILE *stream) {
int ret;
rpc::Client::Port port = rpc::client.open<LIBC_FFLUSH>();
port.send_and_recv(
@@ -27,4 +36,9 @@ LLVM_LIBC_FUNCTION(int, fflush, (::FILE * stream)) {
return ret;
}
+ErrorOr<int> flush_stream(::FILE *stream) { return flush_on_host(stream); }
+
+ErrorOr<int> flush_all_streams() { return flush_on_host(nullptr); }
+
+} // namespace internal
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index 28f93be03d92b..5ae809409de0f 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -850,6 +850,13 @@ if(TARGET libc.src.__support.threads.thread)
libc_add_definition(exit_compile_options "LIBC_COPT_SUPPORT_THREADS")
endif()
+if(LIBC_CONF_EXIT_FLUSH_STREAMS AND TARGET libc.src.stdio.fflush_internal)
+ list(APPEND exit_deps
+ libc.src.stdio.fflush_internal
+ )
+ libc_add_definition(exit_compile_options "LIBC_COPT_EXIT_FLUSH_STREAMS")
+endif()
+
add_entrypoint_object(
exit
SRCS
diff --git a/libc/src/stdlib/exit.cpp b/libc/src/stdlib/exit.cpp
index ab43a52b04b9f..a479019e20e11 100644
--- a/libc/src/stdlib/exit.cpp
+++ b/libc/src/stdlib/exit.cpp
@@ -1,10 +1,15 @@
-//===-- Implementation of exit --------------------------------------------===//
+//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of exit.
+///
+//===----------------------------------------------------------------------===//
#include "src/stdlib/exit.h"
#include "src/__support/OSUtil/exit.h"
@@ -15,6 +20,10 @@
#include "src/__support/threads/thread.h"
#endif
+#ifdef LIBC_COPT_EXIT_FLUSH_STREAMS
+#include "src/stdio/fflush_internal.h"
+#endif
+
namespace LIBC_NAMESPACE_DECL {
extern "C" void __cxa_finalize(void *);
@@ -26,6 +35,10 @@ extern "C" void __cxa_finalize(void *);
internal::call_atexit_callbacks();
#endif
__cxa_finalize(nullptr);
+#ifdef LIBC_COPT_EXIT_FLUSH_STREAMS
+ // C23 7.24.4.4: streams are flushed after the atexit handlers have run.
+ internal::flush_all_streams();
+#endif
internal::exit(status);
}
diff --git a/libc/test/src/stdlib/CMakeLists.txt b/libc/test/src/stdlib/CMakeLists.txt
index bc705754b64a5..4b522cb3be59b 100644
--- a/libc/test/src/stdlib/CMakeLists.txt
+++ b/libc/test/src/stdlib/CMakeLists.txt
@@ -582,15 +582,40 @@ if(LLVM_LIBC_FULL_BUILD AND LIBC_TEST_SUBPROCESS_TESTS)
libc.src.stdlib._Exit
)
+ set(exit_test_copts "")
+ set(exit_test_deps
+ libc.src.__support.macros.config
+ libc.src.stdlib.exit
+ )
+ if(LIBC_CONF_EXIT_FLUSH_STREAMS AND TARGET libc.src.stdio.fflush_internal)
+ libc_add_definition(exit_test_copts "LIBC_COPT_EXIT_FLUSH_STREAMS")
+ list(APPEND exit_test_deps
+ libc.hdr.types.FILE
+ libc.hdr.types.size_t
+ libc.src.__support.CPP.scope
+ libc.src.stdio.fclose
+ libc.src.stdio.fopen
+ libc.src.stdio.fputs
+ libc.src.stdio.fread
+ libc.src.stdio.freopen
+ libc.src.stdio.remove
+ libc.src.stdio.stdout
+ libc.src.stdlib.atexit
+ libc.test.UnitTest.ErrnoCheckingTest
+ libc.test.UnitTest.ErrnoSetterMatcher
+ )
+ endif()
+
add_libc_test(
exit_test
SUITE
libc-stdlib-tests
SRCS
exit_test.cpp
+ COMPILE_OPTIONS
+ ${exit_test_copts}
DEPENDS
- libc.src.stdlib._Exit
- libc.src.stdlib.exit
+ ${exit_test_deps}
)
add_libc_test(
diff --git a/libc/test/src/stdlib/exit_test.cpp b/libc/test/src/stdlib/exit_test.cpp
index 5c82d8303036a..f9ab3ba38106a 100644
--- a/libc/test/src/stdlib/exit_test.cpp
+++ b/libc/test/src/stdlib/exit_test.cpp
@@ -1,15 +1,132 @@
-//===-- Unittests for exit -----------------------------------------------===//
+//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Unittests for exit.
+///
+//===----------------------------------------------------------------------===//
+#include "src/__support/macros/config.h"
#include "src/stdlib/exit.h"
#include "test/UnitTest/Test.h"
+#ifdef LIBC_COPT_EXIT_FLUSH_STREAMS
+#include "hdr/types/FILE.h"
+#include "hdr/types/size_t.h"
+#include "src/__support/CPP/scope.h"
+#include "src/stdio/fclose.h"
+#include "src/stdio/fopen.h"
+#include "src/stdio/fputs.h"
+#include "src/stdio/fread.h"
+#include "src/stdio/freopen.h"
+#include "src/stdio/remove.h"
+#include "src/stdio/stdout.h"
+#include "src/stdlib/atexit.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/ErrnoSetterMatcher.h"
+#endif
+
TEST(LlvmLibcStdlib, exit) {
EXPECT_EXITS([] { LIBC_NAMESPACE::exit(1); }, 1);
EXPECT_EXITS([] { LIBC_NAMESPACE::exit(65); }, 65);
}
+
+#ifdef LIBC_COPT_EXIT_FLUSH_STREAMS
+
+using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
+
+struct LlvmLibcExitTest : public LIBC_NAMESPACE::testing::ErrnoCheckingTest {
+ static constexpr size_t VERIFY_BUFFER_SIZE = 128;
+
+ void verify_file_content(const char *filepath, const char *expected) {
+ size_t len = 0;
+ while (expected[len] != '\0')
+ ++len;
+ ASSERT_LT(len, VERIFY_BUFFER_SIZE);
+
+ ::FILE *file = LIBC_NAMESPACE::fopen(filepath, "r");
+ ASSERT_FALSE(file == nullptr);
+ char read_buf[VERIFY_BUFFER_SIZE] = {0};
+ ASSERT_THAT(
+ LIBC_NAMESPACE::fread(read_buf, 1, VERIFY_BUFFER_SIZE - 1, file),
+ Succeeds(len));
+ ASSERT_STREQ(read_buf, expected);
+ ASSERT_THAT(LIBC_NAMESPACE::fclose(file), Succeeds(0));
+ }
+};
+
+TEST_F(LlvmLibcExitTest, FlushesOpenStreams) {
+ const auto FILENAME = libc_make_test_file_path("exit_flush.test");
+ const char *fn = FILENAME;
+ constexpr char CONTENT[] = "unflushed data";
+
+ auto test = [=] {
+ ::FILE *file = LIBC_NAMESPACE::fopen(fn, "w");
+ if (file == nullptr)
+ __builtin_trap();
+ if (LIBC_NAMESPACE::fputs(CONTENT, file) < 0)
+ __builtin_trap();
+ // Deliberately neither flushed nor closed: exit has to do it.
+ LIBC_NAMESPACE::exit(0);
+ };
+ EXPECT_EXITS(test, 0);
+ LIBC_NAMESPACE::cpp::scope_exit cleanup(
+ [&] { EXPECT_THAT(LIBC_NAMESPACE::remove(fn), Succeeds(0)); });
+
+ verify_file_content(fn, CONTENT);
+}
+
+// stdout is line buffered and not on the open-file list, so it gets its own
+// test; the content has no newline so that only exit can flush it.
+TEST_F(LlvmLibcExitTest, FlushesStdout) {
+ const auto FILENAME = libc_make_test_file_path("exit_flush_stdout.test");
+ const char *fn = FILENAME;
+ constexpr char CONTENT[] = "unflushed stdout data";
+
+ auto test = [=] {
+ if (LIBC_NAMESPACE::freopen(fn, "w", LIBC_NAMESPACE::stdout) == nullptr)
+ __builtin_trap();
+ if (LIBC_NAMESPACE::fputs(CONTENT, LIBC_NAMESPACE::stdout) < 0)
+ __builtin_trap();
+ LIBC_NAMESPACE::exit(0);
+ };
+ EXPECT_EXITS(test, 0);
+ LIBC_NAMESPACE::cpp::scope_exit cleanup(
+ [&] { EXPECT_THAT(LIBC_NAMESPACE::remove(fn), Succeeds(0)); });
+
+ verify_file_content(fn, CONTENT);
+}
+
+static ::FILE *atexit_file = nullptr;
+
+TEST_F(LlvmLibcExitTest, FlushesAfterAtexitHandlers) {
+ const auto FILENAME = libc_make_test_file_path("exit_flush_atexit.test");
+ const char *fn = FILENAME;
+
+ auto test = [=] {
+ atexit_file = LIBC_NAMESPACE::fopen(fn, "w");
+ if (atexit_file == nullptr)
+ __builtin_trap();
+ int status = LIBC_NAMESPACE::atexit(+[] {
+ if (LIBC_NAMESPACE::fputs("atexit", atexit_file) < 0)
+ __builtin_trap();
+ });
+ if (status != 0)
+ __builtin_trap();
+ if (LIBC_NAMESPACE::fputs("main ", atexit_file) < 0)
+ __builtin_trap();
+ LIBC_NAMESPACE::exit(0);
+ };
+ EXPECT_EXITS(test, 0);
+ LIBC_NAMESPACE::cpp::scope_exit cleanup(
+ [&] { EXPECT_THAT(LIBC_NAMESPACE::remove(fn), Succeeds(0)); });
+
+ verify_file_content(fn, "main atexit");
+}
+
+#endif // LIBC_COPT_EXIT_FLUSH_STREAMS
diff --git a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
index cbe1c92e9e6be..b0c4e52423d11 100644
--- a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
@@ -15543,6 +15543,7 @@ libc_function(
":__support_macros_config",
":__support_osutil_exit",
":__support_threads_thread_headers",
+ ":fflush_internal",
],
)
@@ -17482,20 +17483,34 @@ libc_support_library(
],
)
+libc_support_library(
+ name = "fflush_internal",
+ srcs = ["src/stdio/generic/fflush_internal.cpp"],
+ hdrs = ["src/stdio/fflush_internal.h"],
+ deps = [
+ ":__support_error_or",
+ ":__support_file_file",
+ ":__support_file_platform_file",
+ ":__support_macros_config",
+ ":stderr",
+ ":stdin",
+ ":stdout",
+ ":types_FILE",
+ ],
+)
+
libc_function(
name = "fflush",
- srcs = ["src/stdio/generic/fflush.cpp"],
+ srcs = ["src/stdio/fflush.cpp"],
hdrs = ["src/stdio/fflush.h"],
deps = [
":__support_common",
- ":__support_file_file",
- ":__support_file_platform_file",
+ ":__support_error_or",
":__support_libc_errno",
":__support_macros_config",
":errno",
- ":stderr",
- ":stdin",
- ":stdout",
+ ":fflush_internal",
+ ":hdr_stdio_macros",
":types_FILE",
],
)
diff --git a/utils/bazel/llvm-project-overlay/libc/libc_configure_options.bzl b/utils/bazel/llvm-project-overlay/libc/libc_configure_options.bzl
index 9626f49c6e159..092ce461e4937 100644
--- a/utils/bazel/llvm-project-overlay/libc/libc_configure_options.bzl
+++ b/utils/bazel/llvm-project-overlay/libc/libc_configure_options.bzl
@@ -69,6 +69,9 @@ LIBC_CONFIGURE_FULL_BUILD_OPTIONS = [
# Defined for targets which support threads.
"LIBC_COPT_SUPPORT_THREADS",
+
+ # Documentation in libc/config/config.json (LIBC_CONF_EXIT_FLUSH_STREAMS).
+ "LIBC_COPT_EXIT_FLUSH_STREAMS",
]
LIBC_CONFIGURE_OPTIONS = LIBC_CONFIGURE_COMMON_OPTIONS + select({
>From da69302d5925c4be747f3cf7be934d72924bdfaf Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Thu, 8 Oct 2026 21:06:56 +0000
Subject: [PATCH 2/3] address comments
---
libc/src/stdio/gpu/fflush_internal.cpp | 15 ++++++++++++---
libc/test/src/stdlib/CMakeLists.txt | 1 +
libc/test/src/stdlib/exit_test.cpp | 20 ++++++++++----------
3 files changed, 23 insertions(+), 13 deletions(-)
diff --git a/libc/src/stdio/gpu/fflush_internal.cpp b/libc/src/stdio/gpu/fflush_internal.cpp
index 993e5c99e4bab..91fdb53cd472d 100644
--- a/libc/src/stdio/gpu/fflush_internal.cpp
+++ b/libc/src/stdio/gpu/fflush_internal.cpp
@@ -36,9 +36,18 @@ static int flush_on_host(::FILE *stream) {
return ret;
}
-ErrorOr<int> flush_stream(::FILE *stream) { return flush_on_host(stream); }
-
-ErrorOr<int> flush_all_streams() { return flush_on_host(nullptr); }
+ErrorOr<int> flush_stream(::FILE *stream) {
+ int ret = flush_on_host(stream);
+ if (ret != 0)
+ return Error(ret);
+ return 0;
+}
+ErrorOr<int> flush_all_streams() {
+ int ret = flush_on_host(nullptr);
+ if (ret != 0)
+ return Error(ret);
+ return 0;
+}
} // namespace internal
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/stdlib/CMakeLists.txt b/libc/test/src/stdlib/CMakeLists.txt
index 4b522cb3be59b..69ec4cf575b57 100644
--- a/libc/test/src/stdlib/CMakeLists.txt
+++ b/libc/test/src/stdlib/CMakeLists.txt
@@ -593,6 +593,7 @@ if(LLVM_LIBC_FULL_BUILD AND LIBC_TEST_SUBPROCESS_TESTS)
libc.hdr.types.FILE
libc.hdr.types.size_t
libc.src.__support.CPP.scope
+ libc.src.__support.CPP.string_view
libc.src.stdio.fclose
libc.src.stdio.fopen
libc.src.stdio.fputs
diff --git a/libc/test/src/stdlib/exit_test.cpp b/libc/test/src/stdlib/exit_test.cpp
index f9ab3ba38106a..34c8da60bce6e 100644
--- a/libc/test/src/stdlib/exit_test.cpp
+++ b/libc/test/src/stdlib/exit_test.cpp
@@ -19,6 +19,7 @@
#include "hdr/types/FILE.h"
#include "hdr/types/size_t.h"
#include "src/__support/CPP/scope.h"
+#include "src/__support/CPP/string_view.h"
#include "src/stdio/fclose.h"
#include "src/stdio/fopen.h"
#include "src/stdio/fputs.h"
@@ -43,20 +44,19 @@ using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
struct LlvmLibcExitTest : public LIBC_NAMESPACE::testing::ErrnoCheckingTest {
static constexpr size_t VERIFY_BUFFER_SIZE = 128;
- void verify_file_content(const char *filepath, const char *expected) {
- size_t len = 0;
- while (expected[len] != '\0')
- ++len;
- ASSERT_LT(len, VERIFY_BUFFER_SIZE);
+ void verify_file_content(const char *filepath,
+ LIBC_NAMESPACE::cpp::string_view expected) {
+ ASSERT_LT(expected.size(), VERIFY_BUFFER_SIZE);
::FILE *file = LIBC_NAMESPACE::fopen(filepath, "r");
- ASSERT_FALSE(file == nullptr);
- char read_buf[VERIFY_BUFFER_SIZE] = {0};
+ ASSERT_NE(file, nullptr);
+ LIBC_NAMESPACE::cpp::scope_exit close_file(
+ [&] { EXPECT_THAT(LIBC_NAMESPACE::fclose(file), Succeeds(0)); });
+ char read_buf[VERIFY_BUFFER_SIZE] = {};
ASSERT_THAT(
LIBC_NAMESPACE::fread(read_buf, 1, VERIFY_BUFFER_SIZE - 1, file),
- Succeeds(len));
- ASSERT_STREQ(read_buf, expected);
- ASSERT_THAT(LIBC_NAMESPACE::fclose(file), Succeeds(0));
+ Succeeds(expected.size()));
+ ASSERT_EQ(LIBC_NAMESPACE::cpp::string_view(read_buf), expected);
}
};
>From 32c2279a426c834ca03d66d33f4b8a414c14bc9c Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Mon, 12 Oct 2026 18:53:13 +0000
Subject: [PATCH 3/3] remove unnecessary config option
---
libc/config/config.json | 6 ------
libc/src/stdlib/CMakeLists.txt | 2 +-
libc/test/src/stdlib/CMakeLists.txt | 2 +-
.../llvm-project-overlay/libc/libc_configure_options.bzl | 3 ---
4 files changed, 2 insertions(+), 11 deletions(-)
diff --git a/libc/config/config.json b/libc/config/config.json
index 6f611d60ff727..604b03c9b5688 100644
--- a/libc/config/config.json
+++ b/libc/config/config.json
@@ -165,12 +165,6 @@
"doc": "Configures sorting algorithm for qsort and qsort_r. Values accepted are LIBC_QSORT_QUICK_SORT, LIBC_QSORT_HEAP_SORT."
}
},
- "stdlib": {
- "LIBC_CONF_EXIT_FLUSH_STREAMS": {
- "value": true,
- "doc": "Flush all open stdio streams when exit is called, as required by the C standard. Can be disabled in configurations without stdio streams (e.g. baremetal without files) so that exit does not depend on stdio."
- }
- },
"setjmp": {
"LIBC_CONF_SETJMP_AARCH64_RESTORE_PLATFORM_REGISTER": {
"value": true,
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index 5ae809409de0f..382a52204fcca 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -850,7 +850,7 @@ if(TARGET libc.src.__support.threads.thread)
libc_add_definition(exit_compile_options "LIBC_COPT_SUPPORT_THREADS")
endif()
-if(LIBC_CONF_EXIT_FLUSH_STREAMS AND TARGET libc.src.stdio.fflush_internal)
+if("libc.src.stdio.fflush" IN_LIST TARGET_LLVMLIBC_ENTRYPOINTS)
list(APPEND exit_deps
libc.src.stdio.fflush_internal
)
diff --git a/libc/test/src/stdlib/CMakeLists.txt b/libc/test/src/stdlib/CMakeLists.txt
index 69ec4cf575b57..5a634f8505cb8 100644
--- a/libc/test/src/stdlib/CMakeLists.txt
+++ b/libc/test/src/stdlib/CMakeLists.txt
@@ -587,7 +587,7 @@ if(LLVM_LIBC_FULL_BUILD AND LIBC_TEST_SUBPROCESS_TESTS)
libc.src.__support.macros.config
libc.src.stdlib.exit
)
- if(LIBC_CONF_EXIT_FLUSH_STREAMS AND TARGET libc.src.stdio.fflush_internal)
+ if("libc.src.stdio.fflush" IN_LIST TARGET_LLVMLIBC_ENTRYPOINTS)
libc_add_definition(exit_test_copts "LIBC_COPT_EXIT_FLUSH_STREAMS")
list(APPEND exit_test_deps
libc.hdr.types.FILE
diff --git a/utils/bazel/llvm-project-overlay/libc/libc_configure_options.bzl b/utils/bazel/llvm-project-overlay/libc/libc_configure_options.bzl
index 092ce461e4937..9626f49c6e159 100644
--- a/utils/bazel/llvm-project-overlay/libc/libc_configure_options.bzl
+++ b/utils/bazel/llvm-project-overlay/libc/libc_configure_options.bzl
@@ -69,9 +69,6 @@ LIBC_CONFIGURE_FULL_BUILD_OPTIONS = [
# Defined for targets which support threads.
"LIBC_COPT_SUPPORT_THREADS",
-
- # Documentation in libc/config/config.json (LIBC_CONF_EXIT_FLUSH_STREAMS).
- "LIBC_COPT_EXIT_FLUSH_STREAMS",
]
LIBC_CONFIGURE_OPTIONS = LIBC_CONFIGURE_COMMON_OPTIONS + select({
More information about the libc-commits
mailing list