[libcxx] [llvm] [libc++] Add support for picolibc and newlib in RUNTIMES_USE_LIBC (PR #147956)

Volodymyr Turanskyy via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 10 06:08:22 PDT 2025


https://github.com/voltur01 created https://github.com/llvm/llvm-project/pull/147956

This replaces detection of picolibc in libc++ (by checking for and including picolibc.h) with using RUNTIMES_USE_LIBC build time option intriduced in https://github.com/llvm/llvm-project/pull/134893 RUNTIMES_USE_LIBC is extended to accept picolibc and newlib.

Detection of picolibc via the header is kept as a deprecated feature to avoid breaking builds.

libc++ is updated to use dedicated LIBCXX_LIBC_NEWLIB macro to check for newlib specific conditions instead of less informative _NEWLIB_VERSION

>From f892007fa5fe938c65f5bafc0a5fe366eeb3c23a Mon Sep 17 00:00:00 2001
From: Volodymyr Turanskyy <volodymyr.turanskyy at arm.com>
Date: Thu, 10 Jul 2025 13:44:51 +0100
Subject: [PATCH] [libc++] Add support for picolibc and newlib in
 RUNTIMES_USE_LIBC

This replaces detection of picolibc in libc++ (by checking for and including picolibc.h) with using RUNTIMES_USE_LIBC build time option intriduced in https://github.com/llvm/llvm-project/pull/134893 RUNTIMES_USE_LIBC is extended to accept picolibc and newlib.

Detection of picolibc via the header is kept as a deprecated feature to avoid breaking builds.

libc++ is updated to use dedicated LIBCXX_LIBC_NEWLIB macro to check for newlib specific conditions instead of less informative _NEWLIB_VERSION
---
 libcxx/CMakeLists.txt                             | 15 +++++++++++++++
 libcxx/include/__config_site.in                   |  5 +++++
 libcxx/include/__configuration/platform.h         |  7 +++----
 libcxx/include/__cxx03/__fwd/ios.h                |  2 +-
 libcxx/include/__cxx03/__locale                   |  2 +-
 .../__cxx03/__locale_dir/locale_base_api.h        |  2 +-
 libcxx/include/__cxx03/fstream                    |  2 +-
 libcxx/include/__cxx03/locale                     |  2 +-
 libcxx/include/__cxx03/regex                      |  6 ++----
 libcxx/include/__fwd/ios.h                        |  2 +-
 libcxx/include/__locale                           |  2 +-
 libcxx/include/__locale_dir/messages.h            |  2 +-
 libcxx/include/fstream                            |  4 ++--
 libcxx/include/regex                              |  6 ++----
 libcxx/src/include/config_elast.h                 |  2 +-
 libcxx/src/locale.cpp                             |  2 +-
 libcxx/test/libcxx/system_reserved_names.gen.py   |  4 ++--
 .../generic_category.pass.cpp                     |  2 +-
 .../system_category.pass.cpp                      |  2 +-
 libcxx/test/support/platform_support.h            |  2 +-
 libcxx/utils/ci/run-buildbot                      |  1 +
 runtimes/cmake/Modules/HandleLibC.cmake           |  4 ++--
 22 files changed, 47 insertions(+), 31 deletions(-)

diff --git a/libcxx/CMakeLists.txt b/libcxx/CMakeLists.txt
index 85514cc7547a9..5c0ea39473ec4 100644
--- a/libcxx/CMakeLists.txt
+++ b/libcxx/CMakeLists.txt
@@ -481,6 +481,21 @@ include(config-ix)
 include(HandleLibC) # Setup the C library flags
 include(HandleLibCXXABI) # Setup the ABI library flags
 
+# Set C library in use to define respective macro in __config_site
+# RUNTIMES_USE_LIBC was checked in HandleLibC to be one of accepted values
+if (RUNTIMES_USE_LIBC STREQUAL "llvm-libc")
+  set(LIBCXX_LIBC_LLVMLIBC 1)
+elseif (RUNTIMES_USE_LIBC STREQUAL "picolibc")
+  set(LIBCXX_LIBC_PICOLIBC 1)
+  # picolibc is derived from newlib and behaves the same in regards to libc++
+  # so setting both here:
+  # * LIBCXX_LIBC_NEWLIB is used now
+  # * LIBCXX_LIBC_PICOLIBC can be used for further customizations later
+  set(LIBCXX_LIBC_NEWLIB 1) 
+elseif (RUNTIMES_USE_LIBC STREQUAL "newlib")
+  set(LIBCXX_LIBC_NEWLIB 1)
+endif()
+
 # FIXME(EricWF): See the FIXME on LIBCXX_ENABLE_PEDANTIC.
 # Remove the -pedantic flag and -Wno-pedantic and -pedantic-errors
 # so they don't get transformed into -Wno and -errors respectively.
diff --git a/libcxx/include/__config_site.in b/libcxx/include/__config_site.in
index fc01aaf2d8746..f01400bd351ec 100644
--- a/libcxx/include/__config_site.in
+++ b/libcxx/include/__config_site.in
@@ -42,6 +42,11 @@
 // Hardening.
 #cmakedefine _LIBCPP_HARDENING_MODE_DEFAULT @_LIBCPP_HARDENING_MODE_DEFAULT@
 
+// C libraries
+#cmakedefine LIBCXX_LIBC_LLVMLIBC
+#cmakedefine LIBCXX_LIBC_PICOLIBC
+#cmakedefine LIBCXX_LIBC_NEWLIB
+
 // __USE_MINGW_ANSI_STDIO gets redefined on MinGW
 #ifdef __clang__
 #  pragma clang diagnostic push
diff --git a/libcxx/include/__configuration/platform.h b/libcxx/include/__configuration/platform.h
index f3c199dee172b..cb97c97c51ae4 100644
--- a/libcxx/include/__configuration/platform.h
+++ b/libcxx/include/__configuration/platform.h
@@ -42,11 +42,10 @@
 #  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.
+// TODO: Remove this deprecated behavior after LLVM 22 release
+// To build libc++ with picolibc provide RUNTIMES_USE_LIBC=picolibc
 #if __has_include(<picolibc.h>)
-#  include <picolibc.h>
+#  define LIBCXX_LIBC_NEWLIB
 #endif
 
 #ifndef __BYTE_ORDER__
diff --git a/libcxx/include/__cxx03/__fwd/ios.h b/libcxx/include/__cxx03/__fwd/ios.h
index dc03e8c6bab2f..5776ad90ea623 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 defined(LIBCXX_LIBC_NEWLIB)
 // 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..d32ac05d28187 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 defined(LIBCXX_LIBC_NEWLIB)
   // 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..ccb8201e8ab1d 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 defined(LIBCXX_LIBC_NEWLIB)
 #  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..f7290878bbc07 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) || defined(LIBCXX_LIBC_NEWLIB)
 #  define _LIBCPP_HAS_NO_OFF_T_FUNCTIONS
 #endif
 
diff --git a/libcxx/include/__cxx03/locale b/libcxx/include/__cxx03/locale
index 64162f5a4ff2c..0de1380601855 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__) && !defined(LIBCXX_LIBC_NEWLIB) && !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..f282c983a7392 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__) || defined(LIBCXX_LIBC_NEWLIB)
   // 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,9 +993,7 @@ 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
+  // We also need this workaround for newlib since newlib is
   // often used for space constrained environments, so it makes sense not to
   // duplicate the ctype table.
   typedef uint16_t char_class_type;
diff --git a/libcxx/include/__fwd/ios.h b/libcxx/include/__fwd/ios.h
index 831624f4b1c57..4db2c5da3861d 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 defined(LIBCXX_LIBC_NEWLIB)
 // 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..50422b5af9963 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 defined(LIBCXX_LIBC_NEWLIB)
   // 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..4ebedc9de6d45 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__) && !defined(LIBCXX_LIBC_NEWLIB) && !defined(__EMSCRIPTEN__)
 #      define _LIBCPP_HAS_CATOPEN 1
 #      include <nl_types.h>
 #    else
diff --git a/libcxx/include/fstream b/libcxx/include/fstream
index c86f709bedb80..9b6b0267267e6 100644
--- a/libcxx/include/fstream
+++ b/libcxx/include/fstream
@@ -964,7 +964,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 defined(LIBCXX_LIBC_NEWLIB)
   return fseek(__file, __offset, __whence);
 #    else
   return ::fseeko(__file, __offset, __whence);
@@ -975,7 +975,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 defined(LIBCXX_LIBC_NEWLIB)
   return ftell(__file);
 #    else
   return ftello(__file);
diff --git a/libcxx/include/regex b/libcxx/include/regex
index bbc21e244dd17..29ba9f6a06725 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__) || defined(LIBCXX_LIBC_NEWLIB)
   // 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,9 +1013,7 @@ 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
+  // We also need this workaround for newlib since newlib is
   // often used for space constrained environments, so it makes sense not to
   // duplicate the ctype table.
   typedef uint16_t char_class_type;
diff --git a/libcxx/src/include/config_elast.h b/libcxx/src/include/config_elast.h
index 7edff2d9375d4..388286e41330f 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 defined(LIBCXX_LIBC_NEWLIB)
 #  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..da4d16c7963f9 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 defined(LIBCXX_LIBC_NEWLIB)
   // 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..5e4809d20fd82 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__) && !defined(LIBCXX_LIBC_NEWLIB)
 #  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
+#ifndef LIBCXX_LIBC_NEWLIB
 # 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..b4fa377b7f101 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 defined(LIBCXX_LIBC_NEWLIB)
         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..ca3a57006f0d4 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 defined(LIBCXX_LIBC_NEWLIB)
     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..655641e858468 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 defined(LIBCXX_LIBC_NEWLIB) && defined(__STRICT_ANSI__)
 // Newlib provides this, but in the header it's under __STRICT_ANSI__
 extern "C" {
   int mkstemp(char*);
diff --git a/libcxx/utils/ci/run-buildbot b/libcxx/utils/ci/run-buildbot
index d8b23be9a0323..9bd63d175a74c 100755
--- a/libcxx/utils/ci/run-buildbot
+++ b/libcxx/utils/ci/run-buildbot
@@ -230,6 +230,7 @@ function test-armv7m-picolibc() {
         -DLIBUNWIND_TEST_CONFIG="armv7m-picolibc-libunwind.cfg.in" \
         -DCMAKE_C_FLAGS="${flags}" \
         -DCMAKE_CXX_FLAGS="${flags}" \
+        -DRUNTIMES_USE_LIBC=picolibc \
         "${@}"
 
     step "Installing compiler-rt"
diff --git a/runtimes/cmake/Modules/HandleLibC.cmake b/runtimes/cmake/Modules/HandleLibC.cmake
index 51fbf04df7e3b..23f735aee6c02 100644
--- a/runtimes/cmake/Modules/HandleLibC.cmake
+++ b/runtimes/cmake/Modules/HandleLibC.cmake
@@ -10,10 +10,10 @@
 
 include_guard(GLOBAL)
 
-set(RUNTIMES_SUPPORTED_C_LIBRARIES system llvm-libc)
+set(RUNTIMES_SUPPORTED_C_LIBRARIES system llvm-libc picolibc newlib)
 set(RUNTIMES_USE_LIBC "system" CACHE STRING "Specify C library to use. Supported values are ${RUNTIMES_SUPPORTED_C_LIBRARIES}.")
 if (NOT "${RUNTIMES_USE_LIBC}" IN_LIST RUNTIMES_SUPPORTED_C_LIBRARIES)
-  message(FATAL_ERROR "Unsupported C library: '${RUNTIMES_CXX_ABI}'. Supported values are ${RUNTIMES_SUPPORTED_C_LIBRARIES}.")
+  message(FATAL_ERROR "Unsupported C library: '${RUNTIMES_USE_LIBC}'. Supported values are ${RUNTIMES_SUPPORTED_C_LIBRARIES}.")
 endif()
 
 # Link against a system-provided libc



More information about the llvm-commits mailing list