[libc-commits] [libc] [libc] Implement towupper and towlower entrypoints (PR #198659)

Michael Jones via libc-commits libc-commits at lists.llvm.org
Tue May 19 15:00:18 PDT 2026


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

Following up on #187670 to add public entrypoints for the wctype
conversion functions.

Assisted-by: Automated tooling, human reviewed.


>From 9a73382ea10e8ec4bc01b2cb6db0116566a558d9 Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Tue, 19 May 2026 21:53:06 +0000
Subject: [PATCH] [libc] Implement towupper and towlower entrypoints

Following up on #187670 to add public entrypoints for the wctype
conversion functions.

Assisted-by: Automated tooling, human reviewed.
---
 libc/config/linux/x86_64/entrypoints.txt |  2 +
 libc/include/wctype.yaml                 | 12 ++++
 libc/src/wctype/CMakeLists.txt           | 22 ++++++++
 libc/src/wctype/towlower.cpp             | 28 ++++++++++
 libc/src/wctype/towlower.h               | 26 +++++++++
 libc/src/wctype/towupper.cpp             | 28 ++++++++++
 libc/src/wctype/towupper.h               | 26 +++++++++
 libc/test/src/wctype/CMakeLists.txt      | 24 +++++++-
 libc/test/src/wctype/towlower_test.cpp   | 70 ++++++++++++++++++++++++
 libc/test/src/wctype/towupper_test.cpp   | 70 ++++++++++++++++++++++++
 10 files changed, 307 insertions(+), 1 deletion(-)
 create mode 100644 libc/src/wctype/towlower.cpp
 create mode 100644 libc/src/wctype/towlower.h
 create mode 100644 libc/src/wctype/towupper.cpp
 create mode 100644 libc/src/wctype/towupper.h
 create mode 100644 libc/test/src/wctype/towlower_test.cpp
 create mode 100644 libc/test/src/wctype/towupper_test.cpp

diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index eda0426ff2578..adfe5b6374907 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -479,6 +479,8 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.wctype.iswprint
     libc.src.wctype.iswctype
     libc.src.wctype.wctype
+    libc.src.wctype.towlower
+    libc.src.wctype.towupper
 
     # sys/uio.h entrypoints
     libc.src.sys.uio.writev
diff --git a/libc/include/wctype.yaml b/libc/include/wctype.yaml
index a5163ecc2d3f2..ac350c1f9956a 100644
--- a/libc/include/wctype.yaml
+++ b/libc/include/wctype.yaml
@@ -88,3 +88,15 @@ functions:
     return_type: wctype_t
     arguments:
       - type: const char*
+  - name: towlower
+    standards:
+      - stdc
+    return_type: wint_t
+    arguments:
+      - type: wint_t
+  - name: towupper
+    standards:
+      - stdc
+    return_type: wint_t
+    arguments:
+      - type: wint_t
diff --git a/libc/src/wctype/CMakeLists.txt b/libc/src/wctype/CMakeLists.txt
index 84c72c7e6b7ae..234676e20739e 100644
--- a/libc/src/wctype/CMakeLists.txt
+++ b/libc/src/wctype/CMakeLists.txt
@@ -153,3 +153,25 @@ add_entrypoint_object(
     libc.src.__support.wctype_impl
     libc.hdr.types.wctype_t
 )
+
+add_entrypoint_object(
+  towlower
+  SRCS
+    towlower.cpp
+  HDRS
+    towlower.h
+  DEPENDS
+    libc.src.__support.wctype_utils
+    libc.hdr.types.wint_t
+)
+
+add_entrypoint_object(
+  towupper
+  SRCS
+    towupper.cpp
+  HDRS
+    towupper.h
+  DEPENDS
+    libc.src.__support.wctype_utils
+    libc.hdr.types.wint_t
+)
diff --git a/libc/src/wctype/towlower.cpp b/libc/src/wctype/towlower.cpp
new file mode 100644
index 0000000000000..8c0667bfee426
--- /dev/null
+++ b/libc/src/wctype/towlower.cpp
@@ -0,0 +1,28 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of towlower.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/wctype/towlower.h"
+#include "hdr/types/wint_t.h"
+#include "src/__support/common.h"
+#include "src/__support/wctype_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(wint_t, towlower, (wint_t c)) {
+  if (c == static_cast<wint_t>(static_cast<wchar_t>(c))) {
+    return static_cast<wint_t>(internal::tolower(static_cast<wchar_t>(c)));
+  }
+  return c;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wctype/towlower.h b/libc/src/wctype/towlower.h
new file mode 100644
index 0000000000000..01ed7cf652680
--- /dev/null
+++ b/libc/src/wctype/towlower.h
@@ -0,0 +1,26 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation header for towlower.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_WCTYPE_TOWLOWER_H
+#define LLVM_LIBC_SRC_WCTYPE_TOWLOWER_H
+
+#include "hdr/types/wint_t.h"
+#include "src/__support/common.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+wint_t towlower(wint_t c);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_WCTYPE_TOWLOWER_H
diff --git a/libc/src/wctype/towupper.cpp b/libc/src/wctype/towupper.cpp
new file mode 100644
index 0000000000000..9d3ebc747f81c
--- /dev/null
+++ b/libc/src/wctype/towupper.cpp
@@ -0,0 +1,28 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of towupper.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/wctype/towupper.h"
+#include "hdr/types/wint_t.h"
+#include "src/__support/common.h"
+#include "src/__support/wctype_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(wint_t, towupper, (wint_t c)) {
+  if (c == static_cast<wint_t>(static_cast<wchar_t>(c))) {
+    return static_cast<wint_t>(internal::toupper(static_cast<wchar_t>(c)));
+  }
+  return c;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wctype/towupper.h b/libc/src/wctype/towupper.h
new file mode 100644
index 0000000000000..9a146c081c0cf
--- /dev/null
+++ b/libc/src/wctype/towupper.h
@@ -0,0 +1,26 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation header for towupper.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_WCTYPE_TOWUPPER_H
+#define LLVM_LIBC_SRC_WCTYPE_TOWUPPER_H
+
+#include "hdr/types/wint_t.h"
+#include "src/__support/common.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+wint_t towupper(wint_t c);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_WCTYPE_TOWUPPER_H
diff --git a/libc/test/src/wctype/CMakeLists.txt b/libc/test/src/wctype/CMakeLists.txt
index f7cdcc77482e4..79b5993e00180 100644
--- a/libc/test/src/wctype/CMakeLists.txt
+++ b/libc/test/src/wctype/CMakeLists.txt
@@ -42,7 +42,7 @@ add_libc_test(
     libc.src.wctype.iswgraph
 )
 
-add_libc_test(  
+add_libc_test(
   iswlower_test
   SUITE
     libc_wctype_unittests
@@ -142,3 +142,25 @@ add_libc_test(
   DEPENDS
     libc.src.wctype.wctype
 )
+
+add_libc_test(
+  towlower_test
+  SUITE
+    libc_wctype_unittests
+  SRCS
+    towlower_test.cpp
+  DEPENDS
+    libc.src.wctype.towlower
+    libc.src.__support.wctype_utils
+)
+
+add_libc_test(
+  towupper_test
+  SUITE
+    libc_wctype_unittests
+  SRCS
+    towupper_test.cpp
+  DEPENDS
+    libc.src.wctype.towupper
+    libc.src.__support.wctype_utils
+)
diff --git a/libc/test/src/wctype/towlower_test.cpp b/libc/test/src/wctype/towlower_test.cpp
new file mode 100644
index 0000000000000..68448e17aac26
--- /dev/null
+++ b/libc/test/src/wctype/towlower_test.cpp
@@ -0,0 +1,70 @@
+//===-- Unittests for towlower --------------------------------------------===//
+//
+// 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/wchar_macros.h" // for WEOF
+#include "src/__support/wctype_utils.h"
+#include "src/wctype/towlower.h"
+#include "test/UnitTest/Test.h"
+
+TEST(LlvmLibcTowLower, SimpleTest) {
+  // ASCII Conversions
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'A'), static_cast<wint_t>(L'a'));
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'Z'), static_cast<wint_t>(L'z'));
+
+  // ASCII Unchanged
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'a'), static_cast<wint_t>(L'a'));
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'z'), static_cast<wint_t>(L'z'));
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'1'), static_cast<wint_t>(L'1'));
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'\0'), static_cast<wint_t>(L'\0'));
+
+  // WEOF Test
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(WEOF), static_cast<wint_t>(WEOF));
+
+  // Boundary / Out-of-domain Tests (should return unchanged)
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(0xFFFF), static_cast<wint_t>(0xFFFF));
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(0x110000), static_cast<wint_t>(0x110000));
+
+  // Non-ASCII tests
+#if LIBC_CONF_WCTYPE_MODE == 1 // UTF8 Mode
+  // Greek conversions
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'Α'), static_cast<wint_t>(L'α')); // alpha
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'Ω'), static_cast<wint_t>(L'ω')); // omega
+
+  // Cyrillic conversions
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'А'), static_cast<wint_t>(L'а')); // A
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'Я'), static_cast<wint_t>(L'я')); // Ya
+
+  // Accented Latin
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'É'), static_cast<wint_t>(L'é'));
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'Ü'), static_cast<wint_t>(L'ü'));
+
+#if WCHAR_MAX > 0xFFFF
+  // Deseret (Unicode Plane 1) conversions
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'\U00010400'),
+            static_cast<wint_t>(L'\U00010428'));
+#endif
+
+  // Already lowercase / unchanged
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'α'), static_cast<wint_t>(L'α'));
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'а'), static_cast<wint_t>(L'а'));
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'é'), static_cast<wint_t>(L'é'));
+#else // ASCII Mode
+  // Non-ASCII should remain unchanged
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'Α'), static_cast<wint_t>(L'Α'));
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'Ω'), static_cast<wint_t>(L'Ω'));
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'А'), static_cast<wint_t>(L'А'));
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'Я'), static_cast<wint_t>(L'Я'));
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'É'), static_cast<wint_t>(L'É'));
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'Ü'), static_cast<wint_t>(L'Ü'));
+
+#if WCHAR_MAX > 0xFFFF
+  EXPECT_EQ(LIBC_NAMESPACE::towlower(L'\U00010400'),
+            static_cast<wint_t>(L'\U00010400'));
+#endif
+#endif
+}
diff --git a/libc/test/src/wctype/towupper_test.cpp b/libc/test/src/wctype/towupper_test.cpp
new file mode 100644
index 0000000000000..e85969a6fa742
--- /dev/null
+++ b/libc/test/src/wctype/towupper_test.cpp
@@ -0,0 +1,70 @@
+//===-- Unittests for towupper --------------------------------------------===//
+//
+// 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/wchar_macros.h" // for WEOF
+#include "src/__support/wctype_utils.h"
+#include "src/wctype/towupper.h"
+#include "test/UnitTest/Test.h"
+
+TEST(LlvmLibcTowUpper, SimpleTest) {
+  // ASCII Conversions
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'a'), static_cast<wint_t>(L'A'));
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'z'), static_cast<wint_t>(L'Z'));
+
+  // ASCII Unchanged
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'A'), static_cast<wint_t>(L'A'));
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'Z'), static_cast<wint_t>(L'Z'));
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'1'), static_cast<wint_t>(L'1'));
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'\0'), static_cast<wint_t>(L'\0'));
+
+  // WEOF Test
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(WEOF), static_cast<wint_t>(WEOF));
+
+  // Boundary / Out-of-domain Tests (should return unchanged)
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(0xFFFF), static_cast<wint_t>(0xFFFF));
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(0x110000), static_cast<wint_t>(0x110000));
+
+  // Non-ASCII tests
+#if LIBC_CONF_WCTYPE_MODE == 1 // UTF8 Mode
+  // Greek conversions
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'α'), static_cast<wint_t>(L'Α')); // alpha
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'ω'), static_cast<wint_t>(L'Ω')); // omega
+
+  // Cyrillic conversions
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'а'), static_cast<wint_t>(L'А')); // A
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'я'), static_cast<wint_t>(L'Я')); // Ya
+
+  // Accented Latin
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'é'), static_cast<wint_t>(L'É'));
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'ü'), static_cast<wint_t>(L'Ü'));
+
+#if WCHAR_MAX > 0xFFFF
+  // Deseret (Unicode Plane 1) conversions
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'\U00010428'),
+            static_cast<wint_t>(L'\U00010400'));
+#endif
+
+  // Already uppercase / unchanged
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'Α'), static_cast<wint_t>(L'Α'));
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'А'), static_cast<wint_t>(L'А'));
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'É'), static_cast<wint_t>(L'É'));
+#else // ASCII Mode
+  // Non-ASCII should remain unchanged
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'α'), static_cast<wint_t>(L'α'));
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'ω'), static_cast<wint_t>(L'ω'));
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'а'), static_cast<wint_t>(L'а'));
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'я'), static_cast<wint_t>(L'я'));
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'é'), static_cast<wint_t>(L'é'));
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'ü'), static_cast<wint_t>(L'ü'));
+
+#if WCHAR_MAX > 0xFFFF
+  EXPECT_EQ(LIBC_NAMESPACE::towupper(L'\U00010428'),
+            static_cast<wint_t>(L'\U00010428'));
+#endif
+#endif
+}



More information about the libc-commits mailing list