[libcxx-commits] [libcxx] [libc++] Handle Newlib and picolibc using options (PR #152968)

via libcxx-commits libcxx-commits at lists.llvm.org
Mon Aug 11 01:24:42 PDT 2025


https://github.com/inglorion created https://github.com/llvm/llvm-project/pull/152968

Previously, we relied on _NEWLIB_VERSION being defined to detect if we are building with Newlib or its derivative picolibc. This is somewhat unreliable, because whether or not _NEWLIB_VERSION is defined depends on if any headers from the C library have been included or not. This could lead to inconsistent definitions, for example as observed in bug #152763. The code contained a workaround that detected picolibc by looking for its header file, with a TODO to replace this logic with a different mechanism. This change addresses that TODO by handling Newlib and picolibc similarly to how we handle musl: by providing a LIBCXX_HAS_*LIBC option and a corresponding _LIBCPP_HAS_* macro to be used in code.

Specifically:

 - For Newlibc, we provide LIBCXX_HAS_NEWLIB_LIBC and _LIBCPP_HAS_NEWLIB_LIBC.

 - For picolibc, we provide LIBCXX_HAS_PICOLIBC and _LIBCPP_HAS_PICOLIBC.

Existing code has been rewritten so that instead of checking for defined(_NEWLIB_VERSION) it checks for
_LIBCPP_HAS_NEWLIB_LIBC || _LIBCPP_HAS_PICOLIBC.

>From 77927318e78a77dbee3f232955a9716aa84c1f7e Mon Sep 17 00:00:00 2001
From: Bob Haarman <llvm at inglorion.net>
Date: Fri, 8 Aug 2025 17:19:21 +0000
Subject: [PATCH] [libc++] Handle Newlib and picolibc using options

Previously, we relied on _NEWLIB_VERSION being defined to detect if we
are building with Newlib or its derivative picolibc. This is somewhat
unreliable, because whether or not _NEWLIB_VERSION is defined depends
on if any headers from the C library have been included or not. This
could lead to inconsistent definitions, for example as observed in
bug #152763. The code contained a workaround that detected picolibc by
looking for its header file, with a TODO to replace this logic with
a different mechanism. This change addresses that TODO by handling
Newlib and picolibc similarly to how we handle musl: by providing a
LIBCXX_HAS_*LIBC option and a corresponding _LIBCPP_HAS_* macro to be
used in code.

Specifically:

 - For Newlibc, we provide LIBCXX_HAS_NEWLIB_LIBC and
   _LIBCPP_HAS_NEWLIB_LIBC.

 - For picolibc, we provide LIBCXX_HAS_PICOLIBC and
   _LIBCPP_HAS_PICOLIBC.

Existing code has been rewritten so that instead of checking for
defined(_NEWLIB_VERSION) it checks for
_LIBCPP_HAS_NEWLIB_LIBC || _LIBCPP_HAS_PICOLIBC.
---
 libcxx/CMakeLists.txt                                 |  4 ++++
 libcxx/include/__configuration/platform.h             |  7 -------
 libcxx/include/__cxx03/__fwd/ios.h                    |  2 +-
 libcxx/include/__cxx03/__locale                       |  2 +-
 libcxx/include/__cxx03/__locale_dir/locale_base_api.h |  2 +-
 libcxx/include/__cxx03/fstream                        |  2 +-
 libcxx/include/__cxx03/locale                         |  2 +-
 libcxx/include/__cxx03/regex                          | 11 +++++------
 libcxx/include/__fwd/ios.h                            |  2 +-
 libcxx/include/__locale                               |  2 +-
 libcxx/include/__locale_dir/messages.h                |  2 +-
 libcxx/include/fstream                                |  4 ++--
 libcxx/include/regex                                  | 11 +++++------
 libcxx/src/include/config_elast.h                     |  2 +-
 libcxx/src/locale.cpp                                 |  2 +-
 libcxx/test/libcxx/system_reserved_names.gen.py       |  4 ++--
 .../syserr.errcat.objects/generic_category.pass.cpp   |  2 +-
 .../syserr.errcat.objects/system_category.pass.cpp    |  2 +-
 libcxx/test/support/platform_support.h                |  2 +-
 19 files changed, 31 insertions(+), 36 deletions(-)

diff --git a/libcxx/CMakeLists.txt b/libcxx/CMakeLists.txt
index 85514cc7547a9..241dbbccd0600 100644
--- a/libcxx/CMakeLists.txt
+++ b/libcxx/CMakeLists.txt
@@ -285,6 +285,8 @@ endif()
 
 # Feature options -------------------------------------------------------------
 option(LIBCXX_HAS_MUSL_LIBC "Build libc++ with support for the Musl C library" OFF)
+option(LIBCXX_HAS_NEWLIB_LIBC "Build libc++ with support for the Newlib C library" OFF)
+option(LIBCXX_HAS_PICOLIBC "Build libc++ with support for the picolibc C library" OFF)
 option(LIBCXX_HAS_PTHREAD_API "Ignore auto-detection and force use of pthread API" OFF)
 option(LIBCXX_HAS_WIN32_THREAD_API "Ignore auto-detection and force use of win32 thread API" OFF)
 option(LIBCXX_HAS_EXTERNAL_THREAD_API
@@ -742,6 +744,8 @@ config_define(${LIBCXX_HAS_PTHREAD_API} _LIBCPP_HAS_THREAD_API_PTHREAD)
 config_define(${LIBCXX_HAS_EXTERNAL_THREAD_API} _LIBCPP_HAS_THREAD_API_EXTERNAL)
 config_define(${LIBCXX_HAS_WIN32_THREAD_API} _LIBCPP_HAS_THREAD_API_WIN32)
 config_define(${LIBCXX_HAS_MUSL_LIBC} _LIBCPP_HAS_MUSL_LIBC)
+config_define(${LIBCXX_HAS_NEWLIB_LIBC} _LIBCPP_HAS_NEWLIB_LIBC)
+config_define(${LIBCXX_HAS_PICOLIBC} _LIBCPP_HAS_PICOLIBC)
 config_define_if(LIBCXX_NO_VCRUNTIME _LIBCPP_NO_VCRUNTIME)
 config_define(${LIBCXX_ENABLE_FILESYSTEM} _LIBCPP_HAS_FILESYSTEM)
 config_define(${LIBCXX_ENABLE_RANDOM_DEVICE} _LIBCPP_HAS_RANDOM_DEVICE)
diff --git a/libcxx/include/__configuration/platform.h b/libcxx/include/__configuration/platform.h
index f3c199dee172b..8d0f8f63f5213 100644
--- a/libcxx/include/__configuration/platform.h
+++ b/libcxx/include/__configuration/platform.h
@@ -42,13 +42,6 @@
 #  endif
 #endif
 
-// This is required in order for _NEWLIB_VERSION to be defined in places where we use it.
-// TODO: We shouldn't be including arbitrarily-named headers from libc++ since this can break valid
-//       user code. Move code paths that need _NEWLIB_VERSION to another customization mechanism.
-#if __has_include(<picolibc.h>)
-#  include <picolibc.h>
-#endif
-
 #ifndef __BYTE_ORDER__
 #  error                                                                                                               \
       "Your compiler doesn't seem to define __BYTE_ORDER__, which is required by libc++ to know the endianness of your target platform"
diff --git a/libcxx/include/__cxx03/__fwd/ios.h b/libcxx/include/__cxx03/__fwd/ios.h
index dc03e8c6bab2f..356eb18697cab 100644
--- a/libcxx/include/__cxx03/__fwd/ios.h
+++ b/libcxx/include/__cxx03/__fwd/ios.h
@@ -31,7 +31,7 @@ using wios = basic_ios<wchar_t>;
 template <class _CharT, class _Traits>
 class _LIBCPP_PREFERRED_NAME(ios) _LIBCPP_IF_WIDE_CHARACTERS(_LIBCPP_PREFERRED_NAME(wios)) basic_ios;
 
-#if defined(_NEWLIB_VERSION)
+#if _LIBCPP_HAS_NEWLIB_LIBC || _LIBC_HAS_PICOLIBC
 // On newlib, off_t is 'long int'
 using streamoff = long int; // for char_traits in <string>
 #else
diff --git a/libcxx/include/__cxx03/__locale b/libcxx/include/__cxx03/__locale
index d5faa89b99fc0..d16a0f2e7c981 100644
--- a/libcxx/include/__cxx03/__locale
+++ b/libcxx/include/__cxx03/__locale
@@ -384,7 +384,7 @@ public:
   static const mask xdigit       = _ISXDIGIT;
   static const mask blank        = _ISBLANK;
   static const mask __regex_word = 0x8000;
-#elif defined(_NEWLIB_VERSION)
+#elif _LIBCPP_HAS_NEWLIB_LIBC || _LIBC_HAS_PICOLIBC
   // Same type as Newlib's _ctype_ array in newlib/libc/include/ctype.h.
   typedef char mask;
   // In case char is signed, static_cast is needed to avoid warning on
diff --git a/libcxx/include/__cxx03/__locale_dir/locale_base_api.h b/libcxx/include/__cxx03/__locale_dir/locale_base_api.h
index a20f0952f52c3..8a223dc2f7a3c 100644
--- a/libcxx/include/__cxx03/__locale_dir/locale_base_api.h
+++ b/libcxx/include/__cxx03/__locale_dir/locale_base_api.h
@@ -17,7 +17,7 @@
 #  include <__cxx03/__locale_dir/locale_base_api/android.h>
 #elif defined(__sun__)
 #  include <__cxx03/__locale_dir/locale_base_api/solaris.h>
-#elif defined(_NEWLIB_VERSION)
+#elif _LIBCPP_HAS_NEWLIB_LIBC || _LIBC_HAS_PICOLIBC
 #  include <__cxx03/__locale_dir/locale_base_api/newlib.h>
 #elif defined(__OpenBSD__)
 #  include <__cxx03/__locale_dir/locale_base_api/openbsd.h>
diff --git a/libcxx/include/__cxx03/fstream b/libcxx/include/__cxx03/fstream
index 44bdabc4602b5..8ccad9727b353 100644
--- a/libcxx/include/__cxx03/fstream
+++ b/libcxx/include/__cxx03/fstream
@@ -209,7 +209,7 @@ typedef basic_fstream<wchar_t> wfstream;
 _LIBCPP_PUSH_MACROS
 #include <__cxx03/__undef_macros>
 
-#if defined(_LIBCPP_MSVCRT) || defined(_NEWLIB_VERSION)
+#if defined(_LIBCPP_MSVCRT) || _LIBCPP_HAS_NEWLIB_LIBC || _LIBC_HAS_PICOLIBC
 #  define _LIBCPP_HAS_NO_OFF_T_FUNCTIONS
 #endif
 
diff --git a/libcxx/include/__cxx03/locale b/libcxx/include/__cxx03/locale
index 64162f5a4ff2c..4e3c75afa450b 100644
--- a/libcxx/include/__cxx03/locale
+++ b/libcxx/include/__cxx03/locale
@@ -220,7 +220,7 @@ template <class charT> class messages_byname;
 
 #  if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
 // Most unix variants have catopen.  These are the specific ones that don't.
-#    if !defined(__BIONIC__) && !defined(_NEWLIB_VERSION) && !defined(__EMSCRIPTEN__)
+#    if !defined(__BIONIC__) && !_LIBCPP_HAS_NEWLIB_LIBC && !_LIBCPP_HAS_PICOLIBC && !defined(__EMSCRIPTEN__)
 #      define _LIBCPP_HAS_CATOPEN 1
 #      include <nl_types.h>
 #    endif
diff --git a/libcxx/include/__cxx03/regex b/libcxx/include/__cxx03/regex
index b96d59d3a252a..05775c517fd92 100644
--- a/libcxx/include/__cxx03/regex
+++ b/libcxx/include/__cxx03/regex
@@ -984,7 +984,7 @@ public:
   typedef _CharT char_type;
   typedef basic_string<char_type> string_type;
   typedef locale locale_type;
-#if defined(__BIONIC__) || defined(_NEWLIB_VERSION)
+#if defined(__BIONIC__) || _LIBCPP_HAS_NEWLIB_LIBC || _LIBC_HAS_PICOLIBC
   // Originally bionic's ctype_base used its own ctype masks because the
   // builtin ctype implementation wasn't in libc++ yet. Bionic's ctype mask
   // was only 8 bits wide and already saturated, so it used a wider type here
@@ -993,11 +993,10 @@ public:
   // implementation, but this was not updated to match. Since then Android has
   // needed to maintain a stable libc++ ABI, and this can't be changed without
   // an ABI break.
-  // We also need this workaround for newlib since _NEWLIB_VERSION is not
-  // defined yet inside __config, so we can't set the
-  // _LIBCPP_PROVIDES_DEFAULT_RUNE_TABLE macro. Additionally, newlib is
-  // often used for space constrained environments, so it makes sense not to
-  // duplicate the ctype table.
+  //
+  // We also use this logic for Newlib and picolibc. These are often used
+  // for space constrained environments, so it makes sense no to duplicate
+  // the ctype table.
   typedef uint16_t char_class_type;
 #else
   typedef ctype_base::mask char_class_type;
diff --git a/libcxx/include/__fwd/ios.h b/libcxx/include/__fwd/ios.h
index 831624f4b1c57..e2d7e3e82420d 100644
--- a/libcxx/include/__fwd/ios.h
+++ b/libcxx/include/__fwd/ios.h
@@ -31,7 +31,7 @@ using wios = basic_ios<wchar_t>;
 template <class _CharT, class _Traits>
 class _LIBCPP_PREFERRED_NAME(ios) _LIBCPP_IF_WIDE_CHARACTERS(_LIBCPP_PREFERRED_NAME(wios)) basic_ios;
 
-#if defined(_NEWLIB_VERSION)
+#if _LIBCPP_HAS_NEWLIB_LIBC || _LIBC_HAS_PICOLIBC
 // On newlib, off_t is 'long int'
 using streamoff = long int; // for char_traits in <string>
 #else
diff --git a/libcxx/include/__locale b/libcxx/include/__locale
index 757a53951f66e..a3d3f6b257779 100644
--- a/libcxx/include/__locale
+++ b/libcxx/include/__locale
@@ -389,7 +389,7 @@ public:
   static const mask xdigit       = _ISXDIGIT;
   static const mask blank        = _ISBLANK;
   static const mask __regex_word = 0x8000;
-#  elif defined(_NEWLIB_VERSION)
+#  elif _LIBCPP_HAS_NEWLIB_LIBC || _LIBC_HAS_PICOLIBC
   // Same type as Newlib's _ctype_ array in newlib/libc/include/ctype.h.
   typedef char mask;
   // In case char is signed, static_cast is needed to avoid warning on
diff --git a/libcxx/include/__locale_dir/messages.h b/libcxx/include/__locale_dir/messages.h
index c04bf04025ff0..efaf136e58dd3 100644
--- a/libcxx/include/__locale_dir/messages.h
+++ b/libcxx/include/__locale_dir/messages.h
@@ -22,7 +22,7 @@
 
 #  if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
 // Most unix variants have catopen.  These are the specific ones that don't.
-#    if !defined(__BIONIC__) && !defined(_NEWLIB_VERSION) && !defined(__EMSCRIPTEN__)
+#    if !defined(__BIONIC__) && !_LIBCPP_HAS_NEWLIB_LIBC && !_LIBC_HAS_PICOLIBC && !defined(__EMSCRIPTEN__)
 #      define _LIBCPP_HAS_CATOPEN 1
 #      include <nl_types.h>
 #    else
diff --git a/libcxx/include/fstream b/libcxx/include/fstream
index 6d3f20fff688f..2f60ec56433fe 100644
--- a/libcxx/include/fstream
+++ b/libcxx/include/fstream
@@ -977,7 +977,7 @@ template <class _CharT, class _Traits>
 int basic_filebuf<_CharT, _Traits>::__fseek(FILE* __file, pos_type __offset, int __whence) {
 #    if defined(_LIBCPP_MSVCRT_LIKE)
   return _fseeki64(__file, __offset, __whence);
-#    elif defined(_NEWLIB_VERSION)
+#    elif _LIBCPP_HAS_NEWLIB_LIBC || _LIBC_HAS_PICOLIBC
   return fseek(__file, __offset, __whence);
 #    else
   return ::fseeko(__file, __offset, __whence);
@@ -988,7 +988,7 @@ template <class _CharT, class _Traits>
 typename basic_filebuf<_CharT, _Traits>::pos_type basic_filebuf<_CharT, _Traits>::__ftell(FILE* __file) {
 #    if defined(_LIBCPP_MSVCRT_LIKE)
   return _ftelli64(__file);
-#    elif defined(_NEWLIB_VERSION)
+#    elif _LIBCPP_HAS_NEWLIB_LIBC || _LIBC_HAS_PICOLIBC
   return ftell(__file);
 #    else
   return ftello(__file);
diff --git a/libcxx/include/regex b/libcxx/include/regex
index 9bbc3a69021b9..7a051272fd42e 100644
--- a/libcxx/include/regex
+++ b/libcxx/include/regex
@@ -1004,7 +1004,7 @@ public:
   typedef _CharT char_type;
   typedef basic_string<char_type> string_type;
   typedef locale locale_type;
-#    if defined(__BIONIC__) || defined(_NEWLIB_VERSION)
+#    if defined(__BIONIC__) || _LIBCPP_HAS_NEWLIB_LIBC || _LIBC_HAS_PICOLIBC
   // Originally bionic's ctype_base used its own ctype masks because the
   // builtin ctype implementation wasn't in libc++ yet. Bionic's ctype mask
   // was only 8 bits wide and already saturated, so it used a wider type here
@@ -1013,11 +1013,10 @@ public:
   // implementation, but this was not updated to match. Since then Android has
   // needed to maintain a stable libc++ ABI, and this can't be changed without
   // an ABI break.
-  // We also need this workaround for newlib since _NEWLIB_VERSION is not
-  // defined yet inside __config, so we can't set the
-  // _LIBCPP_PROVIDES_DEFAULT_RUNE_TABLE macro. Additionally, newlib is
-  // often used for space constrained environments, so it makes sense not to
-  // duplicate the ctype table.
+  //
+  // We also use this logic for Newlib and picolibc. These are often used
+  // for space constrained environments, so it makes sense no to duplicate
+  // the ctype table.
   typedef uint16_t char_class_type;
 #    else
   typedef ctype_base::mask char_class_type;
diff --git a/libcxx/src/include/config_elast.h b/libcxx/src/include/config_elast.h
index 7edff2d9375d4..9102989a2a2f8 100644
--- a/libcxx/src/include/config_elast.h
+++ b/libcxx/src/include/config_elast.h
@@ -23,7 +23,7 @@
 #  define _LIBCPP_ELAST ELAST
 #elif defined(__LLVM_LIBC__)
 // No _LIBCPP_ELAST needed for LLVM libc
-#elif defined(_NEWLIB_VERSION)
+#elif _LIBCPP_HAS_NEWLIB_LIBC || _LIBC_HAS_PICOLIBC
 #  define _LIBCPP_ELAST __ELASTERROR
 #elif defined(__NuttX__)
 // No _LIBCPP_ELAST needed on NuttX
diff --git a/libcxx/src/locale.cpp b/libcxx/src/locale.cpp
index da735865c322c..94747a05bc314 100644
--- a/libcxx/src/locale.cpp
+++ b/libcxx/src/locale.cpp
@@ -933,7 +933,7 @@ const ctype<char>::mask* ctype<char>::classic_table() noexcept {
   return __pctype_func();
 #  elif defined(__EMSCRIPTEN__)
   return *__ctype_b_loc();
-#  elif defined(_NEWLIB_VERSION)
+#  elif _LIBCPP_HAS_NEWLIB_LIBC || _LIBC_HAS_PICOLIBC
   // Newlib has a 257-entry table in ctype_.c, where (char)0 starts at [1].
   return _ctype_ + 1;
 #  elif defined(_AIX)
diff --git a/libcxx/test/libcxx/system_reserved_names.gen.py b/libcxx/test/libcxx/system_reserved_names.gen.py
index a4f2928eda332..40fc482f728dc 100644
--- a/libcxx/test/libcxx/system_reserved_names.gen.py
+++ b/libcxx/test/libcxx/system_reserved_names.gen.py
@@ -78,7 +78,7 @@
 
 // Test that libc++ doesn't use names that collide with FreeBSD system macros.
 // newlib and picolibc also define these macros
-#if !defined(__FreeBSD__) && !defined(_NEWLIB_VERSION)
+#if !defined(__FreeBSD__) && !_LIBCPP_HAS_NEWLIB_LIBC && !_LIBCPP_HAS_PICOLIBC
 #  define __null_sentinel SYSTEM_RESERVED_NAME
 #  define __generic SYSTEM_RESERVED_NAME
 #endif
@@ -117,7 +117,7 @@
 #endif
 
 // Newlib & picolibc use __input as a parameter name of a64l & l64a
-#ifndef _NEWLIB_VERSION
+#if !_LIBCPP_HAS_NEWLIB_LIBC && !_LIBC_HAS_PICOLIBC
 # define __input SYSTEM_RESERVED_NAME
 #endif
 #define __output SYSTEM_RESERVED_NAME
diff --git a/libcxx/test/std/diagnostics/syserr/syserr.errcat/syserr.errcat.objects/generic_category.pass.cpp b/libcxx/test/std/diagnostics/syserr/syserr.errcat/syserr.errcat.objects/generic_category.pass.cpp
index 5425203304014..fb3514416381a 100644
--- a/libcxx/test/std/diagnostics/syserr/syserr.errcat/syserr.errcat.objects/generic_category.pass.cpp
+++ b/libcxx/test/std/diagnostics/syserr/syserr.errcat/syserr.errcat.objects/generic_category.pass.cpp
@@ -48,7 +48,7 @@ int main(int, char**)
         // responds with an empty message, which we probably want to
         // treat as a failure code otherwise, but we can detect that
         // with the preprocessor.
-#if defined(_NEWLIB_VERSION)
+#if _LIBCPP_HAS_NEWLIB_LIBC || _LIBC_HAS_PICOLIBC
         const bool is_newlib = true;
 #else
         const bool is_newlib = false;
diff --git a/libcxx/test/std/diagnostics/syserr/syserr.errcat/syserr.errcat.objects/system_category.pass.cpp b/libcxx/test/std/diagnostics/syserr/syserr.errcat/syserr.errcat.objects/system_category.pass.cpp
index 255cbe75e2fa9..23ddceb4720ea 100644
--- a/libcxx/test/std/diagnostics/syserr/syserr.errcat/syserr.errcat.objects/system_category.pass.cpp
+++ b/libcxx/test/std/diagnostics/syserr/syserr.errcat/syserr.errcat.objects/system_category.pass.cpp
@@ -59,7 +59,7 @@ int main(int, char**) {
     // responds with an empty message, which we probably want to
     // treat as a failure code otherwise, but we can detect that
     // with the preprocessor.
-#if defined(_NEWLIB_VERSION)
+#if _LIBCPP_HAS_NEWLIB_LIBC || _LIBC_HAS_PICOLIBC
     const bool is_newlib = true;
 #else
     const bool is_newlib = false;
diff --git a/libcxx/test/support/platform_support.h b/libcxx/test/support/platform_support.h
index 99e60f60c5998..cfd5d048c72b0 100644
--- a/libcxx/test/support/platform_support.h
+++ b/libcxx/test/support/platform_support.h
@@ -48,7 +48,7 @@
 # include <string.h> // strverscmp
 #endif
 
-#if defined(_NEWLIB_VERSION) && defined(__STRICT_ANSI__)
+#if (_LIBCPP_HAS_NEWLIB_LIBC || _LIBC_HAS_PICOLIBC) && defined(__STRICT_ANSI__)
 // Newlib provides this, but in the header it's under __STRICT_ANSI__
 extern "C" {
   int mkstemp(char*);



More information about the libcxx-commits mailing list