[libc-commits] [libc] [libc] implement putwchar (PR #196166)

Michael Jones via libc-commits libc-commits at lists.llvm.org
Wed May 6 12:58:38 PDT 2026


https://github.com/michaelrj-google created https://github.com/llvm/llvm-project/pull/196166

Add putwchar function and tests. Part 10/11. All build file changes are in
part 11.

Assisted by Gemini


>From 5768ed398931cc6b590120d28949cb56d32eb334 Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Wed, 6 May 2026 18:28:43 +0000
Subject: [PATCH] [libc] implement putwchar

Add putwchar function and tests. Part 10/11. All build file changes are in
part 11.

Assisted by Gemini
---
 libc/src/wchar/putwchar.cpp           |  32 +++++
 libc/src/wchar/putwchar.h             |  21 +++
 libc/test/src/wchar/putwchar_test.cpp | 178 ++++++++++++++++++++++++++
 3 files changed, 231 insertions(+)
 create mode 100644 libc/src/wchar/putwchar.cpp
 create mode 100644 libc/src/wchar/putwchar.h
 create mode 100644 libc/test/src/wchar/putwchar_test.cpp

diff --git a/libc/src/wchar/putwchar.cpp b/libc/src/wchar/putwchar.cpp
new file mode 100644
index 0000000000000..c45e64f46a387
--- /dev/null
+++ b/libc/src/wchar/putwchar.cpp
@@ -0,0 +1,32 @@
+//===-- Implementation of putwchar ----------------------------------------===//
+//
+// 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/wchar/putwchar.h"
+#include "hdr/types/FILE.h"
+#include "hdr/types/wint_t.h"
+#include "hdr/wchar_macros.h" // For WEOF
+#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/stdio/stdout.h" // For stdout
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(wint_t, putwchar, (wchar_t wc)) {
+  auto *f = reinterpret_cast<File *>(LIBC_NAMESPACE::stdout);
+  FileIOResult result = f->write(&wc, 1);
+  if (result.has_error() || result.value < 1) {
+    if (result.has_error())
+      libc_errno = result.error;
+    return WEOF;
+  }
+  return static_cast<wint_t>(wc);
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/putwchar.h b/libc/src/wchar/putwchar.h
new file mode 100644
index 0000000000000..c7da57e3d4968
--- /dev/null
+++ b/libc/src/wchar/putwchar.h
@@ -0,0 +1,21 @@
+//===-- Implementation header for putwchar --------------------------------===//
+//
+// 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 LLVM_LIBC_SRC_WCHAR_PUTWCHAR_H
+#define LLVM_LIBC_SRC_WCHAR_PUTWCHAR_H
+
+#include "hdr/types/wint_t.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+wint_t putwchar(wchar_t wc);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_WCHAR_PUTWCHAR_H
diff --git a/libc/test/src/wchar/putwchar_test.cpp b/libc/test/src/wchar/putwchar_test.cpp
new file mode 100644
index 0000000000000..a1ebcbc961235
--- /dev/null
+++ b/libc/test/src/wchar/putwchar_test.cpp
@@ -0,0 +1,178 @@
+//===-- Unittests for putwchar --------------------------------------------===//
+//
+// 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 "hdr/errno_macros.h"
+#include "hdr/wchar_macros.h" // For WEOF
+#include "src/stdio/fclose.h"
+#include "src/stdio/ferror.h"
+#include "src/stdio/fopen.h"
+#include "src/stdio/fread.h"
+#include "src/stdio/stdout.h"
+#include "src/wchar/fwide.h"
+#include "src/wchar/putwchar.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/Test.h"
+
+using LlvmLibcPutwcharTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+
+TEST_F(LlvmLibcPutwcharTest, WriteASCII) {
+  auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("putwchar_ascii.test"));
+
+  // Redirect stdout
+  ::FILE *original_stdout = LIBC_NAMESPACE::stdout;
+  LIBC_NAMESPACE::stdout = LIBC_NAMESPACE::fopen(FILENAME, "w");
+  ASSERT_FALSE(LIBC_NAMESPACE::stdout == nullptr);
+
+  // Initial orientation
+  EXPECT_EQ(LIBC_NAMESPACE::fwide(LIBC_NAMESPACE::stdout, 0), 0);
+
+  EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'a'), static_cast<wint_t>(L'a'));
+  EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'b'), static_cast<wint_t>(L'b'));
+  EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'c'), static_cast<wint_t>(L'c'));
+
+  // Orientation should be wide
+  EXPECT_GT(LIBC_NAMESPACE::fwide(LIBC_NAMESPACE::stdout, 0), 0);
+
+  // Restore stdout
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(LIBC_NAMESPACE::stdout), 0);
+  LIBC_NAMESPACE::stdout = original_stdout;
+
+  // Read back raw bytes to verify
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "r");
+  ASSERT_FALSE(file == nullptr);
+
+  unsigned char buffer[5] = {0};
+  ASSERT_EQ(LIBC_NAMESPACE::fread(buffer, 1, 3, file), size_t(3));
+  EXPECT_EQ(buffer[0], static_cast<unsigned char>('a'));
+  EXPECT_EQ(buffer[1], static_cast<unsigned char>('b'));
+  EXPECT_EQ(buffer[2], static_cast<unsigned char>('c'));
+
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+}
+
+TEST_F(LlvmLibcPutwcharTest, WriteUtf8) {
+  auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("putwchar_utf8.test"));
+
+  // Redirect stdout
+  ::FILE *original_stdout = LIBC_NAMESPACE::stdout;
+  LIBC_NAMESPACE::stdout = LIBC_NAMESPACE::fopen(FILENAME, "w");
+  ASSERT_FALSE(LIBC_NAMESPACE::stdout == nullptr);
+
+  // a   -> L'a' (1-byte)
+  // ¢   -> L'¢' (2-byte)
+  // €   -> L'€' (3-byte)
+  // 𐍈   -> L'𐍈' (4-byte)
+  EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'a'), static_cast<wint_t>(L'a'));
+  EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'¢'), static_cast<wint_t>(L'¢'));
+  EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'€'), static_cast<wint_t>(L'€'));
+  EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'𐍈'), static_cast<wint_t>(L'𐍈'));
+
+  // Restore stdout
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(LIBC_NAMESPACE::stdout), 0);
+  LIBC_NAMESPACE::stdout = original_stdout;
+
+  // Read back raw bytes and verify UTF-8 sequences exactly on disk
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "r");
+  ASSERT_FALSE(file == nullptr);
+
+  unsigned char buffer[20] = {0};
+  // Expecting 1 + 2 + 3 + 4 = 10 bytes
+  ASSERT_EQ(LIBC_NAMESPACE::fread(buffer, 1, 10, file), size_t(10));
+
+  // a
+  EXPECT_EQ(buffer[0], static_cast<unsigned char>(0x61));
+
+  // ¢
+  EXPECT_EQ(buffer[1], static_cast<unsigned char>(0xC2));
+  EXPECT_EQ(buffer[2], static_cast<unsigned char>(0xA2));
+
+  // €
+  EXPECT_EQ(buffer[3], static_cast<unsigned char>(0xE2));
+  EXPECT_EQ(buffer[4], static_cast<unsigned char>(0x82));
+  EXPECT_EQ(buffer[5], static_cast<unsigned char>(0xAC));
+
+  // 𐍈
+  EXPECT_EQ(buffer[6], static_cast<unsigned char>(0xF0));
+  EXPECT_EQ(buffer[7], static_cast<unsigned char>(0x90));
+  EXPECT_EQ(buffer[8], static_cast<unsigned char>(0x8D));
+  EXPECT_EQ(buffer[9], static_cast<unsigned char>(0x88));
+
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+}
+
+TEST_F(LlvmLibcPutwcharTest, InvalidStream) {
+  auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("putwchar_invalid.test"));
+
+  // Create the file first
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+  ASSERT_FALSE(file == nullptr);
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
+
+  // Redirect stdout using a read-only file stream
+  ::FILE *original_stdout = LIBC_NAMESPACE::stdout;
+  LIBC_NAMESPACE::stdout = LIBC_NAMESPACE::fopen(FILENAME, "r");
+  ASSERT_FALSE(LIBC_NAMESPACE::stdout == nullptr);
+
+  // Try to write to read-only stdout
+  ASSERT_ERRNO_SUCCESS();
+  EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'a'), static_cast<wint_t>(WEOF));
+  ASSERT_ERRNO_EQ(EBADF);
+  EXPECT_NE(LIBC_NAMESPACE::ferror(LIBC_NAMESPACE::stdout), 0);
+
+  // Restore stdout
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(LIBC_NAMESPACE::stdout), 0);
+  LIBC_NAMESPACE::stdout = original_stdout;
+}
+
+TEST_F(LlvmLibcPutwcharTest, ByteModeFailure) {
+  auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("putwchar_bytemode.test"));
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w+");
+  ASSERT_FALSE(file == nullptr);
+
+  // Redirect stdout
+  ::FILE *original_stdout = LIBC_NAMESPACE::stdout;
+  LIBC_NAMESPACE::stdout = file;
+
+  // Orient to byte mode
+  EXPECT_LT(LIBC_NAMESPACE::fwide(LIBC_NAMESPACE::stdout, -1), 0);
+
+  // Writing should fail and set errno to EINVAL
+  EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'a'), static_cast<wint_t>(WEOF));
+  ASSERT_ERRNO_EQ(EINVAL);
+  EXPECT_NE(LIBC_NAMESPACE::ferror(LIBC_NAMESPACE::stdout), 0);
+
+  // Restore stdout
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(LIBC_NAMESPACE::stdout), 0);
+  LIBC_NAMESPACE::stdout = original_stdout;
+}
+
+TEST_F(LlvmLibcPutwcharTest, RealStdoutNoRedirection) {
+  // This test writes directly to the real un-redirected stdout.
+  // While it cannot be programmatically verified by the test runner,
+  // it is extremely useful for developers running tests manually in a terminal
+  // to visually verify characters are outputted correctly.
+
+  // Print: "putwchar test: [a¢€𐍈]\n"
+  constexpr wchar_t PREFIX[] = L"putwchar test: [";
+  for (size_t i = 0; PREFIX[i] != L'\0'; ++i) {
+    EXPECT_EQ(LIBC_NAMESPACE::putwchar(PREFIX[i]),
+              static_cast<wint_t>(PREFIX[i]));
+  }
+
+  EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'a'), static_cast<wint_t>(L'a'));
+  EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'¢'), static_cast<wint_t>(L'¢'));
+  EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'€'), static_cast<wint_t>(L'€'));
+  EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'𐍈'), static_cast<wint_t>(L'𐍈'));
+
+  EXPECT_EQ(LIBC_NAMESPACE::putwchar(L']'), static_cast<wint_t>(L']'));
+  EXPECT_EQ(LIBC_NAMESPACE::putwchar(L'\n'), static_cast<wint_t>(L'\n'));
+}



More information about the libc-commits mailing list