Skip to content

[NativeAOT] Stop shipping libc++ archives in the runtime packs - #12524

Merged
simonrozsival merged 1 commit into
dev/simonrozsival/nativeaot-remove-libcppfrom
dev/simonrozsival/nativeaot-drop-libcpp-packs
Aug 28, 2026
Merged

[NativeAOT] Stop shipping libc++ archives in the runtime packs#12524
simonrozsival merged 1 commit into
dev/simonrozsival/nativeaot-remove-libcppfrom
dev/simonrozsival/nativeaot-drop-libcpp-packs

Conversation

@simonrozsival

Copy link
Copy Markdown
Member

Contributes to #12139. Builds on #12523, which stopped linking libc++ into NativeAOT applications.

The NativeAOT runtime packs still ship libc++_static.a, libc++abi.a and libunwind.a even though nothing links them any more.

Why this needs a new item kind

_AndroidNdkRedistributable (in build-tools/scripts/Ndk.targets) tagged NDK files with just two kinds:

  • Systemlibc.so, libdl.so, liblog.so, libm.so, libz.so — shipped to every runtime.
  • Toolchaincrtbegin_so.o, crtend_so.o, libc++_static.a, libc++abi.a, libclang_rt.builtins-*.a, libunwind.a — shipped to CoreCLR and NativeAOT, since both do native linking.

NativeAOT still needs crtbegin_so.o, crtend_so.o and libclang_rt.builtins-*.a, so the Toolchain group cannot just be dropped for NativeAOT.

This adds a third kind, CplusPlus, for the three C++ archives, and ships it only for CoreCLR. Both packaging sites are updated:

  • src/native/native.targets — the local bin/<Config>/lib/packs layout.
  • build-tools/create-packs/Microsoft.Android.Runtime.proj — the shipped NuGet packs.

Size

Per ABI, removed from the NativeAOT runtime pack:

Archive Size
libc++_static.a 15,182,348
libc++abi.a 3,125,348
libunwind.a 91,152
Total 18,398,848

Across android-arm, android-arm64 and android-x64 that is roughly 55 MB of pack content. This does not change application size — that was #12523 — but it shrinks what users restore.

Testing

Deleted each pack directory and regenerated it via _CopyToPackDirs, rather than checking a pack that could still contain stale files.

NativeAOT (android-arm64) — the three archives are gone, and everything NativeAOT links is still present:

crtbegin_so.o  crtend_so.o  libc.so  libclang_rt.builtins-aarch64-android.a
libdl.so  liblog.so  libm.so  libz.so
libnaot-android.debug-static-debug.a  libnaot-android.debug.so
libnaot-android.release-static-release.a  libnaot-android.release.so
libxa-java-interop-release.a

CoreCLR (android-arm64) — all three are still shipped:

crtbegin_so.o  crtend_so.o  libarchive-dso-stub.so  libc.so
libc++_static.a  libc++abi.a  libclang_rt.builtins-aarch64-android.a
libdl.so  liblog.so  libm.so  libunwind.a  libz.so

Mono is unaffected — it only ever received the System kind.

Copilot AI lite review requested due to automatic review settings August 26, 2026 13:31

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 reduces the size of the shipped Android NativeAOT runtime packs by stopping the inclusion of libc++-related NDK archives (libc++_static.a, libc++abi.a, libunwind.a) that are no longer linked by NativeAOT. It does this by introducing a new NDK redistributable “Kind” (CplusPlus) and only packaging those assets for the CoreCLR runtime flavor.

Changes:

  • Introduce a new _AndroidNdkRedistributable.Kind value CplusPlus for the three C++ archives.
  • Update local pack staging (src/native/native.targets) to include CplusPlus assets only for CoreCLR.
  • Update shipped NuGet runtime pack creation (Microsoft.Android.Runtime.proj) to include CplusPlus assets only for CoreCLR.
Show a summary per file
File Description
src/native/native.targets Adds CoreCLR-only inclusion of CplusPlus NDK archives when copying assets into the local runtime pack directory layout.
build-tools/scripts/Ndk.targets Re-tags libc++/libunwind NDK archives from Toolchain to the new CplusPlus kind.
build-tools/create-packs/Microsoft.Android.Runtime.proj Adds CoreCLR-only inclusion of CplusPlus NDK archives when creating the shipped runtime pack NuGets.

Review details

  • Files reviewed: 3/3 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread src/native/native.targets
@simonrozsival simonrozsival added the drop-libcpp Work to remove the libc++ dependency from Android NativeAOT label Aug 26, 2026
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from d165234 to 6cd95f9 Compare August 26, 2026 14:39
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from 6cd95f9 to 880e1de Compare August 27, 2026 06:25
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from 880e1de to 98acccd Compare August 27, 2026 10:21
@jonathanpeppers
jonathanpeppers force-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from 98acccd to 1401b7f Compare August 27, 2026 14:25
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from 1401b7f to 4acae6c Compare August 27, 2026 15:47
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from 4acae6c to c81efce Compare August 27, 2026 16:09
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from c81efce to fdd347d Compare August 27, 2026 21:42
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from fdd347d to 7a5a5c7 Compare August 28, 2026 07:54
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from 7a5a5c7 to 2872174 Compare August 28, 2026 08:47
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from 2872174 to 92b7f75 Compare August 28, 2026 08:55
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from 92b7f75 to 15895a5 Compare August 28, 2026 09:51
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from 15895a5 to 08b4765 Compare August 28, 2026 10:29
Now that NativeAOT applications no longer link libc++, the NativeAOT
runtime packs still carry `libc++_static.a`, `libc++abi.a` and `libunwind.a`
as dead weight.

The NDK redistributables were split into just `System` and `Toolchain`, and
the `Toolchain` group was shipped to both CoreCLR and NativeAOT.  NativeAOT
still needs `crtbegin_so.o`, `crtend_so.o` and `libclang_rt.builtins-*.a`
from that group, so the group cannot simply be dropped.

Introduce a third `CplusPlus` kind for the three C++ archives and ship it
only for CoreCLR, which still links libc++.

Per ABI this removes 18,398,848 bytes from the NativeAOT runtime pack:

| Archive | Size |
| --- | ---: |
| `libc++_static.a` | 15,182,348 |
| `libc++abi.a` | 3,125,348 |
| `libunwind.a` | 91,152 |

Across the three shipped ABIs (`android-arm`, `android-arm64`, `android-x64`)
that is roughly 55 MB.

Contributes to #12139.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from 08b4765 to a970e21 Compare August 28, 2026 12:06
@simonrozsival
simonrozsival merged commit a970e21 into main Aug 28, 2026
6 of 42 checks passed
@simonrozsival
simonrozsival deleted the dev/simonrozsival/nativeaot-drop-libcpp-packs branch August 28, 2026 12:42
@simonrozsival

Copy link
Copy Markdown
Member Author

Consolidated into #12523 to reduce the depth of the #12546 stack.

No code changed: the commits from this PR are now part of #12523 unmodified, and the resulting tree is byte-identical. This PR sat directly on top of #12523 and touched the same files, so reviewing them together is easier than reviewing the same file across two intermediate states.

simonrozsival added a commit that referenced this pull request Sep 3, 2026
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** |

Azure DevOps build [#1572420](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1572420) measured the resulting arm64 Release CoreCLR APK savings:

| artifact | before | after | saving |
|---|---:|---:|---:|
| `libmonodroid.so` (uncompressed APK entry) | 1,094,848 B | 564,736 B | **530,112 B (48.4%)** |
| Simple APK (R8 on/off) | 7,034,299 B | 6,854,075 B | **180,224 B (2.56%)** |
| XForms APK | 18,468,429 B | 18,284,109 B | **184,320 B (1.00%)** |
| XForms APK (R8) | 16,317,971 B | 16,133,651 B | **184,320 B (1.13%)** |

The unexpectedly large `libmonodroid.so` decrease was independently checked against the CI logs. Build #1572420's `apkdiff` output 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](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1572418). The PR-only native patch is unchanged after rebasing. The size reduction is plausible because `libc++_static.a` is linked as a normal archive under `--gc-sections`: removing the last `std::format` references 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:

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