Skip to content

[native] Remove std::format from the CoreCLR host - #12534

Open
simonrozsival wants to merge 4 commits into
dev/simonrozsival/nativeaot-remove-libcppfrom
dev/simonrozsival/clr-remove-std-format
Open

[native] Remove std::format from the CoreCLR host#12534
simonrozsival wants to merge 4 commits into
dev/simonrozsival/nativeaot-remove-libcppfrom
dev/simonrozsival/clr-remove-std-format

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Aug 27, 2026

Copy link
Copy Markdown
Member

Part of #12533 (the CoreCLR follow-up to #12139). Stacked on top of #12524.

Why

std::format is by far the biggest single contributor of libc++ symbols in the native host. Every translation unit that formats any value pulls in std::to_chars for float, double and long double, plus std::locale, std::numpunct and std::use_facet — about 15 symbols per object file, whether or not the code ever formats a floating point number.

What

Converts every std::format-based logging call site reachable from the CoreCLR lane to the printf-style log_debugf / log_infof / log_warnf / log_errorf functions that already exist in common/include/shared/log_functions.hh.

This is not just a mechanical swap — unlike the std::format macros, these are annotated with __attribute__((format(printf, ...))), so the compiler now type-checks every format specifier against its argument. -Wformat / -Werror=format-security are already enabled, and the build is clean.

Helpers::abort_application (CAT, std::format (…)) call sites move to the previously unused Helpers::abort_applicationf overload. That overload was only defined in the CoreCLR lane, so an identical definition is added to mono/shared/helpers.cc.

Several of the converted files live in common/ rather than clr/. Those are header-inlined into CoreCLR objects (dso-loader.hh, mainthread-dso-loader.hh, monodroid-dl.hh, …), so leaving them alone would have left the std::format payload in host.cc.o regardless. log_*f is defined in both the CoreCLR and MonoVM lanes, so converting them is safe for Mono too — verified by building it.

Bonus: fixes a latent NativeAOT logging bug

clr/host/bridge-processing.cc is compiled into both the CoreCLR and the NativeAOT hosts. NativeAOT's log_types.hh is a 5-line stub, so those calls fell through to the printf-style macros in java-interop-logger.h — which have no format(printf) attribute, so nothing warned. In NativeAOT builds they printed a literal {} instead of the value. Both sites are now correct in both lanes.

Results

Measured on the arm64 Release CoreCLR archive (libnet-android.release-static-release.a), counting undefined std::__ndk1::* / operator new / operator delete / __cxa_* symbols. Baseline is this PR's base, #12524:

before after
undefined libc++ refs 142 76 (−46%)
objects with the std::format fingerprint 4 0

Per-object, before → after:

object before after
host.cc.o 41 27
assembly-store.cc.o 35 20
typemap.cc.o 22 5
bridge-processing.cc.o 22 2

The std::format fingerprint (co-occurrence of to_chars<float/double/long double> with locale / numpunct / use_facet) is now absent from every object file in the archive.

Deleting the machinery

With the last call site gone, the std::format macros and templates in clr/include/shared/log_types.hh have no users left, so this PR deletes them too. That file is now identical to the existing NativeAOT stub. This generates no code change on its own — the templates were never instantiated, which is why the counts above already show the std::format fingerprint gone — but it means std::format can no longer be reintroduced into the CoreCLR host by accident.

The std::string_view overload of log_write goes with it. It was duplicated in the CoreCLR and MonoVM copies of log_types.hh and existed only so call sites passing a string literal wouldn't have to write .data (). It had four users, all in timing-internal.cc: three pass a literal and now bind to the plain const char* overload, and the fourth passes a view produced by FastTiming::dump () and now uses log_writef () with %.*s.

That last one also retires a fragile invariant. The overload called .data (), so it required a NUL-terminated string — but dump () builds its views from a buffer plus an explicit length. They are all NUL-terminated today and nothing enforced it. %.*s honours the length instead.

std::format is now completely absent from the clr/, common/ and nativeaot/ trees:

lane std::format #include <format>
clr/ 0 0
common/ (shared) 0 0
nativeaot/ 0 0
mono/ 45 2

Verification

Built all three runtime lanes locally for arm64-v8a Release — CoreCLR, MonoVM and NativeAOT — with zero errors and zero new warnings. Building MonoVM caught a real link error (the missing abort_applicationf definition) that a CoreCLR-only build would have missed.

Not in this PR

  • Converting the remaining mono/-only call sites, and deleting the std::format machinery from mono/shared/log_types.hh.
  • Removing the remaining std::string / std::function / std::mutex usage from host.cc and assembly-store.cc (the 76 remaining refs).
  • Dropping the CplusPlusArchive entries from NativeRuntimeComponents.cs — the last step, once the archives are genuinely unreferenced.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR continues the CoreCLR-host libc++ reduction work by removing std::format usage from CoreCLR-reachable native host code paths and switching logging/abort call sites to the existing printf-annotated log_*f / Helpers::abort_applicationf APIs. This reduces libc++ symbol pull-in while also making format/argument mismatches compiler-checked.

Changes:

  • Replaced std::format-style logging with log_debugf / log_infof / log_warnf / log_errorf across CoreCLR host/runtime-base code and shared native headers.
  • Added Helpers::abort_applicationf definition to the Mono shared lane and migrated CoreCLR abort call sites to the abort_applicationf overload.
  • Adjusted a few debug-only type/name helpers and log strings to avoid std::string_view/std::format dependencies in CoreCLR code paths.
Show a summary per file
File Description
src/native/mono/shared/helpers.cc Adds Helpers::abort_applicationf implementation for the Mono lane.
src/native/common/include/runtime-base/timing-internal.hh Switches warning logs to log_warnf (printf-style).
src/native/common/include/runtime-base/system-loadlibrary-wrapper.hh Converts debug logs to log_debugf.
src/native/common/include/runtime-base/mainthread-dso-loader.hh Removes <format> usage and converts logs/abort to printf-style formatting.
src/native/common/include/runtime-base/dso-loader.hh Converts loader tracing/error logs to printf-style formatting.
src/native/clr/runtime-base/android-system.cc Converts environment/system-property diagnostics to printf-style logs.
src/native/clr/pinvoke-override/precompiled.cc Removes <format> usage and converts abort/log statements to abort_applicationf/log_debugf.
src/native/clr/include/runtime-base/monodroid-dl.hh Converts DSO cache and symbol-lookup logs to printf-style formatting.
src/native/clr/include/runtime-base/android-system.hh Converts debug log to log_debugf.
src/native/clr/include/host/typemap.hh Replaces debug label std::string_view constants with const char* and updates debug signatures.
src/native/clr/include/host/pinvoke-override-impl.hh Converts p/invoke override diagnostics to printf-style logging.
src/native/clr/host/typemap.cc Converts typemap debug/release tracing to printf-style logging and updates helper signatures.
src/native/clr/host/host.cc Converts CoreCLR host logging/abort paths to printf-style formatting and adds <cinttypes> usage.
src/native/clr/host/fastdev-assemblies.cc Converts FastDev logs and aborts to printf-style formatting.
src/native/clr/host/bridge-processing.cc Converts GC-related logs to log_*f formatting (including NativeAOT-shared TU).
src/native/clr/host/assembly-store.cc Removes std::format usage (including hex formatting) and converts cache/store logging and aborts to printf-style formatting.

Review details

  • Files reviewed: 16/16 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread src/native/common/include/runtime-base/mainthread-dso-loader.hh
Comment thread src/native/common/include/runtime-base/dso-loader.hh Outdated
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/clr-remove-std-format branch from b4e9cd8 to 01a4b88 Compare August 27, 2026 07:16
@simonrozsival
simonrozsival changed the base branch from main to dev/simonrozsival/nativeaot-drop-libcpp-packs August 27, 2026 07:16
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/clr-remove-std-format branch from ef156f8 to dfe614c Compare August 27, 2026 09:41
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/clr-remove-std-format branch from 0aa1be2 to 05f8061 Compare August 27, 2026 10:21
@jonathanpeppers
jonathanpeppers force-pushed the dev/simonrozsival/clr-remove-std-format branch from 05f8061 to 0d6ad82 Compare August 27, 2026 14:25
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/clr-remove-std-format branch 2 times, most recently from 03be5c7 to f48725d Compare August 27, 2026 16:09
@simonrozsival simonrozsival added the drop-libcpp Work to remove the libc++ dependency from Android NativeAOT label Aug 27, 2026
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/clr-remove-std-format branch 2 times, most recently from 8daa480 to 828dfe6 Compare August 28, 2026 07:54
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/clr-remove-std-format branch from 828dfe6 to c6a73e6 Compare August 28, 2026 08:47
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/clr-remove-std-format branch from c6a73e6 to 5bae941 Compare August 28, 2026 08:55
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/clr-remove-std-format branch from 5bae941 to 71c8a79 Compare August 28, 2026 09:51
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/clr-remove-std-format branch from 71c8a79 to 018724f Compare August 28, 2026 10:29
simonrozsival and others added 4 commits August 28, 2026 14:02
Part of the "drop libc++" effort (#12533). `std::format` is by far the
biggest single contributor of libc++ symbols in the native host: each
translation unit that formats a value pulls in `std::to_chars` for
`float`/`double`/`long double`, `std::locale`, `std::numpunct` and
`std::use_facet`.

This converts every `std::format`-based logging call site in the CoreCLR
lane to the printf-style `log_debugf`/`log_infof`/`log_warnf`/`log_errorf`
functions that already exist in `common/include/shared/log_functions.hh`.
Unlike the `std::format` macros, those are annotated with
`__attribute__((format(printf, ...)))`, so the compiler now type-checks
every format specifier against its argument.

`Helpers::abort_application (CAT, std::format (...))` call sites move to
the previously unused `Helpers::abort_applicationf` overload. That
overload was only defined in the CoreCLR lane, so an identical definition
is added to `mono/shared/helpers.cc`.

Also fixes a latent bug: `clr/host/bridge-processing.cc` is compiled into
both the CoreCLR and the NativeAOT hosts, but NativeAOT's `log_types.hh`
is a stub, so those calls fell through to the printf-style macros in
`java-interop-logger.h` and logged a literal `{}` instead of the value.

Measured on the arm64 Release CoreCLR archive
(`libnet-android.release-static-release.a`), undefined libc++ symbols drop
from 144 to 78 (-46%), and the `std::format` fingerprint is gone from
every object file.

Verified by building the CoreCLR, MonoVM and NativeAOT lanes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e
- Retry the main-thread DSO loader pipe write on EINTR and treat any
  incomplete write as a failure, rather than only checking for -1.
- Drop the duplicated word in the "Trying to load loading shared JNI
  library" log message.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e
With every CoreCLR-lane call site converted to the printf-style `log_*f`
functions, the `std::format`-based macros and templates in
`clr/include/shared/log_types.hh` have no users left. They generated no
code (the templates were never instantiated), but they kept `<format>`
and the whole `std::format` API reachable from every CoreCLR translation
unit. The file is now identical to the NativeAOT stub.

The `std::string_view` overload of `log_write` goes away as well. It was
duplicated in the CoreCLR and MonoVM copies of `log_types.hh` and existed
only so that call sites using a string literal wouldn't have to write
`.data ()`. It had just four users, all in `timing-internal.cc`: three
pass a literal and now rely on the plain `const char*` overload, and the
fourth passes a `std::string_view` produced by `FastTiming::dump ()` and
now uses `log_writef ()` with `%.*s`.

That last one also removes a fragile invariant: the overload called
`.data ()` and so required a NUL-terminated string, but `dump ()` builds
its views from a buffer and an explicit length. They happen to be
NUL-terminated today, and nothing enforced it. `%.*s` honours the length.

`std::format` is now entirely absent from the `clr/`, `common/` and
`nativeaot/` trees; only MonoVM still uses it.

Verified by building the CoreCLR, MonoVM and NativeAOT lanes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e
The `log_time` helper in `FastTiming::dump ()` formatted into a fixed
256-byte buffer and silently clamped the length when the message didn't
fit, so an oversized line would have been quietly cut short.

`snprintf` already reports how much room the message needs, so use it:
format into a stack buffer, and on overflow allocate a heap buffer of
exactly the required size and format again. This matches the existing
`format_message`/`build_message` pair used for the individual timing
events, including freeing the buffer only when it differs from the
stack one.

The stack buffer is now `Constants::MAX_LOGCAT_MESSAGE_LENGTH`, the same
size `dump ()` already uses for each event message, so the heap path is
not expected to be taken in practice.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/clr-remove-std-format branch from 018724f to e3baa71 Compare August 28, 2026 12:06
Base automatically changed from dev/simonrozsival/nativeaot-drop-libcpp-packs to dev/simonrozsival/nativeaot-remove-libcpp August 28, 2026 12:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

drop-libcpp Work to remove the libc++ dependency from Android NativeAOT

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants