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

Jeff Bailey via libc-commits libc-commits at lists.llvm.org
Thu Jul 23 07:17:34 PDT 2026


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

>From 7ef52595d9cae93d9485b614b31143e8d987d72d 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

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 is
designed to eliminate all runtime overhead. Indirection through
locale_t objects and thread-local state lookups are bypassed in favour
of direct, compile-time constant lookups, allowing all runtime locale
machinery and dynamic objects to be elided for embedded environments.

Assisted-by: Automated tooling, human reviewed.
---
 .../modules/LLVMLibCCompileOptionRules.cmake  |  10 +
 libc/config/config.json                       |  10 +
 libc/include/llvm-libc-macros/locale-macros.h |   6 +-
 libc/src/locale/locale.cpp                    | 193 +++++++++++++++++-
 libc/src/locale/locale.h                      |  25 ++-
 libc/src/locale/locale_data.h                 |  66 ++++++
 libc/src/locale/newlocale.cpp                 |  10 +-
 libc/src/locale/uselocale.cpp                 |  24 ++-
 libc/test/src/locale/locale_test.cpp          |  11 +-
 9 files changed, 332 insertions(+), 23 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 c97f906cb663b..fc61d545151b6 100644
--- a/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake
+++ b/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake
@@ -169,6 +169,16 @@ 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)
+    if("${LIBC_CONF_DEFAULT_LOCALE}" MATCHES ".*UTF-8" OR "${LIBC_CONF_DEFAULT_LOCALE}" MATCHES ".*utf8")
+      libc_add_definition(config_options "LIBC_CONF_DEFAULT_LOCALE_IS_UTF8")
+    endif()
+  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 d576d7d14e4af..f3568140f19a0 100644
--- a/libc/config/config.json
+++ b/libc/config/config.json
@@ -190,5 +190,15 @@
       "value": false,
       "doc": "Use the system assert macro for LIBC_ASSERT."
     }
+  },
+  "locale": {
+    "LIBC_CONF_DEFAULT_LOCALE": {
+      "value": "C",
+      "doc": "The default locale for the C library. Options: 'C', '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..e7bd1ebcea723 100644
--- a/libc/include/llvm-libc-macros/locale-macros.h
+++ b/libc/include/llvm-libc-macros/locale-macros.h
@@ -19,14 +19,14 @@
 #define LC_MESSAGES 5
 #define LC_ALL 6
 
-#define LC_GLOBAL_LOCALE ((locale_t)(-1))
-
 #define LC_CTYPE_MASK (1 << LC_CTYPE)
 #define LC_NUMERIC_MASK (1 << LC_NUMERIC)
 #define LC_TIME_MASK (1 << LC_TIME)
 #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/src/locale/locale.cpp b/libc/src/locale/locale.cpp
index 2f7e13fb1d6f7..d337232639722 100644
--- a/libc/src/locale/locale.cpp
+++ b/libc/src/locale/locale.cpp
@@ -1,19 +1,206 @@
-//===-- 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 locale data and thread-local state.
+///
+//===----------------------------------------------------------------------===//
 
 #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};
+const LcCtypeData C_CTYPE_DATA = {
+    // 0x00..0x08
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    // 0x09..0x0D (\t, \n, \v, \f, \r)
+    _ISspace | _ISblank,
+    _ISspace,
+    _ISspace,
+    _ISspace,
+    _ISspace,
+    // 0x0E..0x1F
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    // 0x20 (space)
+    _ISspace | _ISblank | _ISprint,
+    // 0x21..0x2F (!, ", #, $, %, &, ', (, ), *, +, ,, -, ., /)
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    // 0x30..0x39 (0..9)
+    _ISdigit | _ISxdigit | _ISprint,
+    _ISdigit | _ISxdigit | _ISprint,
+    _ISdigit | _ISxdigit | _ISprint,
+    _ISdigit | _ISxdigit | _ISprint,
+    _ISdigit | _ISxdigit | _ISprint,
+    _ISdigit | _ISxdigit | _ISprint,
+    _ISdigit | _ISxdigit | _ISprint,
+    _ISdigit | _ISxdigit | _ISprint,
+    _ISdigit | _ISxdigit | _ISprint,
+    _ISdigit | _ISxdigit | _ISprint,
+    // 0x3A..0x40 (:, ;, <, =, >, ?, @)
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    // 0x41..0x46 (A..F)
+    _ISupper | _ISalpha | _ISxdigit | _ISprint,
+    _ISupper | _ISalpha | _ISxdigit | _ISprint,
+    _ISupper | _ISalpha | _ISxdigit | _ISprint,
+    _ISupper | _ISalpha | _ISxdigit | _ISprint,
+    _ISupper | _ISalpha | _ISxdigit | _ISprint,
+    _ISupper | _ISalpha | _ISxdigit | _ISprint,
+    // 0x47..0x5A (G..Z)
+    _ISupper | _ISalpha | _ISprint,
+    _ISupper | _ISalpha | _ISprint,
+    _ISupper | _ISalpha | _ISprint,
+    _ISupper | _ISalpha | _ISprint,
+    _ISupper | _ISalpha | _ISprint,
+    _ISupper | _ISalpha | _ISprint,
+    _ISupper | _ISalpha | _ISprint,
+    _ISupper | _ISalpha | _ISprint,
+    _ISupper | _ISalpha | _ISprint,
+    _ISupper | _ISalpha | _ISprint,
+    _ISupper | _ISalpha | _ISprint,
+    _ISupper | _ISalpha | _ISprint,
+    _ISupper | _ISalpha | _ISprint,
+    _ISupper | _ISalpha | _ISprint,
+    _ISupper | _ISalpha | _ISprint,
+    _ISupper | _ISalpha | _ISprint,
+    _ISupper | _ISalpha | _ISprint,
+    _ISupper | _ISalpha | _ISprint,
+    _ISupper | _ISalpha | _ISprint,
+    _ISupper | _ISalpha | _ISprint,
+    // 0x5B..0x60 ([, \, ], ^, _, `)
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    // 0x61..0x66 (a..f)
+    _ISlower | _ISalpha | _ISxdigit | _ISprint,
+    _ISlower | _ISalpha | _ISxdigit | _ISprint,
+    _ISlower | _ISalpha | _ISxdigit | _ISprint,
+    _ISlower | _ISalpha | _ISxdigit | _ISprint,
+    _ISlower | _ISalpha | _ISxdigit | _ISprint,
+    _ISlower | _ISalpha | _ISxdigit | _ISprint,
+    // 0x67..0x7A (g..z)
+    _ISlower | _ISalpha | _ISprint,
+    _ISlower | _ISalpha | _ISprint,
+    _ISlower | _ISalpha | _ISprint,
+    _ISlower | _ISalpha | _ISprint,
+    _ISlower | _ISalpha | _ISprint,
+    _ISlower | _ISalpha | _ISprint,
+    _ISlower | _ISalpha | _ISprint,
+    _ISlower | _ISalpha | _ISprint,
+    _ISlower | _ISalpha | _ISprint,
+    _ISlower | _ISalpha | _ISprint,
+    _ISlower | _ISalpha | _ISprint,
+    _ISlower | _ISalpha | _ISprint,
+    _ISlower | _ISalpha | _ISprint,
+    _ISlower | _ISalpha | _ISprint,
+    _ISlower | _ISalpha | _ISprint,
+    _ISlower | _ISalpha | _ISprint,
+    _ISlower | _ISalpha | _ISprint,
+    _ISlower | _ISalpha | _ISprint,
+    _ISlower | _ISalpha | _ISprint,
+    _ISlower | _ISalpha | _ISprint,
+    // 0x7B..0x7E ({, |, }, ~)
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    _ISpunct | _ISprint,
+    // 0x7F (DEL)
+    0,
+};
+
+const LcCtypeData UTF8_CTYPE_DATA = C_CTYPE_DATA;
+
+const LcTimeData C_TIME_DATA = {
+    {"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"},
+    "",
+    "",
+    "",
+    "",
+};
+
+__locale_t c_locale = {{
+    reinterpret_cast<__locale_data *>(
+        const_cast<LcCtypeData *>(&C_CTYPE_DATA)), // LC_CTYPE
+    nullptr,                                       // LC_NUMERIC
+    nullptr,                                       // LC_TIME
+    nullptr,                                       // LC_COLLATE
+    nullptr,                                       // LC_MONETARY
+    nullptr,                                       // LC_MESSAGES
+}};
+
+__locale_t utf8_locale = {{
+    reinterpret_cast<__locale_data *>(
+        const_cast<LcCtypeData *>(&UTF8_CTYPE_DATA)), // LC_CTYPE
+    nullptr,                                          // LC_NUMERIC
+    nullptr,                                          // LC_TIME
+    nullptr,                                          // LC_COLLATE
+    nullptr,                                          // LC_MONETARY
+    nullptr,                                          // LC_MESSAGES
+}};
+
+locale_t global_locale = &c_locale;
 
-locale_t locale = nullptr;
+LIBC_THREAD_LOCAL locale_t thread_locale = nullptr;
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/locale/locale.h b/libc/src/locale/locale.h
index 14befa6e7a9c2..27971fc028a75 100644
--- a/libc/src/locale/locale.h
+++ b/libc/src/locale/locale.h
@@ -1,10 +1,15 @@
-//===-- 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
@@ -12,24 +17,34 @@
 #include "src/__support/macros/attributes.h"
 #include "src/__support/macros/config.h"
 
+#include "hdr/locale_macros.h"
 #include "hdr/types/locale_t.h"
 
 #include <stddef.h>
 
-namespace LIBC_NAMESPACE_DECL {
+#ifndef LC_GLOBAL_LOCALE
+#define LC_GLOBAL_LOCALE ((locale_t)(-1))
+#endif
 
-// We only support the "C" locale right now.
-static constexpr size_t MAX_LOCALE_NAME_SIZE = 2;
+static constexpr size_t MAX_LOCALE_NAME_SIZE = 16;
 
 struct __locale_data {
   char name[MAX_LOCALE_NAME_SIZE];
 };
 
+namespace LIBC_NAMESPACE_DECL {
+
 // The pointer to the default "C" locale.
 extern __locale_t c_locale;
 
+// The pointer to the static UTF-8 locale.
+extern __locale_t utf8_locale;
+
 // The global locale instance.
-extern locale_t locale;
+extern locale_t global_locale;
+
+// The thread-local locale instance.
+extern LIBC_THREAD_LOCAL locale_t thread_locale;
 
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/src/locale/locale_data.h b/libc/src/locale/locale_data.h
new file mode 100644
index 0000000000000..17703057eeb43
--- /dev/null
+++ b/libc/src/locale/locale_data.h
@@ -0,0 +1,66 @@
+//===----------------------------------------------------------------------===//
+//
+// 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/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+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;
+};
+
+extern const LcCtypeData C_CTYPE_DATA;
+extern const LcCtypeData UTF8_CTYPE_DATA;
+extern const LcNumericData C_NUMERIC_DATA;
+extern const LcTimeData C_TIME_DATA;
+extern const LcMonetaryData C_MONETARY_DATA;
+extern const LcMessagesData C_MESSAGES_DATA;
+
+} // 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..90972786f01e2 100644
--- a/libc/src/locale/newlocale.cpp
+++ b/libc/src/locale/newlocale.cpp
@@ -1,10 +1,15 @@
-//===-- 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"
@@ -17,6 +22,9 @@ namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(locale_t, newlocale,
                    (int category_mask, const char *locale_name, locale_t)) {
+  if (!locale_name)
+    return nullptr;
+
   cpp::string_view name(locale_name);
   if ((category_mask & ~LC_ALL_MASK) != 0 || (!name.empty() && name != "C"))
     return nullptr;
diff --git a/libc/src/locale/uselocale.cpp b/libc/src/locale/uselocale.cpp
index d6fdad248f12b..b43bb89ed60e0 100644
--- a/libc/src/locale/uselocale.cpp
+++ b/libc/src/locale/uselocale.cpp
@@ -1,23 +1,35 @@
-//===-- 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;
+  locale_t oldloc = thread_locale;
+
+  if (newloc != nullptr) {
+    if (newloc == LC_GLOBAL_LOCALE)
+      thread_locale = nullptr;
+    else
+      thread_locale = newloc;
+  }
+
+  return (oldloc == nullptr) ? LC_GLOBAL_LOCALE : oldloc;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/locale/locale_test.cpp b/libc/test/src/locale/locale_test.cpp
index d19e2605fca37..7c6b1b0d90c7e 100644
--- a/libc/test/src/locale/locale_test.cpp
+++ b/libc/test/src/locale/locale_test.cpp
@@ -14,27 +14,28 @@
 
 TEST(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, static_cast<locale_t>(nullptr));
 
   locale_t old_locale = LIBC_NAMESPACE::uselocale(new_locale);
-  EXPECT_NE(old_locale, static_cast<locale_t>(nullptr));
+  ASSERT_NE(old_locale, static_cast<locale_t>(nullptr));
 
   LIBC_NAMESPACE::freelocale(new_locale);
 
-  LIBC_NAMESPACE::uselocale(old_locale);
+  locale_t restored_locale = LIBC_NAMESPACE::uselocale(old_locale);
+  ASSERT_NE(restored_locale, static_cast<locale_t>(nullptr));
 }
 
 TEST(LlvmLibcLocale, NewLocaleValidation) {
   // Choosing masks within LC_*_MASK is OK.
   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, static_cast<locale_t>(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, static_cast<locale_t>(nullptr));
   LIBC_NAMESPACE::freelocale(loc);
 
   // Masks outside the valid range are rejected.



More information about the libc-commits mailing list