Skip to content

[RFC] Make _LIBCPP_WIN32API an optional, config-time knob (decouple libc++'s OS-service backend from _WIN32) #212144

Description

@SBKarr

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

  1. 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.

  2. 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.

  3. 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.cppsystem_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.cppGetSystemTimePreciseAsFileTime
    instead of clock_gettime.
  • include/print, include/__ostream/print.h, src/std_stream.hWriteConsoleW
    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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    libc++libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions