Summary
libc++ currently hard-wires its OS-service backend to the target OS: <__config>
unconditionally defines _LIBCPP_WIN32API whenever _WIN32 is defined. That single
macro then switches the filesystem, chrono, system_error, <print>/ostream, and
fstream implementations to their <windows.h> code paths — and, as a side effect,
makes std::filesystem::path::value_type wchar_t.
This proposal asks for a config-time knob (e.g. CMake LIBCXX_ENABLE_WIN32_API,
surfaced as #cmakedefine01 _LIBCPP_HAS_WIN32_API in __config_site) that lets a
vendor build libc++ on a _WIN32 target without the Win32 OS backend, so the
library uses its portable POSIX code paths against a POSIX C library.
The change is opt-out only: the default is derived from _WIN32 exactly as today,
so existing MSVC / MinGW builds are byte-for-byte unaffected.
Motivation
There are (at least) three independent axes of "Windows-ness" in a libc++ build:
| Axis |
What it selects |
Already configurable? |
| C++ ABI |
Itanium vs. Microsoft record layout, EH, RTTI, exception_ptr shape |
Yes — _LIBCPP_ABI_FORCE_ITANIUM / _LIBCPP_ABI_FORCE_MICROSOFT |
| Threading backend |
pthread / Win32 / external / C11 |
Yes — _LIBCPP_HAS_THREAD_API_PTHREAD / …_WIN32 / …_EXTERNAL |
| OS-service backend |
filesystem, clock, error strings, console I/O, file open |
No — hard-wired to _WIN32 via _LIBCPP_WIN32API |
libc++ has already accepted that the first two axes should be chooseable rather than
implied by the triple — a build can request the Itanium C++ ABI on Windows, and it can
request the pthread threading backend on Windows (both are real, supported
configurations selected purely from __config_site). The OS-service axis is the odd one
out: it is the only one still inferred directly from _WIN32, with no override.
That inconsistency blocks a legitimate and increasingly relevant configuration:
a full-POSIX standard library on the Windows triple, i.e. libc++ layered on a POSIX
C library (that internally bridges to the OS) rather than on <windows.h> directly.
Why this configuration matters now
-
llvm-libc. LLVM's own from-scratch libc targets a POSIX-shaped interface and is
growing Windows support. A libc++ built on top of llvm-libc wants to call
stat/statvfs/open/clock_gettime/strerror/fopen — the libc it was built
against — not reach around its own libc into CreateFileW/GetDiskFreeSpaceExW/
FormatMessageA. Today _LIBCPP_WIN32API forces the latter, so the two layers fight
over the same responsibilities.
-
Itanium C++ ABI on Windows. clang already supports selecting the C++ ABI
independently of the triple, and libc++ honours that via _LIBCPP_ABI_FORCE_ITANIUM.
A toolchain that deliberately runs the Itanium ABI on Windows is, by construction, a
"POSIX-leaning" Windows toolchain; forcing the Win32 OS backend onto it is
incongruent. Making the OS axis a peer knob to the ABI axis finishes a decoupling
libc++ has already started.
-
Portability / testability. A POSIX-mode libc++ makes std::filesystem::path
char/UTF-8 with / separators on Windows, so the same path-handling and the same
OS-facing code paths are exercised on every target. That collapses a large, Windows-only
maintenance surface (see below) into the shared POSIX paths that already get the most
testing.
Current behavior
libcxx/include/__config:
# if defined(_WIN32)
# define _LIBCPP_WIN32API // <-- hard-wired, no override
# define _LIBCPP_SHORT_WCHAR 1
# define _LIBCPP_MSVCRT_LIKE
# if defined(_MSC_VER) && !defined(__MINGW32__)
# define _LIBCPP_MSVCRT
# endif
# ...
# define _LIBCPP_HAS_OPEN_WITH_WCHAR 1
# else
# define _LIBCPP_HAS_OPEN_WITH_WCHAR 0
# ...
# endif
_LIBCPP_WIN32API then gates OS behavior across the library:
src/filesystem/posix_compat.h — an entire ~370-line reimplementation of
stat/lstat/fstat/statvfs/readlink/symlink/link/rename/truncate/
mkdir/chdir/realpath/getcwd/fchmod[at] against <windows.h>
(CreateFileW, GetFileInformationByHandleEx, GetDiskFreeSpaceExW,
DeviceIoControl, …), plus __filesystem/path.h selecting value_type = wchar_t.
src/system_error.cpp — system_category().message() via FormatMessageA and
default_error_condition() via a Win32-error→errc table, instead of strerror +
the generic errno mapping.
src/chrono.cpp, src/filesystem/filesystem_clock.cpp — GetSystemTimePreciseAsFileTime
instead of clock_gettime.
include/print, include/__ostream/print.h, src/std_stream.h — WriteConsoleW
console path.
include/fstream, src/fstream.cpp — _wfopen and the _LIBCPP_HAS_OPEN_WITH_WCHAR
wide-open overloads.
Note that these are OS-service concerns, distinct from _LIBCPP_MSVCRT[_LIKE]
(C-runtime flavor: locale backend selection in __locale_dir/locale_base_api.h,
printf quirks) and _LIBCPP_SHORT_WCHAR (the 16-bit wchar_t ABI). A vendor may
well want to keep the MSVCRT-like C-runtime flavor and the 16-bit wchar_t ABI while
turning off the Win32 OS backend. Today that combination is unreachable.
Proposal
Add one config-site knob and gate the existing #define on it.
libcxx/include/__config_site.in (mirroring the existing _LIBCPP_HAS_THREAD_API_*
knobs):
#cmakedefine01 _LIBCPP_HAS_WIN32_API
libcxx/CMakeLists.txt — default the option from the target so nothing changes for
existing builds:
# ON for Windows targets by default; a vendor can force it OFF to build a
# POSIX-backed libc++ on the Windows triple (e.g. atop llvm-libc).
option(LIBCXX_ENABLE_WIN32_API "Use the Win32 API for OS services on Windows targets" ${WIN32})
libcxx/include/__config — derive _LIBCPP_WIN32API from the knob instead of raw
_WIN32, leaving the ABI/CRT/wchar_t macros exactly where they are:
# if defined(_WIN32)
# if _LIBCPP_HAS_WIN32_API
# define _LIBCPP_WIN32API
# endif
# define _LIBCPP_SHORT_WCHAR 1
# define _LIBCPP_MSVCRT_LIKE
# ...
# if _LIBCPP_HAS_WIN32_API
# define _LIBCPP_HAS_OPEN_WITH_WCHAR 1
# else
# define _LIBCPP_HAS_OPEN_WITH_WCHAR 0
# endif
# else
# ...
# endif
(For source builds that predate the new __config_site field, _LIBCPP_HAS_WIN32_API
can be given the usual #ifndef … #define … 1 … #endif fallback in __config keyed on
defined(_WIN32), so out-of-tree consumers keep compiling.)
No behavioral change is proposed for the #else (non-Win32) paths — they already exist
and are the ones a POSIX-mode Windows build would use. The request is only to make the
switch reachable.
Backward compatibility
- Default is
ON on _WIN32 → _LIBCPP_WIN32API defined exactly as today. Existing
MSVC and MinGW builds are unaffected; no ABI change, no behavior change.
- The knob is orthogonal to
_LIBCPP_ABI_MICROSOFT and _LIBCPP_SHORT_WCHAR, so it does
not perturb record layout, name mangling, or wchar_t width.
- Turning it OFF does change
std::filesystem::path::value_type to char — this is
an intentional, opt-in ABI choice for the vendor enabling it, on par with choosing the
Itanium C++ ABI on Windows. It is a vendor/config decision, never a default.
Alternatives considered
- Per-subsystem knobs (separate flags for filesystem, chrono, print, …). More
granular but higher surface area and combinatorial testing cost; _LIBCPP_WIN32API
is already the library's single "use the Win32 OS backend" switch, so gating that one
macro is the minimal, faithful change.
_LIBCPP_HAS_MUSL_LIBC-style flag. Overloading an existing "which libc" flag would
conflate the C-library flavor with the OS-API choice; they are independent (a
POSIX-mode Windows build may still be MSVCRT-like for locale/printf).
- Downstream patching (what vendors do today). Works, but every vendor re-derives the
same one-line __config gate and carries it forever; a first-class knob removes that
duplication and signals the configuration is supported.
Summary
libc++ currently hard-wires its OS-service backend to the target OS:
<__config>unconditionally defines
_LIBCPP_WIN32APIwhenever_WIN32is defined. That singlemacro then switches the filesystem,
chrono,system_error,<print>/ostream, andfstreamimplementations to their<windows.h>code paths — and, as a side effect,makes
std::filesystem::path::value_typewchar_t.This proposal asks for a config-time knob (e.g. CMake
LIBCXX_ENABLE_WIN32_API,surfaced as
#cmakedefine01 _LIBCPP_HAS_WIN32_APIin__config_site) that lets avendor build libc++ on a
_WIN32target without the Win32 OS backend, so thelibrary uses its portable POSIX code paths against a POSIX C library.
The change is opt-out only: the default is derived from
_WIN32exactly as today,so existing MSVC / MinGW builds are byte-for-byte unaffected.
Motivation
There are (at least) three independent axes of "Windows-ness" in a libc++ build:
exception_ptrshape_LIBCPP_ABI_FORCE_ITANIUM/_LIBCPP_ABI_FORCE_MICROSOFT_LIBCPP_HAS_THREAD_API_PTHREAD/…_WIN32/…_EXTERNAL_WIN32via_LIBCPP_WIN32APIlibc++ has already accepted that the first two axes should be chooseable rather than
implied by the triple — a build can request the Itanium C++ ABI on Windows, and it can
request the pthread threading backend on Windows (both are real, supported
configurations selected purely from
__config_site). The OS-service axis is the odd oneout: it is the only one still inferred directly from
_WIN32, with no override.That inconsistency blocks a legitimate and increasingly relevant configuration:
a full-POSIX standard library on the Windows triple, i.e. libc++ layered on a POSIX
C library (that internally bridges to the OS) rather than on
<windows.h>directly.Why this configuration matters now
llvm-libc. LLVM's own from-scratch libc targets a POSIX-shaped interface and is
growing Windows support. A libc++ built on top of llvm-libc wants to call
stat/statvfs/open/clock_gettime/strerror/fopen— the libc it was builtagainst — not reach around its own libc into
CreateFileW/GetDiskFreeSpaceExW/FormatMessageA. Today_LIBCPP_WIN32APIforces the latter, so the two layers fightover the same responsibilities.
Itanium C++ ABI on Windows. clang already supports selecting the C++ ABI
independently of the triple, and libc++ honours that via
_LIBCPP_ABI_FORCE_ITANIUM.A toolchain that deliberately runs the Itanium ABI on Windows is, by construction, a
"POSIX-leaning" Windows toolchain; forcing the Win32 OS backend onto it is
incongruent. Making the OS axis a peer knob to the ABI axis finishes a decoupling
libc++ has already started.
Portability / testability. A POSIX-mode libc++ makes
std::filesystem::pathchar/UTF-8 with/separators on Windows, so the same path-handling and the sameOS-facing code paths are exercised on every target. That collapses a large, Windows-only
maintenance surface (see below) into the shared POSIX paths that already get the most
testing.
Current behavior
libcxx/include/__config:_LIBCPP_WIN32APIthen gates OS behavior across the library:src/filesystem/posix_compat.h— an entire ~370-line reimplementation ofstat/lstat/fstat/statvfs/readlink/symlink/link/rename/truncate/mkdir/chdir/realpath/getcwd/fchmod[at]against<windows.h>(
CreateFileW,GetFileInformationByHandleEx,GetDiskFreeSpaceExW,DeviceIoControl, …), plus__filesystem/path.hselectingvalue_type = wchar_t.src/system_error.cpp—system_category().message()viaFormatMessageAanddefault_error_condition()via a Win32-error→errctable, instead ofstrerror+the generic errno mapping.
src/chrono.cpp,src/filesystem/filesystem_clock.cpp—GetSystemTimePreciseAsFileTimeinstead of
clock_gettime.include/print,include/__ostream/print.h,src/std_stream.h—WriteConsoleWconsole path.
include/fstream,src/fstream.cpp—_wfopenand the_LIBCPP_HAS_OPEN_WITH_WCHARwide-open overloads.
Note that these are OS-service concerns, distinct from
_LIBCPP_MSVCRT[_LIKE](C-runtime flavor: locale backend selection in
__locale_dir/locale_base_api.h,printfquirks) and_LIBCPP_SHORT_WCHAR(the 16-bitwchar_tABI). A vendor maywell want to keep the MSVCRT-like C-runtime flavor and the 16-bit
wchar_tABI whileturning off the Win32 OS backend. Today that combination is unreachable.
Proposal
Add one config-site knob and gate the existing
#defineon it.libcxx/include/__config_site.in(mirroring the existing_LIBCPP_HAS_THREAD_API_*knobs):
libcxx/CMakeLists.txt— default the option from the target so nothing changes forexisting builds:
libcxx/include/__config— derive_LIBCPP_WIN32APIfrom the knob instead of raw_WIN32, leaving the ABI/CRT/wchar_tmacros exactly where they are:(For source builds that predate the new
__config_sitefield,_LIBCPP_HAS_WIN32_APIcan be given the usual
#ifndef … #define … 1 … #endiffallback in__configkeyed ondefined(_WIN32), so out-of-tree consumers keep compiling.)No behavioral change is proposed for the
#else(non-Win32) paths — they already existand are the ones a POSIX-mode Windows build would use. The request is only to make the
switch reachable.
Backward compatibility
ONon_WIN32→_LIBCPP_WIN32APIdefined exactly as today. ExistingMSVC and MinGW builds are unaffected; no ABI change, no behavior change.
_LIBCPP_ABI_MICROSOFTand_LIBCPP_SHORT_WCHAR, so it doesnot perturb record layout, name mangling, or
wchar_twidth.std::filesystem::path::value_typetochar— this isan intentional, opt-in ABI choice for the vendor enabling it, on par with choosing the
Itanium C++ ABI on Windows. It is a vendor/config decision, never a default.
Alternatives considered
granular but higher surface area and combinatorial testing cost;
_LIBCPP_WIN32APIis already the library's single "use the Win32 OS backend" switch, so gating that one
macro is the minimal, faithful change.
_LIBCPP_HAS_MUSL_LIBC-style flag. Overloading an existing "which libc" flag wouldconflate the C-library flavor with the OS-API choice; they are independent (a
POSIX-mode Windows build may still be MSVCRT-like for locale/
printf).same one-line
__configgate and carries it forever; a first-class knob removes thatduplication and signals the configuration is supported.