Drop libc++ from Android NativeAOT linking#11311
Conversation
Emulator validation updateI tested the signed NativeAOT + trimmable typemap sample APK on an arm64 emulator after rebuilding/overlaying the local validation packs. What works:
Important caveats:
|
|
@copilot resolve the merge conflicts in this pull request |
Resolved by merging |
|
@copilot resolve the merge conflicts in this pull request |
Resolved locally by merging |
Remove the explicit NativeAOT final-link dependency on libc++/libc++abi and keep the Android NativeAOT link guarded with -nostdlib++. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Centralize gref log message formatting in OSBridge so call sites can pass printf-style arguments directly and formatting is skipped when gref logging is disabled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep gref file guards at the call sites, allow literal single-argument gref log calls, and centralize printf-style log formatting helpers for CLR native logging. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1c6205f to
6a4feea
Compare
There was a problem hiding this comment.
Pull request overview
This PR (draft/WIP) explores removing the Android NativeAOT link-time dependency on libc++/libc++abi by adjusting MSBuild NativeAOT linking inputs and runtime component packaging, and by refactoring native runtime code paths to avoid pulling in C++ standard library symbols (including introducing minimal C++ allocation/nothrow shims).
Changes:
- Remove explicit libc++/libc++abi static link inputs and runtime component entries for NativeAOT Android builds.
- Add NativeAOT-local C++ runtime shims (operator new/delete +
std::nothrow) and refactor multiple native components to avoidstd::format/other STL usage. - Refactor CLR/native logging and GC bridge processing (including callback plumbing for bridge processing and temporary peer map implementation changes).
Reviewed changes
Copilot reviewed 34 out of 34 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Xamarin.Android.Build.Tasks/Utilities/NativeRuntimeComponents.cs | Drops libc++/libc++abi from the known runtime archive list. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets | Removes libc++/libc++abi from NativeAOT link inputs; updates linker/stdlib commentary. |
| src/native/nativeaot/include/host/host.hh | Adds NativeAOT host header shim include. |
| src/native/nativeaot/include/host/bridge-processing.hh | Adapts NativeAOT bridge-processing to callback-based shared implementation. |
| src/native/nativeaot/host/internal-pinvoke-stubs.cc | Simplifies abort path for unimplemented pinvokes to avoid extra std deps. |
| src/native/nativeaot/host/host.cc | Removes std::format usage from NativeAOT host logging path. |
| src/native/nativeaot/host/cxx-shims.cc | Adds minimal C++ allocation / nothrow shims to avoid libc++ dependency. |
| src/native/nativeaot/host/CMakeLists.txt | Adds cxx-shims.cc to NativeAOT host build. |
| src/native/nativeaot/host/bridge-processing.cc | Removes std::format usage and wires callbacks for NativeAOT bridge processing. |
| src/native/common/include/shared/helpers.hh | Adds Helpers::abort_applicationf formatted abort helper. |
| src/native/common/include/shared/cpp-util.hh | Removes C++ ranges/string allocations from diagnostics helpers and logging. |
| src/native/common/include/runtime-base/timing.hh | Replaces std::format timing log formatting with snprintf. |
| src/native/common/include/runtime-base/timing-internal.hh | Replaces std::format timing/internal warnings with snprintf-based logging. |
| src/native/common/include/runtime-base/strings.hh | Replaces some std-container usage and updates formatting/logging to avoid STL pulls. |
| src/native/common/include/runtime-base/jni-wrappers.hh | Replaces new[]/delete[] usage with malloc + placement-new where needed. |
| src/native/clr/shared/log_functions.cc | Introduces vprintf-style log helpers (log_writev, log_*_fmt). |
| src/native/clr/shared/helpers.cc | Adds implementation of Helpers::abort_applicationf; switches some fatal logging to printf-style. |
| src/native/clr/runtime-base/util.cc | Updates logging calls to new printf-style logging helpers. |
| src/native/clr/runtime-base/logger.cc | Refactors gref/lref log path storage away from std::string; updates open/log messages. |
| src/native/clr/runtime-base/android-system-shared.cc | Replaces some new[]/delete[] allocations and formatting with malloc/snprintf and fmt logging. |
| src/native/clr/include/shared/log_types.hh | Adds printf-style logging APIs and adjusts macro behavior for XA_HOST_NATIVEAOT builds. |
| src/native/clr/include/runtime-base/util.hh | Switches to formatted abort/log helpers; adds mmap failure formatted aborts and info logging. |
| src/native/clr/include/runtime-base/android-system.hh | Changes override-dir storage away from std::string for NativeAOT compatibility; API now returns const char*. |
| src/native/clr/include/host/os-bridge.hh | Adds formatted gref logging overloads and refactors internal logging helpers signatures. |
| src/native/clr/include/host/host-environment.hh | Updates debug/warn logging calls to avoid std::format and skip work when disabled. |
| src/native/clr/include/host/gc-bridge.hh | Replaces std::thread/semaphore with pthread/sem_t for compatibility and reduced STL usage. |
| src/native/clr/include/host/bridge-processing.hh | Removes now-unneeded CoreCLR no-op overrides after shared callback refactor. |
| src/native/clr/include/host/bridge-processing-shared.hh | Introduces callback plumbing and switches temporary peer map to robin_map. |
| src/native/clr/host/os-bridge.cc | Refactors stack-trace/gref logging to avoid std::format and centralize formatted logging. |
| src/native/clr/host/internal-pinvokes-shared.cc | Updates managed->native log forwarding to respect category enablement and use log_write. |
| src/native/clr/host/host-shared.cc | Replaces std::format/string_view literal usage in error logging with printf-style. |
| src/native/clr/host/gc-bridge.cc | Migrates to sem_wait/atomic builtins and updates logging formatting. |
| src/native/clr/host/fastdev-assemblies.cc | Updates override-dir handling to match AndroidSystem API change to const char*. |
| src/native/clr/host/bridge-processing.cc | Implements callback-based bridge processing shared logic and temporary peer lifecycle handling. |
Comments suppressed due to low confidence (1)
src/native/clr/host/os-bridge.cc:203
- OSBridge::log_it() logs the main line to logcat unconditionally via log_write(...), even when logcat_enabled is false. This makes
gref/lreflogging emit to logcat even in the “file-only” configuration, and thelogcat_enabledparameter only affects stack traces. Consider only calling log_write when logcat_enabled is true (and keep file writes independent), so behavior matches Logger::{gref,lref}_to_logcat().
void OSBridge::log_it (LogCategories category, const char *line, FILE *to, const char *const from, bool logcat_enabled) noexcept
{
log_write (category, LogLevel::Info, line);
// We skip logcat here when logging to file is enabled because _write_stack_trace will output to logcat as well, if enabled
if (to == nullptr) {
if (logcat_enabled) {
_write_stack_trace (nullptr, from, category);
}
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add the missing standard and logging declarations needed by the native runtime headers when they are compiled directly by the Android runtime ninja builds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Avoid ambiguous gref log overload resolution, insert temporary GC bridge peers into robin_map without mutating through the iterator proxy, and include robin_map headers in the NativeAOT host build. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep the robin_map-backed temporary peer lookup for the CLR host, but use a simple indexed JNI handle table for the NativeAOT host so app-linked static runtimes do not pull in libc++/c++abi symbols. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Avoid allocating a temporary peer slot for every GC bridge component in the NativeAOT host. The NativeAOT path now stores only the temporary peers it creates, while the CLR host continues to use robin_map. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the CoreCLR-only robin_map implementation so bridge processing uses the same sparse temporary peer list for both CoreCLR and NativeAOT. Leave a note about a possible future lookup optimization using the SCC Count field. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Encapsulate temporary peer storage in a dedicated RAII type and encode temporary peer indexes in the SCC count while the bridge owns the GC cross-reference arguments. Share the implementation across CoreCLR and NativeAOT, keeping only the NativeAOT GCUserPeerable callbacks runtime-specific. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Convert native runtime logging macros to use printf-style helpers across CLR, Mono, and NativeAOT. This removes the NativeAOT-only preformatted logging branch while preserving category gating for debug and info logs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Avoid calling GetMethodID with a null jclass when initializing the temporary peer map. This preserves the intended abort message when the runtime field lookup fails. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Explain that these shims intentionally cover only the no-libc++ allocation symbols needed by the NativeAOT runtime pack while native code is built without C++ exceptions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Drop libc++ from NativeAOT linking reduces libUnnamedProject.so by ~625KB. Refresh the BuildReleaseArm64 size-regression reference descriptions from the latest CI build. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
This removes the Android NativeAOT app link-time dependency on libc++.
The branch now:
libc++_static.aandlibc++abi.alink inputs;ld.lld/NativeLinkerpath;tsl::robin_mapor other libc++-requiring containers;snprintfboilerplate and skip formatting when logging is disabled.Context
This is related to #9926 and the NDK r29 NativeAOT linking work.
The relevant background is that Android NativeAOT should avoid depending on libc++ instead of working around duplicate
libunwindsymbols or switching to shared libc++. Related references:libunwindsymbol failure and discusses removing the libc++ dependency from Android NativeAOT.GC bridge / Java peer shape history
The GC bridge sharing in this PR exposed one remaining runtime-specific difference: classic Mono/CoreCLR Java peers use
mono.android.*andmonodroidAddReference()/monodroidClearReferences(), while NativeAOT trimmable proxy objects usenet.dot.jni.GCUserPeerableandjiAddManagedReference()/jiClearManagedReferences().Brief history:
mono.android.GCUserPeertemporary-peer model.managedReferencesand exposemonodroidAddReference()/monodroidClearReferences().Type.GetType()? java-interop#1165 and [Java.Interop] AvoidType.GetType()inManagedPeerjava-interop#1168 show why Java.Interop's Java-sideManagedPeer.registerNativeMembers()/ string-based type lookup path was problematic for trimming and NativeAOT.Microsoft.Android.Runtime.NativeAOT.dll#9760 introducedMicrosoft.Android.Runtime.NativeAOT.dlland moved NativeAOT Java runtime pieces from the sample into build-time generated sources.JavaProxyObject.java/JavaProxyThrowable.javaonly for_AndroidTypeMapImplementation=trimmable.JnienvInitializeArgswith GC-user-peer class refs, which is where the NativeAOT bridge path had to account for the trimmableGCUserPeerableshape.ManagedPeernative registration.So the current difference appears to be historical layering rather than a fundamental GC bridge requirement: Mono/CoreCLR kept the older Android-compatible
monodroid*Java peer API, while NativeAOT trimmable support was built around new generatednet.dot.jni.*proxy sources that avoid the legacy Java.Interop native-registration shape. Follow-up issue: #11526 tracks unifying these Java peer reference APIs, likely by moving the trimmable path toward a singlenet.dot.*shape while NativeAOT is still experimental.Size impact
Latest measurements on this branch use
samples/NativeAOT/NativeAOT.csprojbuilt in Release with_AndroidTypeMapImplementation=trimmable. APK sizes are for the signed APK.libNativeAOT.solibNativeAOT.solibNativeAOT.sois the per-ABI native shared library packaged in the APK, for examplelib/arm64-v8a/libNativeAOT.so. It is not the whole Android app package; the APK also contains manifest, resources, Java stubs/classes, signatures, and packaging assets.Validation
Latest local validation:
git diff --checkmake prepare CONFIGURATION=Debugmake all CONFIGURATION=Debugprogressed throughnative-mono,native-nativeaot, andnative-clr; the remaining failure was a stale test restore asset issue unrelated to native code../dotnet-local.sh build src/native/native-nativeaot.csproj -c Debug --no-restore -v minimal./dotnet-local.sh build src/native/native-clr.csproj -c Debug --no-restore -v minimal./dotnet-local.sh build src/native/native-mono.csproj -c Debug --no-restore -v minimal./dotnet-local.sh build samples/NativeAOT/NativeAOT.csproj -c Release -p:_AndroidTypeMapImplementation=trimmable -p:RuntimeIdentifier=android-arm64 -v minimal./dotnet-local.sh build samples/NativeAOT/NativeAOT.csproj -c Release -p:_AndroidTypeMapImplementation=trimmable -p:RuntimeIdentifier=android-x64 -v minimal./dotnet-local.sh build samples/NativeAOT/NativeAOT.csproj -c Debug -t:Install -p:RuntimeIdentifier=android-arm64samples/NativeAOTon an arm64 emulator and confirmedApplication.OnCreate()andMainActivity.OnCreate()ran without fatal logcat entries.Previous branch validation:
dotnet test tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj --no-restore— 457 passed.src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj;android-arm64andandroid-x64;samples/NativeAOT/NativeAOT.csprojwith_AndroidTypeMapImplementation=trimmableforandroid-arm64andandroid-x64;libc++/libc++abiinputs;llvm-nm -ureports no undefined C++ runtime-looking symbols in the finallibNativeAOT.sooutputs;libNativeAOT.soloaded without libc++,MainApplicationandMainActivitynative callbacks ran,MainActivity.OnCreate()logged, and the process remained alive.Notes
The x64 APK builds and packages without libc++, but local x64 runtime validation still needs an x64 emulator host because Android emulator x86_64 system images are not accepted on Apple Silicon/aarch64 hosts.