Uh oh!
There was an error while loading. Please reload this page.
[native] Remove std::format from the CoreCLR host - #12534
Conversation
There was a problem hiding this comment.
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 withlog_debugf/log_infof/log_warnf/log_errorfacross CoreCLR host/runtime-base code and shared native headers. - Added
Helpers::abort_applicationfdefinition to the Mono shared lane and migrated CoreCLR abort call sites to theabort_applicationfoverload. - Adjusted a few debug-only type/name helpers and log strings to avoid
std::string_view/std::formatdependencies 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
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
b4e9cd8 to
01a4b88Compareef156f8 to
dfe614cCompare0aa1be2 to
05f8061Compare05f8061 to
0d6ad82Compare03be5c7 to
f48725dCompare8daa480 to
828dfe6Compare828dfe6 to
c6a73e6Comparec6a73e6 to
5bae941Compare5bae941 to
71c8a79Compare018724f to
e3baa71Compare7e2707d to
7b1b9a4Compare9fc677b to
1e82b81Comparesimonrozsival
commented
Sep 2, 2026
/review |
✅ Android PR Reviewer completed successfully!
|
There was a problem hiding this comment.
The printf-style migration removes the CoreCLR std::format dependency, keeps format arguments type-checked, handles bounded string views correctly, and the updated APK descriptors agree with the measured size reduction. One hot type-map path loses the old macros’ lazy argument evaluation and now performs GUID-to-text conversion even when assembly logging is disabled; please restore a category guard or equivalent lazy wrapper.
CI is still in progress with no failures reported so far.
Generated by Android PR Reviewer for #12534 · gpt56 · 280.8 AIC · ⌖ 19.3 AIC · ⊞ 25.7K
Comment /review to run again
Uh oh!
There was an error while loading. Please reload this page.
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
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Guard the converted printf-style debug calls before constructing MonoGuidString so disabled assembly logging retains the previous lazy argument evaluation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
43b8893 to
8627170Comparesimonrozsival
commented
Sep 2, 2026
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
simonrozsival
commented
Sep 2, 2026
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Uh oh!
There was an error while loading. Please reload this page.
…12541) Part of #12533 (drop `libc++` from the CoreCLR host). Stacked on top of #12534. `std::mutex` is a thin wrapper over `pthread_mutex_t`, but using it pulls `<mutex>` and out-of-line libc++ symbols into the native host. This PR stores `pthread_mutex_t` directly and provides a small CoreCLR RAII guard so locked scopes retain automatic unlock behavior without libc++. ### What * Replace CoreCLR's `std::mutex` instances with `pthread_mutex_t`, initialized using `PTHREAD_MUTEX_INITIALIZER`. * Add `xamarin::android::lock_guard` under the CoreCLR include tree. It takes `pthread_mutex_t&` and calls `pthread_mutex_lock` / `pthread_mutex_unlock`. * Update CoreCLR's `StartupAwareLock` to take `pthread_mutex_t&` directly. * Convert shared `Timing` to direct pthread calls and keep it explicitly non-copyable and non-movable. * Leave `src/native/mono` unchanged; MonoVM retains its existing `mutex` and templated `lock_guard` implementation in `mono/shared/cppcompat.hh`. `PTHREAD_MUTEX_INITIALIZER` keeps static instances constant-initialized, so this adds no thread-safe initialization guards. The CoreCLR guard is header-only and compiles down to direct pthread calls. ### Effect This removes the last `#include <mutex>` in the repository: | | files including `<mutex>` | `std::mutex` / `std::lock_guard` uses | |---|---:|---:| | before | 6 | 13 | | after | **0** | **0** | It also drops eight undefined libc++ references: | symbol | before | after | |---|---:|---:| | `std::__ndk1::mutex::lock()` | 3 | 0 | | `std::__ndk1::mutex::unlock()` | 3 | 0 | | `std::__ndk1::mutex::~mutex()` | 2 | 0 | | | refs | `__cxa_guard_*` | |---|---:|---:| | #12534 (base) | 55 | 10 | | this PR | **47** | 10 | The link-time `libc++` requirement only disappears when every cause reaches zero, so this is one of several prerequisites rather than a self-sufficient win. The remaining causes (`operator new`/`delete[]`, `__cxa_guard_*`, `std::string`, `__libcpp_verbose_abort`) are tracked in #12533. ### Verification * CoreCLR, MonoVM and NativeAOT build clean. * `fastdev-assemblies.cc` is `#if defined(DEBUG)` and was additionally compiled with `-DDEBUG`. * The CoreCLR guard compiled with the runtime's no-C++-exceptions settings references only `pthread_mutex_lock` and `pthread_mutex_unlock`; it introduces no C++ exception-runtime or initialization-guard symbols. * Real `libc++` references: **CoreCLR 55 → 47, NativeAOT 0**. The eight removed references are exactly the mutex members listed above. * `__cxa_guard_*` undefined references remain at **10**, confirming that the static mutexes remain constant-initialized.
Part of #12533 (the CoreCLR follow-up to #12139). Stacked on top of #12524.
Why
std::formatis by far the biggest single contributor of libc++ symbols in the native host. Every translation unit that formats any value pulls instd::to_charsforfloat,doubleandlong double, plusstd::locale,std::numpunctandstd::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-stylelog_debugf/log_infof/log_warnf/log_errorffunctions that already exist incommon/include/shared/log_functions.hh.This is not just a mechanical swap — unlike the
std::formatmacros, these are annotated with__attribute__((format(printf, ...))), so the compiler now type-checks every format specifier against its argument.-Wformat/-Werror=format-securityare already enabled, and the build is clean.Helpers::abort_application (CAT, std::format (…))call sites move to the previously unusedHelpers::abort_applicationfoverload. That overload was only defined in the CoreCLR lane, so an identical definition is added tomono/shared/helpers.cc.Several of the converted files live in
common/rather thanclr/. Those are header-inlined into CoreCLR objects (dso-loader.hh,mainthread-dso-loader.hh,monodroid-dl.hh, …), so leaving them alone would have left thestd::formatpayload inhost.cc.oregardless.log_*fis 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.ccis compiled into both the CoreCLR and the NativeAOT hosts. NativeAOT'slog_types.hhis a 5-line stub, so those calls fell through to the printf-style macros injava-interop-logger.h— which have noformat(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 undefinedstd::__ndk1::*/operator new/operator delete/__cxa_*symbols. Baseline is this PR's base, #12524:std::formatfingerprintAzure DevOps build #1572420 measured the resulting arm64 Release CoreCLR APK savings:
libmonodroid.so(uncompressed APK entry)The unexpectedly large
libmonodroid.sodecrease was independently checked against the CI logs. Build #1572420'sapkdiffoutput read the actual signed APK and reported the 564,736-byte entry; the 1,094,848-byte baseline was generated by earlier CI and passed the immediately preceding base validation in build #1572418. The PR-only native patch is unchanged after rebasing. The size reduction is plausible becauselibc++_static.ais linked as a normal archive under--gc-sections: removing the laststd::formatreferences prevents the linker from pulling in its locale and floating-point formatting closure. That code compressed well, which is why the signed APK decreases by 180–184 KB rather than the full 530 KB uncompressed ELF reduction.Per-object, before → after:
host.cc.oassembly-store.cc.otypemap.cc.obridge-processing.cc.oThe
std::formatfingerprint (co-occurrence ofto_chars<float/double/long double>withlocale/numpunct/use_facet) is now absent from every object file in the archive.Deleting the machinery
With the last call site gone, the
std::formatmacros and templates inclr/include/shared/log_types.hhhave 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 thestd::formatfingerprint gone — but it meansstd::formatcan no longer be reintroduced into the CoreCLR host by accident.The
std::string_viewoverload oflog_writegoes with it. It was duplicated in the CoreCLR and MonoVM copies oflog_types.hhand existed only so call sites passing a string literal wouldn't have to write.data (). It had four users, all intiming-internal.cc: three pass a literal and now bind to the plainconst char*overload, and the fourth passes a view produced byFastTiming::dump ()and now useslog_writef ()with%.*s.That last one also retires a fragile invariant. The overload called
.data (), so it required a NUL-terminated string — butdump ()builds its views from a buffer plus an explicit length. They are all NUL-terminated today and nothing enforced it.%.*shonours the length instead.std::formatis now completely absent from theclr/,common/andnativeaot/trees:std::format#include <format>clr/common/(shared)nativeaot/mono/Verification
Built all three runtime lanes locally for
arm64-v8aRelease — CoreCLR, MonoVM and NativeAOT — with zero errors and zero new warnings. Building MonoVM caught a real link error (the missingabort_applicationfdefinition) that a CoreCLR-only build would have missed.Not in this PR
mono/-only call sites, and deleting thestd::formatmachinery frommono/shared/log_types.hh.std::string/std::function/std::mutexusage fromhost.ccandassembly-store.cc(the 76 remaining refs).CplusPlusArchiveentries fromNativeRuntimeComponents.cs— the last step, once the archives are genuinely unreferenced.