[NativeAOT] Stop linking and shipping libc++ and libunwind - #12523
Conversation
6c0530e to
75d714c
Compare
There was a problem hiding this comment.
Pull request overview
This PR advances the NativeAOT size/startup work in #12139 by removing libc++/libunwind from the NativeAOT link, while also refactoring several native runtime paths to avoid local-string helpers and reduce incidental C++ runtime dependencies. It also updates test baselines for resulting native library/package size changes.
Changes:
- Update NativeAOT MSBuild targets to stop linking
libc++_static.a,libc++abi.a, andlibunwind.a, and to stop removinglibstdc++compat.a. - Refactor multiple native runtime components (Mono + CoreCLR hosts) to use explicit bounded buffers /
snprintf/ heap fallback helpers instead of local-string types. - Update
.apkdesctest resource baselines to reflect newlibmonodroid.soand package sizes.
Show a summary per file
| File | Description |
|---|---|
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.CoreCLR.R8.apkdesc | Update expected APK contents/sizes after native size change. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.CoreCLR.apkdesc | Update expected APK contents/sizes after native size change. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.R8.apkdesc | Update expected APK contents/sizes after native size change. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc | Update expected APK contents/sizes after native size change. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets | Stop linking libc++/libunwind for NativeAOT and keep libstdc++compat.a. |
| src/native/mono/runtime-base/util.hh | Add joined-path formatting/join helpers and shared helpers include. |
| src/native/mono/runtime-base/android-system.hh | Add string_view system property overload returning into caller buffer. |
| src/native/mono/runtime-base/android-system.cc | Implement new buffer-based system property retrieval fallback path. |
| src/native/mono/monodroid/monodroid-glue.cc | Replace local-string timing “more info” assembly strings with bounded formatting + heap fallback. |
| src/native/common/runtime-base/timing-internal.cc | Replace property parsing and timing log path composition to use char buffers + join helper. |
| src/native/common/include/runtime-base/timing-internal.hh | Replace dynamic_local_string formatting with snprintf-based message building and add_more_info overloads. |
| src/native/clr/runtime-base/util.cc | Replace dynamic_local_string directory creation with manual mutable buffer + heap fallback. |
| src/native/clr/runtime-base/logger.cc | Switch various path and category parsing to buffer-based helpers and string_view. |
| src/native/clr/runtime-base/android-system.cc | Replace local-string path building with join helpers; refactor DSO path formatting to explicit buffers. |
| src/native/clr/runtime-base/android-system-shared.cc | Change system property retrieval to direct buffer writes; remove small-buffer heap shim in __system_property_get wrapper. |
| src/native/clr/include/runtime-base/util.hh | Add join_paths / format_dso_name helpers; remove path-combine concepts/local-string overloads. |
| src/native/clr/include/runtime-base/logger.hh | Update set_category signature to accept std::string_view. |
| src/native/clr/include/runtime-base/android-system.hh | Introduce buffer-based path formatting helpers; refactor primary override dir formatting. |
| src/native/clr/include/host/pinvoke-override-impl.hh | Build lib*.so pinvoke names via new DSO-name formatter with heap fallback. |
| src/native/clr/include/host/os-bridge.hh | Add missing include for shared utilities used by the header. |
| src/native/clr/include/host/host-environment.hh | Refactor XDG path building to use buffer join logic (and environment setup changes). |
| src/native/clr/include/host/assembly-store.hh | Remove dependency on local-string header. |
| src/native/clr/host/typemap.cc | Build managed type names via bounded formatting + heap fallback. |
| src/native/clr/host/host.cc | Avoid temporary local-string allocations for logging/timing type names. |
| src/native/clr/host/bridge-processing.cc | Add missing includes needed after refactors. |
| src/native/clr/host/assembly-store.cc | Replace local-string timing messages with bounded snprintf messages. |
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 0
- Review effort level: Lite
75d714c to
8021d9f
Compare
dd62525 to
4126bc1
Compare
Linker evidence for keeping
|
| probe | before | after |
|---|---|---|
libc++abi |
2 | 0 |
std::bad_alloc |
1 | 0 |
terminate_handler |
2 | 0 |
_Unwind_Resume |
2 | 0 |
Size impact
Default MAUI app, android-arm64, Release, PublishAot:
| before | after | delta | |
|---|---|---|---|
.so |
25,229,320 | 25,029,728 | −199,592 (−0.79%) |
.so deflated |
9,829,143 | 9,761,291 | −67,852 (−0.69%) |
| APK | 14,903,624 | 14,833,992 | −69,632 (−0.47%) |
Why there is no CMake change in this PR
src/native/**/CMakeLists.txt only produces a static archive (libnaot-android.release-static-release.a) — ar bundling, no linking, so there is nothing there to stop linking. The application link is driven entirely by Microsoft.Android.Sdk.NativeAOT.targets, which is what this PR changes.
4126bc1 to
4dab0f8
Compare
4dab0f8 to
5ac0b54
Compare
5ac0b54 to
31a6570
Compare
4bc321d to
7f9f586
Compare
ea894f2 to
d292114
Compare
d292114 to
935ec9d
Compare
935ec9d to
0b54728
Compare
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
5438683 to
c9386c6
Compare
Promote the ARM EHABI personality symbols already embedded in the NativeAOT runtime archive instead of linking and shipping another libunwind copy. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
/review |
|
✅ Android PR Reviewer completed successfully!
|
There was a problem hiding this comment.
Review Summary
Verdict:
Findings: 0 errors · 1 warning · 0 suggestions
The ARM EHABI archive rewrite is narrowly scoped and the added ARM build coverage verifies both linker sources use the rewritten archive without libunwind.a. One generated-file lifecycle issue is called out inline: the new intermediate archive needs to be registered in @(FileWrites) so Clean owns it.
CI: 🟡 Build #1576810 is still in progress. Five test checks and CLA have passed; the Linux, macOS, and Windows platform builds are running, with no failures currently reported.
Generated by Android PR Reviewer for #12523 · gpt56 · 139.1 AIC · ⌖ 9.15 AIC · ⊞ 25.7K
Comment /review to run again
Register the generated EHABI runtime archive in FileWrites so clean and incremental-clean bookkeeping owns it. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
/review |
|
✅ Android PR Reviewer completed successfully!
|
There was a problem hiding this comment.
Verdict: ⚠️ Needs Changes
0 errors, 0 warnings, 2 suggestions. The implementation cleanly separates C++ runtime archives from the NativeAOT pack while preserving CoreCLR contents, retains the required libstdc++compat.a, and correctly tracks the generated ARM archive for clean builds. I left two inline suggestions for regression coverage of the weak ARM EHABI bindings and the shipped runtime-pack contents.
CI: 🟡 dotnet-android build #1576868 is still in progress; no failing checks were reported at the time of review, so the PR is not yet confirmed green.
Generated by Android PR Reviewer for #12523 · gpt56 · 128.8 AIC · ⌖ 8.97 AIC · ⊞ 30.9K
Comment /review to run again
Verify the promoted ARM EHABI personalities remain weak globals, and assert the shipped NativeAOT/CoreCLR runtime packs exclude or retain the three C++ archives as intended. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Seed the pack project managed input from installed workload packs so the composition test does not depend on source-tree framework outputs that are absent from CI test jobs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
/review |
|
✅ Android PR Reviewer completed successfully!
|
There was a problem hiding this comment.
❌ Reject
Findings: 1 error · 0 warnings · 0 suggestions
The linker change is well-supported by the ARM symbol-binding checks and the generated-package coverage. One incremental local-pack issue remains: previously copied C++ archives survive the new item filtering and continue to appear in the configured local workload until the pack directory is manually cleaned.
CI build 1577186 is still in progress; completed build and tooling lanes are passing so far.
Generated by Android PR Reviewer for #12523 · gpt56 · 119.1 AIC · ⌖ 8.9 AIC · ⊞ 25.7K
Comment /review to run again
Delete C++ archives left by older NativeAOT local packs before copying current assets, cover the incremental transition, and remove the CI-only PDB dependency from the package composition test. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
/review |
|
✅ Android PR Reviewer completed successfully!
|
There was a problem hiding this comment.
Verdict: ⚠️ Needs Changes
Findings: 0 errors · 0 warnings · 1 suggestion
The runtime-pack split preserves CoreCLR’s C++ archives while removing them from NativeAOT, and the ARM EHABI rewrite is narrowly scoped, clean-owned, and verified to produce weak globals. I left one inline suggestion to make the linker regression test cover all three removed C++ archives rather than only libunwind.a.
CI: 🟡 Azure DevOps build #1577542 is still in progress. All completed checks are passing; Package Tests macOS > APKs 2 remains in progress and the aggregate check is queued.
Generated by Android PR Reviewer for #12523 · gpt56 · 188.6 AIC · ⌖ 9.04 AIC · ⊞ 25.7K
Comment /review to run again
Assert that libc++_static.a, libc++abi.a, and libunwind.a are all absent from ARM NativeAOT linker response files. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Closes #12139
Closes #12146
Removes
libc++_static.a,libc++abi.aandlibunwind.afrom the NativeAOT link. This is the payoff for the local-string removal work in the rest of this stack: withstrings.hh,dynamic_local_stringandstatic_local_stringgone, nothing inlibnaot-androiduses libc++ any more.For 32-bit ARM, ILC output still references the EHABI personality routines
__aeabi_unwind_cpp_pr0and__aeabi_unwind_cpp_pr1. The NativeAOT runtime already contains their implementations inlibRuntime.WorkstationGC.a, but dotnet/runtime localizes those symbols.LinkNativeAotSharedLibrarynow makes an intermediate copy of that archive and promotespr0,pr1andpr2to weak globals withllvm-objcopy. This adds no shim implementation and avoids linking or shipping a second copy of libunwind.Who was actually using the C++ runtime?
Linking with libc++ removed leaves exactly six undefined symbols. Only one of them came from our code:
operator new[](size_t)android-system-shared.cc— oursoperator delete(void*)gcenv.ee.cpp,UnixNativeCodeManager.cppoperator delete[](void*)gcenv.ee.cpp,interoplibinterface_java.cppoperator new(size_t, nothrow_t const&)gcenv.ee.cpp,TypeManager.cppoperator new[](size_t, nothrow_t const&)gcenv.ee.cpp,RhConfig.cppstd::nothrowgcenv.ee.cpp,UnixNativeCodeManager.cppThe five runtime-owned ones belong to the NativeAOT runtime from dotnet/runtime, and the ILC SDK already ships definitions for all of them in
libstdc++compat.a. Our targets were unconditionally removing that archive with the comment "This library conflicts with static libc++" — which is only true while libc++ is linked. With libc++ gone there is no conflict, so we simply stop removing it.The one symbol that was ours came from a dead code path in
monodroid__system_property_get, which is now removed earlier in the stack by #12517 (the PR that makes that path unreachable in the first place).The result is no shim implementation in this repo and a link with zero undefined symbols.
32-bit ARM EHABI
android-armuses ARM EHABI unwind tables. The generated NativeAOT object references__aeabi_unwind_cpp_pr0and__aeabi_unwind_cpp_pr1; removing the NDKlibunwind.ainitially exposed these as XA3007 linker failures on both macOS and Windows CI.libRuntime.WorkstationGC.aalready carries the samepr0,pr1andpr2implementations inRuntime.PrivateLibunwind.o, together with the private unwinder they call. They are local symbols, so the application object cannot resolve them directly. Before linking anarmeabi-v7aapplication, the build task createslibRuntime.WorkstationGC.arm-ehabi.ain the intermediate directory and promotes only those three symbols to weak globals. Weak binding preserves compatibility with applications that provide their own strong EHABI personalities, while the app export script keeps them local to the final DSO.No extra unwind code is linked: this reuses the object that the NativeAOT runtime already extracts.
Size impact
Default MAUI app (
dotnet new maui),net11.0-android,android-arm64, Release,PublishAot=true. Both sides built clean from the same tree..so.sodeflated in APKCommits
libunwind.a.Notes
std::unordered_map, which drags in__next_primeand__libcpp_verbose_abortfrom libc++ internals. The rest of this stack is already rebased on top of it, so nothing further is needed.NativeRuntimeComponents.cs(the unified-runtime archive list) is deliberately untouched.Testing
Built, installed and launched a default MAUI app on an API 36 arm64 emulator. Cold start with no crashes.
llvm-nm --undefined-onlyon the resultinglibnaot-android.release-static-release.areports nooperator new/operator delete,__cxa_*,_Unwind_*or__libcpp_*references. The only remainingstd::symbols arestring_viewappearing in mangled names, which is header-only and carries no runtime dependency.The 32-bit ARM path is covered locally in both linker configurations:
BuildNativeAot_AndroidArm_WithoutNdk— workload linker.BuildNativeAot_AndroidArm_WithNdkLinker— NDK linker.Both tests build successfully and assert that the linker response uses
libRuntime.WorkstationGC.arm-ehabi.aand does not containlibunwind.a.Also: stop shipping the archives in the runtime packs
Previously a separate PR stacked directly on this one; folded in here because "stop linking it" and "stop shipping it" are the same change to the reader, and reviewing them apart means reading the same targets twice.
The NativeAOT runtime packs still shipped
libc++_static.a,libc++abi.aandlibunwind.aeven though, after the change above, nothing links them any more.Why this needs a new item kind
_AndroidNdkRedistributable(inbuild-tools/scripts/Ndk.targets) tagged NDK files with just two kinds:System—libc.so,libdl.so,liblog.so,libm.so,libz.so— shipped to every runtime.Toolchain—crtbegin_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.oandlibclang_rt.builtins-*.a, so theToolchaingroup 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 localbin/<Config>/lib/packslayout.build-tools/create-packs/Microsoft.Android.Runtime.proj— the shipped NuGet packs.Size
Per ABI, removed from the NativeAOT runtime pack:
libc++_static.alibc++abi.alibunwind.aAcross
android-arm,android-arm64andandroid-x64that is roughly 55 MB of pack content. This does not change application size — that is the linker change above — 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:CoreCLR (
android-arm64) — all three are still shipped:Mono is unaffected — it only ever received the
Systemkind.