[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