[libc-commits] [libc] [libc] Implement widechar to integer public functions (PR #148683)

Uzair Nawaz via libc-commits libc-commits at lists.llvm.org
Mon Jul 14 10:31:14 PDT 2025


https://github.com/uzairnawaz created https://github.com/llvm/llvm-project/pull/148683

Implement public wchar -> integer public functions using templated internal wcs_to_integer function


>From 2dd27bf7efa685ba4d14dffd708307f047117af9 Mon Sep 17 00:00:00 2001
From: Uzair Nawaz <uzairnawaz at google.com>
Date: Thu, 10 Jul 2025 21:25:33 +0000
Subject: [PATCH 1/5] Implemented public functions

---
 libc/src/wchar/wcstol.cpp   | 30 ++++++++++++++++++++++++++++++
 libc/src/wchar/wcstol.h     | 22 ++++++++++++++++++++++
 libc/src/wchar/wcstoll.cpp  | 30 ++++++++++++++++++++++++++++++
 libc/src/wchar/wcstoll.h    | 22 ++++++++++++++++++++++
 libc/src/wchar/wcstoul.cpp  | 30 ++++++++++++++++++++++++++++++
 libc/src/wchar/wcstoul.h    | 22 ++++++++++++++++++++++
 libc/src/wchar/wcstoull.cpp | 30 ++++++++++++++++++++++++++++++
 libc/src/wchar/wcstoull.h   | 22 ++++++++++++++++++++++
 8 files changed, 208 insertions(+)
 create mode 100644 libc/src/wchar/wcstol.cpp
 create mode 100644 libc/src/wchar/wcstol.h
 create mode 100644 libc/src/wchar/wcstoll.cpp
 create mode 100644 libc/src/wchar/wcstoll.h
 create mode 100644 libc/src/wchar/wcstoul.cpp
 create mode 100644 libc/src/wchar/wcstoul.h
 create mode 100644 libc/src/wchar/wcstoull.cpp
 create mode 100644 libc/src/wchar/wcstoull.h

diff --git a/libc/src/wchar/wcstol.cpp b/libc/src/wchar/wcstol.cpp
new file mode 100644
index 0000000000000..a05718f706dfd
--- /dev/null
+++ b/libc/src/wchar/wcstol.cpp
@@ -0,0 +1,30 @@
+//===-- Implementation of wcstol ------------------------------------------===//
+//
+// 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/wcstol.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/wcs_to_integer.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(long, wcstol,
+                   (const wchar_t *__restrict str, wchar_t **__restrict str_end,
+                    int base)) {
+  auto result = internal::wcstointeger<long>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<wchar_t *>(str + result.parsed_len);
+
+  return result;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/wcstol.h b/libc/src/wchar/wcstol.h
new file mode 100644
index 0000000000000..08acd9717a237
--- /dev/null
+++ b/libc/src/wchar/wcstol.h
@@ -0,0 +1,22 @@
+//===-- Implementation header for wcstol ------------------------*- C++ -*-===//
+//
+// 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_WCSTOL_H
+#define LLVM_LIBC_SRC_WCHAR_WCSTOL_H
+
+#include "hdr/types/wint_t.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+long wcstol(const wchar_t *__restrict str, wchar_t **__restrict str_end,
+            int base);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_WCHAR_WCSTOL_H
diff --git a/libc/src/wchar/wcstoll.cpp b/libc/src/wchar/wcstoll.cpp
new file mode 100644
index 0000000000000..de1299d681cdb
--- /dev/null
+++ b/libc/src/wchar/wcstoll.cpp
@@ -0,0 +1,30 @@
+//===-- Implementation of wcstoll -----------------------------------------===//
+//
+// 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/wcstoll.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/wcs_to_integer.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(long long, wcstoll,
+                   (const wchar_t *__restrict str, wchar_t **__restrict str_end,
+                    int base)) {
+  auto result = internal::wcstointeger<long long>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<wchar_t *>(str + result.parsed_len);
+
+  return result;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/wcstoll.h b/libc/src/wchar/wcstoll.h
new file mode 100644
index 0000000000000..6278043569490
--- /dev/null
+++ b/libc/src/wchar/wcstoll.h
@@ -0,0 +1,22 @@
+//===-- Implementation header for wcstoll -----------------------*- C++ -*-===//
+//
+// 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_WCSTOLL_H
+#define LLVM_LIBC_SRC_WCHAR_WCSTOLL_H
+
+#include "hdr/types/wint_t.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+long long wcstoll(const wchar_t *__restrict str, wchar_t **__restrict str_end,
+                  int base);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_WCHAR_WCSTOLL_H
diff --git a/libc/src/wchar/wcstoul.cpp b/libc/src/wchar/wcstoul.cpp
new file mode 100644
index 0000000000000..79b8c9b5c9fa3
--- /dev/null
+++ b/libc/src/wchar/wcstoul.cpp
@@ -0,0 +1,30 @@
+//===-- Implementation of wcstoul -----------------------------------------===//
+//
+// 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/wcstoul.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/wcs_to_integer.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(unsigned long, wcstoul,
+                   (const wchar_t *__restrict str, wchar_t **__restrict str_end,
+                    int base)) {
+  auto result = internal::wcstointeger<unsigned long>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<wchar_t *>(str + result.parsed_len);
+
+  return result;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/wcstoul.h b/libc/src/wchar/wcstoul.h
new file mode 100644
index 0000000000000..81f530534e81a
--- /dev/null
+++ b/libc/src/wchar/wcstoul.h
@@ -0,0 +1,22 @@
+//===-- Implementation header for wcstoul -----------------------*- C++ -*-===//
+//
+// 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_WCSTOUL_H
+#define LLVM_LIBC_SRC_WCHAR_WCSTOUL_H
+
+#include "hdr/types/wint_t.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+unsigned long wcstoul(const wchar_t *__restrict str,
+                      wchar_t **__restrict str_end, int base);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_WCHAR_WCSTOUL_H
diff --git a/libc/src/wchar/wcstoull.cpp b/libc/src/wchar/wcstoull.cpp
new file mode 100644
index 0000000000000..768e03c4bd189
--- /dev/null
+++ b/libc/src/wchar/wcstoull.cpp
@@ -0,0 +1,30 @@
+//===-- Implementation of wcstoull ----------------------------------------===//
+//
+// 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/wcstoull.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/wcs_to_integer.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(unsigned long long, wcstoull,
+                   (const wchar_t *__restrict str, wchar_t **__restrict str_end,
+                    int base)) {
+  auto result = internal::wcstointeger<unsigned long long>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<wchar_t *>(str + result.parsed_len);
+
+  return result;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/wcstoull.h b/libc/src/wchar/wcstoull.h
new file mode 100644
index 0000000000000..e970a5792338b
--- /dev/null
+++ b/libc/src/wchar/wcstoull.h
@@ -0,0 +1,22 @@
+//===-- Implementation header for wcstoull -----------------------*- C++-*-===//
+//
+// 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_WCSTOULL_H
+#define LLVM_LIBC_SRC_WCHAR_WCSTOULL_H
+
+#include "hdr/types/wint_t.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+unsigned long long wcstoull(const wchar_t *__restrict str,
+                            wchar_t **__restrict str_end, int base);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_WCHAR_WCSTOULL_H

>From deb5a1b41f70bb713b5a2a0b291f3e90e448b862 Mon Sep 17 00:00:00 2001
From: Uzair Nawaz <uzairnawaz at google.com>
Date: Mon, 14 Jul 2025 16:22:06 +0000
Subject: [PATCH 2/5] added tests

---
 libc/test/src/wchar/CMakeLists.txt    |  55 ++++
 libc/test/src/wchar/WcstolTest.h      | 441 ++++++++++++++++++++++++++
 libc/test/src/wchar/wcstol_test.cpp   |  15 +
 libc/test/src/wchar/wcstoll_test.cpp  |  15 +
 libc/test/src/wchar/wcstoul_test.cpp  |  15 +
 libc/test/src/wchar/wcstoull_test.cpp |  15 +
 6 files changed, 556 insertions(+)
 create mode 100644 libc/test/src/wchar/WcstolTest.h
 create mode 100644 libc/test/src/wchar/wcstol_test.cpp
 create mode 100644 libc/test/src/wchar/wcstoll_test.cpp
 create mode 100644 libc/test/src/wchar/wcstoul_test.cpp
 create mode 100644 libc/test/src/wchar/wcstoull_test.cpp

diff --git a/libc/test/src/wchar/CMakeLists.txt b/libc/test/src/wchar/CMakeLists.txt
index dc233fca59741..176cf7c3487cd 100644
--- a/libc/test/src/wchar/CMakeLists.txt
+++ b/libc/test/src/wchar/CMakeLists.txt
@@ -334,3 +334,58 @@ add_libc_test(
   DEPENDS
     libc.src.wchar.wcpncpy
 )
+
+add_header_library(
+  wcstol_test_support
+  HDRS
+    WcstolTest.h
+  DEPENDS
+    libc.src.__support.CPP.limits
+    libc.src.__support.CPP.type_traits
+    libc.src.errno.errno
+    libc.test.UnitTest.ErrnoCheckingTest
+)
+
+add_libc_test(
+  wcstol_test
+  SUITE
+    libc_wchar_unittests
+  SRCS
+    wcstol_test.cpp
+  DEPENDS
+    libc.src.wchar.wcstol
+    .wcstol_test_support
+)
+
+add_libc_test(
+  wcstoll_test
+  SUITE
+    libc_wchar_unittests
+  SRCS
+    wcstoll_test.cpp
+  DEPENDS
+    libc.src.wchar.wcstoll
+    .wcstol_test_support
+)
+
+add_libc_test(
+  wcstoul_test
+  SUITE
+    libc_wchar_unittests
+  SRCS
+    wcstoul_test.cpp
+  DEPENDS
+    libc.src.wchar.wcstoul
+    .wcstol_test_support
+)
+
+add_libc_test(
+  wcstoull_test
+  SUITE
+    libc_wchar_unittests
+  SRCS
+    wcstoull_test.cpp
+  DEPENDS
+    libc.src.wchar.wcstoull
+    .wcstol_test_support
+)
\ No newline at end of file
diff --git a/libc/test/src/wchar/WcstolTest.h b/libc/test/src/wchar/WcstolTest.h
new file mode 100644
index 0000000000000..8a4294ace41cb
--- /dev/null
+++ b/libc/test/src/wchar/WcstolTest.h
@@ -0,0 +1,441 @@
+//===-- A template class for testing wcsto* functions -----------*- C++ -*-===//
+//
+// 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/__support/CPP/limits.h"
+#include "src/__support/CPP/type_traits.h"
+#include "src/__support/macros/properties/architectures.h"
+#include "src/__support/wctype_utils.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/Test.h"
+
+#include <stddef.h>
+
+using LIBC_NAMESPACE::cpp::is_signed_v;
+
+template <typename ReturnT>
+struct WcstoTest : public LIBC_NAMESPACE::testing::ErrnoCheckingTest {
+  using FunctionT = ReturnT (*)(const wchar_t *, wchar_t **, int);
+
+  static constexpr ReturnT T_MAX =
+      LIBC_NAMESPACE::cpp::numeric_limits<ReturnT>::max();
+  static constexpr ReturnT T_MIN =
+      LIBC_NAMESPACE::cpp::numeric_limits<ReturnT>::min();
+
+  void InvalidBase(FunctionT func) {
+    const wchar_t *ten = L"10";
+    ASSERT_EQ(func(ten, nullptr, -1), ReturnT(0));
+    ASSERT_ERRNO_EQ(EINVAL);
+  }
+
+  void CleanBaseTenDecode(FunctionT func) {
+    wchar_t *str_end = nullptr;
+
+    // TODO: Look into collapsing these repeated segments.
+    const wchar_t *ten = L"10";
+    ASSERT_EQ(func(ten, &str_end, 10), ReturnT(10));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - ten, ptrdiff_t(2));
+
+    ASSERT_EQ(func(ten, nullptr, 10), ReturnT(10));
+    ASSERT_ERRNO_SUCCESS();
+
+    const wchar_t *hundred = L"100";
+    ASSERT_EQ(func(hundred, &str_end, 10), ReturnT(100));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - hundred, ptrdiff_t(3));
+
+    const wchar_t *big_number = L"1234567890";
+    ASSERT_EQ(func(big_number, &str_end, 10), ReturnT(1234567890));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - big_number, ptrdiff_t(10));
+
+    // This number is larger than 2^32, meaning that if long is only 32 bits
+    // wide, strtol will return LONG_MAX.
+    const wchar_t *bigger_number = L"12345678900";
+    if constexpr (sizeof(ReturnT) < 8) {
+      ASSERT_EQ(func(bigger_number, &str_end, 10), T_MAX);
+      ASSERT_ERRNO_EQ(ERANGE);
+    } else {
+      ASSERT_EQ(func(bigger_number, &str_end, 10), ReturnT(12345678900));
+      ASSERT_ERRNO_SUCCESS();
+    }
+    EXPECT_EQ(str_end - bigger_number, ptrdiff_t(11));
+
+    const wchar_t *too_big_number = L"123456789012345678901";
+    ASSERT_EQ(func(too_big_number, &str_end, 10), T_MAX);
+    ASSERT_ERRNO_EQ(ERANGE);
+    EXPECT_EQ(str_end - too_big_number, ptrdiff_t(21));
+
+    const wchar_t *long_number_range_test =
+        L"10000000000000000000000000000000000000000000000000";
+    ASSERT_EQ(func(long_number_range_test, &str_end, 10), T_MAX);
+    ASSERT_ERRNO_EQ(ERANGE);
+    EXPECT_EQ(str_end - long_number_range_test, ptrdiff_t(50));
+
+    // For most negative numbers, the unsigned functions treat it the same as
+    // casting a negative variable to an unsigned type.
+    const wchar_t *negative = L"-100";
+    ASSERT_EQ(func(negative, &str_end, 10), ReturnT(-100));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - negative, ptrdiff_t(4));
+
+    const wchar_t *big_negative_number = L"-1234567890";
+    ASSERT_EQ(func(big_negative_number, &str_end, 10), ReturnT(-1234567890));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - big_negative_number, ptrdiff_t(11));
+
+    const wchar_t *too_big_negative_number = L"-123456789012345678901";
+    // If the number is signed, it should return the smallest negative number
+    // for the current type, but if it's unsigned it should max out and return
+    // the largest positive number for the current type. From the standard:
+    // "If the correct value is outside the range of representable values,
+    // LONG_MIN, LONG_MAX, LLONG_MIN, LLONG_MAX, ULONG_MAX, or ULLONG_MAX is
+    // returned"
+    // Note that 0 is not on that list.
+    ASSERT_EQ(func(too_big_negative_number, &str_end, 10),
+              (is_signed_v<ReturnT> ? T_MIN : T_MAX));
+    ASSERT_ERRNO_EQ(ERANGE);
+    EXPECT_EQ(str_end - too_big_negative_number, ptrdiff_t(22));
+  }
+
+  void MessyBaseTenDecode(FunctionT func) {
+    wchar_t *str_end = nullptr;
+
+    const wchar_t *spaces_before = L"     10";
+    ASSERT_EQ(func(spaces_before, &str_end, 10), ReturnT(10));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - spaces_before, ptrdiff_t(7));
+
+    const wchar_t *spaces_after = L"10      ";
+    ASSERT_EQ(func(spaces_after, &str_end, 10), ReturnT(10));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - spaces_after, ptrdiff_t(2));
+
+    const wchar_t *word_before = L"word10";
+    ASSERT_EQ(func(word_before, &str_end, 10), ReturnT(0));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - word_before, ptrdiff_t(0));
+
+    const wchar_t *word_after = L"10word";
+    ASSERT_EQ(func(word_after, &str_end, 10), ReturnT(10));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - word_after, ptrdiff_t(2));
+
+    const wchar_t *two_numbers = L"10 999";
+    ASSERT_EQ(func(two_numbers, &str_end, 10), ReturnT(10));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - two_numbers, ptrdiff_t(2));
+
+    const wchar_t *two_signs = L"--10 999";
+    ASSERT_EQ(func(two_signs, &str_end, 10), ReturnT(0));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - two_signs, ptrdiff_t(0));
+
+    const wchar_t *sign_before = L"+2=4";
+    ASSERT_EQ(func(sign_before, &str_end, 10), ReturnT(2));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - sign_before, ptrdiff_t(2));
+
+    const wchar_t *sign_after = L"2+2=4";
+    ASSERT_EQ(func(sign_after, &str_end, 10), ReturnT(2));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - sign_after, ptrdiff_t(1));
+
+    const wchar_t *tab_before = L"\t10";
+    ASSERT_EQ(func(tab_before, &str_end, 10), ReturnT(10));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - tab_before, ptrdiff_t(3));
+
+    const wchar_t *all_together = L"\t  -12345and+67890";
+    ASSERT_EQ(func(all_together, &str_end, 10), ReturnT(-12345));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - all_together, ptrdiff_t(9));
+
+    const wchar_t *just_spaces = L"  ";
+    ASSERT_EQ(func(just_spaces, &str_end, 10), ReturnT(0));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - just_spaces, ptrdiff_t(0));
+
+    const wchar_t *just_space_and_sign = L" +";
+    ASSERT_EQ(func(just_space_and_sign, &str_end, 10), ReturnT(0));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - just_space_and_sign, ptrdiff_t(0));
+  }
+
+  void DecodeInOtherBases(FunctionT func) {
+    // This test is excessively slow on the GPU, so we limit the innermost loop.
+#if defined(LIBC_TARGET_ARCH_IS_GPU)
+    constexpr int limit = 0;
+#else
+    constexpr int limit = 36;
+#endif
+    wchar_t small_string[4] = {L'\0', L'\0', L'\0', L'\0'};
+    for (int base = 2; base <= 36; ++base) {
+      for (int first_digit = 0; first_digit <= 36; ++first_digit) {
+        small_string[0] = static_cast<wchar_t>(
+            LIBC_NAMESPACE::internal::int_to_b36_wchar(first_digit));
+        if (first_digit < base) {
+          ASSERT_EQ(func(small_string, nullptr, base),
+                    static_cast<ReturnT>(first_digit));
+          ASSERT_ERRNO_SUCCESS();
+        } else {
+          ASSERT_EQ(func(small_string, nullptr, base), ReturnT(0));
+          ASSERT_ERRNO_SUCCESS();
+        }
+      }
+    }
+
+    for (int base = 2; base <= 36; ++base) {
+      for (int first_digit = 0; first_digit <= 36; ++first_digit) {
+        small_string[0] = static_cast<wchar_t>(
+            LIBC_NAMESPACE::internal::int_to_b36_wchar(first_digit));
+        for (int second_digit = 0; second_digit <= 36; ++second_digit) {
+          small_string[1] = static_cast<wchar_t>(
+              LIBC_NAMESPACE::internal::int_to_b36_wchar(second_digit));
+          if (first_digit < base && second_digit < base) {
+            ASSERT_EQ(
+                func(small_string, nullptr, base),
+                static_cast<ReturnT>(second_digit + (first_digit * base)));
+            ASSERT_ERRNO_SUCCESS();
+          } else if (first_digit < base) {
+            ASSERT_EQ(func(small_string, nullptr, base),
+                      static_cast<ReturnT>(first_digit));
+            ASSERT_ERRNO_SUCCESS();
+          } else {
+            ASSERT_EQ(func(small_string, nullptr, base), ReturnT(0));
+            ASSERT_ERRNO_SUCCESS();
+          }
+        }
+      }
+    }
+
+    for (int base = 2; base <= 36; ++base) {
+      for (int first_digit = 0; first_digit <= 36; ++first_digit) {
+        small_string[0] = static_cast<wchar_t>(
+            LIBC_NAMESPACE::internal::int_to_b36_wchar(first_digit));
+        for (int second_digit = 0; second_digit <= 36; ++second_digit) {
+          small_string[1] = static_cast<wchar_t>(
+              LIBC_NAMESPACE::internal::int_to_b36_wchar(second_digit));
+          for (int third_digit = 0; third_digit <= limit; ++third_digit) {
+            small_string[2] = static_cast<wchar_t>(
+                LIBC_NAMESPACE::internal::int_to_b36_wchar(third_digit));
+
+            if (first_digit < base && second_digit < base &&
+                third_digit < base) {
+              ASSERT_EQ(func(small_string, nullptr, base),
+                        static_cast<ReturnT>(third_digit +
+                                             (second_digit * base) +
+                                             (first_digit * base * base)));
+              ASSERT_ERRNO_SUCCESS();
+            } else if (first_digit < base && second_digit < base) {
+              ASSERT_EQ(
+                  func(small_string, nullptr, base),
+                  static_cast<ReturnT>(second_digit + (first_digit * base)));
+              ASSERT_ERRNO_SUCCESS();
+            } else if (first_digit < base) {
+              // if the base is 16 there is a special case for the prefix 0X.
+              // The number is treated as a one digit hexadecimal.
+              if (base == 16 && first_digit == 0 && second_digit == 33) {
+                if (third_digit < base) {
+                  ASSERT_EQ(func(small_string, nullptr, base),
+                            static_cast<ReturnT>(third_digit));
+                  ASSERT_ERRNO_SUCCESS();
+                } else {
+                  ASSERT_EQ(func(small_string, nullptr, base), ReturnT(0));
+                  ASSERT_ERRNO_SUCCESS();
+                }
+              } else {
+                ASSERT_EQ(func(small_string, nullptr, base),
+                          static_cast<ReturnT>(first_digit));
+                ASSERT_ERRNO_SUCCESS();
+              }
+            } else {
+              ASSERT_EQ(func(small_string, nullptr, base), ReturnT(0));
+              ASSERT_ERRNO_SUCCESS();
+            }
+          }
+        }
+      }
+    }
+  }
+
+  void CleanBaseSixteenDecode(FunctionT func) {
+    wchar_t *str_end = nullptr;
+
+    const wchar_t *no_prefix = L"123abc";
+    ASSERT_EQ(func(no_prefix, &str_end, 16), ReturnT(0x123abc));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - no_prefix, ptrdiff_t(6));
+
+    const wchar_t *yes_prefix = L"0x456def";
+    ASSERT_EQ(func(yes_prefix, &str_end, 16), ReturnT(0x456def));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - yes_prefix, ptrdiff_t(8));
+
+    const wchar_t *letter_after_prefix = L"0xabc123";
+    ASSERT_EQ(func(letter_after_prefix, &str_end, 16), ReturnT(0xabc123));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - letter_after_prefix, ptrdiff_t(8));
+
+    // These tests check what happens when the number passed is exactly the max
+    // value for the conversion.
+
+    // Max size for unsigned 32 bit numbers
+
+    const wchar_t *max_32_bit_value = L"0xFFFFFFFF";
+    ASSERT_EQ(func(max_32_bit_value, &str_end, 0),
+              ((is_signed_v<ReturnT> && sizeof(ReturnT) == 4)
+                   ? T_MAX
+                   : ReturnT(0xFFFFFFFF)));
+    ASSERT_ERRNO_EQ(is_signed_v<ReturnT> && sizeof(ReturnT) == 4 ? ERANGE : 0);
+    EXPECT_EQ(str_end - max_32_bit_value, ptrdiff_t(10));
+
+    const wchar_t *negative_max_32_bit_value = L"-0xFFFFFFFF";
+    ASSERT_EQ(func(negative_max_32_bit_value, &str_end, 0),
+              ((is_signed_v<ReturnT> && sizeof(ReturnT) == 4)
+                   ? T_MIN
+                   : -ReturnT(0xFFFFFFFF)));
+    ASSERT_ERRNO_EQ(is_signed_v<ReturnT> && sizeof(ReturnT) == 4 ? ERANGE : 0);
+    EXPECT_EQ(str_end - negative_max_32_bit_value, ptrdiff_t(11));
+
+    // Max size for signed 32 bit numbers
+
+    const wchar_t *max_31_bit_value = L"0x7FFFFFFF";
+    ASSERT_EQ(func(max_31_bit_value, &str_end, 0), ReturnT(0x7FFFFFFF));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - max_31_bit_value, ptrdiff_t(10));
+
+    const wchar_t *negative_max_31_bit_value = L"-0x7FFFFFFF";
+    ASSERT_EQ(func(negative_max_31_bit_value, &str_end, 0),
+              -ReturnT(0x7FFFFFFF));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - negative_max_31_bit_value, ptrdiff_t(11));
+
+    // Max size for unsigned 64 bit numbers
+
+    const wchar_t *max_64_bit_value = L"0xFFFFFFFFFFFFFFFF";
+    ASSERT_EQ(func(max_64_bit_value, &str_end, 0),
+              (is_signed_v<ReturnT> || sizeof(ReturnT) < 8
+                   ? T_MAX
+                   : ReturnT(0xFFFFFFFFFFFFFFFF)));
+    ASSERT_ERRNO_EQ((is_signed_v<ReturnT> || sizeof(ReturnT) < 8 ? ERANGE : 0));
+    EXPECT_EQ(str_end - max_64_bit_value, ptrdiff_t(18));
+
+    // See the end of CleanBase10Decode for an explanation of how this large
+    // negative number can end up as T_MAX.
+    const wchar_t *negative_max_64_bit_value = L"-0xFFFFFFFFFFFFFFFF";
+    ASSERT_EQ(
+        func(negative_max_64_bit_value, &str_end, 0),
+        (is_signed_v<ReturnT>
+             ? T_MIN
+             : (sizeof(ReturnT) < 8 ? T_MAX : -ReturnT(0xFFFFFFFFFFFFFFFF))));
+    ASSERT_ERRNO_EQ((is_signed_v<ReturnT> || sizeof(ReturnT) < 8 ? ERANGE : 0));
+    EXPECT_EQ(str_end - negative_max_64_bit_value, ptrdiff_t(19));
+
+    // Max size for signed 64 bit numbers
+
+    const wchar_t *max_63_bit_value = L"0x7FFFFFFFFFFFFFFF";
+    ASSERT_EQ(func(max_63_bit_value, &str_end, 0),
+              (sizeof(ReturnT) < 8 ? T_MAX : ReturnT(0x7FFFFFFFFFFFFFFF)));
+    ASSERT_ERRNO_EQ(sizeof(ReturnT) < 8 ? ERANGE : 0);
+    EXPECT_EQ(str_end - max_63_bit_value, ptrdiff_t(18));
+
+    const wchar_t *negative_max_63_bit_value = L"-0x7FFFFFFFFFFFFFFF";
+    ASSERT_EQ(func(negative_max_63_bit_value, &str_end, 0),
+              (sizeof(ReturnT) >= 8 ? -ReturnT(0x7FFFFFFFFFFFFFFF)
+                                    : (is_signed_v<ReturnT> ? T_MIN : T_MAX)));
+    ASSERT_ERRNO_EQ(sizeof(ReturnT) < 8 ? ERANGE : 0);
+    EXPECT_EQ(str_end - negative_max_63_bit_value, ptrdiff_t(19));
+  }
+
+  void MessyBaseSixteenDecode(FunctionT func) {
+    wchar_t *str_end = nullptr;
+
+    const wchar_t *just_prefix = L"0x";
+    ASSERT_EQ(func(just_prefix, &str_end, 16), ReturnT(0));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - just_prefix, ptrdiff_t(1));
+
+    ASSERT_EQ(func(just_prefix, &str_end, 0), ReturnT(0));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - just_prefix, ptrdiff_t(1));
+
+    const wchar_t *prefix_with_x_after = L"0xx";
+    ASSERT_EQ(func(prefix_with_x_after, &str_end, 16), ReturnT(0));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - prefix_with_x_after, ptrdiff_t(1));
+
+    ASSERT_EQ(func(prefix_with_x_after, &str_end, 0), ReturnT(0));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - prefix_with_x_after, ptrdiff_t(1));
+  }
+
+  void AutomaticBaseSelection(FunctionT func) {
+    wchar_t *str_end = nullptr;
+
+    const wchar_t *base_ten = L"12345";
+    ASSERT_EQ(func(base_ten, &str_end, 0), ReturnT(12345));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - base_ten, ptrdiff_t(5));
+
+    const wchar_t *base_sixteen_no_prefix = L"123abc";
+    ASSERT_EQ(func(base_sixteen_no_prefix, &str_end, 0), ReturnT(123));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - base_sixteen_no_prefix, ptrdiff_t(3));
+
+    const wchar_t *base_sixteen_with_prefix = L"0x456def";
+    ASSERT_EQ(func(base_sixteen_with_prefix, &str_end, 0), ReturnT(0x456def));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - base_sixteen_with_prefix, ptrdiff_t(8));
+
+    const wchar_t *base_eight_with_prefix = L"012345";
+    ASSERT_EQ(func(base_eight_with_prefix, &str_end, 0), ReturnT(012345));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - base_eight_with_prefix, ptrdiff_t(6));
+
+    const wchar_t *just_zero = L"0";
+    ASSERT_EQ(func(just_zero, &str_end, 0), ReturnT(0));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - just_zero, ptrdiff_t(1));
+
+    const wchar_t *just_zero_x = L"0x";
+    ASSERT_EQ(func(just_zero_x, &str_end, 0), ReturnT(0));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - just_zero_x, ptrdiff_t(1));
+
+    const wchar_t *just_zero_eight = L"08";
+    ASSERT_EQ(func(just_zero_eight, &str_end, 0), ReturnT(0));
+    ASSERT_ERRNO_SUCCESS();
+    EXPECT_EQ(str_end - just_zero_eight, ptrdiff_t(1));
+  }
+};
+
+template <typename ReturnType>
+WcstoTest(ReturnType (*)(const wchar_t *)) -> WcstoTest<ReturnType>;
+
+#define WCSTOL_TEST(name, func)                                                \
+  using LlvmLibc##name##Test = WcstoTest<decltype(func(L"", nullptr, 0))>;     \
+  TEST_F(LlvmLibc##name##Test, InvalidBase) { InvalidBase(func); }             \
+  TEST_F(LlvmLibc##name##Test, CleanBaseTenDecode) {                           \
+    CleanBaseTenDecode(func);                                                  \
+  }                                                                            \
+  TEST_F(LlvmLibc##name##Test, MessyBaseTenDecode) {                           \
+    MessyBaseTenDecode(func);                                                  \
+  }                                                                            \
+  TEST_F(LlvmLibc##name##Test, DecodeInOtherBases) {                           \
+    DecodeInOtherBases(func);                                                  \
+  }                                                                            \
+  TEST_F(LlvmLibc##name##Test, CleanBaseSixteenDecode) {                       \
+    CleanBaseSixteenDecode(func);                                              \
+  }                                                                            \
+  TEST_F(LlvmLibc##name##Test, MessyBaseSixteenDecode) {                       \
+    MessyBaseSixteenDecode(func);                                              \
+  }                                                                            \
+  TEST_F(LlvmLibc##name##Test, AutomaticBaseSelection) {                       \
+    AutomaticBaseSelection(func);                                              \
+  }
diff --git a/libc/test/src/wchar/wcstol_test.cpp b/libc/test/src/wchar/wcstol_test.cpp
new file mode 100644
index 0000000000000..9ae32ba5183e6
--- /dev/null
+++ b/libc/test/src/wchar/wcstol_test.cpp
@@ -0,0 +1,15 @@
+//===-- Unittests for wcstol ----------------------------------------------===//
+//
+// 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/wcstol.h"
+
+#include "test/UnitTest/Test.h"
+
+#include "WcstolTest.h"
+
+WCSTOL_TEST(Wcstol, LIBC_NAMESPACE::wcstol)
\ No newline at end of file
diff --git a/libc/test/src/wchar/wcstoll_test.cpp b/libc/test/src/wchar/wcstoll_test.cpp
new file mode 100644
index 0000000000000..c24c1f69b1cec
--- /dev/null
+++ b/libc/test/src/wchar/wcstoll_test.cpp
@@ -0,0 +1,15 @@
+//===-- Unittests for wcstoll ---------------------------------------------===//
+//
+// 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/wcstoll.h"
+
+#include "test/UnitTest/Test.h"
+
+#include "WcstolTest.h"
+
+WCSTOL_TEST(Wcstoll, LIBC_NAMESPACE::wcstoll)
\ No newline at end of file
diff --git a/libc/test/src/wchar/wcstoul_test.cpp b/libc/test/src/wchar/wcstoul_test.cpp
new file mode 100644
index 0000000000000..ab0afb951c495
--- /dev/null
+++ b/libc/test/src/wchar/wcstoul_test.cpp
@@ -0,0 +1,15 @@
+//===-- Unittests for wcstoul ---------------------------------------------===//
+//
+// 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/wcstoul.h"
+
+#include "test/UnitTest/Test.h"
+
+#include "WcstolTest.h"
+
+WCSTOL_TEST(Wcstoul, LIBC_NAMESPACE::wcstoul)
\ No newline at end of file
diff --git a/libc/test/src/wchar/wcstoull_test.cpp b/libc/test/src/wchar/wcstoull_test.cpp
new file mode 100644
index 0000000000000..adba4f16e68d6
--- /dev/null
+++ b/libc/test/src/wchar/wcstoull_test.cpp
@@ -0,0 +1,15 @@
+//===-- Unittests for wcstoull --------------------------------------------===//
+//
+// 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/wcstoull.h"
+
+#include "test/UnitTest/Test.h"
+
+#include "WcstolTest.h"
+
+WCSTOL_TEST(Wcstoull, LIBC_NAMESPACE::wcstoull)
\ No newline at end of file

>From e2e8612e00b15d21c15b3108b5b5c430518f8bbe Mon Sep 17 00:00:00 2001
From: Uzair Nawaz <uzairnawaz at google.com>
Date: Mon, 14 Jul 2025 16:53:31 +0000
Subject: [PATCH 3/5] public functions cmake

---
 libc/src/wchar/CMakeLists.txt | 44 +++++++++++++++++++++++++++++++++++
 1 file changed, 44 insertions(+)

diff --git a/libc/src/wchar/CMakeLists.txt b/libc/src/wchar/CMakeLists.txt
index e3bd357a8fd1f..7ace1a6ca66ba 100644
--- a/libc/src/wchar/CMakeLists.txt
+++ b/libc/src/wchar/CMakeLists.txt
@@ -45,6 +45,50 @@ add_entrypoint_object(
     libc.src.__support.wctype_utils
 )
 
+add_entrypoint_object(
+  wcstol
+  SRCS
+    wcstol.cpp
+  HDRS
+    wcstol.h
+  DEPENDS
+    libc.src.errno.errno
+    libc.src.__support.wcs_to_integer
+)
+
+add_entrypoint_object(
+  wcstoll
+  SRCS
+    wcstoll.cpp
+  HDRS
+    wcstoll.h
+  DEPENDS
+    libc.src.errno.errno
+    libc.src.__support.wcs_to_integer
+)
+
+add_entrypoint_object(
+  wcstoul
+  SRCS
+    wcstoul.cpp
+  HDRS
+    wcstoul.h
+  DEPENDS
+    libc.src.errno.errno
+    libc.src.__support.wcs_to_integer
+)
+
+add_entrypoint_object(
+  wcstoull
+  SRCS
+    wcstoull.cpp
+  HDRS
+    wcstoull.h
+  DEPENDS
+    libc.src.errno.errno
+    libc.src.__support.wcs_to_integer
+)
+
 add_entrypoint_object(
   wcstok
   SRCS

>From a2205efb93fcd90d864a596c542e03107cededfc Mon Sep 17 00:00:00 2001
From: Uzair Nawaz <uzairnawaz at google.com>
Date: Mon, 14 Jul 2025 17:26:35 +0000
Subject: [PATCH 4/5] yaml + entrypoints

---
 libc/config/linux/x86_64/entrypoints.txt |  5 ++++
 libc/include/wchar.yaml                  | 32 ++++++++++++++++++++++++
 2 files changed, 37 insertions(+)

diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index edad30634b6da..41d7ae7c66ada 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -391,6 +391,11 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.wchar.wcpcpy
     libc.src.wchar.wcpncpy
     libc.src.wchar.wcstok
+    libc.src.wchar.wcstol
+    libc.src.wchar.wcstoll
+    libc.src.wchar.wcstoul
+    libc.src.wchar.wcstoull
+
 
     # sys/uio.h entrypoints
     libc.src.sys.uio.writev
diff --git a/libc/include/wchar.yaml b/libc/include/wchar.yaml
index c6488fa937885..fa53aaecba6c4 100644
--- a/libc/include/wchar.yaml
+++ b/libc/include/wchar.yaml
@@ -242,3 +242,35 @@ functions:
       - type: wchar_t *__restrict
       - type: const wchar_t *__restrict
       - type: size_t
+  - name: wcstol
+    standards:
+      - stdc
+    return_type: long
+    arguments:
+      - type: const wchar_t *__restrict str
+      - type: wchar_t **__restrict str_end,
+      - type: int base
+  - name: wcstoll
+    standards:
+      - stdc
+    return_type: long long
+    arguments:
+      - type: const wchar_t *__restrict str
+      - type: wchar_t **__restrict str_end,
+      - type: int base
+  - name: wcstoul
+    standards:
+      - stdc
+    return_type: unsigned long
+    arguments:
+      - type: const wchar_t *__restrict str
+      - type: wchar_t **__restrict str_end,
+      - type: int base
+  - name: wcstoull
+    standards:
+      - stdc
+    return_type: unsigned long long
+    arguments:
+      - type: const wchar_t *__restrict str
+      - type: wchar_t **__restrict str_end,
+      - type: int base

>From 4bd086c3e123cb61255cfecafbf56ce5ef4728a4 Mon Sep 17 00:00:00 2001
From: Uzair Nawaz <uzairnawaz at google.com>
Date: Mon, 14 Jul 2025 17:27:28 +0000
Subject: [PATCH 5/5] fix yaml

---
 libc/include/wchar.yaml | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/libc/include/wchar.yaml b/libc/include/wchar.yaml
index fa53aaecba6c4..123d3440aeec3 100644
--- a/libc/include/wchar.yaml
+++ b/libc/include/wchar.yaml
@@ -247,30 +247,30 @@ functions:
       - stdc
     return_type: long
     arguments:
-      - type: const wchar_t *__restrict str
-      - type: wchar_t **__restrict str_end,
-      - type: int base
+      - type: const wchar_t *__restrict
+      - type: wchar_t **__restrict
+      - type: int
   - name: wcstoll
     standards:
       - stdc
     return_type: long long
     arguments:
-      - type: const wchar_t *__restrict str
-      - type: wchar_t **__restrict str_end,
-      - type: int base
+      - type: const wchar_t *__restrict
+      - type: wchar_t **__restrict
+      - type: int
   - name: wcstoul
     standards:
       - stdc
     return_type: unsigned long
     arguments:
-      - type: const wchar_t *__restrict str
-      - type: wchar_t **__restrict str_end,
-      - type: int base
+      - type: const wchar_t *__restrict
+      - type: wchar_t **__restrict
+      - type: int
   - name: wcstoull
     standards:
       - stdc
     return_type: unsigned long long
     arguments:
-      - type: const wchar_t *__restrict str
-      - type: wchar_t **__restrict str_end,
-      - type: int base
+      - type: const wchar_t *__restrict
+      - type: wchar_t **__restrict
+      - type: int



More information about the libc-commits mailing list