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:

ArchiveSize
libc++_static.a15,182,348
libc++abi.a3,125,348
libunwind.a91,152
Total18,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.

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

CopilotAI 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
FileDescription
src/native/native.targetsAdds CoreCLR-only inclusion of CplusPlus NDK archives when copying assets into the local runtime pack directory layout.
build-tools/scripts/Ndk.targetsRe-tags libc++/libunwind NDK archives from Toolchain to the new CplusPlus kind.
build-tools/create-packs/Microsoft.Android.Runtime.projAdds 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 threadsrc/native/native.targets
@simonrozsivalsimonrozsival added the drop-libcpp Work to remove the libc++ dependency from Android NativeAOT label Aug 26, 2026
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from d165234 to 6cd95f9CompareAugust 26, 2026 14:39
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from 6cd95f9 to 880e1deCompareAugust 27, 2026 06:25
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from 880e1de to 98acccdCompareAugust 27, 2026 10:21
@jonathanpeppers
jonathanpeppersforce-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from 98acccd to 1401b7fCompareAugust 27, 2026 14:25
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from 1401b7f to 4acae6cCompareAugust 27, 2026 15:47
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from 4acae6c to c81efceCompareAugust 27, 2026 16:09
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from c81efce to fdd347dCompareAugust 27, 2026 21:42
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from fdd347d to 7a5a5c7CompareAugust 28, 2026 07:54
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from 7a5a5c7 to 2872174CompareAugust 28, 2026 08:47
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from 2872174 to 92b7f75CompareAugust 28, 2026 08:55
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from 92b7f75 to 15895a5CompareAugust 28, 2026 09:51
@simonrozsival
simonrozsivalforce-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from 15895a5 to 08b4765CompareAugust 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
simonrozsivalforce-pushed the dev/simonrozsival/nativeaot-drop-libcpp-packs branch from 08b4765 to a970e21CompareAugust 28, 2026 12:06
@simonrozsival
simonrozsival merged commit a970e21 into mainAug 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
MemberAuthor

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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

drop-libcppWork to remove the libc++ dependency from Android NativeAOT

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@simonrozsival