[libc-commits] [libc] 51c961b - [libc] implement fputws (#196160)

via libc-commits libc-commits at lists.llvm.org
Tue May 19 09:54:26 PDT 2026


Author: Michael Jones
Date: 2026-05-19T16:54:20Z
New Revision: 51c961b7eb5c55b6770fce7399d522ea112370ce

URL: https://github.com/llvm/llvm-project/commit/51c961b7eb5c55b6770fce7399d522ea112370ce
DIFF: https://github.com/llvm/llvm-project/commit/51c961b7eb5c55b6770fce7399d522ea112370ce.diff

LOG: [libc] implement fputws (#196160)

Add fputws function and tests. Part 4/10.

Assisted by Gemini

Added: 
    libc/src/wchar/fputws.cpp
    libc/src/wchar/fputws.h
    libc/test/src/wchar/fputws_test.cpp

Modified: 
    libc/config/linux/aarch64/entrypoints.txt
    libc/config/linux/riscv/entrypoints.txt
    libc/config/linux/x86_64/entrypoints.txt
    libc/src/wchar/CMakeLists.txt
    libc/test/src/wchar/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 784fdc687cee7..ffe21e6854b5d 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1258,7 +1258,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.wchar.fgetwc
     # libc.src.wchar.fgetws
     libc.src.wchar.fputwc
-    # libc.src.wchar.fputws
+    libc.src.wchar.fputws
     libc.src.wchar.fwide
     libc.src.wchar.getwc
     # libc.src.wchar.getwchar

diff  --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 1ef9198aad889..297c152a2b2e4 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1392,7 +1392,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.wchar.fgetwc
     # libc.src.wchar.fgetws
     libc.src.wchar.fputwc
-    # libc.src.wchar.fputws
+    libc.src.wchar.fputws
     libc.src.wchar.fwide
     libc.src.wchar.getwc
     # libc.src.wchar.getwchar

diff  --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index c0affdddda272..89871dd3ad0ce 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1474,7 +1474,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.wchar.fgetwc
     # libc.src.wchar.fgetws
     libc.src.wchar.fputwc
-    # libc.src.wchar.fputws
+    libc.src.wchar.fputws
     libc.src.wchar.fwide
     libc.src.wchar.getwc
     # libc.src.wchar.getwchar

diff  --git a/libc/src/wchar/CMakeLists.txt b/libc/src/wchar/CMakeLists.txt
index 9eef6217d7ba6..6613ebe763cac 100644
--- a/libc/src/wchar/CMakeLists.txt
+++ b/libc/src/wchar/CMakeLists.txt
@@ -619,6 +619,8 @@ add_entrypoint_object(
   HDRS
     fputws.h
   DEPENDS
+    libc.src.__support.common
+    libc.src.__support.macros.config
     libc.hdr.types.FILE
     libc.hdr.types.wchar_t
     libc.src.__support.File.file

diff  --git a/libc/src/wchar/fputws.cpp b/libc/src/wchar/fputws.cpp
new file mode 100644
index 0000000000000..5af26bf125d7b
--- /dev/null
+++ b/libc/src/wchar/fputws.cpp
@@ -0,0 +1,44 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// This file contains the implementation of the fputws function, which writes a
+/// wide-character string to a file.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/wchar/fputws.h"
+#include "hdr/types/FILE.h"
+#include "src/__support/File/file.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
+#include "src/string/string_length.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, fputws,
+                   (const wchar_t *__restrict str, ::FILE *__restrict stream)) {
+  LIBC_CRASH_ON_NULLPTR(str);
+  LIBC_CRASH_ON_NULLPTR(stream);
+
+  size_t len = internal::string_length(str);
+
+  auto *f = reinterpret_cast<File *>(stream);
+  FileIOResult result = f->write(str, len);
+  if (result.has_error() || result.value < len) {
+    if (result.has_error())
+      libc_errno = result.error;
+    return -1;
+  }
+
+  return static_cast<int>(result.value);
+}
+
+} // namespace LIBC_NAMESPACE_DECL

diff  --git a/libc/src/wchar/fputws.h b/libc/src/wchar/fputws.h
new file mode 100644
index 0000000000000..7887f68a41d4d
--- /dev/null
+++ b/libc/src/wchar/fputws.h
@@ -0,0 +1,27 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file contains the prototype for the fputws function, which
+/// writes a wide-character string to a file.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_WCHAR_FPUTWS_H
+#define LLVM_LIBC_SRC_WCHAR_FPUTWS_H
+
+#include "hdr/types/FILE.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int fputws(const wchar_t *__restrict str, ::FILE *__restrict stream);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_WCHAR_FPUTWS_H

diff  --git a/libc/test/src/wchar/CMakeLists.txt b/libc/test/src/wchar/CMakeLists.txt
index 55b9f32e8226b..1201160a8a58c 100644
--- a/libc/test/src/wchar/CMakeLists.txt
+++ b/libc/test/src/wchar/CMakeLists.txt
@@ -559,6 +559,8 @@ add_libc_test(
     fputwc_test.cpp
   DEPENDS
     libc.include.stdio
+    libc.hdr.errno_macros
+    libc.hdr.stdint_proxy
     libc.src.stdio.fopen
     libc.src.stdio.fclose
     libc.src.stdio.fread
@@ -596,7 +598,8 @@ add_libc_test(
   SRCS
     fputws_test.cpp
   DEPENDS
-    libc.include.stdio
+    libc.hdr.errno_macros
+    libc.hdr.stdint_proxy
     libc.src.stdio.fopen
     libc.src.stdio.fclose
     libc.src.stdio.fread

diff  --git a/libc/test/src/wchar/fputws_test.cpp b/libc/test/src/wchar/fputws_test.cpp
new file mode 100644
index 0000000000000..c31795806aa6f
--- /dev/null
+++ b/libc/test/src/wchar/fputws_test.cpp
@@ -0,0 +1,168 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 fputws
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/errno_macros.h"
+#include "hdr/stdint_proxy.h"
+#include "src/stdio/fclose.h"
+#include "src/stdio/ferror.h"
+#include "src/stdio/fopen.h"
+#include "src/stdio/fread.h"
+#include "src/wchar/fputws.h"
+#include "src/wchar/fwide.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/Test.h"
+
+using LlvmLibcFputwsTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+
+TEST_F(LlvmLibcFputwsTest, WriteWideString) {
+  const auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("fputws_string.test"));
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+  ASSERT_FALSE(file == nullptr);
+
+  constexpr wchar_t STR[] = L"Hello, ¢ world!\n";
+#if WCHAR_MAX > 0xFFFF
+  // if they're available, try 3 and 4 byte wchars.
+  constexpr wchar_t STR2[] = L"€𐍈";
+  constexpr size_t EXPECTED_BYTES = 24;
+#else
+  constexpr wchar_t STR2[] = L"";
+  constexpr size_t EXPECTED_BYTES = 17;
+#endif
+  EXPECT_GE(LIBC_NAMESPACE::fputws(STR, file), 0);
+  EXPECT_GE(LIBC_NAMESPACE::fputws(STR2, file), 0);
+
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+
+  // Open to read raw bytes and verify UTF-8 mapping
+  file = LIBC_NAMESPACE::fopen(FILENAME, "r");
+  ASSERT_FALSE(file == nullptr);
+
+  unsigned char buffer[50] = {0};
+  ASSERT_EQ(LIBC_NAMESPACE::fread(buffer, 1, EXPECTED_BYTES, file),
+            EXPECTED_BYTES);
+
+  // "Hello, "
+  EXPECT_EQ(buffer[0], static_cast<unsigned char>('H'));
+  EXPECT_EQ(buffer[1], static_cast<unsigned char>('e'));
+  EXPECT_EQ(buffer[2], static_cast<unsigned char>('l'));
+  EXPECT_EQ(buffer[3], static_cast<unsigned char>('l'));
+  EXPECT_EQ(buffer[4], static_cast<unsigned char>('o'));
+  EXPECT_EQ(buffer[5], static_cast<unsigned char>(','));
+  EXPECT_EQ(buffer[6], static_cast<unsigned char>(' '));
+
+  // "¢"
+  EXPECT_EQ(buffer[7], static_cast<unsigned char>(0xC2));
+  EXPECT_EQ(buffer[8], static_cast<unsigned char>(0xA2));
+
+  // " world!\n"
+  EXPECT_EQ(buffer[9], static_cast<unsigned char>(' '));
+  EXPECT_EQ(buffer[10], static_cast<unsigned char>('w'));
+  EXPECT_EQ(buffer[11], static_cast<unsigned char>('o'));
+  EXPECT_EQ(buffer[12], static_cast<unsigned char>('r'));
+  EXPECT_EQ(buffer[13], static_cast<unsigned char>('l'));
+  EXPECT_EQ(buffer[14], static_cast<unsigned char>('d'));
+  EXPECT_EQ(buffer[15], static_cast<unsigned char>('!'));
+  EXPECT_EQ(buffer[16], static_cast<unsigned char>('\n'));
+
+#if WCHAR_MAX > 0xFFFF
+  // "€"
+  EXPECT_EQ(buffer[17], static_cast<unsigned char>(0xE2));
+  EXPECT_EQ(buffer[18], static_cast<unsigned char>(0x82));
+  EXPECT_EQ(buffer[19], static_cast<unsigned char>(0xAC));
+
+  // "𐍈"
+  EXPECT_EQ(buffer[20], static_cast<unsigned char>(0xF0));
+  EXPECT_EQ(buffer[21], static_cast<unsigned char>(0x90));
+  EXPECT_EQ(buffer[22], static_cast<unsigned char>(0x8D));
+  EXPECT_EQ(buffer[23], static_cast<unsigned char>(0x88));
+#endif
+
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+}
+
+TEST_F(LlvmLibcFputwsTest, EmptyString) {
+  const auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("fputws_empty.test"));
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+  ASSERT_FALSE(file == nullptr);
+
+  EXPECT_GE(LIBC_NAMESPACE::fputws(L"", file), 0);
+
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+
+  // Verify nothing was written
+  file = LIBC_NAMESPACE::fopen(FILENAME, "r");
+  ASSERT_FALSE(file == nullptr);
+
+  unsigned char buffer[5] = {0};
+  ASSERT_EQ(LIBC_NAMESPACE::fread(buffer, 1, 5, file), size_t(0));
+
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+}
+
+TEST_F(LlvmLibcFputwsTest, InvalidStream) {
+  const auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("fputws_invalid.test"));
+
+  // Create the file first
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+  ASSERT_FALSE(file == nullptr);
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+
+  // Open in read-only mode
+  file = LIBC_NAMESPACE::fopen(FILENAME, "r");
+  ASSERT_FALSE(file == nullptr);
+
+  // Try to write to read-only file
+  ASSERT_ERRNO_SUCCESS();
+  EXPECT_LT(LIBC_NAMESPACE::fputws(L"fail", file), 0);
+  ASSERT_ERRNO_EQ(EBADF);
+
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+}
+
+#if WCHAR_MAX > 0xFFFF
+TEST_F(LlvmLibcFputwsTest, EncodingErrorEILSEQ) {
+  const auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("fputws_eilseq.test"));
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+  ASSERT_FALSE(file == nullptr);
+
+  // String with invalid wide character point (outside Unicode range) which
+  // fails encoding
+  constexpr wchar_t STR[] = {static_cast<wchar_t>(0x110000), L'\0'};
+  ASSERT_ERRNO_SUCCESS();
+  EXPECT_LT(LIBC_NAMESPACE::fputws(STR, file), 0);
+  ASSERT_ERRNO_EQ(EILSEQ);
+  EXPECT_NE(LIBC_NAMESPACE::ferror(file), 0);
+
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+}
+#endif
+
+TEST_F(LlvmLibcFputwsTest, ByteModeFailure) {
+  const auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("fputws_bytemode.test"));
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w+");
+  ASSERT_FALSE(file == nullptr);
+
+  // Orient to byte mode
+  EXPECT_LT(LIBC_NAMESPACE::fwide(file, -1), 0);
+
+  // Writing wide string should fail and set errno to EINVAL
+  EXPECT_LT(LIBC_NAMESPACE::fputws(L"fail", file), 0);
+  ASSERT_ERRNO_EQ(EINVAL);
+
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+}


        


More information about the libc-commits mailing list