Skip to content

[CoreCLR] Remove libc++ from the CoreCLR host #12533

Description

@simonrozsival

Follow-up to #12139, which removed the libc++/libunwind link-time dependency from NativeAOT applications. This issue tracks doing the same for the CoreCLR host.

Current state

NativeRuntimeComponents.cs still links the C++ runtime into every CoreCLR app:

https://github.com/dotnet/android/blob/main/src/Xamarin.Android.Build.Tasks/Utilities/NativeRuntimeComponents.cs#L129-L140

// C++ standard library
new CplusPlusArchive ("libc++_static.a"),
new CplusPlusArchive ("libc++abi.a"),
...
new CplusPlusArchive ("libunwind.a"), // techically it's from clang

The mechanism already exists

Each runtime lane gets its own shared/log_types.hh via XA_RUNTIME_INCLUDE_DIR. The NativeAOT one is a 5-line stub; the CoreCLR one is 119 lines of std::format wrappers.

The consequence is measurable. I audited freshly built android-arm64 Release archives with llvm-nm --undefined-only. bridge-processing.cc — literally the same source file, compiled into both lanes — has zero libc++ dependencies under NativeAOT and the full std::format payload under CoreCLR:

Lane libc++/ABI undefined symbols in bridge-processing.cc.o
NativeAOT none
CoreCLR to_chars ×9 (float/double/long double), std::locale ×4, numpunct<char>::id, use_facet, basic_string::append/push_back, __libcpp_verbose_abort, operator new, operator delete

The whole NativeAOT host archive has no real libc++ undefined symbols. So this is a tractable, already-proven path — not a rewrite.

What actually blocks CoreCLR

Measured across libnet-android, libruntime-base, libruntime-base-common (arm64, Release):

# Blocker Where Proposed fix
1 std::format — 21 sites. Drags to_chars<float/double/long double> (Ryu tables), locale/numpunct/use_facet, std::string assembly-store.cc, host.cc, fastdev-assemblies.cc, pinvoke-override/precompiled.cc, timing-internal.cc, mainthread-dso-loader.hh 18 of 21 are Helpers::abort_application (...) error paths → fixed char [] + snprintf. No perf concern
2 std::string (43 refs) mostly format results + assembly-store.cc path building (std::to_string (getpid ()), std::format ("{:x}")) Fixed buffers + the format_X ()/build_X () stack→heap retry convention
3 std::function AssemblyStore::configure_from_payload, FastTiming::dump Function pointer + void* context, or a template parameter
4 std::shared_ptr<Timing> host.hh Single owner → raw pointer / static
5 std::mutex ×9 assembly-store.cc, monodroid-dl.hh, startup-aware-lock.hh pthread_mutex_t (Bionic)
6 std::unordered_map<std::string, std::string> bundled_properties clr/include/runtime-base/android-system.hh Currently fully inlined (emits no symbols) but drags <string> → sorted static array + binary search
7 std::stack / std::vector / steady_clock timing-internal.hh, timing.hh Fixed-capacity array + clock_gettime (CLOCK_MONOTONIC)
8 Residue: operator new/delete, __cxa_guard_*, __cxa_thread_atexit, __libcpp_verbose_abort, __cxx_atomic_notify_all all TUs Small shim TU (malloc/free) — unless CoreCLR already provides them

Amplifier worth fixing early

common/include/runtime-base/mainthread-dso-loader.hh uses std::format in a header, reaching android-system.cc and typemap.cc through dso-loader.hhmonodroid-dl.hh. Fixing that one header shrinks the blast radius considerably.

Good news

There are no C++ exceptions anywhere in the host — zero _Unwind_*, __cxa_throw, typeinfo or vtable symbols. -fno-exceptions is doing its job, so libunwind.a is only a question for CoreCLR itself, not for our code.

Gating unknown ⚠️

Does libcoreclr_static.a itself require libc++? CoreCLR's VM and PAL are heavily C++, so it may, regardless of what we do to our host.

For NativeAOT the trick was that dotnet/runtime ships libstdc++compat.a, which satisfied operator new (see #12523 (comment)). CoreCLR has no equivalent, so we would likely need our own shim TU.

This needs to be answered before step 5 below, otherwise we could do all the work and still link libc++:

llvm-nm --undefined-only libcoreclr_static.a \
  | grep -cE '__ndk1|^_ZSt|^_ZNSt|^_Zn[wa]m|^_Zd[la]Pv'

(libcoreclr_static.a is not in the CoreCLR runtime pack — microsoft.netcore.app.runtime.android-arm64 ships only the BCL natives — so this needs a build that produces it.)

Proposed sequencing

Mirroring the stack that worked for NativeAOT (#12513#12524):

  1. Helpers::abort_application non-format overload + convert the 18 abort sites
  2. mainthread-dso-loader.hh + assembly-store.cc path building
  3. Timing: std::stack / std::vector / std::function / std::shared_ptr / steady_clock
  4. std::mutex + bundled_properties
  5. Shims + drop the archives from NativeRuntimeComponents.cs and CMake

Steps 1–4 are worth doing on their own merits (they remove allocation and std::string from startup paths). Step 5 is the one gated on the question above.

Also spotted

format_managed_type_name in clr/host/typemap.cc is dead code — it produces an -Wunused-function warning on every CoreCLR build.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area: App+Library BuildIssues when building Library projects or Application projects.drop-libcppWork to remove the libc++ dependency from Android NativeAOT

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions