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

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

Assisted-by: Automated tooling, human reviewed.

---------

Co-authored-by: Muhammad Bassiouni <60100307+bassiounix@users.noreply.github.com>
This commit is contained in:
Michael Jones 2026-05-28 10:21:20 -07:00 committed by GitHub
parent 218a21cee5
commit f1c26bce3f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 468 additions and 9 deletions

View file

@ -417,6 +417,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

View file

@ -421,6 +421,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

View file

@ -481,6 +481,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

View file

@ -1,4 +1,6 @@
header: wctype.h
standards:
- stdc
types:
- type_name: wint_t
- type_name: wctype_t
@ -88,3 +90,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

View file

@ -165,6 +165,7 @@ list(APPEND wctype_utils_deps
if ("${LIBC_CONF_WCTYPE_MODE}" STREQUAL "LIBC_WCTYPE_MODE_UTF8")
list(APPEND wctype_utils_deps
libc.src.__support.wctype.wctype_classification_utils
libc.src.__support.wctype.wctype_conversion_utils
)
endif()

View file

@ -49,3 +49,21 @@ add_object_library(
libc.src.__support.CPP.limits
libc.src.__support.libc_assert
)
add_object_library(
wctype_conversion_utils
HDRS
wctype_conversion_utils.h
SRCS
wctype_conversion_utils.cpp
COMPILE_OPTIONS
"-DLIBC_ENABLE_CONSTEXPR=1"
DEPENDS
libc.hdr.types.wchar_t
libc.hdr.types.wint_t
libc.src.__support.macros.attributes
libc.src.__support.macros.config
.lower_to_upper
.upper_to_lower
)

View file

@ -9,8 +9,6 @@
#ifndef LLVM_LIBC_SRC___SUPPORT_WCTYPE_PERFECT_HASH_MAP_H
#define LLVM_LIBC_SRC___SUPPORT_WCTYPE_PERFECT_HASH_MAP_H
#define LIBC_ENABLE_CONSTEXPR 1
#include "hdr/types/size_t.h"
#include "hdr/types/wint_t.h"
#include "src/__support/CPP/array.h"
@ -29,8 +27,6 @@
#include "src/__support/OSUtil/io.h"
#endif
#undef LIBC_ENABLE_CONSTEXPR
namespace LIBC_NAMESPACE_DECL {
namespace wctype_internal {

View file

@ -0,0 +1,46 @@
//===----------------------------------------------------------------------===//
//
// 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 wctype conversion helpers.
///
//===----------------------------------------------------------------------===//
#include "wctype_conversion_utils.h"
#include "lower_to_upper.h"
#include "upper_to_lower.h"
namespace LIBC_NAMESPACE_DECL {
namespace wctype_internal {
wint_t tolower(wchar_t wch) {
if (auto mapped = UPPER_TO_LOWER_MAP.find(wch)) {
return mapped.value();
}
#if WINT_MAX > 0xFFFF
if (auto mapped = UPPER_TO_LOWER_MAP_32.find(wch)) {
return mapped.value();
}
#endif
return wch;
}
wint_t toupper(wchar_t wch) {
if (auto mapped = LOWER_TO_UPPER_MAP.find(wch)) {
return mapped.value();
}
#if WINT_MAX > 0xFFFF
if (auto mapped = LOWER_TO_UPPER_MAP_32.find(wch)) {
return mapped.value();
}
#endif
return wch;
}
} // namespace wctype_internal
} // namespace LIBC_NAMESPACE_DECL

View file

@ -0,0 +1,34 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Interface for wctype conversion functions.
///
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_SRC___SUPPORT_WCTYPE_WCTYPE_CONVERSION_UTILS_H
#define LLVM_LIBC_SRC___SUPPORT_WCTYPE_WCTYPE_CONVERSION_UTILS_H
#include "hdr/types/wchar_t.h"
#include "hdr/types/wint_t.h"
#include "src/__support/macros/attributes.h"
#include "src/__support/macros/config.h"
namespace LIBC_NAMESPACE_DECL {
namespace wctype_internal {
// Helper functions for non-ASCII case conversions.
// These are implemented in wctype_conversion_utils.cpp using the generated
// maps.
wint_t tolower(wchar_t wch);
wint_t toupper(wchar_t wch);
} // namespace wctype_internal
} // namespace LIBC_NAMESPACE_DECL
#endif // LLVM_LIBC_SRC___SUPPORT_WCTYPE_WCTYPE_CONVERSION_UTILS_H

View file

@ -22,6 +22,7 @@
#if LIBC_CONF_WCTYPE_MODE == LIBC_WCTYPE_MODE_UTF8
#include "src/__support/wctype/wctype_classification_utils.h"
#include "src/__support/wctype/wctype_conversion_utils.h"
#endif
namespace LIBC_NAMESPACE_DECL {
@ -585,8 +586,7 @@ LIBC_INLINE constexpr wchar_t tolower(wchar_t wch) {
if (static_cast<uint32_t>(wch) < 128) {
return ascii::tolower(wch);
}
// TODO: Add UTF8 implementation.
return wch;
return static_cast<wchar_t>(wctype_internal::tolower(wch));
#endif
}
@ -597,8 +597,7 @@ LIBC_INLINE constexpr wchar_t toupper(wchar_t wch) {
if (static_cast<uint32_t>(wch) < 128) {
return ascii::toupper(wch);
}
// TODO: Add UTF8 implementation.
return wch;
return static_cast<wchar_t>(wctype_internal::toupper(wch));
#endif
}

View file

@ -153,3 +153,27 @@ 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.hdr.types.wint_t
libc.src.__support.common
libc.src.__support.wctype_utils
)
add_entrypoint_object(
towupper
SRCS
towupper.cpp
HDRS
towupper.h
DEPENDS
libc.hdr.types.wint_t
libc.src.__support.common
libc.src.__support.wctype_utils
)

View file

@ -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
/// 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

View file

@ -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

View file

@ -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
/// 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

View file

@ -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

View file

@ -23,6 +23,7 @@ add_libc_test(
SRCS
wctype_perfect_hash_test.cpp
COMPILE_OPTIONS
-DLIBC_ENABLE_CONSTEXPR=1
${CONSTEXPR_FLAGS}
DEPENDS
libc.src.__support.wctype.lower_to_upper

View file

@ -9,6 +9,7 @@
#include "hdr/types/wctype_t.h"
#include "src/__support/macros/config.h"
#include "src/__support/wctype/wctype_classification_utils.h"
#include "src/__support/wctype/wctype_conversion_utils.h"
#include "test/UnitTest/Test.h"
namespace {
@ -32,6 +33,10 @@ namespace utf8_mode {
namespace LIBC_NAMESPACE_DECL {
using ::LIBC_NAMESPACE::lookup_properties;
using ::LIBC_NAMESPACE::PropertyFlag;
namespace wctype_internal {
using ::LIBC_NAMESPACE::wctype_internal::tolower;
using ::LIBC_NAMESPACE::wctype_internal::toupper;
} // namespace wctype_internal
namespace cpp = ::LIBC_NAMESPACE::cpp;
} // namespace LIBC_NAMESPACE_DECL

View file

@ -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,27 @@ add_libc_test(
DEPENDS
libc.src.wctype.wctype
)
add_libc_test(
towlower_test
SUITE
libc_wctype_unittests
SRCS
towlower_test.cpp
DEPENDS
libc.hdr.wchar_macros
libc.src.wctype.towlower
libc.src.__support.wctype_utils
)
add_libc_test(
towupper_test
SUITE
libc_wctype_unittests
SRCS
towupper_test.cpp
DEPENDS
libc.hdr.wchar_macros
libc.src.wctype.towupper
libc.src.__support.wctype_utils
)

View file

@ -6,6 +6,7 @@
//
//===----------------------------------------------------------------------===//
#include "src/__support/wctype_utils.h"
#include "src/wctype/iswprint.h"
#include "test/UnitTest/Test.h"
@ -15,7 +16,21 @@ TEST(LlvmLibciswprint, SimpleTest) {
if (' ' <= ch && ch <= '~') {
EXPECT_NE(LIBC_NAMESPACE::iswprint(ch), 0);
} else {
#if LIBC_CONF_WCTYPE_MODE == LIBC_WCTYPE_MODE_UTF8
// In UTF-8 mode, characters above 127 can be printable.
if (ch < 128) {
EXPECT_EQ(LIBC_NAMESPACE::iswprint(ch), 0);
}
#else
// In ASCII mode, everything outside ASCII printable is not printable.
EXPECT_EQ(LIBC_NAMESPACE::iswprint(ch), 0);
#endif
}
}
#if LIBC_CONF_WCTYPE_MODE == LIBC_WCTYPE_MODE_UTF8
EXPECT_NE(LIBC_NAMESPACE::iswprint(L'á'), 0);
#else
EXPECT_EQ(LIBC_NAMESPACE::iswprint(L'á'), 0);
#endif
}

View file

@ -0,0 +1,85 @@
//===----------------------------------------------------------------------===//
//
// 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
/// Unit tests for towlower.
///
//===----------------------------------------------------------------------===//
#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));
#if WCHAR_MAX > 0xFFFF
EXPECT_EQ(LIBC_NAMESPACE::towlower(0x110000), static_cast<wint_t>(0x110000));
#endif
// Non-ASCII Lowercase (Already lowercase: should remain unchanged
// unconditionally in BOTH modes)
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 LIBC_CONF_WCTYPE_MODE == LIBC_WCTYPE_MODE_UTF8
TEST(LlvmLibcTowLower, NonAsciiTestUtf8) {
// 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
}
#endif
#if LIBC_CONF_WCTYPE_MODE == LIBC_WCTYPE_MODE_ASCII
TEST(LlvmLibcTowLower, NonAsciiTestAscii) {
// Non-ASCII uppercase characters must return unchanged under ASCII-only mode
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

View file

@ -0,0 +1,85 @@
//===----------------------------------------------------------------------===//
//
// 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
/// Unit tests for towupper.
///
//===----------------------------------------------------------------------===//
#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));
#if WCHAR_MAX > 0xFFFF
EXPECT_EQ(LIBC_NAMESPACE::towupper(0x110000), static_cast<wint_t>(0x110000));
#endif
// Non-ASCII Uppercase (Already uppercase: should remain unchanged
// unconditionally in BOTH modes)
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 LIBC_CONF_WCTYPE_MODE == LIBC_WCTYPE_MODE_UTF8
TEST(LlvmLibcTowUpper, NonAsciiTestUtf8) {
// 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
}
#endif
#if LIBC_CONF_WCTYPE_MODE == LIBC_WCTYPE_MODE_ASCII
TEST(LlvmLibcTowUpper, NonAsciiTestAscii) {
// Non-ASCII lowercase characters must return unchanged under ASCII-only mode
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