Skip to content

[CoreCLR] Remove robin-map from p/invoke override - #12010

Merged
jonathanpeppers merged 6 commits into
dotnet:mainfrom
simonrozsival:android-remove-robin-map-from-coreclr
Jul 13, 2026
Merged

[CoreCLR] Remove robin-map from p/invoke override#12010
jonathanpeppers merged 6 commits into
dotnet:mainfrom
simonrozsival:android-remove-robin-map-from-coreclr

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Jul 7, 2026

Copy link
Copy Markdown
Member

Summary

Removes the CoreCLR native p/invoke override dependency on external/robin-map.

The motivation is primarily dependency and code simplification, not size or startup wins. After #11537, CoreCLR no longer needs a hand-maintained BCL p/invoke table or a custom p/invoke cache in the precompiled override. CoreCLR already caches resolved p/invokes at the runtime/call-site level, and arbitrary fallback libraries can be resolved directly through the existing dotnet/android loader path.

CoreCLR now:

  • has no tsl::robin_map / external/robin-map dependency
  • has no custom p/invoke cache in the override
  • has no generated precompiled p/invoke table or CoreCLR p/invoke-table generator
  • resolves the small internal symbol set explicitly for xa-internal-api and liblog
  • returns nullptr for BCL native libraries so CoreCLR's own resolver handles them
  • falls back directly through MonodroidDl::monodroid_dlopen + monodroid_dlsym for java-interop and other libraries

NativeAOT remains free of robin-map references. MonoVM keeps its existing robin-map usage.

Size impact

This is not a major app-size change. The size reduction is small but in the expected direction.

Scenario APK/package delta Main native payload delta
CI BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc -32,768 bytes shared libraries -212,128 bytes
dotnet new maui --sample-content Release/CoreCLR/R2R android-arm64 -4,096 bytes libmonodroid.so -17,400 raw / -3,584 ZIP

The committed BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc update reflects the expected CI size-reference change.

Startup performance

No measurable startup-time difference.

Device: Samsung Galaxy A16 (SM-A165F, arm64-v8a, API 36). Method: 30 interleaved cold-start pairs using am start -W, comparing default Release/CoreCLR/R2R dotnet new maui --sample-content APKs built from main and this PR.

Variant n TotalTime mean TotalTime median p90 stdev
main 31 2289.4 ms 2272.0 ms 2312.0 ms 119.8 ms
this PR 31 2269.4 ms 2262.0 ms 2318.0 ms 35.4 ms

Paired current - main TotalTime delta across the 30 measured pairs:

n mean median p90 stdev
30 +0.1 ms -0.5 ms +60.2 ms 51.0 ms

Conclusion: startup is unchanged within measurement noise.

Validation

  • make prepare && make all on this PR
  • make prepare && make all on origin/main
  • dotnet new maui --sample-content Release/CoreCLR/R2R APK build on both local SDKs
  • 30x interleaved startup comparison on Samsung A16 (SM-A165F, API 36)
  • Earlier targeted native validation:
    • dotnet build src/native/native-clr.csproj --nologo -v:minimal -p:AndroidSupportedTargetJitAbis=arm64-v8a -p:AndroidSupportedTargetAotAbis=arm64 -p:EnableNativeAnalyzers=false
    • dotnet build src/native/native-nativeaot.csproj --nologo -v:minimal -p:AndroidSupportedTargetJitAbis=arm64-v8a -p:AndroidSupportedTargetAotAbis=arm64 -p:EnableNativeAnalyzers=false

Remove the CoreCLR p/invoke override dependency on robin-map by deleting the custom cache layer and generated precompiled p/invoke table.

The precompiled CoreCLR path now resolves only the small internal symbol set explicitly, lets CoreCLR handle BCL native libraries, and falls back directly through the dotnet/android loader for other libraries. NativeAOT remains free of robin-map references, and MonoVM keeps its existing robin-map usage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

simonrozsival and others added 2 commits July 7, 2026 22:14
Refresh BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc for the intended CoreCLR native size reduction.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Separate the precompiled CoreCLR p/invoke override resolver by logical library: java-interop, xa-internal-api, and liblog.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@github-actions github-actions Bot 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.

🤖 Android PR Review

Independent pass over the native CoreCLR p/invoke-override changes — I verified the mechanics before reading the description. The core rewrite is correct.

Verified ✅

  • Symbol completenessload_internal_symbol covers all 28 entries from the removed generated internal_pinvokes table and matches the extern "C" set in internal-pinvokes.hh; every string literal matches its identifier, nothing dropped.
  • Library-name matching — the new == "java-interop" / "xa-internal-api" / "liblog" / "libSystem.*" comparisons are exactly the strings the deleted generator hashed into the old *_hash constants. Behavior-equivalent.
  • handle_jni_on_load removal — dead code on the CLR path (no callers; Java_JNI_OnLoad in clr/host/host.cc never used it). NativeAOT keeps its own live copy. Clean.
  • robin-map removal — every remaining ROBIN_MAP_DIR / tsl:: reference is confined to src/native/mono/ under the new if(IS_MONO_RUNTIME) guard; no dangling references anywhere in src/native/clr/.
  • Cache removal — relying on CoreCLR's per-method p/invoke caching is sound, and build-time-known DSOs are still handle-cached inside monodroid_dlopen, so dropping other_pinvoke_map causes no meaningful dlopen refcount regression.
  • Tabs/formatting, added includes, and inline macros all look correct.

Non-blocking suggestions 💡 (inline)

  1. load_internal_symbol reintroduces a hand-maintained table with the same drift → abort_application failure mode this PR removes for the BCL libs (#11530). Worth a static_assert/cross-reference guard.
  2. Test coverage gap — the only test targets libmonosgen-2.0 (MonoVM-only), so the rewritten CoreCLR fallback path has no on-device coverage.

CI

Checks are still pending on 00428d9 (draft PR) — I could not confirm green. Please make sure the CoreCLR and NativeAOT native builds plus the on-device p/invoke tests pass before merging.


Verdict: No blocking code issues found — 2 optional suggestions above. Nice cleanup: −633/+143 and one fewer external dependency on the CoreCLR path. 👍 (Confirm CI is green before un-drafting.)

Generated by Android PR Reviewer for #12010 · 383.9 AIC · ⌖ 27.3 AIC · ⊞ 6.8K
Comment /review to run again

Comment thread src/native/clr/pinvoke-override/precompiled.cc Outdated
@simonrozsival
simonrozsival marked this pull request as ready for review July 7, 2026 22:58
Copilot AI review requested due to automatic review settings July 7, 2026 22:58
@simonrozsival simonrozsival added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label Jul 7, 2026
Move the internal p/invoke missing-symbol abort into the library-specific resolvers so the dispatcher can return directly for each internal library.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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.

Pull request overview

This PR simplifies the CoreCLR native p/invoke override by removing the robin-map dependency and the custom p/invoke cache/table, relying instead on explicit handling for a small set of internal symbols plus the existing dotnet/android loader fallback for other libraries.

Changes:

  • Remove CoreCLR’s robin-map-based p/invoke cache and associated include/compile plumbing.
  • Replace the CoreCLR precompiled override’s generated internal table with explicit per-library symbol resolution, and route “other” libraries through MonodroidDl::monodroid_dlopen/monodroid_dlsym.
  • Update size reference (*.apkdesc) and adjust test commentary accordingly.
Show a summary per file
File Description
tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/PInvokeOverrideTests.cs Updates test commentary to reflect new fallback resolution (no robin-map cache).
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc Updates expected size baselines for CoreCLR build artifacts.
src/native/CMakeLists.txt Makes ROBIN_MAP_DIR conditional on Mono runtime builds only.
src/native/clr/pinvoke-override/precompiled.cc Reworks precompiled CoreCLR override to explicit symbol handling + loader fallback; removes generated include usage.
src/native/clr/pinvoke-override/pinvoke-tables.include Deletes the generated CoreCLR internal-pinvoke table include.
src/native/clr/pinvoke-override/generate-pinvoke-tables.cc Removes the CoreCLR pinvoke table generator source.
src/native/clr/pinvoke-override/dynamic.cc Simplifies dynamic override path and routes unknown libraries via load_library_symbol.
src/native/clr/pinvoke-override/CMakeLists.txt Removes robin-map include paths and compile definitions.
src/native/clr/include/host/pinvoke-override.hh Removes robin-map types/caching and narrows the public API to load_library_symbol + override entrypoint.
src/native/clr/include/host/pinvoke-override-impl.hh Removes robin-map caching logic; keeps direct dlopen/dlsym-based resolution via MonodroidDl.
src/native/clr/host/CMakeLists.txt Drops robin-map include directories from CoreCLR host build.
.gitignore Removes ignore entries for deleted CoreCLR pinvoke generator artifacts.

Copilot's findings

  • Files reviewed: 11/12 changed files
  • Comments generated: 1

Comment thread src/native/clr/pinvoke-override/precompiled.cc Outdated
@simonrozsival simonrozsival added ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). and removed ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). labels Jul 8, 2026
…map-from-coreclr

# Conflicts:
#	src/native/clr/pinvoke-override/dynamic.cc
#	src/native/clr/pinvoke-override/precompiled.cc
Comment on lines +20 to +21
// Handle p/invokes of the form [DllImport ("liblog")] or [DllImport ("log")]
// TODO: try modifying the name to contain both the `log` prefix and the `.so` suffix

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this actually break something?

Someone might be doing: [DllImport ("libfoo")] or [DllImport ("foo")]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it does not as far as I know, but I'll confirm it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, neither spelling breaks. load_library_symbol normalizes the name before dlopen:

  • [DllImport ("foo")] → tries libfoo.so
  • [DllImport ("libfoo")] → tries libfoo.so
  • [DllImport ("libfoo.so")] → used as-is

…and if the normalized name fails to load, it falls back to dlopen of the original library_name. This normalize-and-fallback behavior is unchanged by this PR — the diff only removed the robin_map cache that wrapped it, not the name handling itself.

I added on-device coverage for exactly these spellings in PInvokeOverrideTests: a new JNI-free rt_get_pinvoke_test_value export in libreuse-threads.so (bundled as an <AndroidNativeLibrary/>, so present under both MonoVM and CoreCLR) is invoked via reuse-threads, libreuse-threads, and libreuse-threads.so, asserting all three resolve to the same function.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can drop or improve the TODO comment in a follow-up PR

@jonathanpeppers
jonathanpeppers merged commit 4afbe75 into dotnet:main Jul 13, 2026
42 checks passed
simonrozsival added a commit that referenced this pull request Jul 15, 2026
## Goal

Remove the CoreCLR native dependency on xxHash/constexpr-xxh3 in the .NET for Android lookup paths while preserving runtime behavior and startup performance. The primary improvement is dependency removal; app size and startup time are unchanged within measurement noise.

## Summary

- Replace CoreCLR LLVM typemap xxHash lookups with 32-bit CRC-32 (`System.IO.Hashing.Crc32` in the managed generators, matched by a small `runtime-base/crc32.hh` in the native runtime).
- Replace CoreCLR assembly-store name hashes with 32-bit CRC-32 and bump the CoreCLR store format to v4 (MonoVM stays on v3/xxHash).
- Replace CoreCLR DSO APK/cache hashes with 32-bit CRC-32.
- Replace the CoreCLR preserved-p/invoke (`find_pinvoke`) generator hashes with 32-bit CRC-32.
- Keep MonoVM assembly stores and runtime lookup data on xxHash; move the xxHash-specific `Search` helpers to MonoVM so CoreCLR no longer pulls in `shared/xxhash.hh` through the common search header.
- Remove `external/xxHash` and `constexpr-xxh3` include paths from the **CoreCLR** native CMake targets (both remain for MonoVM/NativeAOT, which still use xxHash).
- Remove CoreCLR-only dead paths exposed by the cleanup: AOT DSO cache, debug typemap string-fallback switch, duplicate runtime-property hash index, and the runtime-property host callback.
- Update generated-layout test parsing and the mk2 assembly-store reader for the new CoreCLR layouts.

### Collision safety

CRC-32 is 32-bit, so collisions between two names in the same app — while extremely unlikely — are possible where 64-bit xxHash effectively never collided. Every CoreCLR lookup that matches on these hashes now verifies the actual name after a hash match:

- **Typemap** — compares the original managed/Java name (`same_string`).
- **Assembly store** — `find_assembly_store_entry` walks the run of entries sharing a hash and compares the requested name against the real assembly name from the store's `ASSEMBLY_NAMES` section.
- **DSO cache** — `find_dso_cache_entry` re-derives the name mutations of the matched entry's real library name (mirroring managed `AddNameMutations`) and confirms the requested name is one of them.
- **DSO APK table** (`find_dso_apk_entry`) intentionally stays hash-only: `DSOApkEntry` stores no name, and it's only reached with the `real_name_hash` of an already-name-verified DSO cache entry, so the residual risk is limited to a CRC-32 collision between two shared libraries in the same APK — a deliberate 64→32-bit trade-off.

The empty-input CRC-32 is a shared `UINT32_MAX` sentinel between the managed generators and the native runtime, documented in `crc32.hh`.

### Relationship to #12010

Rebased on top of #12010 (`[CoreCLR] Remove robin-map from p/invoke override`), now in `main`. #12010 removed the CoreCLR p/invoke override's hand-maintained tables and cache entirely, so the earlier CRC-32 conversion of those tables here is obsolete and was dropped in the merge. CoreCLR p/invoke resolution now falls back through `MonodroidDl::monodroid_dlopen`, which makes the DSO-cache name verification above the load-bearing path.

## Benchmark

CoreCLR MAUI Release/R2R sample, Android arm64, Samsung SM-A165F, interleaved runs, no reboot. These numbers were measured against #12010 as the xxHash baseline (before it merged) to isolate the CRC-32/dependency-removal change; they predate the collision-safety additions, which add only a name comparison on hash matches.

### Size

| Variant | APK size | libmonodroid.so uncompressed |
| --- | ---: | ---: |
| #12010 baseline / xxHash | 30,693,198 B | 1,247,192 B |
| #12013 current / CRC32 | 30,709,582 B | 1,250,616 B |

### Startup, 30x mixed cold + hot, IQR outliers removed

| Mode | #12010 baseline mean / median | #12013 current mean / median | Delta |
| --- | ---: | ---: | ---: |
| Cold WaitTime | 2005.0 / 1998 ms | 1998.7 / 1995 ms | -6.3 ms mean, -3 ms median |
| Hot WaitTime | 148.2 / 146.5 ms | 148.1 / 148 ms | -0.1 ms mean, +1.5 ms median |

Result: app size and startup times are unchanged within measurement noise. The meaningful win is removing CoreCLR's dependency on the xxHash/constexpr-xxh3 native dependency chain.

## Validation

- `dotnet build` of Xamarin.Android.Build.Tasks, the mk2 assembly-store reader, native-clr (arm64), native-mono (arm64), and the build test project.
- Clean CoreCLR native arm64 configure/build after removing the xxHash include paths; no `external/xxHash` or `external/constexpr-xxh3` paths remain in the CoreCLR compile output.
- Full `make all CONFIGURATION=Release` after merging #12010.
- Direct `CreateAssemblyStore` probe generated a CoreCLR arm64 v4 store with 9-byte index entries that the mk2 reader opened successfully.
- CoreCLR MAUI benchmark APKs built and launched on Samsung SM-A165F.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants