[libcxx-commits] [libcxx] 3f65f71 - [libc++][print] Adds FILE functions.

Mark de Wever via libcxx-commits libcxx-commits at lists.llvm.org
Tue Jul 18 11:07:19 PDT 2023


Author: Mark de Wever
Date: 2023-07-18T20:07:11+02:00
New Revision: 3f65f718332c96c7ececcd9cde84be740076a4f1

URL: https://github.com/llvm/llvm-project/commit/3f65f718332c96c7ececcd9cde84be740076a4f1
DIFF: https://github.com/llvm/llvm-project/commit/3f65f718332c96c7ececcd9cde84be740076a4f1.diff

LOG: [libc++][print] Adds FILE functions.

Drive-by fix to make sure the __retarget_buffer works correctly whan
using a hint of 1. This was discovered in one of the new tests.

Drive-by fixes __retarget_buffer when initialized with size 1.

Implements parts of
- P2093R14 Formatted output
- P2539R4  Should the output of std::print to a terminal be
           synchronized with the underlying stream?

Reviewed By: #libc, ldionne

Differential Revision: https://reviews.llvm.org/D150044

Added: 
    libcxx/src/print.cpp
    libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_posix.pass.cpp
    libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_windows.pass.cpp
    libcxx/test/std/input.output/iostream.format/print.fun/no_file_description.pass.cpp
    libcxx/test/std/input.output/iostream.format/print.fun/print.file.pass.cpp
    libcxx/test/std/input.output/iostream.format/print.fun/print_tests.h
    libcxx/test/std/input.output/iostream.format/print.fun/println.file.pass.cpp
    libcxx/test/std/input.output/iostream.format/print.fun/vprint_nonunicode.file.pass.cpp
    libcxx/test/std/input.output/iostream.format/print.fun/vprint_unicode.file.pass.cpp
    libcxx/test/std/language.support/support.limits/support.limits.general/print.version.compile.pass.cpp

Modified: 
    libcxx/docs/FeatureTestMacroTable.rst
    libcxx/docs/Status/Cxx23Papers.csv
    libcxx/docs/Status/FormatIssues.csv
    libcxx/docs/Status/FormatPaper.csv
    libcxx/include/__format/buffer.h
    libcxx/include/print
    libcxx/include/version
    libcxx/modules/std/print.cppm
    libcxx/src/CMakeLists.txt
    libcxx/test/libcxx/transitive_includes/cxx03.csv
    libcxx/test/libcxx/transitive_includes/cxx11.csv
    libcxx/test/libcxx/transitive_includes/cxx14.csv
    libcxx/test/libcxx/transitive_includes/cxx17.csv
    libcxx/test/libcxx/transitive_includes/cxx20.csv
    libcxx/test/libcxx/transitive_includes/cxx23.csv
    libcxx/test/libcxx/transitive_includes/cxx26.csv
    libcxx/test/std/language.support/support.limits/support.limits.general/ostream.version.compile.pass.cpp
    libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
    libcxx/test/std/utilities/format/format.functions/fill.unicode.pass.cpp
    libcxx/utils/ci/run-buildbot
    libcxx/utils/generate_feature_test_macro_components.py
    libcxx/utils/libcxx/test/header_information.py

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index deb3b3dacebd1d..14a251ad5fef62 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -340,6 +340,8 @@ Status
     --------------------------------------------------- -----------------
     ``__cpp_lib_out_ptr``                               *unimplemented*
     --------------------------------------------------- -----------------
+    ``__cpp_lib_print``                                 *unimplemented*
+    --------------------------------------------------- -----------------
     ``__cpp_lib_ranges_as_rvalue``                      ``202207L``
     --------------------------------------------------- -----------------
     ``__cpp_lib_ranges_chunk``                          *unimplemented*

diff  --git a/libcxx/docs/Status/Cxx23Papers.csv b/libcxx/docs/Status/Cxx23Papers.csv
index b8bbe6909864b4..c8a8c9f4d7f76d 100644
--- a/libcxx/docs/Status/Cxx23Papers.csv
+++ b/libcxx/docs/Status/Cxx23Papers.csv
@@ -59,7 +59,7 @@
 "`P1467R9 <https://wg21.link/P1467R9>`__","LWG","Extended ``floating-point`` types and standard names","July 2022","",""
 "`P1642R11 <https://wg21.link/P1642R11>`__","LWG","Freestanding ``[utilities]``, ``[ranges]``, and ``[iterators]``","July 2022","",""
 "`P1899R3 <https://wg21.link/P1899R3>`__","LWG","``stride_view``","July 2022","","","|ranges|"
-"`P2093R14 <https://wg21.link/P2093R14>`__","LWG","Formatted output","July 2022","",""
+"`P2093R14 <https://wg21.link/P2093R14>`__","LWG","Formatted output","July 2022","","|In Progress|"
 "`P2165R4 <https://wg21.link/P2165R4>`__","LWG","Compatibility between ``tuple``, ``pair`` and ``tuple-like`` objects","July 2022","",""
 "`P2278R4 <https://wg21.link/P2278R4>`__","LWG","``cbegin`` should always return a constant iterator","July 2022","","","|ranges|"
 "`P2286R8 <https://wg21.link/P2286R8>`__","LWG","Formatting Ranges","July 2022","|Complete|","16.0","|format| |ranges|"
@@ -101,7 +101,7 @@
 "`P2167R3 <https://wg21.link/P2167R3>`__","LWG", "Improved Proposed Wording for LWG 2114", "November 2022","","",""
 "`P2396R1 <https://wg21.link/P2396R1>`__","LWG", "Concurrency TS 2 fixes ", "November 2022","","","|concurrency TS|"
 "`P2505R5 <https://wg21.link/P2505R5>`__","LWG", "Monadic Functions for ``std::expected``", "November 2022","|Complete|","17.0",""
-"`P2539R4 <https://wg21.link/P2539R4>`__","LWG", "Should the output of ``std::print`` to a terminal be synchronized with the underlying stream?", "November 2022","","","|format|"
+"`P2539R4 <https://wg21.link/P2539R4>`__","LWG", "Should the output of ``std::print`` to a terminal be synchronized with the underlying stream?", "November 2022","|In Progress|","","|format|"
 "`P2602R2 <https://wg21.link/P2602R2>`__","LWG", "Poison Pills are Too Toxic", "November 2022","","","|ranges|"
 "`P2708R1 <https://wg21.link/P2708R1>`__","LWG", "No Further Fundamentals TSes", "November 2022","|Nothing to do|","",""
 "","","","","","",""

diff  --git a/libcxx/docs/Status/FormatIssues.csv b/libcxx/docs/Status/FormatIssues.csv
index 5fc53ece784316..a6cde93f23ee74 100644
--- a/libcxx/docs/Status/FormatIssues.csv
+++ b/libcxx/docs/Status/FormatIssues.csv
@@ -5,16 +5,16 @@ Number,Name,Standard,Assignee,Status,First released version
 `P1868 <https://wg21.link/P1868>`_,"width: clarifying units of width and precision in std::format (Implements the unicode support.)","C++20",Mark de Wever,|Complete|,14.0
 `P2216 <https://wg21.link/P2216>`_,"std::format improvements","C++20",Mark de Wever,|Complete|,15.0
 `P2418 <https://wg21.link/P2418>`__,"Add support for ``std::generator``-like types to ``std::format``","C++20",Mark de Wever,|Complete|,15.0
-"`P2093R14 <https://wg21.link/P2093R14>`__","Formatted output","C++23",Mark de Wever,|In Progress|,
+"`P2093R14 <https://wg21.link/P2093R14>`__","Formatted output","C++23",Mark de Wever,|In Progress|
 "`P2286R8 <https://wg21.link/P2286R8>`__","Formatting Ranges","C++23","Mark de Wever","|Complete|",16.0
 "`P2508R1 <https://wg21.link/P2508R1>`__","Exposing ``std::basic-format-string``","C++23","Mark de Wever","|Complete|",15.0
 "`P2585R0 <https://wg21.link/P2585R0>`__","Improving default container formatting","C++23","Mark de Wever","|Complete|",17.0
-"`P2539R4 <https://wg21.link/P2539R4>`__","Should the output of ``std::print`` to a terminal be synchronized with the underlying stream?","C++23","Mark de Wever"
+"`P2539R4 <https://wg21.link/P2539R4>`__","Should the output of ``std::print`` to a terminal be synchronized with the underlying stream?","C++23","Mark de Wever","|In Progress|"
 "`P2713R1 <https://wg21.link/P2713R1>`__","Escaping improvements in ``std::format``","C++23","Mark de Wever",""
 "`P2675R1 <https://wg21.link/P2675R1>`__","``format``'s width estimation is too approximate and not forward compatible","C++23","Mark de Wever","|Complete|",17.0
 "`P2572R1 <https://wg21.link/P2572R1>`__","``std::format`` fill character allowances","C++23","Mark de Wever","|Complete|",17.0
-"`P2693R1 <https://wg21.link/P2693R1>`__","Formatting ``thread::id`` and ``stacktrace``","C++23","Mark de Wever","|In progress|"
-"`P2510R3 <https://wg21.link/P2510R3>`__","Formatting pointers","C++26","Mark de Wever","|Complete|", 17.0
+"`P2693R1 <https://wg21.link/P2693R1>`__","Formatting ``thread::id`` and ``stacktrace``","C++23","Mark de Wever","|In Progress|"
+"`P2510R3 <https://wg21.link/P2510R3>`__","Formatting pointers","C++26","Mark de Wever","|Complete|",17.0
 "`P2757R3 <https://wg21.link/P2757R3>`__","Type-checking format args","C++26","","",
 "`P2637R3 <https://wg21.link/P2637R3>`__","Member ``visit``","C++26","","",
 `P1361 <https://wg21.link/P1361>`_,"Integration of chrono with text formatting","C++20",Mark de Wever,|In Progress|,

diff  --git a/libcxx/docs/Status/FormatPaper.csv b/libcxx/docs/Status/FormatPaper.csv
index 9fbf2bed558745..4d44db81b806a1 100644
--- a/libcxx/docs/Status/FormatPaper.csv
+++ b/libcxx/docs/Status/FormatPaper.csv
@@ -48,5 +48,5 @@ Section,Description,Dependencies,Assignee,Status,First released version
 
 "`P2093R14 <https://wg21.link/P2093R14>`__","Formatted output"
 `[print.fun] <https://wg21.link/print.fun>`__,"Output to ``stdout``",,Mark de Wever,|In Progress|,
-`[print.fun] <https://wg21.link/print.fun>`__,"Output to ``FILE*``",,Mark de Wever,,
+`[print.fun] <https://wg21.link/print.fun>`__,"Output to ``FILE*``",,Mark de Wever,|Complete|, 17.0
 `[ostream.formatted.print] <https://wg21.link/ostream.formatted.print>`__,"Output to ``ostream``",,Mark de Wever

diff  --git a/libcxx/include/__format/buffer.h b/libcxx/include/__format/buffer.h
index f43fd13d6a9cae..45f9da801722cc 100644
--- a/libcxx/include/__format/buffer.h
+++ b/libcxx/include/__format/buffer.h
@@ -529,6 +529,7 @@ class _LIBCPP_TEMPLATE_VIS __retarget_buffer {
 
   struct __iterator {
     using 
diff erence_type = ptr
diff _t;
+    using value_type      = _CharT;
 
     _LIBCPP_HIDE_FROM_ABI constexpr explicit __iterator(__retarget_buffer& __buffer)
         : __buffer_(std::addressof(__buffer)) {}
@@ -551,7 +552,14 @@ class _LIBCPP_TEMPLATE_VIS __retarget_buffer {
   __retarget_buffer& operator=(const __retarget_buffer&) = delete;
 
   _LIBCPP_HIDE_FROM_ABI explicit __retarget_buffer(size_t __size_hint) {
-    auto __result = std::__allocate_at_least(__alloc_, __size_hint ? __size_hint : 256 / sizeof(_CharT));
+    // When the initial size is very small a lot of resizes happen
+    // when elements added. So use a hard-coded minimum size.
+    //
+    // Note a size < 2 will not work
+    // - 0 there is no buffer, while push_back requires 1 empty element.
+    // - 1 multiplied by the grow factor is 1 and thus the buffer never
+    //   grows.
+    auto __result = std::__allocate_at_least(__alloc_, std::max(__size_hint, 256 / sizeof(_CharT)));
     __ptr_        = __result.ptr;
     __capacity_   = __result.count;
   }

diff  --git a/libcxx/include/print b/libcxx/include/print
index 6fcd53c5db65a6..97f0047aee54be 100644
--- a/libcxx/include/print
+++ b/libcxx/include/print
@@ -10,19 +10,67 @@
 #ifndef _LIBCPP_PRINT
 #define _LIBCPP_PRINT
 
+/*
+namespace std {
+  // [print.fun], print functions
+  template<class... Args>
+    void print(FILE* stream, format_string<Args...> fmt, Args&&... args);
+
+  template<class... Args>
+    void println(FILE* stream, format_string<Args...> fmt, Args&&... args);
+
+  void vprint_unicode(FILE* stream, string_view fmt, format_args args);
+
+  void vprint_nonunicode(FILE* stream, string_view fmt, format_args args);
+}
+*/
+
 #include <__assert> // all public C++ headers provide the assertion handler
 #include <__concepts/same_as.h>
 #include <__config>
+#include <__format/buffer.h>
+#include <__format/format_arg_store.h>
+#include <__format/format_args.h>
+#include <__format/format_context.h>
+#include <__format/format_error.h>
+#include <__format/format_functions.h>
 #include <__format/unicode.h>
+#include <__system_error/system_error.h>
+#include <__utility/forward.h>
+#include <cerrno>
+#include <cstdio>
+#include <string>
 #include <string_view>
 #include <version>
 
+#if __has_include(<unistd.h>)
+#  include <unistd.h>
+#endif
+
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
 #endif
 
 _LIBCPP_BEGIN_NAMESPACE_STD
 
+#ifdef _WIN32
+_LIBCPP_EXPORTED_FROM_ABI bool __is_windows_terminal(FILE* __stream);
+
+#  ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+// A wrapper for WriteConsoleW which is used to write to the Windows
+// console. This function is in the dylib to avoid pulling in windows.h
+// in the library headers. The function itself uses some private parts
+// of the dylib too.
+//
+// The function does not depend on the language standard used. Guarding
+// it with C++23 would fail since the dylib is currently built using C++20.
+//
+// Note the function is only implemented on the Windows platform.
+_LIBCPP_EXPORTED_FROM_ABI void __write_to_windows_console(FILE* __stream, wstring_view __view);
+#  endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS
+
+#endif // _WIN32
+
 #if _LIBCPP_STD_VER >= 23
 
 #  ifndef _LIBCPP_HAS_NO_UNICODE
@@ -68,7 +116,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr void __encode(_OutIt&, char32_t) = delete;
 template <class _OutIt>
   requires __utf16_code_unit<iter_value_t<_OutIt>>
 _LIBCPP_HIDE_FROM_ABI constexpr void __encode(_OutIt& __out_it, char32_t __value) {
-  _LIBCPP_ASSERT(__is_scalar_value(__value), "an invalid unicode scalar value results in invalid UTF-16");
+  _LIBCPP_ASSERT_UNCATEGORIZED(__is_scalar_value(__value), "an invalid unicode scalar value results in invalid UTF-16");
 
   if (__value < 0x10000) {
     *__out_it++ = __value;
@@ -83,7 +131,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr void __encode(_OutIt& __out_it, char32_t __value
 template <class _OutIt>
   requires __utf32_code_unit<iter_value_t<_OutIt>>
 _LIBCPP_HIDE_FROM_ABI constexpr void __encode(_OutIt& __out_it, char32_t __value) {
-  _LIBCPP_ASSERT(__is_scalar_value(__value), "an invalid unicode scalar value results in invalid UTF-32");
+  _LIBCPP_ASSERT_UNCATEGORIZED(__is_scalar_value(__value), "an invalid unicode scalar value results in invalid UTF-32");
   *__out_it++ = __value;
 }
 
@@ -101,6 +149,9 @@ _LIBCPP_HIDE_FROM_ABI constexpr _OutIt __transcode(_InIt __first, _InIt __last,
   // Note if P2728 is accepted types like int may become valid. In that case
   // the __code_point_view should use a span. Libc++ will remove support for
   // char_traits<int>.
+
+  // TODO PRINT Validate with clang-tidy
+  // NOLINTNEXTLINE(bugprone-dangling-handle)
   basic_string_view<iter_value_t<_InIt>> __data{__first, __last};
   __code_point_view<iter_value_t<_InIt>> __view{__data.begin(), __data.end()};
   while (!__view.__at_end())
@@ -112,6 +163,198 @@ _LIBCPP_HIDE_FROM_ABI constexpr _OutIt __transcode(_InIt __first, _InIt __last,
 
 #  endif //  _LIBCPP_HAS_NO_UNICODE
 
+namespace __print {
+
+// [print.fun]/2
+//   Effects: If the ordinary literal encoding ([lex.charset]) is UTF-8, equivalent to:
+//     vprint_unicode(stream, fmt.str, make_format_args(args...));
+//   Otherwise, equivalent to:
+//     vprint_nonunicode(stream, fmt.str, make_format_args(args...));
+//
+// Based on the compiler and its compilation flags this value is or is
+// not true. As mentioned in P2093R14 this only affects Windows. The
+// test below could also be done for
+// - GCC using __GNUC_EXECUTION_CHARSET_NAME
+//   https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html
+// - Clang using __clang_literal_encoding__
+//   https://clang.llvm.org/docs/LanguageExtensions.html#builtin-macros
+//   (note at the time of writing Clang is hard-coded to UTF-8.)
+//
+
+#  ifdef _LIBCPP_HAS_NO_UNICODE
+inline constexpr bool __use_unicode = false;
+#  elif defined(_MSVC_EXECUTION_CHARACTER_SET)
+// This is the same test MSVC STL uses in their implementation of <print>
+// See: https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers
+inline constexpr bool __use_unicode = _MSVC_EXECUTION_CHARACTER_SET == 65001;
+#  else
+inline constexpr bool __use_unicode = true;
+#  endif
+
+_LIBCPP_HIDE_FROM_ABI inline bool __is_terminal(FILE* __stream) {
+#  ifdef _WIN32
+  return std::__is_windows_terminal(__stream);
+#  elif __has_include(<unistd.h>)
+  return isatty(fileno(__stream));
+#  else
+#    error "Provide a way to determine whether a FILE* is a terminal"
+#  endif
+}
+
+template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
+_LIBCPP_HIDE_FROM_ABI inline void
+__vprint_nonunicode(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl) {
+  _LIBCPP_ASSERT_UNCATEGORIZED(__stream, "__stream is a valid pointer to an output C stream");
+  string __str = std::vformat(__fmt, __args);
+  if (__write_nl)
+    __str.push_back('\n');
+
+  size_t __size = fwrite(__str.data(), 1, __str.size(), __stream);
+  if (__size < __str.size()) {
+    if (std::feof(__stream))
+      std::__throw_system_error(EIO, "EOF while writing the formatted output");
+    std::__throw_system_error(std::ferror(__stream), "failed to write formatted output");
+  }
+}
+
+#  ifndef _LIBCPP_HAS_NO_UNICODE
+
+// Note these helper functions are mainly used to aid testing.
+// On POSIX systems and Windows the output is no longer considered a
+// terminal when the output is redirected. Typically during testing the
+// output is redirected to be able to capture it. This makes it hard to
+// test this code path.
+template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
+_LIBCPP_HIDE_FROM_ABI inline void
+__vprint_unicode_posix(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) {
+  // TODO PRINT Should flush errors throw too?
+  if (__is_terminal)
+    std::fflush(__stream);
+
+  __print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl);
+}
+
+#    ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
+_LIBCPP_HIDE_FROM_ABI inline void
+__vprint_unicode_windows(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) {
+  if (!__is_terminal)
+    return __print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl);
+
+  // TODO PRINT Should flush errors throw too?
+  std::fflush(__stream);
+
+  string __str = std::vformat(__fmt, __args);
+  // UTF-16 uses the same number or less code units than UTF-8.
+  // However the size of the code unit is 16 bits instead of 8 bits.
+  //
+  // The buffer uses the worst-case estimate and should never resize.
+  // However when the string is large this could lead to OOM. Using a
+  // smaller size might work, but since the buffer uses a grow factor
+  // the final size might be larger when the estimate is wrong.
+  //
+  // TODO PRINT profile and improve the speed of this code.
+  __format::__retarget_buffer<wchar_t> __buffer{__str.size()};
+  __unicode::__transcode(__str.begin(), __str.end(), __buffer.__make_output_iterator());
+  if (__write_nl)
+    __buffer.push_back(L'\n');
+
+  [[maybe_unused]] wstring_view __view = __buffer.__view();
+
+  // The macro _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION is used to change
+  // the behavior in the test. This is not part of the public API.
+#      ifdef _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION
+  _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION(__stream, __view);
+#      elif defined(_WIN32)
+  std::__write_to_windows_console(__stream, __view);
+#      else
+  std::__throw_runtime_error("No defintion of _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION and "
+                             "__write_to_windows_console is not available.");
+#      endif
+}
+#    endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS
+
+template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
+_LIBCPP_HIDE_FROM_ABI inline void
+__vprint_unicode([[maybe_unused]] FILE* __stream,
+                 [[maybe_unused]] string_view __fmt,
+                 [[maybe_unused]] format_args __args,
+                 [[maybe_unused]] bool __write_nl) {
+  _LIBCPP_ASSERT_UNCATEGORIZED(__stream, "__stream is a valid pointer to an output C stream");
+
+  // [print.fun]
+  //   7 - Effects: If stream refers to a terminal capable of displaying
+  //       Unicode, writes out to the terminal using the native Unicode
+  //       API; if out contains invalid code units, the behavior is
+  //       undefined and implementations are encouraged to diagnose it.
+  //       Otherwise writes out to stream unchanged. If the native
+  //       Unicode API is used, the function flushes stream before
+  //       writing out.
+  //   8 - Throws: Any exception thrown by the call to vformat
+  //       ([format.err.report]). system_error if writing to the terminal
+  //       or stream fails. May throw bad_alloc.
+  //   9 - Recommended practice: If invoking the native Unicode API
+  //       requires transcoding, implementations should substitute
+  //       invalid code units with U+FFFD replacement character per the
+  //       Unicode Standard, Chapter 3.9 U+FFFD Substitution in
+  //       Conversion.
+
+  // On non-Windows platforms the Unicode API is the normal file I/O API
+  // so there the call can be forwarded to the non_unicode API. On
+  // Windows there is a 
diff erent API. This API requires transcoding.
+
+#    ifndef _WIN32
+  __print::__vprint_unicode_posix(__stream, __fmt, __args, __write_nl, __print::__is_terminal(__stream));
+#    elif !defined(_LIBCPP_HAS_NO_WIDE_CHARACTERS)
+  __print::__vprint_unicode_windows(__stream, __fmt, __args, __write_nl, __print::__is_terminal(__stream));
+#    else
+#      error "Windows builds with wchar_t disabled are not supported."
+#    endif
+}
+
+#  endif // _LIBCPP_HAS_NO_UNICODE
+
+} // namespace __print
+
+template <class... _Args>
+_LIBCPP_HIDE_FROM_ABI void print(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args) {
+#  ifndef _LIBCPP_HAS_NO_UNICODE
+  if constexpr (__print::__use_unicode)
+    __print::__vprint_unicode(__stream, __fmt.get(), std::make_format_args(__args...), false);
+  else
+    __print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), false);
+#  else  // _LIBCPP_HAS_NO_UNICODE
+  __print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), false);
+#  endif // _LIBCPP_HAS_NO_UNICODE
+}
+
+template <class... _Args>
+_LIBCPP_HIDE_FROM_ABI void println(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args) {
+#  ifndef _LIBCPP_HAS_NO_UNICODE
+  // Note the wording in the Standard is inefficient. The output of
+  // std::format is a std::string which is then copied. This solution
+  // just appends a newline at the end of the output.
+  if constexpr (__print::__use_unicode)
+    __print::__vprint_unicode(__stream, __fmt.get(), std::make_format_args(__args...), true);
+  else
+    __print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), true);
+#  else  // _LIBCPP_HAS_NO_UNICODE
+  __print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), true);
+#  endif // _LIBCPP_HAS_NO_UNICODE
+}
+
+#  ifndef _LIBCPP_HAS_NO_UNICODE
+template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
+_LIBCPP_HIDE_FROM_ABI inline void vprint_unicode(FILE* __stream, string_view __fmt, format_args __args) {
+  __print::__vprint_unicode(__stream, __fmt, __args, false);
+}
+#  endif // _LIBCPP_HAS_NO_UNICODE
+
+template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
+_LIBCPP_HIDE_FROM_ABI inline void vprint_nonunicode(FILE* __stream, string_view __fmt, format_args __args) {
+  __print::__vprint_nonunicode(__stream, __fmt, __args, false);
+}
+
 #endif // _LIBCPP_STD_VER >= 23
 
 _LIBCPP_END_NAMESPACE_STD

diff  --git a/libcxx/include/version b/libcxx/include/version
index 4091a491d9fadc..6f588c82b45b77 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -149,6 +149,7 @@ __cpp_lib_optional                                      202110L <optional>
 __cpp_lib_out_ptr                                       202106L <memory>
 __cpp_lib_parallel_algorithm                            201603L <algorithm> <numeric>
 __cpp_lib_polymorphic_allocator                         201902L <memory_resource>
+__cpp_lib_print                                         202207L <ostream> <print>
 __cpp_lib_quoted_string_io                              201304L <iomanip>
 __cpp_lib_ranges                                        202207L <algorithm> <functional> <iterator>
                                                                 <memory> <ranges>
@@ -429,6 +430,7 @@ __cpp_lib_within_lifetime                               202306L <type_traits>
 # undef  __cpp_lib_optional
 # define __cpp_lib_optional                             202110L
 // # define __cpp_lib_out_ptr                              202106L
+// # define __cpp_lib_print                                202207L
 # define __cpp_lib_ranges_as_rvalue                     202207L
 // # define __cpp_lib_ranges_chunk                         202202L
 // # define __cpp_lib_ranges_chunk_by                      202202L

diff  --git a/libcxx/modules/std/print.cppm b/libcxx/modules/std/print.cppm
index 21de1cd7019be5..9d089a6a5fa686 100644
--- a/libcxx/modules/std/print.cppm
+++ b/libcxx/modules/std/print.cppm
@@ -12,12 +12,10 @@ module;
 
 export module std:print;
 export namespace std {
-#if 0
   // [print.fun], print functions
   using std::print;
   using std::println;
 
   using std::vprint_nonunicode;
   using std::vprint_unicode;
-#endif
 } // namespace std

diff  --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt
index 9091311aef44bb..35b4665270960d 100644
--- a/libcxx/src/CMakeLists.txt
+++ b/libcxx/src/CMakeLists.txt
@@ -41,6 +41,7 @@ set(LIBCXX_SOURCES
   new_handler.cpp
   new_helpers.cpp
   optional.cpp
+  print.cpp
   random_shuffle.cpp
   ryu/d2fixed.cpp
   ryu/d2s.cpp

diff  --git a/libcxx/src/print.cpp b/libcxx/src/print.cpp
new file mode 100644
index 00000000000000..3d8a4c291ee53c
--- /dev/null
+++ b/libcxx/src/print.cpp
@@ -0,0 +1,62 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 <__config>
+#include <cstdlib>
+#include <print>
+
+#if defined(_LIBCPP_WIN32API)
+#  define WIN32_LEAN_AND_MEAN
+#  define NOMINMAX
+#  include <io.h>
+#  include <windows.h>
+
+#  include <__system_error/system_error.h>
+
+#  include "filesystem/error.h"
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#ifdef _WIN32
+_LIBCPP_EXPORTED_FROM_ABI bool __is_windows_terminal(FILE* __stream) {
+  // Note the Standard does this in one call, but it's unclear whether
+  // an invalid handle is allowed when calling GetConsoleMode.
+  //
+  // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/get-osfhandle?view=msvc-170
+  // https://learn.microsoft.com/en-us/windows/console/getconsolemode
+  intptr_t __handle = _get_osfhandle(fileno(__stream));
+  if (__handle == -1)
+    return false;
+
+  unsigned long __mode;
+  return GetConsoleMode(reinterpret_cast<void*>(__handle), &__mode);
+}
+
+#  ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+_LIBCPP_EXPORTED_FROM_ABI void
+__write_to_windows_console([[maybe_unused]] FILE* __stream, [[maybe_unused]] wstring_view __view) {
+  // https://learn.microsoft.com/en-us/windows/console/writeconsole
+  if (WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fileno(__stream))), // clang-format aid
+                    __view.data(),
+                    __view.size(),
+                    nullptr,
+                    nullptr) == 0) {
+#    ifndef _LIBCPP_HAS_NO_EXCEPTIONS
+    // There is no __throw_system_error overload that takes an error code.
+    throw system_error{filesystem::detail::make_windows_error(GetLastError()), "failed to write formatted output"};
+#    else  // _LIBCPP_HAS_NO_EXCEPTIONS
+    std::abort();
+#    endif // _LIBCPP_HAS_NO_EXCEPTIONS
+  }
+}
+#  endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS
+
+#endif // _WIN32
+
+_LIBCPP_END_NAMESPACE_STD

diff  --git a/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_posix.pass.cpp b/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_posix.pass.cpp
new file mode 100644
index 00000000000000..4817111e4b285b
--- /dev/null
+++ b/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_posix.pass.cpp
@@ -0,0 +1,73 @@
+//===----------------------------------------------------------------------===//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME
+
+// XFAIL: availability-fp_to_chars-missing
+
+// REQUIRES: has-unix-headers
+
+// <print>
+
+// Tests the implementation of
+//   void __print::__vprint_unicode_posix(FILE* __stream, string_view __fmt,
+//                                        format_args __args, bool __write_nl,
+//                                        bool __is_terminal);
+//
+// In the library when the stdout is redirected to a file it is no
+// longer considered a terminal and the special terminal handling is no
+// longer executed. By testing this function we can "force" emulate a
+// terminal.
+// Note __write_nl is tested by the public API.
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <cstdio>
+#include <print>
+
+#include "test_macros.h"
+
+int main(int, char**) {
+  std::array<char, 100> buffer;
+  std::ranges::fill(buffer, '*');
+
+  FILE* file = fmemopen(buffer.data(), buffer.size(), "wb");
+  assert(file);
+
+  // Test the file is buffered.
+  std::fprintf(file, "Hello");
+  assert(std::ftell(file) == 5);
+#if defined(TEST_HAS_GLIBC) &&                                                                                         \
+    !(__has_feature(address_sanitizer) || __has_feature(thread_sanitizer) || __has_feature(memory_sanitizer))
+  assert(std::ranges::all_of(buffer, [](char c) { return c == '*'; }));
+#endif
+
+  // Test writing to a "non-terminal" stream does not flush.
+  std::__print::__vprint_unicode_posix(file, " world", std::make_format_args(), false, false);
+  assert(std::ftell(file) == 11);
+#if defined(TEST_HAS_GLIBC) &&                                                                                         \
+    !(__has_feature(address_sanitizer) || __has_feature(thread_sanitizer) || __has_feature(memory_sanitizer))
+  assert(std::ranges::all_of(buffer, [](char c) { return c == '*'; }));
+#endif
+
+  // Test writing to a "terminal" stream flushes before writing.
+  std::__print::__vprint_unicode_posix(file, "!", std::make_format_args(), false, true);
+  assert(std::ftell(file) == 12);
+  assert(std::string_view(buffer.data(), buffer.data() + 11) == "Hello world");
+#if defined(TEST_HAS_GLIBC)
+  // glibc does not flush after a write.
+  assert(buffer[11] != '!');
+#endif
+
+  // Test everything is written when closing the stream.
+  std::fclose(file);
+  assert(std::string_view(buffer.data(), buffer.data() + 12) == "Hello world!");
+
+  return 0;
+}

diff  --git a/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_windows.pass.cpp b/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_windows.pass.cpp
new file mode 100644
index 00000000000000..f85609f0da5965
--- /dev/null
+++ b/libcxx/test/libcxx/input.output/iostream.format/print.fun/vprint_unicode_windows.pass.cpp
@@ -0,0 +1,133 @@
+//===----------------------------------------------------------------------===//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: no-filesystem
+// UNSUPPORTED: no-wide-characters
+// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME
+
+// Clang modules do not work with the definiton of _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION
+// XFAIL: modules-build
+
+// XFAIL: availability-fp_to_chars-missing
+
+// <print>
+
+// Tests the implementation of
+//   void __print::__vprint_unicode_windows(FILE* __stream, string_view __fmt,
+//                                          format_args __args, bool __write_nl,
+//                                          bool __is_terminal);
+//
+// In the library when the stdout is redirected to a file it is no
+// longer considered a terminal and the special terminal handling is no
+// longer executed. By testing this function we can "force" emulate a
+// terminal.
+// Note __write_nl is tested by the public API.
+
+#include <string_view>
+#include <cstdio>
+#include <algorithm>
+#include <cassert>
+
+void write_to_console(FILE*, std::wstring_view data);
+#define _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION ::write_to_console
+#include <print>
+
+#include "test_macros.h"
+#include "filesystem_test_helper.h"
+#include "make_string.h"
+
+TEST_GCC_DIAGNOSTIC_IGNORED("-Wuse-after-free")
+
+#define SV(S) MAKE_STRING_VIEW(wchar_t, S)
+
+bool calling               = false;
+std::wstring_view expected = L" world";
+
+void write_to_console(FILE*, std::wstring_view data) {
+  assert(calling);
+  assert(data == expected);
+}
+
+scoped_test_env env;
+std::string filename = env.create_file("output.txt");
+
+static void test_basics() {
+  FILE* file = std::fopen(filename.c_str(), "wb");
+  assert(file);
+
+  // Test writing to a "non-terminal" stream does not call WriteConsoleW.
+  std::__print::__vprint_unicode_windows(file, "Hello", std::make_format_args(), false, false);
+  assert(std::ftell(file) == 5);
+
+  // It's not possible to reliably test whether writing to a "terminal" stream
+  // flushes before writing. Testing flushing a closed stream worked on some
+  // platforms, but was unreliable.
+  calling = true;
+  std::__print::__vprint_unicode_windows(file, " world", std::make_format_args(), false, true);
+}
+
+// When the output is a file the data is written as-is.
+// When the output is a "terminal" invalid UTF-8 input is flagged.
+static void test(std::wstring_view output, std::string_view input) {
+  // *** File ***
+  FILE* file = std::fopen(filename.c_str(), "wb");
+  assert(file);
+
+  std::__print::__vprint_unicode_windows(file, input, std::make_format_args(), false, false);
+  assert(std::ftell(file) == static_cast<long>(input.size()));
+  std::fclose(file);
+
+  file = std::fopen(filename.c_str(), "rb");
+  assert(file);
+
+  std::vector<char> buffer(input.size());
+  size_t read = fread(buffer.data(), 1, buffer.size(), file);
+  assert(read == input.size());
+  assert(input == std::string_view(buffer.begin(), buffer.end()));
+  std::fclose(file);
+
+  // *** Terminal ***
+  expected = output;
+  std::__print::__vprint_unicode_windows(file, input, std::make_format_args(), false, true);
+}
+
+static void test() {
+  // *** Test valid UTF-8 ***
+#define TEST(S) test(SV(S), S)
+  TEST("hello world");
+
+  // copied from benchmarks/std_format_spec_string_unicode.bench.cpp
+  TEST("Lorem ipsum dolor sit amet, ne sensibus evertitur aliquando his. Iuvaret fabulas qui ex.");
+  TEST("Lōrem ipsūm dolor sīt æmeÞ, ea vel nostrud feuġǣit, muciūs tēmporiȝusrefērrēnÞur no mel.");
+  TEST("Лорем ипсум долор сит амет, еу диам тамяуам принципес вис, еяуидем цонцептам диспутандо");
+  TEST("入ト年媛ろ舗学ラロ準募ケカ社金スノ屋検れう策他セヲシ引口ぎ集7独ぱクふ出車ぽでぱ円輪ルノ受打わ。");
+  TEST("\U0001f636\u200d\U0001f32b\ufe0f");
+#undef TEST
+
+  // *** Test invalid utf-8 ***
+  test(SV("\ufffd"), "\xc3");
+  test(SV("\ufffd("), "\xc3\x28");
+
+  // surrogate range
+  test(SV("\ufffd"), "\xed\xa0\x80"); // U+D800
+  test(SV("\ufffd"), "\xed\xaf\xbf"); // U+DBFF
+  test(SV("\ufffd"), "\xed\xbf\x80"); // U+DC00
+  test(SV("\ufffd"), "\xed\xbf\xbf"); // U+DFFF
+
+  // beyond valid values
+  test(SV("\ufffd"), "\xf4\x90\x80\x80"); // U+110000
+  test(SV("\ufffd"), "\xf4\xbf\xbf\xbf"); // U+11FFFF
+
+  // Validates  http://unicode.org/review/pr-121.html option 3.
+  test(SV("\u0061\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0062"), "\x61\xf1\x80\x80\xe1\x80\xc2\x62");
+}
+
+int main(int, char**) {
+  test_basics();
+  test();
+}

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx03.csv b/libcxx/test/libcxx/transitive_includes/cxx03.csv
index 7a6ba4f3b0f3cd..53e4aa4cd7342a 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx03.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx03.csv
@@ -634,11 +634,22 @@ ostream string
 ostream type_traits
 ostream typeinfo
 ostream version
+print array
+print cerrno
+print cmath
 print cstddef
 print cstdint
+print cstdio
+print cstdlib
 print initializer_list
 print limits
+print locale
+print new
+print optional
+print stdexcept
+print string
 print string_view
+print tuple
 print version
 queue compare
 queue concepts

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx11.csv b/libcxx/test/libcxx/transitive_includes/cxx11.csv
index cfce8c074265de..29b323b35b6917 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx11.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx11.csv
@@ -635,11 +635,22 @@ ostream string
 ostream type_traits
 ostream typeinfo
 ostream version
+print array
+print cerrno
+print cmath
 print cstddef
 print cstdint
+print cstdio
+print cstdlib
 print initializer_list
 print limits
+print locale
+print new
+print optional
+print stdexcept
+print string
 print string_view
+print tuple
 print version
 queue compare
 queue concepts

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx14.csv b/libcxx/test/libcxx/transitive_includes/cxx14.csv
index 7c6e8c947394d0..1a03dd9285c7a7 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx14.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx14.csv
@@ -637,11 +637,22 @@ ostream string
 ostream type_traits
 ostream typeinfo
 ostream version
+print array
+print cerrno
+print cmath
 print cstddef
 print cstdint
+print cstdio
+print cstdlib
 print initializer_list
 print limits
+print locale
+print new
+print optional
+print stdexcept
+print string
 print string_view
+print tuple
 print version
 queue compare
 queue concepts

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx17.csv b/libcxx/test/libcxx/transitive_includes/cxx17.csv
index 7c6e8c947394d0..1a03dd9285c7a7 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx17.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx17.csv
@@ -637,11 +637,22 @@ ostream string
 ostream type_traits
 ostream typeinfo
 ostream version
+print array
+print cerrno
+print cmath
 print cstddef
 print cstdint
+print cstdio
+print cstdlib
 print initializer_list
 print limits
+print locale
+print new
+print optional
+print stdexcept
+print string
 print string_view
+print tuple
 print version
 queue compare
 queue concepts

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx20.csv b/libcxx/test/libcxx/transitive_includes/cxx20.csv
index 603d7892b38b0a..55860c75f572de 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx20.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv
@@ -643,11 +643,22 @@ ostream string
 ostream type_traits
 ostream typeinfo
 ostream version
+print array
+print cerrno
+print cmath
 print cstddef
 print cstdint
+print cstdio
+print cstdlib
 print initializer_list
 print limits
+print locale
+print new
+print optional
+print stdexcept
+print string
 print string_view
+print tuple
 print version
 queue compare
 queue concepts

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx23.csv b/libcxx/test/libcxx/transitive_includes/cxx23.csv
index c1cd8e97fc48d8..3fa2d9445f7b8f 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx23.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv
@@ -452,11 +452,22 @@ ostream streambuf
 ostream string
 ostream typeinfo
 ostream version
+print array
+print cerrno
+print cmath
 print cstddef
 print cstdint
+print cstdio
+print cstdlib
 print initializer_list
 print limits
+print locale
+print new
+print optional
+print stdexcept
+print string
 print string_view
+print tuple
 print version
 queue compare
 queue cstddef

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv
index c1cd8e97fc48d8..3fa2d9445f7b8f 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx26.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv
@@ -452,11 +452,22 @@ ostream streambuf
 ostream string
 ostream typeinfo
 ostream version
+print array
+print cerrno
+print cmath
 print cstddef
 print cstdint
+print cstdio
+print cstdlib
 print initializer_list
 print limits
+print locale
+print new
+print optional
+print stdexcept
+print string
 print string_view
+print tuple
 print version
 queue compare
 queue cstddef

diff  --git a/libcxx/test/std/input.output/iostream.format/print.fun/no_file_description.pass.cpp b/libcxx/test/std/input.output/iostream.format/print.fun/no_file_description.pass.cpp
new file mode 100644
index 00000000000000..d1593c429f2824
--- /dev/null
+++ b/libcxx/test/std/input.output/iostream.format/print.fun/no_file_description.pass.cpp
@@ -0,0 +1,90 @@
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME
+
+// XFAIL: msvc, target={{.+}}-windows-gnu
+// XFAIL: availability-fp_to_chars-missing
+
+// <print>
+
+// The FILE returned by fmemopen does not have file descriptor.
+// This means the test could fail when the implementation uses a
+// function that requires a file descriptor, for example write.
+//
+// This tests all print functions which takes a FILE* as argument.
+
+// template<class... Args>
+//   void print(FILE* stream, format_string<Args...> fmt, Args&&... args);
+// template<class... Args>
+//   void println(FILE* stream, format_string<Args...> fmt, Args&&... args);
+// void vprint_unicode(FILE* stream, string_view fmt, format_args args);
+// void vprint_nonunicode(FILE* stream, string_view fmt, format_args args);
+
+#include <array>
+#include <cstdio>
+#include <cassert>
+#include <print>
+
+static void test_print() {
+  std::array<char, 100> buffer{0};
+
+  FILE* file = fmemopen(buffer.data(), buffer.size(), "wb");
+  assert(file);
+
+  std::print(file, "hello world{}", '!');
+  long pos = std::ftell(file);
+  std::fclose(file);
+
+  assert(pos > 0);
+  assert(std::string_view(buffer.data(), pos) == "hello world!");
+}
+
+static void test_println() {
+  std::array<char, 100> buffer{0};
+
+  FILE* file = fmemopen(buffer.data(), buffer.size(), "wb");
+  assert(file);
+
+  std::println(file, "hello world{}", '!');
+  long pos = std::ftell(file);
+  std::fclose(file);
+
+  assert(pos > 0);
+  assert(std::string_view(buffer.data(), pos) == "hello world!\n");
+}
+
+static void test_vprint_unicode() {
+  std::array<char, 100> buffer{0};
+
+  FILE* file = fmemopen(buffer.data(), buffer.size(), "wb");
+  assert(file);
+
+  std::vprint_unicode(file, "hello world{}", std::make_format_args('!'));
+  long pos = std::ftell(file);
+  std::fclose(file);
+
+  assert(pos > 0);
+  assert(std::string_view(buffer.data(), pos) == "hello world!");
+}
+
+static void test_vprint_nonunicode() {
+  std::array<char, 100> buffer{0};
+
+  FILE* file = fmemopen(buffer.data(), buffer.size(), "wb");
+  assert(file);
+
+  std::vprint_nonunicode(file, "hello world{}", std::make_format_args('!'));
+  long pos = std::ftell(file);
+  std::fclose(file);
+
+  assert(pos > 0);
+  assert(std::string_view(buffer.data(), pos) == "hello world!");
+}
+
+int main(int, char**) {
+  test_print();
+  test_println();
+  test_vprint_unicode();
+  test_vprint_nonunicode();
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/iostream.format/print.fun/print.file.pass.cpp b/libcxx/test/std/input.output/iostream.format/print.fun/print.file.pass.cpp
new file mode 100644
index 00000000000000..9fc2fcc4ca36f4
--- /dev/null
+++ b/libcxx/test/std/input.output/iostream.format/print.fun/print.file.pass.cpp
@@ -0,0 +1,139 @@
+//===----------------------------------------------------------------------===//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: no-filesystem
+// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME
+
+// XFAIL: availability-fp_to_chars-missing
+
+// <print>
+
+// template<class... Args>
+//   void print(FILE* stream, format_string<Args...> fmt, Args&&... args);
+
+// In the library when the stdout is redirected to a file it is no
+// longer considered a terminal and the special terminal handling is no
+// longer executed. There are tests in
+//   libcxx/test/libcxx/input.output/iostream.format/print.fun/
+// to validate that behaviour
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <cstddef>
+#include <cstdio>
+#include <fstream>
+#include <iterator>
+#include <print>
+#include <string_view>
+
+#include "assert_macros.h"
+#include "concat_macros.h"
+#include "filesystem_test_helper.h"
+#include "print_tests.h"
+#include "test_format_string.h"
+#include "test_macros.h"
+
+scoped_test_env env;
+std::string filename = env.create_file("output.txt");
+
+auto test_file = []<class... Args>(std::string_view expected, test_format_string<char, Args...> fmt, Args&&... args) {
+  FILE* file = fopen(filename.c_str(), "wb");
+  assert(file);
+
+  std::print(file, fmt, std::forward<Args>(args)...);
+  std::fclose(file);
+
+  std::ifstream stream{filename.c_str(), std::ios_base::in | std::ios_base::binary};
+  std::string out(std::istreambuf_iterator<char>{stream}, {});
+  TEST_REQUIRE(out == expected,
+               TEST_WRITE_CONCATENATED(
+                   "\nFormat string   ", fmt.get(), "\nExpected output ", expected, "\nActual output   ", out, '\n'));
+};
+
+auto test_exception = []<class... Args>(std::string_view, std::string_view, Args&&...) {
+  // After P2216 most exceptions thrown by std::format become ill-formed.
+  // Therefore this tests does nothing.
+  // A basic ill-formed test is done in format.verify.cpp
+  // The exceptions are tested by other functions that don't use the basic-format-string as fmt argument.
+};
+
+// Glibc fails writing to a wide stream.
+#if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS)
+static void test_wide_stream() {
+  FILE* file = fopen(filename.c_str(), "wb");
+  assert(file);
+
+  int mode = std::fwide(file, 1);
+  assert(mode > 0);
+
+  TEST_VALIDATE_EXCEPTION(
+      std::system_error,
+      [&]([[maybe_unused]] const std::system_error& e) {
+        [[maybe_unused]] std::string_view what{"failed to write formatted output"};
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception   ", e.what(), '\n'));
+      },
+      std::print(file, "hello"));
+}
+#endif // defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS)
+
+static void test_read_only() {
+  FILE* file = fopen(filename.c_str(), "r");
+  assert(file);
+
+  TEST_VALIDATE_EXCEPTION(
+      std::system_error,
+      [&]([[maybe_unused]] const std::system_error& e) {
+#ifdef _AIX
+        [[maybe_unused]] std::string_view what{"failed to write formatted output: Broken pipe"};
+#else
+        [[maybe_unused]] std::string_view what{"failed to write formatted output: Operation not permitted"};
+#endif
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception   ", e.what(), '\n'));
+      },
+      std::print(file, "hello"));
+}
+
+static void test_new_line() {
+  // Text does newline translation.
+  {
+    FILE* file = fopen(filename.c_str(), "w");
+    assert(file);
+
+    std::print(file, "\n");
+#ifndef _WIN32
+    assert(std::ftell(file) == 1);
+#else
+    assert(std::ftell(file) == 2);
+#endif
+  }
+  // Binary no newline translation.
+  {
+    FILE* file = fopen(filename.c_str(), "wb");
+    assert(file);
+
+    std::print(file, "\n");
+    assert(std::ftell(file) == 1);
+  }
+}
+
+int main(int, char**) {
+  print_tests(test_file, test_exception);
+
+#if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS)
+  test_wide_stream();
+#endif
+  test_read_only();
+  test_new_line();
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/iostream.format/print.fun/print_tests.h b/libcxx/test/std/input.output/iostream.format/print.fun/print_tests.h
new file mode 100644
index 00000000000000..46b6eebab6a961
--- /dev/null
+++ b/libcxx/test/std/input.output/iostream.format/print.fun/print_tests.h
@@ -0,0 +1,83 @@
+//===----------------------------------------------------------------------===//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef TEST_STD_INPUT_OUTPUT_IOSTREAM_FORMAT_PRINT_FUN_PRINT_TESTS_H
+#define TEST_STD_INPUT_OUTPUT_IOSTREAM_FORMAT_PRINT_FUN_PRINT_TESTS_H
+
+template <class TestFunction, class ExceptionTest>
+void print_tests(TestFunction check, ExceptionTest check_exception) {
+  // *** Test escaping  ***
+
+  check("{", "{{");
+  check("{:^}", "{{:^}}");
+  check("{: ^}", "{{:{}^}}", ' ');
+  check("{:{}^}", "{{:{{}}^}}");
+  check("{:{ }^}", "{{:{{{}}}^}}", ' ');
+
+  // *** Test argument ID ***
+  check("hello false true", "hello {0:} {1:}", false, true);
+  check("hello true false", "hello {1:} {0:}", false, true);
+
+  // *** Test many arguments ***
+  check(
+      "1234567890\t1234567890",
+      "{}{}{}{}{}{}{}{}{}{}\t{}{}{}{}{}{}{}{}{}{}",
+      1,
+      2,
+      3,
+      4,
+      5,
+      6,
+      7,
+      8,
+      9,
+      0,
+      1,
+      2,
+      3,
+      4,
+      5,
+      6,
+      7,
+      8,
+      9,
+      0);
+
+  // *** Test embedded NUL character ***
+  using namespace std::literals;
+  check("hello\0world"sv, "hello{}{}", '\0', "world");
+  check("hello\0world"sv, "hello\0{}"sv, "world");
+  check("hello\0world"sv, "hello{}", "\0world"sv);
+
+  // *** Test Unicode ***
+  // 2-byte code points
+  check("\u00a1"sv, "{}"sv, "\u00a1");  // INVERTED EXCLAMATION MARK
+  check("\u07ff"sv, "{:}"sv, "\u07ff"); // NKO TAMAN SIGN
+
+  // 3-byte code points
+  check("\u0800"sv, "{}"sv, "\u0800"); // SAMARITAN LETTER ALAF
+  check("\ufffd"sv, "{}"sv, "\ufffd"); // REPLACEMENT CHARACTER
+
+  // 4-byte code points
+  check("\U00010000"sv, "{}"sv, "\U00010000"); // LINEAR B SYLLABLE B008 A
+  check("\U0010FFFF"sv, "{}"sv, "\U0010FFFF"); // Undefined Character
+
+  // *** Test invalid format strings ***
+  check_exception("The format string terminates at a '{'", "{");
+  check_exception("The replacement field misses a terminating '}'", "{:", 42);
+
+  check_exception("The format string contains an invalid escape sequence", "}");
+  check_exception("The format string contains an invalid escape sequence", "{:}-}", 42);
+
+  check_exception("The format string contains an invalid escape sequence", "} ");
+  check_exception("The arg-id of the format-spec starts with an invalid character", "{-", 42);
+  check_exception("Argument index out of bounds", "hello {}");
+  check_exception("Argument index out of bounds", "hello {0}");
+  check_exception("Argument index out of bounds", "hello {1}", 42);
+}
+
+#endif // TEST_STD_INPUT_OUTPUT_IOSTREAM_FORMAT_PRINT_FUN_PRINT_TESTS_H

diff  --git a/libcxx/test/std/input.output/iostream.format/print.fun/println.file.pass.cpp b/libcxx/test/std/input.output/iostream.format/print.fun/println.file.pass.cpp
new file mode 100644
index 00000000000000..7c46d35215f698
--- /dev/null
+++ b/libcxx/test/std/input.output/iostream.format/print.fun/println.file.pass.cpp
@@ -0,0 +1,142 @@
+//===----------------------------------------------------------------------===//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: no-filesystem
+// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME
+
+// XFAIL: availability-fp_to_chars-missing
+
+// <print>
+
+// template<class... Args>
+//   void println(FILE* stream, format_string<Args...> fmt, Args&&... args);
+
+// In the library when the stdout is redirected to a file it is no
+// longer considered a terminal and the special terminal handling is no
+// longer executed. There are tests in
+//   libcxx/test/libcxx/input.output/iostream.format/print.fun/
+// to validate that behaviour
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <cstddef>
+#include <cstdio>
+#include <fstream>
+#include <fstream>
+#include <iterator>
+#include <print>
+#include <string_view>
+
+#include "assert_macros.h"
+#include "concat_macros.h"
+#include "filesystem_test_helper.h"
+#include "print_tests.h"
+#include "test_format_string.h"
+#include "test_macros.h"
+
+scoped_test_env env;
+std::string filename = env.create_file("output.txt");
+
+auto test_file = []<class... Args>(std::string_view e, test_format_string<char, Args...> fmt, Args&&... args) {
+  std::string expected = std::string{e} + '\n';
+
+  FILE* file = fopen(filename.c_str(), "wb");
+  assert(file);
+
+  std::println(file, fmt, std::forward<Args>(args)...);
+  std::fclose(file);
+
+  std::ifstream stream{filename.c_str(), std::ios_base::in | std::ios_base::binary};
+  std::string out(std::istreambuf_iterator<char>{stream}, {});
+  TEST_REQUIRE(out == expected,
+               TEST_WRITE_CONCATENATED(
+                   "\nFormat string   ", fmt.get(), "\nExpected output ", expected, "\nActual output   ", out, '\n'));
+};
+
+auto test_exception = []<class... Args>(std::string_view, std::string_view, Args&&...) {
+  // After P2216 most exceptions thrown by std::format become ill-formed.
+  // Therefore this tests does nothing.
+  // A basic ill-formed test is done in format.verify.cpp
+  // The exceptions are tested by other functions that don't use the basic-format-string as fmt argument.
+};
+
+// Glibc fails writing to a wide stream.
+#if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS)
+static void test_wide_stream() {
+  FILE* file = fopen(filename.c_str(), "wb");
+  assert(file);
+
+  int mode = std::fwide(file, 1);
+  assert(mode > 0);
+
+  TEST_VALIDATE_EXCEPTION(
+      std::system_error,
+      [&]([[maybe_unused]] const std::system_error& e) {
+        [[maybe_unused]] std::string_view what{"failed to write formatted output"};
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception   ", e.what(), '\n'));
+      },
+      std::println(file, "hello"));
+}
+#endif // defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS)
+
+static void test_read_only() {
+  FILE* file = fopen(filename.c_str(), "r");
+  assert(file);
+
+  TEST_VALIDATE_EXCEPTION(
+      std::system_error,
+      [&]([[maybe_unused]] const std::system_error& e) {
+#ifdef _AIX
+        [[maybe_unused]] std::string_view what{"failed to write formatted output: Broken pipe"};
+#else
+        [[maybe_unused]] std::string_view what{"failed to write formatted output: Operation not permitted"};
+#endif
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception   ", e.what(), '\n'));
+      },
+      std::println(file, "hello"));
+}
+
+static void test_new_line() {
+  // Text does newline translation.
+  {
+    FILE* file = fopen(filename.c_str(), "w");
+    assert(file);
+
+    std::println(file, "");
+#ifndef _WIN32
+    assert(std::ftell(file) == 1);
+#else
+    assert(std::ftell(file) == 2);
+#endif
+  }
+  // Binary no newline translation.
+  {
+    FILE* file = fopen(filename.c_str(), "wb");
+    assert(file);
+
+    std::println(file, "");
+    assert(std::ftell(file) == 1);
+  }
+}
+
+int main(int, char**) {
+  print_tests(test_file, test_exception);
+
+#if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS)
+  test_wide_stream();
+#endif
+  test_read_only();
+  test_new_line();
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/iostream.format/print.fun/vprint_nonunicode.file.pass.cpp b/libcxx/test/std/input.output/iostream.format/print.fun/vprint_nonunicode.file.pass.cpp
new file mode 100644
index 00000000000000..6953558671428c
--- /dev/null
+++ b/libcxx/test/std/input.output/iostream.format/print.fun/vprint_nonunicode.file.pass.cpp
@@ -0,0 +1,143 @@
+//===----------------------------------------------------------------------===//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: no-filesystem
+// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME
+
+// XFAIL: availability-fp_to_chars-missing
+
+// <print>
+
+// void vprint_nonunicode(FILE* stream, string_view fmt, format_args args);
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <cstddef>
+#include <cstdio>
+#include <fstream>
+#include <iterator>
+#include <print>
+#include <string_view>
+
+#include "assert_macros.h"
+#include "concat_macros.h"
+#include "filesystem_test_helper.h"
+#include "print_tests.h"
+#include "test_macros.h"
+
+scoped_test_env env;
+std::string filename = env.create_file("output.txt");
+
+auto test_file = []<class... Args>(std::string_view expected, std::string_view fmt, Args&&... args) {
+  FILE* file = fopen(filename.c_str(), "wb");
+  assert(file);
+
+  std::vprint_nonunicode(file, fmt, std::make_format_args(args...));
+  std::fclose(file);
+
+  std::ifstream stream{filename.c_str(), std::ios_base::in | std::ios_base::binary};
+  std::string out(std::istreambuf_iterator<char>{stream}, {});
+  TEST_REQUIRE(out == expected,
+               TEST_WRITE_CONCATENATED(
+                   "\nFormat string   ", fmt, "\nExpected output ", expected, "\nActual output   ", out, '\n'));
+};
+
+auto test_exception = []<class... Args>([[maybe_unused]] std::string_view what,
+                                        [[maybe_unused]] std::string_view fmt,
+                                        [[maybe_unused]] Args&&... args) {
+  FILE* file = fopen(filename.c_str(), "wb");
+  assert(file);
+
+  TEST_VALIDATE_EXCEPTION(
+      std::format_error,
+      [&]([[maybe_unused]] const std::format_error& e) {
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            TEST_WRITE_CONCATENATED(
+                "\nFormat string   ", fmt, "\nExpected exception ", what, "\nActual exception   ", e.what(), '\n'));
+      },
+      std::vprint_nonunicode(file, fmt, std::make_format_args(args...)));
+
+  fclose(file);
+};
+
+// Glibc fails writing to a wide stream.
+#if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS)
+static void test_wide_stream() {
+  FILE* file = fopen(filename.c_str(), "wb");
+  assert(file);
+
+  int mode = std::fwide(file, 1);
+  assert(mode > 0);
+
+  TEST_VALIDATE_EXCEPTION(
+      std::system_error,
+      [&]([[maybe_unused]] const std::system_error& e) {
+        [[maybe_unused]] std::string_view what{"failed to write formatted output"};
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception   ", e.what(), '\n'));
+      },
+      std::vprint_nonunicode(file, "hello", std::make_format_args()));
+}
+#endif // defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS)
+
+static void test_read_only() {
+  FILE* file = fopen(filename.c_str(), "r");
+  assert(file);
+
+  TEST_VALIDATE_EXCEPTION(
+      std::system_error,
+      [&]([[maybe_unused]] const std::system_error& e) {
+#ifdef _AIX
+        [[maybe_unused]] std::string_view what{"failed to write formatted output: Broken pipe"};
+#else
+        [[maybe_unused]] std::string_view what{"failed to write formatted output: Operation not permitted"};
+#endif
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception   ", e.what(), '\n'));
+      },
+      std::vprint_nonunicode(file, "hello", std::make_format_args()));
+}
+
+static void test_new_line() {
+  // Text does newline translation.
+  {
+    FILE* file = fopen(filename.c_str(), "w");
+    assert(file);
+
+    std::vprint_nonunicode(file, "\n", std::make_format_args());
+#ifndef _WIN32
+    assert(std::ftell(file) == 1);
+#else
+    assert(std::ftell(file) == 2);
+#endif
+  }
+  // Binary no newline translation.
+  {
+    FILE* file = fopen(filename.c_str(), "wb");
+    assert(file);
+
+    std::vprint_nonunicode(file, "\n", std::make_format_args());
+    assert(std::ftell(file) == 1);
+  }
+}
+
+int main(int, char**) {
+  print_tests(test_file, test_exception);
+
+#if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS)
+  test_wide_stream();
+#endif
+  test_read_only();
+  test_new_line();
+
+  return 0;
+}

diff  --git a/libcxx/test/std/input.output/iostream.format/print.fun/vprint_unicode.file.pass.cpp b/libcxx/test/std/input.output/iostream.format/print.fun/vprint_unicode.file.pass.cpp
new file mode 100644
index 00000000000000..c26ed0b05fd44c
--- /dev/null
+++ b/libcxx/test/std/input.output/iostream.format/print.fun/vprint_unicode.file.pass.cpp
@@ -0,0 +1,150 @@
+//===----------------------------------------------------------------------===//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: no-filesystem
+// UNSUPPORTED: libcpp-has-no-unicode
+// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME
+
+// XFAIL: availability-fp_to_chars-missing
+
+// <print>
+
+// void vprint_unicode(FILE* stream, string_view fmt, format_args args);
+
+// In the library when the stdout is redirected to a file it is no
+// longer considered a terminal and the special terminal handling is no
+// longer executed. There are tests in
+//   libcxx/test/libcxx/input.output/iostream.format/print.fun/
+// to validate that behaviour
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <cstddef>
+#include <cstdio>
+#include <fstream>
+#include <iterator>
+#include <print>
+#include <string_view>
+
+#include "assert_macros.h"
+#include "concat_macros.h"
+#include "filesystem_test_helper.h"
+#include "print_tests.h"
+#include "test_macros.h"
+
+scoped_test_env env;
+std::string filename = env.create_file("output.txt");
+
+auto test_file = []<class... Args>(std::string_view expected, std::string_view fmt, Args&&... args) {
+  FILE* file = fopen(filename.c_str(), "wb");
+  assert(file);
+
+  std::vprint_unicode(file, fmt, std::make_format_args(args...));
+  std::fclose(file);
+
+  std::ifstream stream{filename.c_str(), std::ios_base::in | std::ios_base::binary};
+  std::string out(std::istreambuf_iterator<char>{stream}, {});
+  TEST_REQUIRE(out == expected,
+               TEST_WRITE_CONCATENATED(
+                   "\nFormat string   ", fmt, "\nExpected output ", expected, "\nActual output   ", out, '\n'));
+};
+
+auto test_exception = []<class... Args>([[maybe_unused]] std::string_view what,
+                                        [[maybe_unused]] std::string_view fmt,
+                                        [[maybe_unused]] Args&&... args) {
+  FILE* file = fopen(filename.c_str(), "wb");
+  assert(file);
+
+  TEST_VALIDATE_EXCEPTION(
+      std::format_error,
+      [&]([[maybe_unused]] const std::format_error& e) {
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            TEST_WRITE_CONCATENATED(
+                "\nFormat string   ", fmt, "\nExpected exception ", what, "\nActual exception   ", e.what(), '\n'));
+      },
+      std::vprint_unicode(file, fmt, std::make_format_args(args...)));
+
+  fclose(file);
+};
+
+// Glibc fails writing to a wide stream.
+#if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS)
+static void test_wide_stream() {
+  FILE* file = fopen(filename.c_str(), "wb");
+  assert(file);
+
+  int mode = std::fwide(file, 1);
+  assert(mode > 0);
+
+  TEST_VALIDATE_EXCEPTION(
+      std::system_error,
+      [&]([[maybe_unused]] const std::system_error& e) {
+        [[maybe_unused]] std::string_view what{"failed to write formatted output"};
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception   ", e.what(), '\n'));
+      },
+      std::vprint_unicode(file, "hello", std::make_format_args()));
+}
+#endif // defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS)
+
+static void test_read_only() {
+  FILE* file = fopen(filename.c_str(), "r");
+  assert(file);
+
+  TEST_VALIDATE_EXCEPTION(
+      std::system_error,
+      [&]([[maybe_unused]] const std::system_error& e) {
+#ifdef _AIX
+        [[maybe_unused]] std::string_view what{"failed to write formatted output: Broken pipe"};
+#else
+        [[maybe_unused]] std::string_view what{"failed to write formatted output: Operation not permitted"};
+#endif
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception   ", e.what(), '\n'));
+      },
+      std::vprint_unicode(file, "hello", std::make_format_args()));
+}
+
+static void test_new_line() {
+  // Text does newline translation.
+  {
+    FILE* file = fopen(filename.c_str(), "w");
+    assert(file);
+
+    std::vprint_unicode(file, "\n", std::make_format_args());
+#ifndef _WIN32
+    assert(std::ftell(file) == 1);
+#else
+    assert(std::ftell(file) == 2);
+#endif
+  }
+  // Binary no newline translation.
+  {
+    FILE* file = fopen(filename.c_str(), "wb");
+    assert(file);
+
+    std::vprint_unicode(file, "\n", std::make_format_args());
+    assert(std::ftell(file) == 1);
+  }
+}
+
+int main(int, char**) {
+  print_tests(test_file, test_exception);
+
+#if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS)
+  test_wide_stream();
+#endif
+  test_read_only();
+  test_new_line();
+
+  return 0;
+}

diff  --git a/libcxx/test/std/language.support/support.limits/support.limits.general/ostream.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/ostream.version.compile.pass.cpp
index 234653cb774cbb..720322081e1ef2 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/ostream.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/ostream.version.compile.pass.cpp
@@ -19,6 +19,7 @@
 
 /*  Constant             Value
     __cpp_lib_char8_t    201907L [C++20]
+    __cpp_lib_print      202207L [C++23]
 */
 
 #include <ostream>
@@ -30,18 +31,30 @@
 #   error "__cpp_lib_char8_t should not be defined before c++20"
 # endif
 
+# ifdef __cpp_lib_print
+#   error "__cpp_lib_print should not be defined before c++23"
+# endif
+
 #elif TEST_STD_VER == 14
 
 # ifdef __cpp_lib_char8_t
 #   error "__cpp_lib_char8_t should not be defined before c++20"
 # endif
 
+# ifdef __cpp_lib_print
+#   error "__cpp_lib_print should not be defined before c++23"
+# endif
+
 #elif TEST_STD_VER == 17
 
 # ifdef __cpp_lib_char8_t
 #   error "__cpp_lib_char8_t should not be defined before c++20"
 # endif
 
+# ifdef __cpp_lib_print
+#   error "__cpp_lib_print should not be defined before c++23"
+# endif
+
 #elif TEST_STD_VER == 20
 
 # if defined(__cpp_char8_t)
@@ -57,6 +70,10 @@
 #   endif
 # endif
 
+# ifdef __cpp_lib_print
+#   error "__cpp_lib_print should not be defined before c++23"
+# endif
+
 #elif TEST_STD_VER == 23
 
 # if defined(__cpp_char8_t)
@@ -72,6 +89,19 @@
 #   endif
 # endif
 
+# if !defined(_LIBCPP_VERSION)
+#   ifndef __cpp_lib_print
+#     error "__cpp_lib_print should be defined in c++23"
+#   endif
+#   if __cpp_lib_print != 202207L
+#     error "__cpp_lib_print should have the value 202207L in c++23"
+#   endif
+# else // _LIBCPP_VERSION
+#   ifdef __cpp_lib_print
+#     error "__cpp_lib_print should not be defined because it is unimplemented in libc++!"
+#   endif
+# endif
+
 #elif TEST_STD_VER > 23
 
 # if defined(__cpp_char8_t)
@@ -87,5 +117,18 @@
 #   endif
 # endif
 
+# if !defined(_LIBCPP_VERSION)
+#   ifndef __cpp_lib_print
+#     error "__cpp_lib_print should be defined in c++26"
+#   endif
+#   if __cpp_lib_print != 202207L
+#     error "__cpp_lib_print should have the value 202207L in c++26"
+#   endif
+# else // _LIBCPP_VERSION
+#   ifdef __cpp_lib_print
+#     error "__cpp_lib_print should not be defined because it is unimplemented in libc++!"
+#   endif
+# endif
+
 #endif // TEST_STD_VER > 23
 

diff  --git a/libcxx/test/std/language.support/support.limits/support.limits.general/print.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/print.version.compile.pass.cpp
new file mode 100644
index 00000000000000..ae5cc89ce79bd7
--- /dev/null
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/print.version.compile.pass.cpp
@@ -0,0 +1,80 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// WARNING: This test was generated by generate_feature_test_macro_components.py
+// and should not be edited manually.
+//
+// clang-format off
+
+// <print>
+
+// Test the feature test macros defined by <print>
+
+/*  Constant           Value
+    __cpp_lib_print    202207L [C++23]
+*/
+
+#include <print>
+#include "test_macros.h"
+
+#if TEST_STD_VER < 14
+
+# ifdef __cpp_lib_print
+#   error "__cpp_lib_print should not be defined before c++23"
+# endif
+
+#elif TEST_STD_VER == 14
+
+# ifdef __cpp_lib_print
+#   error "__cpp_lib_print should not be defined before c++23"
+# endif
+
+#elif TEST_STD_VER == 17
+
+# ifdef __cpp_lib_print
+#   error "__cpp_lib_print should not be defined before c++23"
+# endif
+
+#elif TEST_STD_VER == 20
+
+# ifdef __cpp_lib_print
+#   error "__cpp_lib_print should not be defined before c++23"
+# endif
+
+#elif TEST_STD_VER == 23
+
+# if !defined(_LIBCPP_VERSION)
+#   ifndef __cpp_lib_print
+#     error "__cpp_lib_print should be defined in c++23"
+#   endif
+#   if __cpp_lib_print != 202207L
+#     error "__cpp_lib_print should have the value 202207L in c++23"
+#   endif
+# else // _LIBCPP_VERSION
+#   ifdef __cpp_lib_print
+#     error "__cpp_lib_print should not be defined because it is unimplemented in libc++!"
+#   endif
+# endif
+
+#elif TEST_STD_VER > 23
+
+# if !defined(_LIBCPP_VERSION)
+#   ifndef __cpp_lib_print
+#     error "__cpp_lib_print should be defined in c++26"
+#   endif
+#   if __cpp_lib_print != 202207L
+#     error "__cpp_lib_print should have the value 202207L in c++26"
+#   endif
+# else // _LIBCPP_VERSION
+#   ifdef __cpp_lib_print
+#     error "__cpp_lib_print should not be defined because it is unimplemented in libc++!"
+#   endif
+# endif
+
+#endif // TEST_STD_VER > 23
+

diff  --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
index e969d22af55d47..75991c25955442 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -138,6 +138,7 @@
     __cpp_lib_out_ptr                                202106L [C++23]
     __cpp_lib_parallel_algorithm                     201603L [C++17]
     __cpp_lib_polymorphic_allocator                  201902L [C++20]
+    __cpp_lib_print                                  202207L [C++23]
     __cpp_lib_quoted_string_io                       201304L [C++14]
     __cpp_lib_ranges                                 202207L [C++20]
     __cpp_lib_ranges_as_rvalue                       202207L [C++23]
@@ -674,6 +675,10 @@
 #   error "__cpp_lib_polymorphic_allocator should not be defined before c++20"
 # endif
 
+# ifdef __cpp_lib_print
+#   error "__cpp_lib_print should not be defined before c++23"
+# endif
+
 # ifdef __cpp_lib_quoted_string_io
 #   error "__cpp_lib_quoted_string_io should not be defined before c++14"
 # endif
@@ -1421,6 +1426,10 @@
 #   error "__cpp_lib_polymorphic_allocator should not be defined before c++20"
 # endif
 
+# ifdef __cpp_lib_print
+#   error "__cpp_lib_print should not be defined before c++23"
+# endif
+
 # ifndef __cpp_lib_quoted_string_io
 #   error "__cpp_lib_quoted_string_io should be defined in c++14"
 # endif
@@ -2342,6 +2351,10 @@
 #   error "__cpp_lib_polymorphic_allocator should not be defined before c++20"
 # endif
 
+# ifdef __cpp_lib_print
+#   error "__cpp_lib_print should not be defined before c++23"
+# endif
+
 # ifndef __cpp_lib_quoted_string_io
 #   error "__cpp_lib_quoted_string_io should be defined in c++17"
 # endif
@@ -3530,6 +3543,10 @@
 #   endif
 # endif
 
+# ifdef __cpp_lib_print
+#   error "__cpp_lib_print should not be defined before c++23"
+# endif
+
 # ifndef __cpp_lib_quoted_string_io
 #   error "__cpp_lib_quoted_string_io should be defined in c++20"
 # endif
@@ -4889,6 +4906,19 @@
 #   endif
 # endif
 
+# if !defined(_LIBCPP_VERSION)
+#   ifndef __cpp_lib_print
+#     error "__cpp_lib_print should be defined in c++23"
+#   endif
+#   if __cpp_lib_print != 202207L
+#     error "__cpp_lib_print should have the value 202207L in c++23"
+#   endif
+# else // _LIBCPP_VERSION
+#   ifdef __cpp_lib_print
+#     error "__cpp_lib_print should not be defined because it is unimplemented in libc++!"
+#   endif
+# endif
+
 # ifndef __cpp_lib_quoted_string_io
 #   error "__cpp_lib_quoted_string_io should be defined in c++23"
 # endif
@@ -6428,6 +6458,19 @@
 #   endif
 # endif
 
+# if !defined(_LIBCPP_VERSION)
+#   ifndef __cpp_lib_print
+#     error "__cpp_lib_print should be defined in c++26"
+#   endif
+#   if __cpp_lib_print != 202207L
+#     error "__cpp_lib_print should have the value 202207L in c++26"
+#   endif
+# else // _LIBCPP_VERSION
+#   ifdef __cpp_lib_print
+#     error "__cpp_lib_print should not be defined because it is unimplemented in libc++!"
+#   endif
+# endif
+
 # ifndef __cpp_lib_quoted_string_io
 #   error "__cpp_lib_quoted_string_io should be defined in c++26"
 # endif

diff  --git a/libcxx/test/std/utilities/format/format.functions/fill.unicode.pass.cpp b/libcxx/test/std/utilities/format/format.functions/fill.unicode.pass.cpp
index 9fe478a19e1262..c9d80f6d44c291 100644
--- a/libcxx/test/std/utilities/format/format.functions/fill.unicode.pass.cpp
+++ b/libcxx/test/std/utilities/format/format.functions/fill.unicode.pass.cpp
@@ -6,7 +6,6 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
-// UNSUPPORTED: libcpp-has-no-incomplete-format
 // UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME
 
 // This version runs the test when the platform has Unicode support.

diff  --git a/libcxx/utils/ci/run-buildbot b/libcxx/utils/ci/run-buildbot
index 6934634ad07b4b..c8954d377935ec 100755
--- a/libcxx/utils/ci/run-buildbot
+++ b/libcxx/utils/ci/run-buildbot
@@ -248,6 +248,7 @@ check-generated-output)
            --exclude 'ostream.pass.cpp' \
            --exclude 'std_format_spec_string_unicode.bench.cpp' \
            --exclude 'transcoding.pass.cpp' \
+           --exclude 'vprint_unicode_windows.pass.cpp' \
            --exclude 'underflow.pass.cpp' \
            || false
 

diff  --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index 759badf09ec011..67545f27280a6c 100755
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -771,6 +771,12 @@ def add_version_header(tc):
             "test_suite_guard": "!defined(_LIBCPP_AVAILABILITY_HAS_NO_PMR)",
             "libcxx_guard": "!defined(_LIBCPP_AVAILABILITY_HAS_NO_PMR)",
         },
+        {
+            "name": "__cpp_lib_print",
+            "values": {"c++23": 202207},
+            "headers": ["ostream", "print"],
+            "unimplemented": True,
+        },
         {
             "name": "__cpp_lib_quoted_string_io",
             "values": {"c++14": 201304},

diff  --git a/libcxx/utils/libcxx/test/header_information.py b/libcxx/utils/libcxx/test/header_information.py
index 5d3bc8bda00c05..8a02e697488bc9 100644
--- a/libcxx/utils/libcxx/test/header_information.py
+++ b/libcxx/utils/libcxx/test/header_information.py
@@ -41,10 +41,11 @@
     "iostream": "// UNSUPPORTED: no-localization",
     "istream": "// UNSUPPORTED: no-localization",
     "latch": "// UNSUPPORTED: no-threads, c++03, c++11, c++14, c++17",
-    "locale.h": "// UNSUPPORTED: no-localization",
     "locale": "// UNSUPPORTED: no-localization",
+    "locale.h": "// UNSUPPORTED: no-localization",
     "mutex": "// UNSUPPORTED: no-threads, c++03",
     "ostream": "// UNSUPPORTED: no-localization",
+    "print": "// UNSUPPORTED: availability-fp_to_chars-missing", # TODO PRINT investigate
     "regex": "// UNSUPPORTED: no-localization",
     "semaphore": "// UNSUPPORTED: no-threads, c++03, c++11, c++14, c++17",
     "shared_mutex": "// UNSUPPORTED: no-threads, c++03, c++11",


        


More information about the libcxx-commits mailing list