[libc-commits] [libc] [libc] Add wcscasecmp and wcsncasecmp implementations. (PR #222154)

Alex Strelnikov via libc-commits libc-commits at lists.llvm.org
Wed Sep 9 10:53:48 PDT 2026


https://github.com/strel-12 updated https://github.com/llvm/llvm-project/pull/222154

>From 2595c89896ce29110be24b8afea9ddefbe612782 Mon Sep 17 00:00:00 2001
From: Alex Strelnikov <strel at google.com>
Date: Tue, 8 Sep 2026 21:17:49 +0000
Subject: [PATCH 1/3] Add wcscasecmp and wcsncasecmp.

---
 libc/config/linux/aarch64/entrypoints.txt     |  2 +
 libc/config/linux/x86_64/entrypoints.txt      |  2 +
 libc/include/wchar.yaml                       | 15 ++++
 libc/src/__support/wctype_utils.h             | 10 +++
 libc/src/wchar/CMakeLists.txt                 | 29 +++++++
 libc/src/wchar/wcscasecmp.cpp                 | 38 +++++++++
 libc/src/wchar/wcscasecmp.h                   | 26 ++++++
 libc/src/wchar/wcsncasecmp.cpp                | 42 ++++++++++
 libc/src/wchar/wcsncasecmp.h                  | 27 +++++++
 libc/test/src/__support/wctype_utils_test.cpp | 18 +++++
 libc/test/src/wchar/CMakeLists.txt            | 20 +++++
 libc/test/src/wchar/wcscasecmp_test.cpp       | 69 ++++++++++++++++
 libc/test/src/wchar/wcsncasecmp_test.cpp      | 80 +++++++++++++++++++
 libc/utils/docgen/wchar.yaml                  |  4 +
 14 files changed, 382 insertions(+)
 create mode 100644 libc/src/wchar/wcscasecmp.cpp
 create mode 100644 libc/src/wchar/wcscasecmp.h
 create mode 100644 libc/src/wchar/wcsncasecmp.cpp
 create mode 100644 libc/src/wchar/wcsncasecmp.h
 create mode 100644 libc/test/src/wchar/wcscasecmp_test.cpp
 create mode 100644 libc/test/src/wchar/wcsncasecmp_test.cpp

diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index fbd9239728bac..dd668aaa0e9f9 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -454,6 +454,8 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.unistd.write
 
     # wchar.h entrypoints
+    libc.src.wchar.wcscasecmp
+    libc.src.wchar.wcsncasecmp
     libc.src.wchar.wcslen
     libc.src.wchar.wctob
 
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index e6aeb3abe0672..551ab19c50af5 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -494,6 +494,8 @@ set(TARGET_LIBC_ENTRYPOINTS
 
     # wchar.h entrypoints
     libc.src.wchar.btowc
+    libc.src.wchar.wcscasecmp
+    libc.src.wchar.wcsncasecmp
     libc.src.wchar.wcslen
     libc.src.wchar.wcsnlen
     libc.src.wchar.wctob
diff --git a/libc/include/wchar.yaml b/libc/include/wchar.yaml
index 1017eb827cf45..2b321f6502ff1 100644
--- a/libc/include/wchar.yaml
+++ b/libc/include/wchar.yaml
@@ -117,6 +117,21 @@ functions:
     arguments:
       - type: const wchar_t *
       - type: const wchar_t *
+  - name: wcscasecmp
+    standards:
+      - posix
+    return_type: int
+    arguments:
+      - type: const wchar_t *
+      - type: const wchar_t *
+  - name: wcsncasecmp
+    standards:
+      - posix
+    return_type: int
+    arguments:
+      - type: const wchar_t *
+      - type: const wchar_t *
+      - type: size_t
   - name: wcscoll
     standards:
       - stdc
diff --git a/libc/src/__support/wctype_utils.h b/libc/src/__support/wctype_utils.h
index 4d0ecd2c1c0dc..9b2eb784e257c 100644
--- a/libc/src/__support/wctype_utils.h
+++ b/libc/src/__support/wctype_utils.h
@@ -10,6 +10,7 @@
 #define LLVM_LIBC_SRC___SUPPORT_WCTYPE_UTILS_H
 
 #include "hdr/types/wchar_t.h"
+#include "src/__support/CPP/bit.h"
 #include "src/__support/macros/attributes.h" // LIBC_INLINE
 #include "src/__support/macros/config.h"
 
@@ -796,6 +797,15 @@ is_char_or_wchar(wchar_t ch, [[maybe_unused]] char, wchar_t wc_value) {
   return (ch == wc_value);
 }
 
+// Returns a three-way comparison between two wide character code points.
+LIBC_INLINE int threeway_cmp_single(wchar_t left, wchar_t right) {
+  // Valid UTF-32 code points are in [0, 0x10FFFF]. If invalid code points were
+  // treated as UB, the comparison below could instead be done in 32 bits.
+  uint64_t diff = static_cast<uint64_t>(left) - static_cast<uint64_t>(right);
+  return cpp::bit_cast<int32_t>(static_cast<uint32_t>(diff >> 32) |
+                                static_cast<uint32_t>(diff & 0xFFFF));
+}
+
 } // namespace internal
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/src/wchar/CMakeLists.txt b/libc/src/wchar/CMakeLists.txt
index 722619d652500..938b7ad2e4c73 100644
--- a/libc/src/wchar/CMakeLists.txt
+++ b/libc/src/wchar/CMakeLists.txt
@@ -373,6 +373,35 @@ add_entrypoint_object(
     libc.hdr.types.size_t
 )
 
+add_entrypoint_object(
+  wcscasecmp
+  SRCS
+    wcscasecmp.cpp
+  HDRS
+    wcscasecmp.h
+  DEPENDS
+    libc.hdr.types.wchar_t
+    libc.src.__support.common
+    libc.src.__support.macros.config
+    libc.src.__support.macros.null_check
+    libc.src.__support.wctype_utils
+)
+
+add_entrypoint_object(
+  wcsncasecmp
+  SRCS
+    wcsncasecmp.cpp
+  HDRS
+    wcsncasecmp.h
+  DEPENDS
+    libc.hdr.types.size_t
+    libc.hdr.types.wchar_t
+    libc.src.__support.common
+    libc.src.__support.macros.config
+    libc.src.__support.macros.null_check
+    libc.src.__support.wctype_utils
+)
+
 add_entrypoint_object(
   wcsspn
   SRCS
diff --git a/libc/src/wchar/wcscasecmp.cpp b/libc/src/wchar/wcscasecmp.cpp
new file mode 100644
index 0000000000000..29c938c549a4a
--- /dev/null
+++ b/libc/src/wchar/wcscasecmp.cpp
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 wcscasecmp function implementation.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/wchar/wcscasecmp.h"
+
+#include "hdr/types/wchar_t.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
+#include "src/__support/wctype_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, wcscasecmp,
+                   (const wchar_t *left, const wchar_t *right)) {
+  LIBC_CRASH_ON_NULLPTR(left);
+  LIBC_CRASH_ON_NULLPTR(right);
+
+  for (;; ++left, ++right) {
+    wchar_t lc = *left;
+    if (lc == L'\0' || internal::tolower(lc) != internal::tolower(*right))
+      break;
+  }
+  return internal::threeway_cmp_single(internal::tolower(*left),
+                                       internal::tolower(*right));
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/wcscasecmp.h b/libc/src/wchar/wcscasecmp.h
new file mode 100644
index 0000000000000..51a6f1b415c56
--- /dev/null
+++ b/libc/src/wchar/wcscasecmp.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
+/// This file contains the wcscasecmp function declaration.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_WCHAR_WCSCASECMP_H
+#define LLVM_LIBC_SRC_WCHAR_WCSCASECMP_H
+
+#include "hdr/types/wchar_t.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int wcscasecmp(const wchar_t *left, const wchar_t *right);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_WCHAR_WCSCASECMP_H
diff --git a/libc/src/wchar/wcsncasecmp.cpp b/libc/src/wchar/wcsncasecmp.cpp
new file mode 100644
index 0000000000000..2fc29ff972896
--- /dev/null
+++ b/libc/src/wchar/wcsncasecmp.cpp
@@ -0,0 +1,42 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 wcsncasecmp function implementation.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/wchar/wcsncasecmp.h"
+
+#include "hdr/types/size_t.h"
+#include "hdr/types/wchar_t.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
+#include "src/__support/wctype_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, wcsncasecmp,
+                   (const wchar_t *left, const wchar_t *right, size_t n)) {
+  LIBC_CRASH_ON_NULLPTR(left);
+  LIBC_CRASH_ON_NULLPTR(right);
+
+  if (n == 0)
+    return 0;
+
+  for (; n > 1; --n, ++left, ++right) {
+    wchar_t lc = *left;
+    if (lc == L'\0' || internal::tolower(lc) != internal::tolower(*right))
+      break;
+  }
+  return internal::threeway_cmp_single(internal::tolower(*left),
+                                       internal::tolower(*right));
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/wcsncasecmp.h b/libc/src/wchar/wcsncasecmp.h
new file mode 100644
index 0000000000000..754ac54ec2ae2
--- /dev/null
+++ b/libc/src/wchar/wcsncasecmp.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 wcsncasecmp function declaration.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_WCHAR_WCSNCASECMP_H
+#define LLVM_LIBC_SRC_WCHAR_WCSNCASECMP_H
+
+#include "hdr/types/size_t.h"
+#include "hdr/types/wchar_t.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int wcsncasecmp(const wchar_t *left, const wchar_t *right, size_t n);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_WCHAR_WCSNCASECMP_H
diff --git a/libc/test/src/__support/wctype_utils_test.cpp b/libc/test/src/__support/wctype_utils_test.cpp
index b2140ab72a3be..ae4770a7765a2 100644
--- a/libc/test/src/__support/wctype_utils_test.cpp
+++ b/libc/test/src/__support/wctype_utils_test.cpp
@@ -553,4 +553,22 @@ TEST(LlvmLibcWctypeUtilsTest, IsCntrlUtf8) {
   }
 }
 
+TEST(LlvmLibcWctypeUtilsTest, ThreewayCmpSingle) {
+  using utf8_mode::LIBC_NAMESPACE::internal::threeway_cmp_single;
+
+  EXPECT_EQ(threeway_cmp_single(L'A', L'A'), 0);
+
+  EXPECT_LT(threeway_cmp_single(wchar_t{67}, wchar_t{120}), 0);
+  EXPECT_GT(threeway_cmp_single(wchar_t{120}, wchar_t{67}), 0);
+
+  EXPECT_LT(threeway_cmp_single(wchar_t{1}, WCHAR_MAX), 0);
+  EXPECT_GT(threeway_cmp_single(WCHAR_MAX, wchar_t{1}), 0);
+
+  EXPECT_LT(threeway_cmp_single(WCHAR_MIN, wchar_t{1}), 0);
+  EXPECT_GT(threeway_cmp_single(wchar_t{1}, WCHAR_MIN), 0);
+
+  EXPECT_LT(threeway_cmp_single(WCHAR_MIN, WCHAR_MAX), 0);
+  EXPECT_GT(threeway_cmp_single(WCHAR_MAX, WCHAR_MIN), 0);
+}
+
 } // namespace
diff --git a/libc/test/src/wchar/CMakeLists.txt b/libc/test/src/wchar/CMakeLists.txt
index 1183d5c44d685..85ebc6e41fe0d 100644
--- a/libc/test/src/wchar/CMakeLists.txt
+++ b/libc/test/src/wchar/CMakeLists.txt
@@ -221,6 +221,26 @@ add_libc_test(
     libc.src.wchar.wcsncmp
 )
 
+add_libc_test(
+  wcscasecmp_test
+  SUITE
+    libc_wchar_unittests
+  SRCS
+    wcscasecmp_test.cpp
+  DEPENDS
+    libc.src.wchar.wcscasecmp
+)
+
+add_libc_test(
+  wcsncasecmp_test
+  SUITE
+    libc_wchar_unittests
+  SRCS
+    wcsncasecmp_test.cpp
+  DEPENDS
+    libc.src.wchar.wcsncasecmp
+)
+
 add_libc_test(
   wcscmp_test
   SUITE
diff --git a/libc/test/src/wchar/wcscasecmp_test.cpp b/libc/test/src/wchar/wcscasecmp_test.cpp
new file mode 100644
index 0000000000000..4a226fb048e33
--- /dev/null
+++ b/libc/test/src/wchar/wcscasecmp_test.cpp
@@ -0,0 +1,69 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 unit tests for wcscasecmp.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/wchar/wcscasecmp.h"
+#include "test/UnitTest/Test.h"
+
+TEST(LlvmLibcWcscasecmpTest, EmptyStrings) {
+  EXPECT_EQ(LIBC_NAMESPACE::wcscasecmp(L"", L""), 0);
+}
+
+TEST(LlvmLibcWcscasecmpTest, EmptyVsNonEmptyStrings) {
+  EXPECT_LT(LIBC_NAMESPACE::wcscasecmp(L"", L"a"), 0);
+  EXPECT_GT(LIBC_NAMESPACE::wcscasecmp(L"a", L""), 0);
+}
+
+TEST(LlvmLibcWcscasecmpTest, Substrings) {
+  EXPECT_GT(LIBC_NAMESPACE::wcscasecmp(L"abc", L"ab"), 0);
+  EXPECT_GT(LIBC_NAMESPACE::wcscasecmp(L"abc", L"a"), 0);
+  EXPECT_GT(LIBC_NAMESPACE::wcscasecmp(L"abc", L""), 0);
+
+  EXPECT_LT(LIBC_NAMESPACE::wcscasecmp(L"ab", L"abc"), 0);
+  EXPECT_LT(LIBC_NAMESPACE::wcscasecmp(L"a", L"abc"), 0);
+  EXPECT_LT(LIBC_NAMESPACE::wcscasecmp(L"", L"abc"), 0);
+}
+
+TEST(LlvmLibcWcscasecmpTest, MatchingStringsIgnoreCase) {
+  EXPECT_EQ(LIBC_NAMESPACE::wcscasecmp(L"abc", L"abc"), 0);
+
+  EXPECT_EQ(LIBC_NAMESPACE::wcscasecmp(L"Abc", L"abc"), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::wcscasecmp(L"aBc", L"abc"), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::wcscasecmp(L"abC", L"abc"), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::wcscasecmp(L"ABC", L"abc"), 0);
+
+  EXPECT_EQ(LIBC_NAMESPACE::wcscasecmp(L"abc", L"Abc"), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::wcscasecmp(L"abc", L"aBc"), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::wcscasecmp(L"abc", L"abC"), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::wcscasecmp(L"abc", L"ABC"), 0);
+}
+
+TEST(LlvmLibcWcscasecmpTest, NonMatchingStrings) {
+  EXPECT_GT(LIBC_NAMESPACE::wcscasecmp(L"Xbc", L"abc"), 0);
+  EXPECT_GT(LIBC_NAMESPACE::wcscasecmp(L"aXc", L"abc"), 0);
+  EXPECT_GT(LIBC_NAMESPACE::wcscasecmp(L"abX", L"abc"), 0);
+
+  EXPECT_LT(LIBC_NAMESPACE::wcscasecmp(L"abc", L"Xbc"), 0);
+  EXPECT_LT(LIBC_NAMESPACE::wcscasecmp(L"abc", L"aXc"), 0);
+  EXPECT_LT(LIBC_NAMESPACE::wcscasecmp(L"abc", L"abX"), 0);
+}
+
+#if defined(LIBC_ADD_NULL_CHECKS)
+
+TEST(LlvmLibcWcscasecmpTest, CrashOnNullPtr) {
+  EXPECT_DEATH([] { LIBC_NAMESPACE::wcscasecmp(L"hello", nullptr); },
+               WITH_SIGNAL(-1));
+  EXPECT_DEATH([] { LIBC_NAMESPACE::wcscasecmp(nullptr, L"hello"); },
+               WITH_SIGNAL(-1));
+}
+
+#endif // LIBC_ADD_NULL_CHECKS
diff --git a/libc/test/src/wchar/wcsncasecmp_test.cpp b/libc/test/src/wchar/wcsncasecmp_test.cpp
new file mode 100644
index 0000000000000..61a1fae6d08fb
--- /dev/null
+++ b/libc/test/src/wchar/wcsncasecmp_test.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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file contains unit tests for wcsncasecmp.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/limits_macros.h"
+#include "src/wchar/wcsncasecmp.h"
+#include "test/UnitTest/Test.h"
+
+TEST(LlvmLibcWcsncasecmpTest, EmptyStrings) {
+  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"", L"", 0), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"", L"", INT_MAX), 0);
+}
+
+TEST(LlvmLibcWcsncasecmpTest, EmptyVsNonEmptyStringsUnlimited) {
+  EXPECT_LT(LIBC_NAMESPACE::wcsncasecmp(L"", L"a", INT_MAX), 0);
+  EXPECT_GT(LIBC_NAMESPACE::wcsncasecmp(L"a", L"", INT_MAX), 0);
+}
+
+TEST(LlvmLibcWcsncasecmpTest, SubstringsUnlimited) {
+  EXPECT_GT(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"ab", INT_MAX), 0);
+  EXPECT_GT(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"a", INT_MAX), 0);
+  EXPECT_GT(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"", INT_MAX), 0);
+
+  EXPECT_LT(LIBC_NAMESPACE::wcsncasecmp(L"ab", L"abc", INT_MAX), 0);
+  EXPECT_LT(LIBC_NAMESPACE::wcsncasecmp(L"a", L"abc", INT_MAX), 0);
+  EXPECT_LT(LIBC_NAMESPACE::wcsncasecmp(L"", L"abc", INT_MAX), 0);
+}
+
+TEST(LlvmLibcWcsncasecmpTest, MatchingStringsIgnoreCaseUnlimited) {
+  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"abc", INT_MAX), 0);
+
+  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"Abc", L"abc", INT_MAX), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"aBc", L"abc", INT_MAX), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"abC", L"abc", INT_MAX), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"ABC", L"abc", INT_MAX), 0);
+
+  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"Abc", INT_MAX), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"aBc", INT_MAX), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"abC", INT_MAX), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"ABC", INT_MAX), 0);
+}
+
+TEST(LlvmLibcWcsncasecmpTest, NonMatchingStringsUnlimited) {
+  EXPECT_GT(LIBC_NAMESPACE::wcsncasecmp(L"Xbc", L"abc", INT_MAX), 0);
+  EXPECT_GT(LIBC_NAMESPACE::wcsncasecmp(L"aXc", L"abc", INT_MAX), 0);
+  EXPECT_GT(LIBC_NAMESPACE::wcsncasecmp(L"abX", L"abc", INT_MAX), 0);
+
+  EXPECT_LT(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"Xbc", INT_MAX), 0);
+  EXPECT_LT(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"aXc", INT_MAX), 0);
+  EXPECT_LT(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"abX", INT_MAX), 0);
+}
+
+TEST(LlvmLibcWcsncasecmpTest, CompareAtMostStartingMatch) {
+  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"", 0), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"", L"def", 0), 0);
+
+  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"def", 0), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"ade", 1), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"abd", 2), 0);
+}
+
+#if defined(LIBC_ADD_NULL_CHECKS)
+
+TEST(LlvmLibcWcsncasecmpTest, CrashOnNullPtr) {
+  EXPECT_DEATH([] { LIBC_NAMESPACE::wcsncasecmp(L"hello", nullptr, 1); },
+               WITH_SIGNAL(-1));
+  EXPECT_DEATH([] { LIBC_NAMESPACE::wcsncasecmp(nullptr, L"hello", 1); },
+               WITH_SIGNAL(-1));
+}
+
+#endif // LIBC_ADD_NULL_CHECKS
diff --git a/libc/utils/docgen/wchar.yaml b/libc/utils/docgen/wchar.yaml
index dcc8963efdd35..85bfbe31085d1 100644
--- a/libc/utils/docgen/wchar.yaml
+++ b/libc/utils/docgen/wchar.yaml
@@ -57,6 +57,10 @@ functions:
     c-definition: 7.31.4.6.2
   wcscmp:
     c-definition: 7.31.4.5.2
+  wcscasecmp:
+    in-latest-posix: ''
+  wcsncasecmp:
+    in-latest-posix: ''
   wcscoll:
     c-definition: 7.31.4.5.3
   wcscpy:

>From 9972c084a3ee8688f9e0968a7fe6c53f56c5c08c Mon Sep 17 00:00:00 2001
From: Alex Strelnikov <strel at google.com>
Date: Wed, 9 Sep 2026 13:47:38 +0000
Subject: [PATCH 2/3] Switch if-based threeway comparison.

---
 libc/src/__support/wctype_utils.h | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/libc/src/__support/wctype_utils.h b/libc/src/__support/wctype_utils.h
index 9b2eb784e257c..633c9139f7369 100644
--- a/libc/src/__support/wctype_utils.h
+++ b/libc/src/__support/wctype_utils.h
@@ -800,10 +800,12 @@ is_char_or_wchar(wchar_t ch, [[maybe_unused]] char, wchar_t wc_value) {
 // Returns a three-way comparison between two wide character code points.
 LIBC_INLINE int threeway_cmp_single(wchar_t left, wchar_t right) {
   // Valid UTF-32 code points are in [0, 0x10FFFF]. If invalid code points were
-  // treated as UB, the comparison below could instead be done in 32 bits.
-  uint64_t diff = static_cast<uint64_t>(left) - static_cast<uint64_t>(right);
-  return cpp::bit_cast<int32_t>(static_cast<uint32_t>(diff >> 32) |
-                                static_cast<uint32_t>(diff & 0xFFFF));
+  // treated as UB, the comparison below could done with 32-bit arithmetic.
+  if (left == right)
+    return 0;
+  if (left < right)
+    return -1;
+  return 1;
 }
 
 } // namespace internal

>From f37c34651701e2e967ba87cc2ea73a1483624237 Mon Sep 17 00:00:00 2001
From: Alex Strelnikov <strel at google.com>
Date: Wed, 9 Sep 2026 17:53:25 +0000
Subject: [PATCH 3/3] Add some non-ascii characters

---
 libc/test/src/wchar/wcscasecmp_test.cpp  | 24 +++++++++---------
 libc/test/src/wchar/wcsncasecmp_test.cpp | 32 ++++++++++++------------
 2 files changed, 28 insertions(+), 28 deletions(-)

diff --git a/libc/test/src/wchar/wcscasecmp_test.cpp b/libc/test/src/wchar/wcscasecmp_test.cpp
index 4a226fb048e33..e5093edeefc83 100644
--- a/libc/test/src/wchar/wcscasecmp_test.cpp
+++ b/libc/test/src/wchar/wcscasecmp_test.cpp
@@ -24,13 +24,13 @@ TEST(LlvmLibcWcscasecmpTest, EmptyVsNonEmptyStrings) {
 }
 
 TEST(LlvmLibcWcscasecmpTest, Substrings) {
-  EXPECT_GT(LIBC_NAMESPACE::wcscasecmp(L"abc", L"ab"), 0);
-  EXPECT_GT(LIBC_NAMESPACE::wcscasecmp(L"abc", L"a"), 0);
-  EXPECT_GT(LIBC_NAMESPACE::wcscasecmp(L"abc", L""), 0);
+  EXPECT_GT(LIBC_NAMESPACE::wcscasecmp(L"aβc", L"aβ"), 0);
+  EXPECT_GT(LIBC_NAMESPACE::wcscasecmp(L"aβc", L"a"), 0);
+  EXPECT_GT(LIBC_NAMESPACE::wcscasecmp(L"aβc", L""), 0);
 
-  EXPECT_LT(LIBC_NAMESPACE::wcscasecmp(L"ab", L"abc"), 0);
-  EXPECT_LT(LIBC_NAMESPACE::wcscasecmp(L"a", L"abc"), 0);
-  EXPECT_LT(LIBC_NAMESPACE::wcscasecmp(L"", L"abc"), 0);
+  EXPECT_LT(LIBC_NAMESPACE::wcscasecmp(L"aβ", L"aβc"), 0);
+  EXPECT_LT(LIBC_NAMESPACE::wcscasecmp(L"a", L"aβc"), 0);
+  EXPECT_LT(LIBC_NAMESPACE::wcscasecmp(L"", L"aβc"), 0);
 }
 
 TEST(LlvmLibcWcscasecmpTest, MatchingStringsIgnoreCase) {
@@ -48,13 +48,13 @@ TEST(LlvmLibcWcscasecmpTest, MatchingStringsIgnoreCase) {
 }
 
 TEST(LlvmLibcWcscasecmpTest, NonMatchingStrings) {
-  EXPECT_GT(LIBC_NAMESPACE::wcscasecmp(L"Xbc", L"abc"), 0);
-  EXPECT_GT(LIBC_NAMESPACE::wcscasecmp(L"aXc", L"abc"), 0);
-  EXPECT_GT(LIBC_NAMESPACE::wcscasecmp(L"abX", L"abc"), 0);
+  EXPECT_GT(LIBC_NAMESPACE::wcscasecmp(L"Δbc", L"abc"), 0);
+  EXPECT_GT(LIBC_NAMESPACE::wcscasecmp(L"aΔc", L"abc"), 0);
+  EXPECT_GT(LIBC_NAMESPACE::wcscasecmp(L"abΔ", L"abc"), 0);
 
-  EXPECT_LT(LIBC_NAMESPACE::wcscasecmp(L"abc", L"Xbc"), 0);
-  EXPECT_LT(LIBC_NAMESPACE::wcscasecmp(L"abc", L"aXc"), 0);
-  EXPECT_LT(LIBC_NAMESPACE::wcscasecmp(L"abc", L"abX"), 0);
+  EXPECT_LT(LIBC_NAMESPACE::wcscasecmp(L"abc", L"Δbc"), 0);
+  EXPECT_LT(LIBC_NAMESPACE::wcscasecmp(L"abc", L"aΔc"), 0);
+  EXPECT_LT(LIBC_NAMESPACE::wcscasecmp(L"abc", L"abΔ"), 0);
 }
 
 #if defined(LIBC_ADD_NULL_CHECKS)
diff --git a/libc/test/src/wchar/wcsncasecmp_test.cpp b/libc/test/src/wchar/wcsncasecmp_test.cpp
index 61a1fae6d08fb..1989aabd61fa7 100644
--- a/libc/test/src/wchar/wcsncasecmp_test.cpp
+++ b/libc/test/src/wchar/wcsncasecmp_test.cpp
@@ -26,13 +26,13 @@ TEST(LlvmLibcWcsncasecmpTest, EmptyVsNonEmptyStringsUnlimited) {
 }
 
 TEST(LlvmLibcWcsncasecmpTest, SubstringsUnlimited) {
-  EXPECT_GT(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"ab", INT_MAX), 0);
-  EXPECT_GT(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"a", INT_MAX), 0);
-  EXPECT_GT(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"", INT_MAX), 0);
+  EXPECT_GT(LIBC_NAMESPACE::wcsncasecmp(L"aβc", L"aβ", INT_MAX), 0);
+  EXPECT_GT(LIBC_NAMESPACE::wcsncasecmp(L"aβc", L"a", INT_MAX), 0);
+  EXPECT_GT(LIBC_NAMESPACE::wcsncasecmp(L"aβc", L"", INT_MAX), 0);
 
-  EXPECT_LT(LIBC_NAMESPACE::wcsncasecmp(L"ab", L"abc", INT_MAX), 0);
-  EXPECT_LT(LIBC_NAMESPACE::wcsncasecmp(L"a", L"abc", INT_MAX), 0);
-  EXPECT_LT(LIBC_NAMESPACE::wcsncasecmp(L"", L"abc", INT_MAX), 0);
+  EXPECT_LT(LIBC_NAMESPACE::wcsncasecmp(L"aβ", L"aβc", INT_MAX), 0);
+  EXPECT_LT(LIBC_NAMESPACE::wcsncasecmp(L"a", L"aβc", INT_MAX), 0);
+  EXPECT_LT(LIBC_NAMESPACE::wcsncasecmp(L"", L"aβc", INT_MAX), 0);
 }
 
 TEST(LlvmLibcWcsncasecmpTest, MatchingStringsIgnoreCaseUnlimited) {
@@ -50,22 +50,22 @@ TEST(LlvmLibcWcsncasecmpTest, MatchingStringsIgnoreCaseUnlimited) {
 }
 
 TEST(LlvmLibcWcsncasecmpTest, NonMatchingStringsUnlimited) {
-  EXPECT_GT(LIBC_NAMESPACE::wcsncasecmp(L"Xbc", L"abc", INT_MAX), 0);
-  EXPECT_GT(LIBC_NAMESPACE::wcsncasecmp(L"aXc", L"abc", INT_MAX), 0);
-  EXPECT_GT(LIBC_NAMESPACE::wcsncasecmp(L"abX", L"abc", INT_MAX), 0);
+  EXPECT_GT(LIBC_NAMESPACE::wcsncasecmp(L"Δbc", L"abc", INT_MAX), 0);
+  EXPECT_GT(LIBC_NAMESPACE::wcsncasecmp(L"aΔc", L"abc", INT_MAX), 0);
+  EXPECT_GT(LIBC_NAMESPACE::wcsncasecmp(L"abΔ", L"abc", INT_MAX), 0);
 
-  EXPECT_LT(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"Xbc", INT_MAX), 0);
-  EXPECT_LT(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"aXc", INT_MAX), 0);
-  EXPECT_LT(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"abX", INT_MAX), 0);
+  EXPECT_LT(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"Δbc", INT_MAX), 0);
+  EXPECT_LT(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"aΔc", INT_MAX), 0);
+  EXPECT_LT(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"abΔ", INT_MAX), 0);
 }
 
 TEST(LlvmLibcWcsncasecmpTest, CompareAtMostStartingMatch) {
-  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"", 0), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"βγδ", L"", 0), 0);
   EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"", L"def", 0), 0);
 
-  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"def", 0), 0);
-  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"ade", 1), 0);
-  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"abc", L"abd", 2), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"βγδ", L"def", 0), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"βγδ", L"βde", 1), 0);
+  EXPECT_EQ(LIBC_NAMESPACE::wcsncasecmp(L"βγδ", L"βγd", 2), 0);
 }
 
 #if defined(LIBC_ADD_NULL_CHECKS)



More information about the libc-commits mailing list