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.

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 NativeAOTneeds-triageIssues that need to be assigned.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions