[native] Avoid redundant GC bridge type checks - #12617
Conversation
Reuse successful IGCUserPeer checks for repeated cross-reference sources and skip the redundant release check before clearing references. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review tier: Lite
Findings: 2
New issues introduced by this change (2)
| Severity | Finding |
|---|---|
src/native/clr/host/bridge-processing.cc — 🤖 ❌ error Error handling — The log message references a non-existent method name… |
|
src/native/clr/host/bridge-processing.cc — 🤖 💡 suggestion Formatting — Repo convention is to annotate #endif with its condition for… |
What changed in this PR
This PR optimizes the native GC bridge’s cross-reference processing by avoiding redundant IGCUserPeer type checks within a single bridge round, while keeping the existing reachability semantics and reference lifecycle intact.
Changes:
- Added a per-round “known
IGCUserPeer” fast-path to skip repeatedIsInstanceOfchecks for subsequent edges from the same source. - Treated temporary
GCUserPeerinstances as already-knownIGCUserPeerimplementations for cross-reference additions. - Removed the Release-path
IsInstanceOfguard beforemonodroidClearReferences, retaining a Debug-only invariant assertion, and removed the unused “missing clear method” diagnostic path.
| File | Description |
|---|---|
| src/native/clr/include/host/bridge-processing.hh | Extends the cross-reference target API and add_reference signature to support caching/fast-path behavior. |
| src/native/clr/host/bridge-processing.cc | Implements the fast-path logic, updates reference-clearing invariants, and removes the redundant clear-method diagnostic path. |
Suppressed comments (1)
src/native/clr/host/bridge-processing.cc:483
- 🤖 ❌ error Error handling — This debug log string also uses
monodroidAddReferences(plural), but the interface method name ismonodroidAddReference(singular). Aligning the message with the real method name avoids confusion when diagnosing bridge failures.
log_errorf (LOG_GC, "Missing monodroidAddReferences method for object of class %s", optional_string (class_name));
|
/review |
|
✅ Android PR Reviewer completed successfully!
|
There was a problem hiding this comment.
✅ LGTM
The refs_added fast path preserves the GC-bridge lifecycle invariant: it is set only after successful IGCUserPeer verification, cleared for surviving peers after callback cleanup, and irrelevant for collected contexts that are released. Temporary peers are also valid known implementations. The performance claim is appropriately scoped and backed by direct JNI-call observations and device measurements.
Findings: 0 errors · 0 warnings · 1 suggestion. All 45 CI checks completed successfully.
Generated by Android PR Reviewer for #12617 · gpt56 · 92.7 AIC · ⌖ 8.85 AIC · ⊞ 25.7K
Comment /review to run again
| // refs_added is set only after add_reference verifies that the source implements IGCUserPeer. | ||
| #if DEBUG | ||
| abort_unless (env->IsInstanceOf (handle, IGCUserPeer_class), "Object with added references must implement IGCUserPeer"); | ||
| #endif |
There was a problem hiding this comment.
🤖 💡 Formatting — Please annotate this newly added directive as #endif // DEBUG, as required by the repository convention for conditional-compilation blocks. This also keeps the invariant assertion’s scope explicit during future edits.
Rule: Annotate #else/#endif (Postmortem #37)

Summary
Reduce JNI work in the shared CoreCLR/NativeAOT GC bridge without changing its reachability algorithm or reference lifecycle.
IGCUserPeercheck for subsequent cross-references from the same source during a bridge round.GCUserPeerinstances as already known implementations.IsInstanceOfbeforemonodroidClearReferences; retain a Debug assertion documenting and checking the invariant.refs_addedis set only afteradd_referencehas verified that the source implementsIGCUserPeer, and it is reset after references are cleared. It therefore also acts as per-round proof that later interface dispatch is valid.Observed workload
A .NET MAUI
--sample-contentstartup capture contained 99 bridge cross-references. Fourteen discardedPlatformGraphicsViewinstances each had seven outgoing edges, so this change removes 84 repeated pre-GCIsInstanceOfcalls; one surviving source also avoids its redundant post-GC check, for approximately 85 fewer JNI checks in that round.Physical-device startup A/B
Measured on a Samsung S23 (API 36, arm64, 60 Hz) using otherwise identical Release CoreCLR
dotnet new maui --sample-contentAPKs. ReadyToRun was disabled in both arms because the exact preview SDK's Crossgen2 package was unavailable.Method:
pm clearandcmd package compile -f -m speedafter every APK switch.am start -W -STotalTimerecorded.The final hot blocks reached 42.1 °C and produced asymmetric thermal outliers, so the thermally controlled subset (all 60 samples at or below 41.0 °C) is the useful comparison:
TotalTimeA sample-level bootstrap gave a 95% interval of -31.1 to -3.7 ms for the mean and -25.0 to +0.5 ms for the median. This supports no startup regression and a modest favorable signal, but the effect is small relative to whole-app launch variance; the PR's direct claim remains the observed JNI-call reduction.
Raw measurements are preserved in the Copilot session artifact
maui-gc-benchmark/startup-results-s23-all.csv.Validation