[libc-commits] [libc] [libc] Add internal locale structures and config options (PR #211470)

Jeff Bailey via libc-commits libc-commits at lists.llvm.org
Tue Sep 29 21:47:10 PDT 2026


https://github.com/kaladron updated https://github.com/llvm/llvm-project/pull/211470

>From 8b0f6d9764f4e7cc6ac006a5da94499bfe4464a6 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Tue, 21 Jul 2026 00:00:17 +0100
Subject: [PATCH] [libc] Add internal locale structures and config options
 (#211470)

Add internal category data structures and static locale objects for C
and UTF-8 locales.  Introduce LIBC_CONF_DEFAULT_LOCALE and
LIBC_CONF_DISABLE_RUNTIME_LOCALE configuration options.

When LIBC_CONF_DISABLE_RUNTIME_LOCALE is set, the locale subsystem
eliminates all runtime overhead.  Lookups bypass locale_t indirection
and thread-local state in favour of compile-time constants, eliding
dynamic locale machinery for embedded environments.

* libc/cmake/modules/LLVMLibCCompileOptionRules.cmake: Add locale config flags
* libc/config/config.json: Add LIBC_CONF_DEFAULT_LOCALE and LIBC_CONF_DISABLE_RUNTIME_LOCALE
* libc/include/llvm-libc-macros/locale-macros.h: Define LC_ALL_MASK from category masks
* libc/include/llvm-libc-types/locale_t.h: Make locale_t an opaque pointer
* libc/src/locale/locale_data.h: Add category data structures and static constants
* libc/src/locale/locale.h: Add LocaleStorage and thread-local locale accessors
* libc/src/locale/locale.cpp: Define c_locale, utf8_locale, and thread-local state
* libc/src/locale/newlocale.cpp: Support "POSIX" and set errno on failure
* libc/src/locale/uselocale.cpp: Use thread-local locale state
* libc/test/src/locale/locale_test.cpp: Update unit tests

Assisted-by: Automated tooling, human reviewed.
---
 .../modules/LLVMLibCCompileOptionRules.cmake  |   8 ++
 libc/config/config.json                       |  10 ++
 libc/include/llvm-libc-macros/locale-macros.h |   4 +-
 libc/include/llvm-libc-types/locale_t.h       |   8 --
 libc/src/locale/CMakeLists.txt                |  16 ++-
 libc/src/locale/locale.cpp                    |  29 +++-
 libc/src/locale/locale.h                      |  85 ++++++++++--
 libc/src/locale/locale_data.h                 | 124 ++++++++++++++++++
 libc/src/locale/newlocale.cpp                 |  23 +++-
 libc/src/locale/newlocale.h                   |  13 +-
 libc/src/locale/uselocale.cpp                 |  24 +++-
 libc/test/src/locale/CMakeLists.txt           |   5 +-
 libc/test/src/locale/locale_test.cpp          |  53 +++++---
 13 files changed, 340 insertions(+), 62 deletions(-)
 create mode 100644 libc/src/locale/locale_data.h

diff --git a/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake b/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake
index 79f2d80bcba7a..4e9abd97ed0cc 100644
--- a/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake
+++ b/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake
@@ -169,6 +169,14 @@ function(_get_compile_options_from_config output_var)
     libc_add_definition(config_options "LIBC_CONF_WCTYPE_MODE=${LIBC_CONF_WCTYPE_MODE}")
   endif()
 
+  if(LIBC_CONF_DEFAULT_LOCALE)
+    libc_add_definition(config_options "LIBC_CONF_DEFAULT_LOCALE=\"${LIBC_CONF_DEFAULT_LOCALE}\"")
+  endif()
+
+  if(LIBC_CONF_DISABLE_RUNTIME_LOCALE)
+    libc_add_definition(config_options "LIBC_CONF_DISABLE_RUNTIME_LOCALE")
+  endif()
+
   if(LIBC_CONF_RAW_MUTEX_DEFAULT_SPIN_COUNT)
     libc_add_definition(config_options "LIBC_COPT_RAW_MUTEX_DEFAULT_SPIN_COUNT=${LIBC_CONF_RAW_MUTEX_DEFAULT_SPIN_COUNT}")
   endif()
diff --git a/libc/config/config.json b/libc/config/config.json
index 604b03c9b5688..6f0936beb4390 100644
--- a/libc/config/config.json
+++ b/libc/config/config.json
@@ -204,5 +204,15 @@
       "value": "LIBC_HARDENING_MODE_NONE",
       "doc": "Hardening mode for the library."
     }
+  },
+  "locale": {
+    "LIBC_CONF_DEFAULT_LOCALE": {
+      "value": "C.UTF-8",
+      "doc": "The default locale for the C library. Options: 'C', 'POSIX', 'C.UTF-8', 'en_US.UTF-8'."
+    },
+    "LIBC_CONF_DISABLE_RUNTIME_LOCALE": {
+      "value": false,
+      "doc": "Bypass runtime locale objects and use hardcoded lookups for embedded builds."
+    }
   }
 }
diff --git a/libc/include/llvm-libc-macros/locale-macros.h b/libc/include/llvm-libc-macros/locale-macros.h
index 892f8b69f3a77..b95a56462f2e7 100644
--- a/libc/include/llvm-libc-macros/locale-macros.h
+++ b/libc/include/llvm-libc-macros/locale-macros.h
@@ -27,6 +27,8 @@
 #define LC_COLLATE_MASK (1 << LC_COLLATE)
 #define LC_MONETARY_MASK (1 << LC_MONETARY)
 #define LC_MESSAGES_MASK (1 << LC_MESSAGES)
-#define LC_ALL_MASK 0x7fffffff
+#define LC_ALL_MASK                                                            \
+  (LC_CTYPE_MASK | LC_NUMERIC_MASK | LC_TIME_MASK | LC_COLLATE_MASK |          \
+   LC_MONETARY_MASK | LC_MESSAGES_MASK)
 
 #endif // LLVM_LIBC_MACROS_LOCALE_MACROS_H
diff --git a/libc/include/llvm-libc-types/locale_t.h b/libc/include/llvm-libc-types/locale_t.h
index 6d783001acf9f..1424f70115af7 100644
--- a/libc/include/llvm-libc-types/locale_t.h
+++ b/libc/include/llvm-libc-types/locale_t.h
@@ -9,14 +9,6 @@
 #ifndef LLVM_LIBC_TYPES_LOCALE_T_H
 #define LLVM_LIBC_TYPES_LOCALE_T_H
 
-#define NUM_LOCALE_CATEGORIES 6
-
-struct __locale_data;
-
-struct __locale_t {
-  struct __locale_data *data[NUM_LOCALE_CATEGORIES];
-};
-
 typedef struct __locale_t *locale_t;
 
 #endif // LLVM_LIBC_TYPES_LOCALE_T_H
diff --git a/libc/src/locale/CMakeLists.txt b/libc/src/locale/CMakeLists.txt
index 9a3abf76a55c5..0806aee9bf4d5 100644
--- a/libc/src/locale/CMakeLists.txt
+++ b/libc/src/locale/CMakeLists.txt
@@ -4,8 +4,13 @@ add_object_library(
     locale.cpp
   HDRS
     locale.h
+    locale_data.h
   DEPENDS
     libc.hdr.types.locale_t
+    libc.src.__support.common
+    libc.src.__support.CPP.string_view
+    libc.src.__support.macros.attributes
+    libc.src.__support.macros.config
 )
 
 add_entrypoint_object(
@@ -27,9 +32,13 @@ add_entrypoint_object(
   HDRS
     newlocale.h
   DEPENDS
+    .locale
     libc.hdr.locale_macros
     libc.hdr.types.locale_t
-    .locale
+    libc.src.__support.common
+    libc.src.__support.CPP.string_view
+    libc.src.__support.libc_errno
+    libc.src.__support.macros.config
 )
 
 add_entrypoint_object(
@@ -60,8 +69,11 @@ add_entrypoint_object(
   HDRS
     uselocale.h
   DEPENDS
-    libc.hdr.types.locale_t
     .locale
+    libc.hdr.locale_macros
+    libc.hdr.types.locale_t
+    libc.src.__support.common
+    libc.src.__support.macros.config
 )
 
 add_entrypoint_object(
diff --git a/libc/src/locale/locale.cpp b/libc/src/locale/locale.cpp
index 2f7e13fb1d6f7..f356d0ba352fe 100644
--- a/libc/src/locale/locale.cpp
+++ b/libc/src/locale/locale.cpp
@@ -1,19 +1,42 @@
-//===-- Implementation of locale ------------------------------------------===//
+//===----------------------------------------------------------------------===//
 //
 // 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 static locale instances and locale category data.
+///
+//===----------------------------------------------------------------------===//
 
 #include "src/locale/locale.h"
 #include "src/__support/common.h"
 #include "src/__support/macros/config.h"
+#include "src/locale/locale_data.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
-__locale_t c_locale = {nullptr};
+__locale_t c_locale(&C_CTYPE_DATA, &C_NUMERIC_DATA, &C_TIME_DATA, nullptr,
+                    &C_MONETARY_DATA, &C_MESSAGES_DATA);
+
+__locale_t utf8_locale(&UTF8_CTYPE_DATA, &C_NUMERIC_DATA, &C_TIME_DATA, nullptr,
+                       &C_MONETARY_DATA, &C_MESSAGES_DATA);
+
+locale_t global_locale = DEFAULT_LOCALE_IS_UTF8 ? &utf8_locale : &c_locale;
+
+[[maybe_unused]] static LIBC_THREAD_LOCAL locale_t thread_locale = nullptr;
+
+locale_t get_thread_locale() {
+  if constexpr (DISABLE_RUNTIME_LOCALE)
+    return nullptr;
+  return thread_locale;
+}
 
-locale_t locale = nullptr;
+void set_thread_locale(locale_t loc) {
+  if constexpr (!DISABLE_RUNTIME_LOCALE)
+    thread_locale = loc;
+}
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/locale/locale.h b/libc/src/locale/locale.h
index 14befa6e7a9c2..554fdd7628121 100644
--- a/libc/src/locale/locale.h
+++ b/libc/src/locale/locale.h
@@ -1,36 +1,95 @@
-//===-- Implementation header for the locale --------------------*- 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
 //
 //===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation header for locale state and structures.
+///
+//===----------------------------------------------------------------------===//
 
-#ifndef LLVM_LIBC_SRC_LOCALE_LOCALECONV_H
-#define LLVM_LIBC_SRC_LOCALE_LOCALECONV_H
+#ifndef LLVM_LIBC_SRC_LOCALE_LOCALE_H
+#define LLVM_LIBC_SRC_LOCALE_LOCALE_H
 
+#include "hdr/types/locale_t.h"
 #include "src/__support/macros/attributes.h"
 #include "src/__support/macros/config.h"
+#include "src/locale/locale_data.h"
 
-#include "hdr/types/locale_t.h"
+namespace LIBC_NAMESPACE_DECL {
 
-#include <stddef.h>
+template <bool DisableRuntimeLocale> struct LocaleStorage {
+  const LcCtypeData *ctype_ptr;
+  const LcNumericData *numeric_ptr;
+  const LcTimeData *time_ptr;
+  const void *collate_ptr;
+  const LcMonetaryData *monetary_ptr;
+  const LcMessagesData *messages_ptr;
 
-namespace LIBC_NAMESPACE_DECL {
+  LIBC_INLINE constexpr LocaleStorage(const LcCtypeData *c,
+                                      const LcNumericData *num,
+                                      const LcTimeData *t, const void *col,
+                                      const LcMonetaryData *mon,
+                                      const LcMessagesData *msg)
+      : ctype_ptr(c), numeric_ptr(num), time_ptr(t), collate_ptr(col),
+        monetary_ptr(mon), messages_ptr(msg) {}
+
+  LIBC_INLINE constexpr const LcCtypeData *ctype() const { return ctype_ptr; }
+  LIBC_INLINE constexpr const LcNumericData *numeric() const {
+    return numeric_ptr;
+  }
+  LIBC_INLINE constexpr const LcTimeData *time() const { return time_ptr; }
+  LIBC_INLINE constexpr const LcMonetaryData *monetary() const {
+    return monetary_ptr;
+  }
+  LIBC_INLINE constexpr const LcMessagesData *messages() const {
+    return messages_ptr;
+  }
+};
 
-// We only support the "C" locale right now.
-static constexpr size_t MAX_LOCALE_NAME_SIZE = 2;
+template <> struct LocaleStorage<true> {
+  LIBC_INLINE constexpr LocaleStorage(const LcCtypeData *,
+                                      const LcNumericData *, const LcTimeData *,
+                                      const void *, const LcMonetaryData *,
+                                      const LcMessagesData *) {}
 
-struct __locale_data {
-  char name[MAX_LOCALE_NAME_SIZE];
+  LIBC_INLINE constexpr const LcCtypeData *ctype() const { return nullptr; }
+  LIBC_INLINE constexpr const LcNumericData *numeric() const { return nullptr; }
+  LIBC_INLINE constexpr const LcTimeData *time() const { return nullptr; }
+  LIBC_INLINE constexpr const LcMonetaryData *monetary() const {
+    return nullptr;
+  }
+  LIBC_INLINE constexpr const LcMessagesData *messages() const {
+    return nullptr;
+  }
 };
 
-// The pointer to the default "C" locale.
+} // namespace LIBC_NAMESPACE_DECL
+
+struct __locale_t
+    : LIBC_NAMESPACE::LocaleStorage<LIBC_NAMESPACE::DISABLE_RUNTIME_LOCALE> {
+  using LIBC_NAMESPACE::LocaleStorage<
+      LIBC_NAMESPACE::DISABLE_RUNTIME_LOCALE>::LocaleStorage;
+};
+
+namespace LIBC_NAMESPACE_DECL {
+
+// The static "C" locale instance.
 extern __locale_t c_locale;
 
+// The static UTF-8 locale instance.
+extern __locale_t utf8_locale;
+
 // The global locale instance.
-extern locale_t locale;
+extern locale_t global_locale;
+
+// Thread-local locale accessor functions.
+locale_t get_thread_locale();
+void set_thread_locale(locale_t loc);
 
 } // namespace LIBC_NAMESPACE_DECL
 
-#endif // LLVM_LIBC_SRC_LOCALE_LOCALECONV_H
+#endif // LLVM_LIBC_SRC_LOCALE_LOCALE_H
diff --git a/libc/src/locale/locale_data.h b/libc/src/locale/locale_data.h
new file mode 100644
index 0000000000000..93fe859e66c77
--- /dev/null
+++ b/libc/src/locale/locale_data.h
@@ -0,0 +1,124 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Internal structures for locale category data.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_LOCALE_LOCALE_DATA_H
+#define LLVM_LIBC_SRC_LOCALE_LOCALE_DATA_H
+
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/macros/attributes.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+#ifdef LIBC_CONF_DEFAULT_LOCALE
+LIBC_INLINE_VAR constexpr cpp::string_view DEFAULT_LOCALE_NAME =
+    LIBC_CONF_DEFAULT_LOCALE;
+#else
+LIBC_INLINE_VAR constexpr cpp::string_view DEFAULT_LOCALE_NAME = "C.UTF-8";
+#endif
+
+LIBC_INLINE constexpr bool is_c_locale_name(cpp::string_view name) {
+  return name == "C" || name == "POSIX";
+}
+
+LIBC_INLINE constexpr bool is_utf8_locale_name(cpp::string_view name) {
+  size_t dot = name.find_first_of('.');
+  if (dot != cpp::string_view::npos) {
+    if (dot == 0)
+      return false;
+    name.remove_prefix(dot + 1);
+  }
+  return name == "UTF-8" || name == "utf-8" || name == "utf8" || name == "UTF8";
+}
+
+static_assert(is_c_locale_name(DEFAULT_LOCALE_NAME) ||
+                  is_utf8_locale_name(DEFAULT_LOCALE_NAME),
+              "Unsupported LIBC_CONF_DEFAULT_LOCALE value.");
+
+LIBC_INLINE_VAR constexpr bool DEFAULT_LOCALE_IS_UTF8 =
+    is_utf8_locale_name(DEFAULT_LOCALE_NAME);
+
+#ifdef LIBC_CONF_DISABLE_RUNTIME_LOCALE
+LIBC_INLINE_VAR constexpr bool DISABLE_RUNTIME_LOCALE = true;
+#else
+LIBC_INLINE_VAR constexpr bool DISABLE_RUNTIME_LOCALE = false;
+#endif
+
+struct LcCtypeData {
+  const char *codeset;
+};
+
+struct LcNumericData {
+  const char *radixchar;
+  const char *thousep;
+};
+
+struct LcTimeData {
+  const char *d_t_fmt;
+  const char *d_fmt;
+  const char *t_fmt;
+  const char *t_fmt_ampm;
+  const char *am_str;
+  const char *pm_str;
+  const char *days[7];
+  const char *ab_days[7];
+  const char *months[12];
+  const char *ab_months[12];
+  const char *era;
+  const char *era_d_fmt;
+  const char *era_d_t_fmt;
+  const char *era_t_fmt;
+  const char *alt_digits;
+};
+
+struct LcMonetaryData {
+  const char *crncystr;
+};
+
+struct LcMessagesData {
+  const char *yesexpr;
+  const char *noexpr;
+};
+
+LIBC_INLINE_VAR constexpr LcCtypeData C_CTYPE_DATA = {"US-ASCII"};
+LIBC_INLINE_VAR constexpr LcCtypeData UTF8_CTYPE_DATA = {"UTF-8"};
+
+LIBC_INLINE_VAR constexpr LcNumericData C_NUMERIC_DATA = {".", ""};
+
+LIBC_INLINE_VAR constexpr LcTimeData C_TIME_DATA = {
+    "%a %b %e %H:%M:%S %Y",
+    "%m/%d/%y",
+    "%H:%M:%S",
+    "%I:%M:%S %p",
+    "AM",
+    "PM",
+    {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
+     "Saturday"},
+    {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"},
+    {"January", "February", "March", "April", "May", "June", "July", "August",
+     "September", "October", "November", "December"},
+    {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
+     "Nov", "Dec"},
+    "",
+    "",
+    "",
+    "",
+    ""};
+
+LIBC_INLINE_VAR constexpr LcMonetaryData C_MONETARY_DATA = {""};
+
+LIBC_INLINE_VAR constexpr LcMessagesData C_MESSAGES_DATA = {"^[yY]", "^[nN]"};
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_LOCALE_LOCALE_DATA_H
diff --git a/libc/src/locale/newlocale.cpp b/libc/src/locale/newlocale.cpp
index 246d31c95cb58..b2d14dd750ac6 100644
--- a/libc/src/locale/newlocale.cpp
+++ b/libc/src/locale/newlocale.cpp
@@ -1,15 +1,21 @@
-//===-- Implementation of newlocale ---------------------------------------===//
+//===----------------------------------------------------------------------===//
 //
 // 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 newlocale.
+///
+//===----------------------------------------------------------------------===//
 
 #include "src/locale/newlocale.h"
 #include "hdr/locale_macros.h"
 #include "src/__support/CPP/string_view.h"
 #include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
 #include "src/locale/locale.h"
 
@@ -17,11 +23,20 @@ namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(locale_t, newlocale,
                    (int category_mask, const char *locale_name, locale_t)) {
-  cpp::string_view name(locale_name);
-  if ((category_mask & ~LC_ALL_MASK) != 0 || (!name.empty() && name != "C"))
+  if (!locale_name || (category_mask & ~LC_ALL_MASK) != 0) {
+    libc_errno = EINVAL;
     return nullptr;
+  }
+
+  cpp::string_view name(locale_name);
+  if (name.empty())
+    return DEFAULT_LOCALE_IS_UTF8 ? &utf8_locale : &c_locale;
+
+  if (is_c_locale_name(name))
+    return &c_locale;
 
-  return &c_locale;
+  libc_errno = ENOENT;
+  return nullptr;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/locale/newlocale.h b/libc/src/locale/newlocale.h
index 07972fa384433..211bbb19b9f8c 100644
--- a/libc/src/locale/newlocale.h
+++ b/libc/src/locale/newlocale.h
@@ -1,13 +1,18 @@
-//===-- Implementation header for setlocale ---------------------*- 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
 //
 //===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation header for newlocale.
+///
+//===----------------------------------------------------------------------===//
 
-#ifndef LLVM_LIBC_SRC_LOCALE_SETLOCALE_H
-#define LLVM_LIBC_SRC_LOCALE_SETLOCALE_H
+#ifndef LLVM_LIBC_SRC_LOCALE_NEWLOCALE_H
+#define LLVM_LIBC_SRC_LOCALE_NEWLOCALE_H
 
 #include "hdr/types/locale_t.h"
 #include "src/__support/macros/config.h"
@@ -18,4 +23,4 @@ locale_t newlocale(int category_mask, const char *locale_name, locale_t base);
 
 } // namespace LIBC_NAMESPACE_DECL
 
-#endif // LLVM_LIBC_SRC_LOCALE_SETLOCALE_H
+#endif // LLVM_LIBC_SRC_LOCALE_NEWLOCALE_H
diff --git a/libc/src/locale/uselocale.cpp b/libc/src/locale/uselocale.cpp
index d6fdad248f12b..a517f655f476c 100644
--- a/libc/src/locale/uselocale.cpp
+++ b/libc/src/locale/uselocale.cpp
@@ -1,23 +1,33 @@
-//===-- Implementation of uselocale ---------------------------------------===//
+//===----------------------------------------------------------------------===//
 //
 // 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 uselocale.
+///
+//===----------------------------------------------------------------------===//
 
 #include "src/locale/uselocale.h"
-#include "src/locale/locale.h"
-
+#include "hdr/locale_macros.h"
 #include "src/__support/common.h"
 #include "src/__support/macros/config.h"
+#include "src/locale/locale.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
-LLVM_LIBC_FUNCTION(locale_t, uselocale, (locale_t newloc)) {
-  if (!newloc)
-    return locale;
-  return locale = newloc;
+LLVM_LIBC_FUNCTION(locale_t, uselocale, ([[maybe_unused]] locale_t newloc)) {
+  if constexpr (DISABLE_RUNTIME_LOCALE)
+    return LC_GLOBAL_LOCALE;
+
+  locale_t oldloc = get_thread_locale();
+  if (newloc != nullptr)
+    set_thread_locale(newloc == LC_GLOBAL_LOCALE ? nullptr : newloc);
+
+  return (oldloc == nullptr) ? LC_GLOBAL_LOCALE : oldloc;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/locale/CMakeLists.txt b/libc/test/src/locale/CMakeLists.txt
index 5032ce9c324e2..bee4fb54dd008 100644
--- a/libc/test/src/locale/CMakeLists.txt
+++ b/libc/test/src/locale/CMakeLists.txt
@@ -7,10 +7,13 @@ add_libc_test(
   SRCS
     locale_test.cpp
   DEPENDS
+    libc.hdr.errno_macros
     libc.hdr.locale_macros
+    libc.src.__support.CPP.scope
+    libc.src.locale.freelocale
     libc.src.locale.newlocale
     libc.src.locale.uselocale
-    libc.src.locale.freelocale
+    libc.test.UnitTest.ErrnoCheckingTest
 )
 
 add_libc_test(
diff --git a/libc/test/src/locale/locale_test.cpp b/libc/test/src/locale/locale_test.cpp
index d19e2605fca37..d3b407b2b3b5b 100644
--- a/libc/test/src/locale/locale_test.cpp
+++ b/libc/test/src/locale/locale_test.cpp
@@ -1,47 +1,62 @@
-//===-- Unittests for locale ----------------------------------------------===//
+//===----------------------------------------------------------------------===//
 //
 // 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
+/// Unittests for locale functions.
+///
+//===----------------------------------------------------------------------===//
 
+#include "hdr/errno_macros.h"
 #include "hdr/locale_macros.h"
+#include "src/__support/CPP/scope.h"
 #include "src/locale/freelocale.h"
 #include "src/locale/newlocale.h"
 #include "src/locale/uselocale.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
 #include "test/UnitTest/Test.h"
 
-TEST(LlvmLibcLocale, DefaultLocale) {
+using LlvmLibcLocale = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+
+TEST_F(LlvmLibcLocale, DefaultLocale) {
   locale_t new_locale = LIBC_NAMESPACE::newlocale(LC_ALL_MASK, "C", nullptr);
-  EXPECT_NE(new_locale, static_cast<locale_t>(nullptr));
+  ASSERT_NE(new_locale, nullptr);
+  LIBC_NAMESPACE::cpp::scope_exit free_new(
+      [&] { LIBC_NAMESPACE::freelocale(new_locale); });
 
   locale_t old_locale = LIBC_NAMESPACE::uselocale(new_locale);
-  EXPECT_NE(old_locale, static_cast<locale_t>(nullptr));
-
-  LIBC_NAMESPACE::freelocale(new_locale);
+  ASSERT_NE(old_locale, nullptr);
+  EXPECT_NE(LIBC_NAMESPACE::uselocale(nullptr), nullptr);
 
-  LIBC_NAMESPACE::uselocale(old_locale);
+  locale_t restored_locale = LIBC_NAMESPACE::uselocale(old_locale);
+  EXPECT_NE(restored_locale, nullptr);
 }
 
-TEST(LlvmLibcLocale, NewLocaleValidation) {
-  // Choosing masks within LC_*_MASK is OK.
+TEST_F(LlvmLibcLocale, NewLocaleValidation) {
   locale_t loc =
       LIBC_NAMESPACE::newlocale(LC_CTYPE_MASK | LC_NUMERIC_MASK, "C", nullptr);
-  EXPECT_NE(loc, static_cast<locale_t>(nullptr));
+  ASSERT_NE(loc, nullptr);
+  LIBC_NAMESPACE::freelocale(loc);
+
+  loc = LIBC_NAMESPACE::newlocale(LC_ALL_MASK, "POSIX", nullptr);
+  ASSERT_NE(loc, nullptr);
   LIBC_NAMESPACE::freelocale(loc);
 
-  // Empty locale name is implementation-defined,
-  // defaults to C locale.
   loc = LIBC_NAMESPACE::newlocale(LC_ALL_MASK, "", nullptr);
-  EXPECT_NE(loc, static_cast<locale_t>(nullptr));
+  ASSERT_NE(loc, nullptr);
   LIBC_NAMESPACE::freelocale(loc);
 
-  // Masks outside the valid range are rejected.
-  loc = LIBC_NAMESPACE::newlocale(~0, "C", nullptr);
-  EXPECT_EQ(loc, static_cast<locale_t>(nullptr));
+  EXPECT_EQ(LIBC_NAMESPACE::newlocale(~0, "C", nullptr), nullptr);
+  ASSERT_ERRNO_EQ(EINVAL);
+
+  EXPECT_EQ(LIBC_NAMESPACE::newlocale(LC_ALL_MASK, nullptr, nullptr), nullptr);
+  ASSERT_ERRNO_EQ(EINVAL);
 
-  // Invalid locale name is rejected.
-  loc = LIBC_NAMESPACE::newlocale(LC_ALL_MASK, "does-not-exist", nullptr);
-  EXPECT_EQ(loc, static_cast<locale_t>(nullptr));
+  EXPECT_EQ(LIBC_NAMESPACE::newlocale(LC_ALL_MASK, "does-not-exist", nullptr),
+            nullptr);
+  ASSERT_ERRNO_EQ(ENOENT);
 }



More information about the libc-commits mailing list