Skip to content

[native] Remove the libc++ dependencies from the CoreCLR host's startup path - #12570

Open
simonrozsival wants to merge 1 commit into
dev/simonrozsival/fix-jstring-array-wrapperfrom
dev/simonrozsival/clr-host-drop-strings
Open

[native] Remove the libc++ dependencies from the CoreCLR host's startup path#12570
simonrozsival wants to merge 1 commit into
dev/simonrozsival/fix-jstring-array-wrapperfrom
dev/simonrozsival/clr-host-drop-strings

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Aug 28, 2026

Copy link
Copy Markdown
Member

Part of #12533 (drop libc++ from the CoreCLR host). Stacked on top of #12568.

This clears host.cc entirely. The CoreCLR host goes from 21 undefined libc++ references to 12, and every one that remains is now in a single file, assembly-store.cc.

The three uses

1. The assembly store path

scan_filesystem_for_assemblies_and_libraries joined the native library directory and the store filename with a std::string. It now uses Util::join_paths, added earlier in this stack, which keeps the path in a stack buffer unless it does not fit and frees only when the returned pointer differs from that buffer.

2. APP_CONTEXT_BASE_DIRECTORY

static std::string app_context_base_directory;

The static is needed because the value has to outlive coreclr_initialize — CoreCLR keeps the pointer. But std::string has a non-trivial constructor, so a function-local static of that type forces a guard variable, and this was the only source of __cxa_guard_acquire/__cxa_guard_release in the file. A plain char* is constant-initialized, so the guard pair disappears along with the string.

3. The FastDev TPA list

static std::string fastdev_tpa_list;
static std::vector<const char*> fastdev_prop_names;
static std::vector<const char*> fastdev_prop_values;

This block is inside if constexpr (Constants::is_debug_build), so it contributes nothing to the Release numbers below — but it has to go before libc++ can be dropped from Debug builds, which is why it is included here rather than left for later. The two property arrays are a fixed prop_count + 1 entries, so they become calloced arrays.

build_tpa_list now returns the string

It previously filled a std::string& out-parameter and returned bool; it now returns a malloced string the caller owns, or nullptr.

The TPA list is the one place in this PR with no useful upper bound — it holds one absolute path per assembly in the override directory — so a fixed buffer is not appropriate. It is accumulated through a small growable buffer that doubles on demand. Allocation failure falls back to the probe-only path rather than aborting, since FastDev is a debug convenience and must not take the application down.

open_assembly

new uint8_t[]/delete[] become malloc/free. That buffer is handed to CoreCLR and never freed — see the existing TODO above it about ownership — so this is not a behavioural change.

It also gains a null check. That is a small bug fix: new uint8_t[] would have thrown std::bad_alloc and aborted, whereas the new path logs, closes the file descriptor and returns nullptr. The previous code would have leaked asm_fd had it ever returned rather than aborted.

Header cleanup

<string> and <vector> are no longer included by host.cc, and <string> is gone from fastdev-assemblies.hh — which is where host.cc was picking it up from in the first place.

Results

Undefined libc++ references in the CoreCLR host, Release:

object before after
host.cc.o 9 0
assembly-store.cc.o 12 12
total 21 12

The nine removed from host.cc.o: __cxa_guard_acquire, __cxa_guard_release, operator new, operator delete, std::string::append, std::string::push_back, std::string::__grow_by_and_replace, ~basic_string and __libcpp_verbose_abort.

before after delta
libnet-android.release.so 523,224 520,112 −3,112 B

Verification

  • CoreCLR, MonoVM and NativeAOT all build clean.
  • Debug was verified explicitly. fastdev-assemblies.cc is only added to the build under if(DEBUG_BUILD) (src/native/clr/host/CMakeLists.txt), so a Release build never compiles it and would not have caught a mistake in the FastDev work. There is no Debug CoreCLR build configured locally, so both host.cc and fastdev-assemblies.cc were compiled standalone with -DDEBUG -DDEBUG_BUILD using the exact flags from compile_commands.json. Both compile clean and llvm-nm --undefined-only reports 0 libc++ references for each.

Not covered

Only android-arm64 was built locally; the other ABIs rely on CI. The FastDev path is Debug-only and was not exercised on a device as part of this change.

Part of #12533. This clears `host.cc` entirely, taking the CoreCLR host
from 21 undefined libc++ references to 12 - all of which now live in
`assembly-store.cc`.

Three separate uses, two of which only compile in Release:

* `scan_filesystem_for_assemblies_and_libraries` built the assembly store
  path with a `std::string`. It now uses `Util::join_paths`, added earlier
  in this stack, which keeps the path on the stack unless it does not fit
  and frees only when the returned pointer differs from the stack buffer.

* `APP_CONTEXT_BASE_DIRECTORY` was held in a function-local
  `static std::string`. The static needs process lifetime because the value
  has to outlive `coreclr_initialize`, and a `std::string` has a non-trivial
  constructor, so it forced a guard variable - this was the only source of
  `__cxa_guard_acquire`/`__cxa_guard_release` in the file. A plain `char*`
  is constant-initialized, so the guard pair disappears.

* The FastDev block used a `std::string` plus two `std::vector<const char*>`.
  It is inside `if constexpr (Constants::is_debug_build)`, so it contributes
  nothing to Release, but it has to go before libc++ can be dropped from
  Debug builds too. The property arrays are a fixed `prop_count + 1` entries,
  so they become `calloc`ed arrays.

`FastDevAssemblies::build_tpa_list` consequently returns a `malloc`ed string
the caller owns rather than filling a `std::string&` out-parameter. The TPA
list has no useful upper bound - one absolute path per assembly in the
override directory - so it is accumulated through a small growable buffer
that doubles on demand. Allocation failure there falls back to the
probe-only path rather than aborting, since FastDev is a debug convenience.

`open_assembly` swaps `new uint8_t[]`/`delete[]` for `malloc`/`free`. That
buffer is handed to CoreCLR and never freed (see the existing TODO), so this
is not a behavioural change. It also gains a null check, which fixes a file
descriptor leak on the allocation-failure path that `new` would have turned
into a `std::bad_alloc` abort.

`<string>` and `<vector>` are no longer included by `host.cc`, and `<string>`
is gone from `fastdev-assemblies.hh`, which `host.cc` was picking it up from.

### Results

Undefined libc++ references in the CoreCLR host, Release:

| object | before | after |
|---|---:|---:|
| `host.cc.o` | 9 | **0** |
| `assembly-store.cc.o` | 12 | 12 |
| **total** | **21** | **12** |

`libnet-android.release.so` goes from 523,224 to 520,112 bytes (-3,112).

Debug is not built locally, so `host.cc` and `fastdev-assemblies.cc` were
compiled standalone with `-DDEBUG -DDEBUG_BUILD` using the flags from
`compile_commands.json`. Both compile clean and report 0 undefined libc++
references.

### Verification

* CoreCLR, MonoVM and NativeAOT all build clean.
* Debug-mode compilation of both affected files verified as described above,
  which matters because `fastdev-assemblies.cc` is only added to the build
  under `if(DEBUG_BUILD)` and is therefore never compiled in Release.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0a35a0db-502d-48c0-8468-e73b5dd0ab2e
Copilot AI lite review requested due to automatic review settings August 28, 2026 13:12
@simonrozsival simonrozsival changed the title [native] Remove the libc++ dependencies from the CoreCLR host [native] Remove the libc++ dependencies from the CoreCLR host's startup path Aug 28, 2026

Copilot AI 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.

Copilot review overview

Review tier: Lite
Findings: 2 Medium severity

New issues introduced by this change (2)
Severity Finding
Medium severity src/​native/​clr/​host/​fastdev-assemblies.cc — ERROR: FastDevAssemblies::tpa_in_use is set to true on success but never reset on failure. If…
Medium severity src/​native/​clr/​host/​host.cc — WARNING: This Debug-only FastDev path aborts the process if allocating the extended runtime…
What changed in this PR

This PR refactors the CoreCLR native host FastDev/initialization code to remove dependencies on libc++/STL types (e.g., std::string, std::vector) by switching to C allocation patterns and existing in-tree path helpers.

Changes:

  • Change FastDev TPA list construction to return a malloc-owned char* instead of filling a std::string.
  • Replace STL-based path/property list handling in host.cc with Util::join_paths, malloc/calloc, and explicit lifetime management.
  • Update FastDev assembly loading buffer allocation to use malloc/free instead of new[]/delete[].
File Description
src/​native/​clr/​include/​host/​fastdev-assemblies.hh Updates the FastDev API to return an owned char* TPA list (no STL types).
src/​native/​clr/​host/​host.cc Reworks CoreCLR init property handling and path building to avoid STL and manage memory explicitly.
src/​native/​clr/​host/​fastdev-assemblies.cc Implements a growable C buffer for TPA list building and switches FastDev file buffer allocation to malloc.

Comment on lines 191 to 194
const char *override_dir_path = AndroidSystem::get_primary_override_dir ();
if (!Util::dir_exists (override_dir_path)) {
return false;
return nullptr;
}
Comment on lines +427 to 444
std::free (fastdev_prop_names);
std::free (fastdev_prop_values);
fastdev_prop_names = static_cast<const char**> (std::calloc (new_count, sizeof (const char*)));
fastdev_prop_values = static_cast<const char**> (std::calloc (new_count, sizeof (const char*)));
abort_unless (
fastdev_prop_names != nullptr && fastdev_prop_values != nullptr,
"Unable to allocate memory for the FastDev runtime properties"
);

memcpy (fastdev_prop_names, prop_names, old_count * sizeof (const char*));
memcpy (fastdev_prop_values, prop_values, old_count * sizeof (const char*));
fastdev_prop_names [old_count] = HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES;
fastdev_prop_values [old_count] = fastdev_tpa_list;

prop_names = fastdev_prop_names;
prop_values = fastdev_prop_values;
prop_count = static_cast<int>(new_count);
}
@simonrozsival simonrozsival added the drop-libcpp Work to remove the libc++ dependency from Android NativeAOT label Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

drop-libcpp Work to remove the libc++ dependency from Android NativeAOT

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants