[mono][sgen] Make color visible to client permanent - #121247

Merged
BrzVlad merged 4 commits into
dotnet:mainfrom
BrzVlad:fix-gcbridge-non-bridge-scc
Nov 5, 2025
Merged

[mono][sgen] Make color visible to client permanent#121247
BrzVlad merged 4 commits into
dotnet:mainfrom
BrzVlad:fix-gcbridge-non-bridge-scc

Conversation

@BrzVlad

Copy link
Copy Markdown
Member

A color (SCC) that isn't containing any bridge objects is made visible to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge processing, we need to build the callback to pass for .net android. During this stage, we reduce from the full set of SCCs to SCCs that should be visible to client (containing bridge objects or satisfying the above condition). If an SCC has an xref to a color that is not visible to client, we need to do a recursive traversal to find all neighbors that are visible to client. The problem is that this process can end up making an SCC no longer visible to client, leading to inconsistencies in the computation. Consider a color(C1) that has a neighbor that is not visible to client(C2). In this final stage, we compute the neighbors of C1 by traversing recursively through the neighbors of C2. If C2 ends up pointing to colors that were already neighbors of C1, then, following this computation, C1 would end up with fewer xrefs_out, making the color no longer visible to client. This make future checks incorrect, resulting in building incorrect graph for client.

This scenario seems rare in practice, we should have gotten way more reports otherwise. We fix this by pinning the visible_to_client property for a color once it first satisfies it, so it will no longer matter how many actual xrefs the color has.

Fixes assertions like:

* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met

A color (SCC) that isn't containing any bridge objects is made visible to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge processing, we need to build the callback to pass for .net android. During this stage, we reduce from the full set of SCCs to SCCs that should be visible to client (containing bridge objects or satisfying the above condition). If an SCC has an xref to a color that is not visible to client, we need to do a recursive traversal to find all neighbors that are visible to client. The problem is that this process can end up making an SCC no longer visible to client, leading to inconsistencies in the computation. Consider a color(C1) that has a neighbor that is not visible to client(C2). In this final stage, we compute the neighbors of C1 by traversing recursively through the neighbors of C2. If C2 ends up pointing to colors that were already neighbors of C1, then, following this computation, C1 would end up with fewer xrefs_out, making the color no longer visible to client. This make future checks incorrect, resulting in building incorrect graph for client.
This scenario seems rare in practice, we should have gotten way more reports otherwise. We fix this by pinning the visible_to_client property for a color once it first satisfies it, so it will no longer matter how many actual xrefs the color has.
CopilotAI review requested due to automatic review settings October 31, 2025 14:29
@github-actionsgithub-actionsBot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Oct 31, 2025
@BrzVlad

Copy link
Copy Markdown
MemberAuthor
graph TD;
BL0-->NBMID;
BL1-->NBMID;
BL2-->NBMID;
BL3-->NBMID;
BL4-->NBMID;
BL5-->NBMID;
BL6-->NBMID;
BL7-->NBMID;
NBMID-->BR0;
NBMID-->BR1;
NBMID-->BR2;
NBMID-->BR3;
NBMID-->BR4;
NBMID-->BR5;
NBMID-->NBR7;
NBMID-->BR6;
NBR7-->BR5;
NBR7-->BR6;
Loading

Consider the following graph, that is identical to the one from the added testcase. B prefix is for bridge objects, NB is for normal objects. Every object is an SCC in this scenario. The optimization passing SCCs containing non bridge objects is meant to prevent the addition of excessive links on the java side. This graph leads to the addition of 8+7 = 15 refs on java. If we didn't allow to pass NBMID SCC over to the java side, we would need to add 7 refs for each one of the BLx objects, totalling 56 refs!

NBMID is initially considered to be a visible to client color, because it has 8 xrefs_in and 8 xrefs_out (8x8 > 60). However, when computing the final xrefs for this color, NBR7 is not included (because it is a color that doesn't contain any bridges and it doesn't have enough links). It will instead be traversed, ending up with BR5 and BR6 xrefs that were already present. This means that NBMID will only have 7 xrefs_out and it would no longer satisfy the condition of being a color visible to client.

@BrzVladBrzVlad added area-GC-mono and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Oct 31, 2025
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @BrzVlad
See info in area-owners.md if you want to be subscribed.

CopilotAI 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 fixes a bug in the GC bridge processing where a color's visibility to the client could change during processing, causing incorrect behavior. The fix introduces a visibleToClient flag that pins a color as visible once detected, preventing it from becoming invisible later even if it loses xrefs.

Key changes:

  • Added a visibleToClient flag to ColorData structures in both CoreCLR and Mono runtimes
  • Modified ColorVisibleToClient/color_visible_to_client functions to cache visibility status
  • Added a test case BridgelessHeavyColorChanging to verify the fix using inline arrays

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/gc/gcbridge.cppAdded visibleToClient flag to ColorData and updated ColorVisibleToClient to cache visibility determination
src/mono/mono/metadata/sgen-tarjan-bridge.cAdded visible_to_client flag to ColorData and updated color_visible_to_client to cache visibility determination
src/tests/GC/Features/Bridge/Bridge.csAdded InlineData struct, updated NonBridge14 to use it, and added BridgelessHeavyColorChanging test method

Comment threadsrc/coreclr/gc/gcbridge.cpp Outdated
Comment threadsrc/coreclr/gc/gcbridge.cpp

@filipnavarafilipnavara left a comment

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.

Generally LGTM and makes it much easier to reason about the code, just one nit about the data structure layout.

@lateralusXlateralusX left a comment

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.

LGTM!

@BrzVlad

Copy link
Copy Markdown
MemberAuthor

/ba-g android infra issue

@BrzVlad
BrzVlad merged commit fe17c2c into dotnet:mainNov 5, 2025
144 of 149 checks passed
BrzVlad added a commit to BrzVlad/runtime that referenced this pull request Nov 10, 2025
A color (SCC) that isn't containing any bridge objects is made visible
to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge
processing, we need to build the callback to pass for .net android.
During this stage, we reduce from the full set of SCCs to SCCs that
should be visible to client (containing bridge objects or satisfying the
above condition). If an SCC has an xref to a color that is not visible
to client, we need to do a recursive traversal to find all neighbors
that are visible to client. The problem is that this process can end up
making an SCC no longer visible to client, leading to inconsistencies in
the computation. Consider a color(C1) that has a neighbor that is not
visible to client(C2). In this final stage, we compute the neighbors of
C1 by traversing recursively through the neighbors of C2. If C2 ends up
pointing to colors that were already neighbors of C1, then, following
this computation, C1 would end up with fewer xrefs_out, making the color
no longer visible to client. This make future checks incorrect,
resulting in building incorrect graph for client.
This scenario seems rare in practice, we should have gotten way more
reports otherwise. We fix this by pinning the visible_to_client property
for a color once it first satisfies it, so it will no longer matter how
many actual xrefs the color has.
Fixes assertions like:
```
* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met
```
@srxqds

Copy link
Copy Markdown
Contributor

Does the release/9.0 branch not have this impact?

@BrzVlad

Copy link
Copy Markdown
MemberAuthor

For .net9 I've backported only #121376. Rather that disabling tarjan gc bridge, on .net9 users can set MONO_GC_PARAMS=disable-non-bridge-scc

steveisok pushed a commit that referenced this pull request Nov 10, 2025
…121483)
Backport #121247 and
#121243 to release/10.0.
This fixes assertions like
```
* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met
```
## Customer Impact
- [x] Customer reported
- [ ] Found internally
Some applications on maui-android can randomly crash during GC, when
using the default gc bridge (the tarjan bridge). We've had a few fixes
for the tarjan bridge merged a few months ago, but there is still this
one issue. The workaround used by customers is to fallback to an older
GC bridge which has worse performance. For some this performance impact
is not acceptable. This backport also fixes the same issue in the
CoreCLR gcbridge implementation. CoreCLR doesn't have a fallback GC
bridge implementation, so this fix is essential for the successful use
of CoreCLR/NativeAOT on android, at least for some customers.
## Regression
- [ ] Yes
- [x] No
## Testing
Tested on our own gc bridge tests, with scenario causing the issue.
## Risk
The GC bridge is a sensitive area and fixes here typically have some
carried risk. This fix however is quite straightforward, it simply pins
the value of a property inside an SCC node, rather than having it
recomputed with unstable value that was leading to problems. No changes
are done to the core algorithm. Low risk.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Dec 11, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@BrzVlad@srxqds@filipnavara@lateralusX
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[mono][sgen] Make color visible to client permanent - #121247

Merged
BrzVlad merged 4 commits into
dotnet:mainfrom
BrzVlad:fix-gcbridge-non-bridge-scc
Nov 5, 2025
Merged

[mono][sgen] Make color visible to client permanent#121247
BrzVlad merged 4 commits into
dotnet:mainfrom
BrzVlad:fix-gcbridge-non-bridge-scc

Conversation

@BrzVlad

Copy link
Copy Markdown
Member

A color (SCC) that isn't containing any bridge objects is made visible to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge processing, we need to build the callback to pass for .net android. During this stage, we reduce from the full set of SCCs to SCCs that should be visible to client (containing bridge objects or satisfying the above condition). If an SCC has an xref to a color that is not visible to client, we need to do a recursive traversal to find all neighbors that are visible to client. The problem is that this process can end up making an SCC no longer visible to client, leading to inconsistencies in the computation. Consider a color(C1) that has a neighbor that is not visible to client(C2). In this final stage, we compute the neighbors of C1 by traversing recursively through the neighbors of C2. If C2 ends up pointing to colors that were already neighbors of C1, then, following this computation, C1 would end up with fewer xrefs_out, making the color no longer visible to client. This make future checks incorrect, resulting in building incorrect graph for client.

This scenario seems rare in practice, we should have gotten way more reports otherwise. We fix this by pinning the visible_to_client property for a color once it first satisfies it, so it will no longer matter how many actual xrefs the color has.

Fixes assertions like:

* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met

A color (SCC) that isn't containing any bridge objects is made visible to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge processing, we need to build the callback to pass for .net android. During this stage, we reduce from the full set of SCCs to SCCs that should be visible to client (containing bridge objects or satisfying the above condition). If an SCC has an xref to a color that is not visible to client, we need to do a recursive traversal to find all neighbors that are visible to client. The problem is that this process can end up making an SCC no longer visible to client, leading to inconsistencies in the computation. Consider a color(C1) that has a neighbor that is not visible to client(C2). In this final stage, we compute the neighbors of C1 by traversing recursively through the neighbors of C2. If C2 ends up pointing to colors that were already neighbors of C1, then, following this computation, C1 would end up with fewer xrefs_out, making the color no longer visible to client. This make future checks incorrect, resulting in building incorrect graph for client.
This scenario seems rare in practice, we should have gotten way more reports otherwise. We fix this by pinning the visible_to_client property for a color once it first satisfies it, so it will no longer matter how many actual xrefs the color has.
CopilotAI review requested due to automatic review settings October 31, 2025 14:29
@github-actionsgithub-actionsBot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Oct 31, 2025
@BrzVlad

Copy link
Copy Markdown
MemberAuthor
graph TD;
BL0-->NBMID;
BL1-->NBMID;
BL2-->NBMID;
BL3-->NBMID;
BL4-->NBMID;
BL5-->NBMID;
BL6-->NBMID;
BL7-->NBMID;
NBMID-->BR0;
NBMID-->BR1;
NBMID-->BR2;
NBMID-->BR3;
NBMID-->BR4;
NBMID-->BR5;
NBMID-->NBR7;
NBMID-->BR6;
NBR7-->BR5;
NBR7-->BR6;
Loading

Consider the following graph, that is identical to the one from the added testcase. B prefix is for bridge objects, NB is for normal objects. Every object is an SCC in this scenario. The optimization passing SCCs containing non bridge objects is meant to prevent the addition of excessive links on the java side. This graph leads to the addition of 8+7 = 15 refs on java. If we didn't allow to pass NBMID SCC over to the java side, we would need to add 7 refs for each one of the BLx objects, totalling 56 refs!

NBMID is initially considered to be a visible to client color, because it has 8 xrefs_in and 8 xrefs_out (8x8 > 60). However, when computing the final xrefs for this color, NBR7 is not included (because it is a color that doesn't contain any bridges and it doesn't have enough links). It will instead be traversed, ending up with BR5 and BR6 xrefs that were already present. This means that NBMID will only have 7 xrefs_out and it would no longer satisfy the condition of being a color visible to client.

@BrzVladBrzVlad added area-GC-mono and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Oct 31, 2025
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @BrzVlad
See info in area-owners.md if you want to be subscribed.

CopilotAI 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 fixes a bug in the GC bridge processing where a color's visibility to the client could change during processing, causing incorrect behavior. The fix introduces a visibleToClient flag that pins a color as visible once detected, preventing it from becoming invisible later even if it loses xrefs.

Key changes:

  • Added a visibleToClient flag to ColorData structures in both CoreCLR and Mono runtimes
  • Modified ColorVisibleToClient/color_visible_to_client functions to cache visibility status
  • Added a test case BridgelessHeavyColorChanging to verify the fix using inline arrays

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/gc/gcbridge.cppAdded visibleToClient flag to ColorData and updated ColorVisibleToClient to cache visibility determination
src/mono/mono/metadata/sgen-tarjan-bridge.cAdded visible_to_client flag to ColorData and updated color_visible_to_client to cache visibility determination
src/tests/GC/Features/Bridge/Bridge.csAdded InlineData struct, updated NonBridge14 to use it, and added BridgelessHeavyColorChanging test method

Comment threadsrc/coreclr/gc/gcbridge.cpp Outdated
Comment threadsrc/coreclr/gc/gcbridge.cpp

@filipnavarafilipnavara left a comment

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.

Generally LGTM and makes it much easier to reason about the code, just one nit about the data structure layout.

@lateralusXlateralusX left a comment

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.

LGTM!

@BrzVlad

Copy link
Copy Markdown
MemberAuthor

/ba-g android infra issue

@BrzVlad
BrzVlad merged commit fe17c2c into dotnet:mainNov 5, 2025
144 of 149 checks passed
BrzVlad added a commit to BrzVlad/runtime that referenced this pull request Nov 10, 2025
A color (SCC) that isn't containing any bridge objects is made visible
to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge
processing, we need to build the callback to pass for .net android.
During this stage, we reduce from the full set of SCCs to SCCs that
should be visible to client (containing bridge objects or satisfying the
above condition). If an SCC has an xref to a color that is not visible
to client, we need to do a recursive traversal to find all neighbors
that are visible to client. The problem is that this process can end up
making an SCC no longer visible to client, leading to inconsistencies in
the computation. Consider a color(C1) that has a neighbor that is not
visible to client(C2). In this final stage, we compute the neighbors of
C1 by traversing recursively through the neighbors of C2. If C2 ends up
pointing to colors that were already neighbors of C1, then, following
this computation, C1 would end up with fewer xrefs_out, making the color
no longer visible to client. This make future checks incorrect,
resulting in building incorrect graph for client.
This scenario seems rare in practice, we should have gotten way more
reports otherwise. We fix this by pinning the visible_to_client property
for a color once it first satisfies it, so it will no longer matter how
many actual xrefs the color has.
Fixes assertions like:
```
* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met
```
@srxqds

Copy link
Copy Markdown
Contributor

Does the release/9.0 branch not have this impact?

@BrzVlad

Copy link
Copy Markdown
MemberAuthor

For .net9 I've backported only #121376. Rather that disabling tarjan gc bridge, on .net9 users can set MONO_GC_PARAMS=disable-non-bridge-scc

steveisok pushed a commit that referenced this pull request Nov 10, 2025
…121483)
Backport #121247 and
#121243 to release/10.0.
This fixes assertions like
```
* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met
```
## Customer Impact
- [x] Customer reported
- [ ] Found internally
Some applications on maui-android can randomly crash during GC, when
using the default gc bridge (the tarjan bridge). We've had a few fixes
for the tarjan bridge merged a few months ago, but there is still this
one issue. The workaround used by customers is to fallback to an older
GC bridge which has worse performance. For some this performance impact
is not acceptable. This backport also fixes the same issue in the
CoreCLR gcbridge implementation. CoreCLR doesn't have a fallback GC
bridge implementation, so this fix is essential for the successful use
of CoreCLR/NativeAOT on android, at least for some customers.
## Regression
- [ ] Yes
- [x] No
## Testing
Tested on our own gc bridge tests, with scenario causing the issue.
## Risk
The GC bridge is a sensitive area and fixes here typically have some
carried risk. This fix however is quite straightforward, it simply pins
the value of a property inside an SCC node, rather than having it
recomputed with unstable value that was leading to problems. No changes
are done to the core algorithm. Low risk.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Dec 11, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@BrzVlad@srxqds@filipnavara@lateralusX
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[mono][sgen] Make color visible to client permanent - #121247

Merged
BrzVlad merged 4 commits into
dotnet:mainfrom
BrzVlad:fix-gcbridge-non-bridge-scc
Nov 5, 2025
Merged

[mono][sgen] Make color visible to client permanent#121247
BrzVlad merged 4 commits into
dotnet:mainfrom
BrzVlad:fix-gcbridge-non-bridge-scc

Conversation

@BrzVlad

Copy link
Copy Markdown
Member

A color (SCC) that isn't containing any bridge objects is made visible to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge processing, we need to build the callback to pass for .net android. During this stage, we reduce from the full set of SCCs to SCCs that should be visible to client (containing bridge objects or satisfying the above condition). If an SCC has an xref to a color that is not visible to client, we need to do a recursive traversal to find all neighbors that are visible to client. The problem is that this process can end up making an SCC no longer visible to client, leading to inconsistencies in the computation. Consider a color(C1) that has a neighbor that is not visible to client(C2). In this final stage, we compute the neighbors of C1 by traversing recursively through the neighbors of C2. If C2 ends up pointing to colors that were already neighbors of C1, then, following this computation, C1 would end up with fewer xrefs_out, making the color no longer visible to client. This make future checks incorrect, resulting in building incorrect graph for client.

This scenario seems rare in practice, we should have gotten way more reports otherwise. We fix this by pinning the visible_to_client property for a color once it first satisfies it, so it will no longer matter how many actual xrefs the color has.

Fixes assertions like:

* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met

A color (SCC) that isn't containing any bridge objects is made visible to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge processing, we need to build the callback to pass for .net android. During this stage, we reduce from the full set of SCCs to SCCs that should be visible to client (containing bridge objects or satisfying the above condition). If an SCC has an xref to a color that is not visible to client, we need to do a recursive traversal to find all neighbors that are visible to client. The problem is that this process can end up making an SCC no longer visible to client, leading to inconsistencies in the computation. Consider a color(C1) that has a neighbor that is not visible to client(C2). In this final stage, we compute the neighbors of C1 by traversing recursively through the neighbors of C2. If C2 ends up pointing to colors that were already neighbors of C1, then, following this computation, C1 would end up with fewer xrefs_out, making the color no longer visible to client. This make future checks incorrect, resulting in building incorrect graph for client.
This scenario seems rare in practice, we should have gotten way more reports otherwise. We fix this by pinning the visible_to_client property for a color once it first satisfies it, so it will no longer matter how many actual xrefs the color has.
CopilotAI review requested due to automatic review settings October 31, 2025 14:29
@github-actionsgithub-actionsBot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Oct 31, 2025
@BrzVlad

Copy link
Copy Markdown
MemberAuthor
graph TD;
BL0-->NBMID;
BL1-->NBMID;
BL2-->NBMID;
BL3-->NBMID;
BL4-->NBMID;
BL5-->NBMID;
BL6-->NBMID;
BL7-->NBMID;
NBMID-->BR0;
NBMID-->BR1;
NBMID-->BR2;
NBMID-->BR3;
NBMID-->BR4;
NBMID-->BR5;
NBMID-->NBR7;
NBMID-->BR6;
NBR7-->BR5;
NBR7-->BR6;
Loading

Consider the following graph, that is identical to the one from the added testcase. B prefix is for bridge objects, NB is for normal objects. Every object is an SCC in this scenario. The optimization passing SCCs containing non bridge objects is meant to prevent the addition of excessive links on the java side. This graph leads to the addition of 8+7 = 15 refs on java. If we didn't allow to pass NBMID SCC over to the java side, we would need to add 7 refs for each one of the BLx objects, totalling 56 refs!

NBMID is initially considered to be a visible to client color, because it has 8 xrefs_in and 8 xrefs_out (8x8 > 60). However, when computing the final xrefs for this color, NBR7 is not included (because it is a color that doesn't contain any bridges and it doesn't have enough links). It will instead be traversed, ending up with BR5 and BR6 xrefs that were already present. This means that NBMID will only have 7 xrefs_out and it would no longer satisfy the condition of being a color visible to client.

@BrzVladBrzVlad added area-GC-mono and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Oct 31, 2025
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @BrzVlad
See info in area-owners.md if you want to be subscribed.

CopilotAI 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 fixes a bug in the GC bridge processing where a color's visibility to the client could change during processing, causing incorrect behavior. The fix introduces a visibleToClient flag that pins a color as visible once detected, preventing it from becoming invisible later even if it loses xrefs.

Key changes:

  • Added a visibleToClient flag to ColorData structures in both CoreCLR and Mono runtimes
  • Modified ColorVisibleToClient/color_visible_to_client functions to cache visibility status
  • Added a test case BridgelessHeavyColorChanging to verify the fix using inline arrays

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/gc/gcbridge.cppAdded visibleToClient flag to ColorData and updated ColorVisibleToClient to cache visibility determination
src/mono/mono/metadata/sgen-tarjan-bridge.cAdded visible_to_client flag to ColorData and updated color_visible_to_client to cache visibility determination
src/tests/GC/Features/Bridge/Bridge.csAdded InlineData struct, updated NonBridge14 to use it, and added BridgelessHeavyColorChanging test method

Comment threadsrc/coreclr/gc/gcbridge.cpp Outdated
Comment threadsrc/coreclr/gc/gcbridge.cpp

@filipnavarafilipnavara left a comment

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.

Generally LGTM and makes it much easier to reason about the code, just one nit about the data structure layout.

@lateralusXlateralusX left a comment

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.

LGTM!

@BrzVlad

Copy link
Copy Markdown
MemberAuthor

/ba-g android infra issue

@BrzVlad
BrzVlad merged commit fe17c2c into dotnet:mainNov 5, 2025
144 of 149 checks passed
BrzVlad added a commit to BrzVlad/runtime that referenced this pull request Nov 10, 2025
A color (SCC) that isn't containing any bridge objects is made visible
to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge
processing, we need to build the callback to pass for .net android.
During this stage, we reduce from the full set of SCCs to SCCs that
should be visible to client (containing bridge objects or satisfying the
above condition). If an SCC has an xref to a color that is not visible
to client, we need to do a recursive traversal to find all neighbors
that are visible to client. The problem is that this process can end up
making an SCC no longer visible to client, leading to inconsistencies in
the computation. Consider a color(C1) that has a neighbor that is not
visible to client(C2). In this final stage, we compute the neighbors of
C1 by traversing recursively through the neighbors of C2. If C2 ends up
pointing to colors that were already neighbors of C1, then, following
this computation, C1 would end up with fewer xrefs_out, making the color
no longer visible to client. This make future checks incorrect,
resulting in building incorrect graph for client.
This scenario seems rare in practice, we should have gotten way more
reports otherwise. We fix this by pinning the visible_to_client property
for a color once it first satisfies it, so it will no longer matter how
many actual xrefs the color has.
Fixes assertions like:
```
* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met
```
@srxqds

Copy link
Copy Markdown
Contributor

Does the release/9.0 branch not have this impact?

@BrzVlad

Copy link
Copy Markdown
MemberAuthor

For .net9 I've backported only #121376. Rather that disabling tarjan gc bridge, on .net9 users can set MONO_GC_PARAMS=disable-non-bridge-scc

steveisok pushed a commit that referenced this pull request Nov 10, 2025
…121483)
Backport #121247 and
#121243 to release/10.0.
This fixes assertions like
```
* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met
```
## Customer Impact
- [x] Customer reported
- [ ] Found internally
Some applications on maui-android can randomly crash during GC, when
using the default gc bridge (the tarjan bridge). We've had a few fixes
for the tarjan bridge merged a few months ago, but there is still this
one issue. The workaround used by customers is to fallback to an older
GC bridge which has worse performance. For some this performance impact
is not acceptable. This backport also fixes the same issue in the
CoreCLR gcbridge implementation. CoreCLR doesn't have a fallback GC
bridge implementation, so this fix is essential for the successful use
of CoreCLR/NativeAOT on android, at least for some customers.
## Regression
- [ ] Yes
- [x] No
## Testing
Tested on our own gc bridge tests, with scenario causing the issue.
## Risk
The GC bridge is a sensitive area and fixes here typically have some
carried risk. This fix however is quite straightforward, it simply pins
the value of a property inside an SCC node, rather than having it
recomputed with unstable value that was leading to problems. No changes
are done to the core algorithm. Low risk.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Dec 11, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@BrzVlad@srxqds@filipnavara@lateralusX
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[mono][sgen] Make color visible to client permanent - #121247

Merged
BrzVlad merged 4 commits into
dotnet:mainfrom
BrzVlad:fix-gcbridge-non-bridge-scc
Nov 5, 2025
Merged

[mono][sgen] Make color visible to client permanent#121247
BrzVlad merged 4 commits into
dotnet:mainfrom
BrzVlad:fix-gcbridge-non-bridge-scc

Conversation

@BrzVlad

Copy link
Copy Markdown
Member

A color (SCC) that isn't containing any bridge objects is made visible to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge processing, we need to build the callback to pass for .net android. During this stage, we reduce from the full set of SCCs to SCCs that should be visible to client (containing bridge objects or satisfying the above condition). If an SCC has an xref to a color that is not visible to client, we need to do a recursive traversal to find all neighbors that are visible to client. The problem is that this process can end up making an SCC no longer visible to client, leading to inconsistencies in the computation. Consider a color(C1) that has a neighbor that is not visible to client(C2). In this final stage, we compute the neighbors of C1 by traversing recursively through the neighbors of C2. If C2 ends up pointing to colors that were already neighbors of C1, then, following this computation, C1 would end up with fewer xrefs_out, making the color no longer visible to client. This make future checks incorrect, resulting in building incorrect graph for client.

This scenario seems rare in practice, we should have gotten way more reports otherwise. We fix this by pinning the visible_to_client property for a color once it first satisfies it, so it will no longer matter how many actual xrefs the color has.

Fixes assertions like:

* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met

A color (SCC) that isn't containing any bridge objects is made visible to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge processing, we need to build the callback to pass for .net android. During this stage, we reduce from the full set of SCCs to SCCs that should be visible to client (containing bridge objects or satisfying the above condition). If an SCC has an xref to a color that is not visible to client, we need to do a recursive traversal to find all neighbors that are visible to client. The problem is that this process can end up making an SCC no longer visible to client, leading to inconsistencies in the computation. Consider a color(C1) that has a neighbor that is not visible to client(C2). In this final stage, we compute the neighbors of C1 by traversing recursively through the neighbors of C2. If C2 ends up pointing to colors that were already neighbors of C1, then, following this computation, C1 would end up with fewer xrefs_out, making the color no longer visible to client. This make future checks incorrect, resulting in building incorrect graph for client.
This scenario seems rare in practice, we should have gotten way more reports otherwise. We fix this by pinning the visible_to_client property for a color once it first satisfies it, so it will no longer matter how many actual xrefs the color has.
CopilotAI review requested due to automatic review settings October 31, 2025 14:29
@github-actionsgithub-actionsBot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Oct 31, 2025
@BrzVlad

Copy link
Copy Markdown
MemberAuthor
graph TD;
BL0-->NBMID;
BL1-->NBMID;
BL2-->NBMID;
BL3-->NBMID;
BL4-->NBMID;
BL5-->NBMID;
BL6-->NBMID;
BL7-->NBMID;
NBMID-->BR0;
NBMID-->BR1;
NBMID-->BR2;
NBMID-->BR3;
NBMID-->BR4;
NBMID-->BR5;
NBMID-->NBR7;
NBMID-->BR6;
NBR7-->BR5;
NBR7-->BR6;
Loading

Consider the following graph, that is identical to the one from the added testcase. B prefix is for bridge objects, NB is for normal objects. Every object is an SCC in this scenario. The optimization passing SCCs containing non bridge objects is meant to prevent the addition of excessive links on the java side. This graph leads to the addition of 8+7 = 15 refs on java. If we didn't allow to pass NBMID SCC over to the java side, we would need to add 7 refs for each one of the BLx objects, totalling 56 refs!

NBMID is initially considered to be a visible to client color, because it has 8 xrefs_in and 8 xrefs_out (8x8 > 60). However, when computing the final xrefs for this color, NBR7 is not included (because it is a color that doesn't contain any bridges and it doesn't have enough links). It will instead be traversed, ending up with BR5 and BR6 xrefs that were already present. This means that NBMID will only have 7 xrefs_out and it would no longer satisfy the condition of being a color visible to client.

@BrzVladBrzVlad added area-GC-mono and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Oct 31, 2025
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @BrzVlad
See info in area-owners.md if you want to be subscribed.

CopilotAI 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 fixes a bug in the GC bridge processing where a color's visibility to the client could change during processing, causing incorrect behavior. The fix introduces a visibleToClient flag that pins a color as visible once detected, preventing it from becoming invisible later even if it loses xrefs.

Key changes:

  • Added a visibleToClient flag to ColorData structures in both CoreCLR and Mono runtimes
  • Modified ColorVisibleToClient/color_visible_to_client functions to cache visibility status
  • Added a test case BridgelessHeavyColorChanging to verify the fix using inline arrays

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/gc/gcbridge.cppAdded visibleToClient flag to ColorData and updated ColorVisibleToClient to cache visibility determination
src/mono/mono/metadata/sgen-tarjan-bridge.cAdded visible_to_client flag to ColorData and updated color_visible_to_client to cache visibility determination
src/tests/GC/Features/Bridge/Bridge.csAdded InlineData struct, updated NonBridge14 to use it, and added BridgelessHeavyColorChanging test method

Comment threadsrc/coreclr/gc/gcbridge.cpp Outdated
Comment threadsrc/coreclr/gc/gcbridge.cpp

@filipnavarafilipnavara left a comment

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.

Generally LGTM and makes it much easier to reason about the code, just one nit about the data structure layout.

@lateralusXlateralusX left a comment

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.

LGTM!

@BrzVlad

Copy link
Copy Markdown
MemberAuthor

/ba-g android infra issue

@BrzVlad
BrzVlad merged commit fe17c2c into dotnet:mainNov 5, 2025
144 of 149 checks passed
BrzVlad added a commit to BrzVlad/runtime that referenced this pull request Nov 10, 2025
A color (SCC) that isn't containing any bridge objects is made visible
to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge
processing, we need to build the callback to pass for .net android.
During this stage, we reduce from the full set of SCCs to SCCs that
should be visible to client (containing bridge objects or satisfying the
above condition). If an SCC has an xref to a color that is not visible
to client, we need to do a recursive traversal to find all neighbors
that are visible to client. The problem is that this process can end up
making an SCC no longer visible to client, leading to inconsistencies in
the computation. Consider a color(C1) that has a neighbor that is not
visible to client(C2). In this final stage, we compute the neighbors of
C1 by traversing recursively through the neighbors of C2. If C2 ends up
pointing to colors that were already neighbors of C1, then, following
this computation, C1 would end up with fewer xrefs_out, making the color
no longer visible to client. This make future checks incorrect,
resulting in building incorrect graph for client.
This scenario seems rare in practice, we should have gotten way more
reports otherwise. We fix this by pinning the visible_to_client property
for a color once it first satisfies it, so it will no longer matter how
many actual xrefs the color has.
Fixes assertions like:
```
* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met
```
@srxqds

Copy link
Copy Markdown
Contributor

Does the release/9.0 branch not have this impact?

@BrzVlad

Copy link
Copy Markdown
MemberAuthor

For .net9 I've backported only #121376. Rather that disabling tarjan gc bridge, on .net9 users can set MONO_GC_PARAMS=disable-non-bridge-scc

steveisok pushed a commit that referenced this pull request Nov 10, 2025
…121483)
Backport #121247 and
#121243 to release/10.0.
This fixes assertions like
```
* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met
```
## Customer Impact
- [x] Customer reported
- [ ] Found internally
Some applications on maui-android can randomly crash during GC, when
using the default gc bridge (the tarjan bridge). We've had a few fixes
for the tarjan bridge merged a few months ago, but there is still this
one issue. The workaround used by customers is to fallback to an older
GC bridge which has worse performance. For some this performance impact
is not acceptable. This backport also fixes the same issue in the
CoreCLR gcbridge implementation. CoreCLR doesn't have a fallback GC
bridge implementation, so this fix is essential for the successful use
of CoreCLR/NativeAOT on android, at least for some customers.
## Regression
- [ ] Yes
- [x] No
## Testing
Tested on our own gc bridge tests, with scenario causing the issue.
## Risk
The GC bridge is a sensitive area and fixes here typically have some
carried risk. This fix however is quite straightforward, it simply pins
the value of a property inside an SCC node, rather than having it
recomputed with unstable value that was leading to problems. No changes
are done to the core algorithm. Low risk.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Dec 11, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@BrzVlad@srxqds@filipnavara@lateralusX
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

[mono][sgen] Make color visible to client permanent - #121247

Merged
BrzVlad merged 4 commits into
dotnet:mainfrom
BrzVlad:fix-gcbridge-non-bridge-scc
Nov 5, 2025
Merged

[mono][sgen] Make color visible to client permanent#121247
BrzVlad merged 4 commits into
dotnet:mainfrom
BrzVlad:fix-gcbridge-non-bridge-scc

Conversation

@BrzVlad

Copy link
Copy Markdown
Member

A color (SCC) that isn't containing any bridge objects is made visible to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge processing, we need to build the callback to pass for .net android. During this stage, we reduce from the full set of SCCs to SCCs that should be visible to client (containing bridge objects or satisfying the above condition). If an SCC has an xref to a color that is not visible to client, we need to do a recursive traversal to find all neighbors that are visible to client. The problem is that this process can end up making an SCC no longer visible to client, leading to inconsistencies in the computation. Consider a color(C1) that has a neighbor that is not visible to client(C2). In this final stage, we compute the neighbors of C1 by traversing recursively through the neighbors of C2. If C2 ends up pointing to colors that were already neighbors of C1, then, following this computation, C1 would end up with fewer xrefs_out, making the color no longer visible to client. This make future checks incorrect, resulting in building incorrect graph for client.

This scenario seems rare in practice, we should have gotten way more reports otherwise. We fix this by pinning the visible_to_client property for a color once it first satisfies it, so it will no longer matter how many actual xrefs the color has.

Fixes assertions like:

* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met

A color (SCC) that isn't containing any bridge objects is made visible to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge processing, we need to build the callback to pass for .net android. During this stage, we reduce from the full set of SCCs to SCCs that should be visible to client (containing bridge objects or satisfying the above condition). If an SCC has an xref to a color that is not visible to client, we need to do a recursive traversal to find all neighbors that are visible to client. The problem is that this process can end up making an SCC no longer visible to client, leading to inconsistencies in the computation. Consider a color(C1) that has a neighbor that is not visible to client(C2). In this final stage, we compute the neighbors of C1 by traversing recursively through the neighbors of C2. If C2 ends up pointing to colors that were already neighbors of C1, then, following this computation, C1 would end up with fewer xrefs_out, making the color no longer visible to client. This make future checks incorrect, resulting in building incorrect graph for client.
This scenario seems rare in practice, we should have gotten way more reports otherwise. We fix this by pinning the visible_to_client property for a color once it first satisfies it, so it will no longer matter how many actual xrefs the color has.
CopilotAI review requested due to automatic review settings October 31, 2025 14:29
@github-actionsgithub-actionsBot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Oct 31, 2025
@BrzVlad

Copy link
Copy Markdown
MemberAuthor
graph TD;
BL0-->NBMID;
BL1-->NBMID;
BL2-->NBMID;
BL3-->NBMID;
BL4-->NBMID;
BL5-->NBMID;
BL6-->NBMID;
BL7-->NBMID;
NBMID-->BR0;
NBMID-->BR1;
NBMID-->BR2;
NBMID-->BR3;
NBMID-->BR4;
NBMID-->BR5;
NBMID-->NBR7;
NBMID-->BR6;
NBR7-->BR5;
NBR7-->BR6;
Loading

Consider the following graph, that is identical to the one from the added testcase. B prefix is for bridge objects, NB is for normal objects. Every object is an SCC in this scenario. The optimization passing SCCs containing non bridge objects is meant to prevent the addition of excessive links on the java side. This graph leads to the addition of 8+7 = 15 refs on java. If we didn't allow to pass NBMID SCC over to the java side, we would need to add 7 refs for each one of the BLx objects, totalling 56 refs!

NBMID is initially considered to be a visible to client color, because it has 8 xrefs_in and 8 xrefs_out (8x8 > 60). However, when computing the final xrefs for this color, NBR7 is not included (because it is a color that doesn't contain any bridges and it doesn't have enough links). It will instead be traversed, ending up with BR5 and BR6 xrefs that were already present. This means that NBMID will only have 7 xrefs_out and it would no longer satisfy the condition of being a color visible to client.

@BrzVladBrzVlad added area-GC-mono and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Oct 31, 2025
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @BrzVlad
See info in area-owners.md if you want to be subscribed.

CopilotAI 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 fixes a bug in the GC bridge processing where a color's visibility to the client could change during processing, causing incorrect behavior. The fix introduces a visibleToClient flag that pins a color as visible once detected, preventing it from becoming invisible later even if it loses xrefs.

Key changes:

  • Added a visibleToClient flag to ColorData structures in both CoreCLR and Mono runtimes
  • Modified ColorVisibleToClient/color_visible_to_client functions to cache visibility status
  • Added a test case BridgelessHeavyColorChanging to verify the fix using inline arrays

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/gc/gcbridge.cppAdded visibleToClient flag to ColorData and updated ColorVisibleToClient to cache visibility determination
src/mono/mono/metadata/sgen-tarjan-bridge.cAdded visible_to_client flag to ColorData and updated color_visible_to_client to cache visibility determination
src/tests/GC/Features/Bridge/Bridge.csAdded InlineData struct, updated NonBridge14 to use it, and added BridgelessHeavyColorChanging test method

Comment threadsrc/coreclr/gc/gcbridge.cpp Outdated
Comment threadsrc/coreclr/gc/gcbridge.cpp

@filipnavarafilipnavara left a comment

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.

Generally LGTM and makes it much easier to reason about the code, just one nit about the data structure layout.

@lateralusXlateralusX left a comment

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.

LGTM!

@BrzVlad

Copy link
Copy Markdown
MemberAuthor

/ba-g android infra issue

@BrzVlad
BrzVlad merged commit fe17c2c into dotnet:mainNov 5, 2025
144 of 149 checks passed
BrzVlad added a commit to BrzVlad/runtime that referenced this pull request Nov 10, 2025
A color (SCC) that isn't containing any bridge objects is made visible
to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge
processing, we need to build the callback to pass for .net android.
During this stage, we reduce from the full set of SCCs to SCCs that
should be visible to client (containing bridge objects or satisfying the
above condition). If an SCC has an xref to a color that is not visible
to client, we need to do a recursive traversal to find all neighbors
that are visible to client. The problem is that this process can end up
making an SCC no longer visible to client, leading to inconsistencies in
the computation. Consider a color(C1) that has a neighbor that is not
visible to client(C2). In this final stage, we compute the neighbors of
C1 by traversing recursively through the neighbors of C2. If C2 ends up
pointing to colors that were already neighbors of C1, then, following
this computation, C1 would end up with fewer xrefs_out, making the color
no longer visible to client. This make future checks incorrect,
resulting in building incorrect graph for client.
This scenario seems rare in practice, we should have gotten way more
reports otherwise. We fix this by pinning the visible_to_client property
for a color once it first satisfies it, so it will no longer matter how
many actual xrefs the color has.
Fixes assertions like:
```
* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met
```
@srxqds

Copy link
Copy Markdown
Contributor

Does the release/9.0 branch not have this impact?

@BrzVlad

Copy link
Copy Markdown
MemberAuthor

For .net9 I've backported only #121376. Rather that disabling tarjan gc bridge, on .net9 users can set MONO_GC_PARAMS=disable-non-bridge-scc

steveisok pushed a commit that referenced this pull request Nov 10, 2025
…121483)
Backport #121247 and
#121243 to release/10.0.
This fixes assertions like
```
* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met
```
## Customer Impact
- [x] Customer reported
- [ ] Found internally
Some applications on maui-android can randomly crash during GC, when
using the default gc bridge (the tarjan bridge). We've had a few fixes
for the tarjan bridge merged a few months ago, but there is still this
one issue. The workaround used by customers is to fallback to an older
GC bridge which has worse performance. For some this performance impact
is not acceptable. This backport also fixes the same issue in the
CoreCLR gcbridge implementation. CoreCLR doesn't have a fallback GC
bridge implementation, so this fix is essential for the successful use
of CoreCLR/NativeAOT on android, at least for some customers.
## Regression
- [ ] Yes
- [x] No
## Testing
Tested on our own gc bridge tests, with scenario causing the issue.
## Risk
The GC bridge is a sensitive area and fixes here typically have some
carried risk. This fix however is quite straightforward, it simply pins
the value of a property inside an SCC node, rather than having it
recomputed with unstable value that was leading to problems. No changes
are done to the core algorithm. Low risk.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Dec 11, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@BrzVlad@srxqds@filipnavara@lateralusX
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[mono][sgen] Make color visible to client permanent - #121247

Merged
BrzVlad merged 4 commits into
dotnet:mainfrom
BrzVlad:fix-gcbridge-non-bridge-scc
Nov 5, 2025
Merged

[mono][sgen] Make color visible to client permanent#121247
BrzVlad merged 4 commits into
dotnet:mainfrom
BrzVlad:fix-gcbridge-non-bridge-scc

Conversation

@BrzVlad

Copy link
Copy Markdown
Member

A color (SCC) that isn't containing any bridge objects is made visible to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge processing, we need to build the callback to pass for .net android. During this stage, we reduce from the full set of SCCs to SCCs that should be visible to client (containing bridge objects or satisfying the above condition). If an SCC has an xref to a color that is not visible to client, we need to do a recursive traversal to find all neighbors that are visible to client. The problem is that this process can end up making an SCC no longer visible to client, leading to inconsistencies in the computation. Consider a color(C1) that has a neighbor that is not visible to client(C2). In this final stage, we compute the neighbors of C1 by traversing recursively through the neighbors of C2. If C2 ends up pointing to colors that were already neighbors of C1, then, following this computation, C1 would end up with fewer xrefs_out, making the color no longer visible to client. This make future checks incorrect, resulting in building incorrect graph for client.

This scenario seems rare in practice, we should have gotten way more reports otherwise. We fix this by pinning the visible_to_client property for a color once it first satisfies it, so it will no longer matter how many actual xrefs the color has.

Fixes assertions like:

* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met

A color (SCC) that isn't containing any bridge objects is made visible to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge processing, we need to build the callback to pass for .net android. During this stage, we reduce from the full set of SCCs to SCCs that should be visible to client (containing bridge objects or satisfying the above condition). If an SCC has an xref to a color that is not visible to client, we need to do a recursive traversal to find all neighbors that are visible to client. The problem is that this process can end up making an SCC no longer visible to client, leading to inconsistencies in the computation. Consider a color(C1) that has a neighbor that is not visible to client(C2). In this final stage, we compute the neighbors of C1 by traversing recursively through the neighbors of C2. If C2 ends up pointing to colors that were already neighbors of C1, then, following this computation, C1 would end up with fewer xrefs_out, making the color no longer visible to client. This make future checks incorrect, resulting in building incorrect graph for client.
This scenario seems rare in practice, we should have gotten way more reports otherwise. We fix this by pinning the visible_to_client property for a color once it first satisfies it, so it will no longer matter how many actual xrefs the color has.
CopilotAI review requested due to automatic review settings October 31, 2025 14:29
@github-actionsgithub-actionsBot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Oct 31, 2025
@BrzVlad

Copy link
Copy Markdown
MemberAuthor
graph TD;
BL0-->NBMID;
BL1-->NBMID;
BL2-->NBMID;
BL3-->NBMID;
BL4-->NBMID;
BL5-->NBMID;
BL6-->NBMID;
BL7-->NBMID;
NBMID-->BR0;
NBMID-->BR1;
NBMID-->BR2;
NBMID-->BR3;
NBMID-->BR4;
NBMID-->BR5;
NBMID-->NBR7;
NBMID-->BR6;
NBR7-->BR5;
NBR7-->BR6;
Loading

Consider the following graph, that is identical to the one from the added testcase. B prefix is for bridge objects, NB is for normal objects. Every object is an SCC in this scenario. The optimization passing SCCs containing non bridge objects is meant to prevent the addition of excessive links on the java side. This graph leads to the addition of 8+7 = 15 refs on java. If we didn't allow to pass NBMID SCC over to the java side, we would need to add 7 refs for each one of the BLx objects, totalling 56 refs!

NBMID is initially considered to be a visible to client color, because it has 8 xrefs_in and 8 xrefs_out (8x8 > 60). However, when computing the final xrefs for this color, NBR7 is not included (because it is a color that doesn't contain any bridges and it doesn't have enough links). It will instead be traversed, ending up with BR5 and BR6 xrefs that were already present. This means that NBMID will only have 7 xrefs_out and it would no longer satisfy the condition of being a color visible to client.

@BrzVladBrzVlad added area-GC-mono and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Oct 31, 2025
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @BrzVlad
See info in area-owners.md if you want to be subscribed.

CopilotAI 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 fixes a bug in the GC bridge processing where a color's visibility to the client could change during processing, causing incorrect behavior. The fix introduces a visibleToClient flag that pins a color as visible once detected, preventing it from becoming invisible later even if it loses xrefs.

Key changes:

  • Added a visibleToClient flag to ColorData structures in both CoreCLR and Mono runtimes
  • Modified ColorVisibleToClient/color_visible_to_client functions to cache visibility status
  • Added a test case BridgelessHeavyColorChanging to verify the fix using inline arrays

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/gc/gcbridge.cppAdded visibleToClient flag to ColorData and updated ColorVisibleToClient to cache visibility determination
src/mono/mono/metadata/sgen-tarjan-bridge.cAdded visible_to_client flag to ColorData and updated color_visible_to_client to cache visibility determination
src/tests/GC/Features/Bridge/Bridge.csAdded InlineData struct, updated NonBridge14 to use it, and added BridgelessHeavyColorChanging test method

Comment threadsrc/coreclr/gc/gcbridge.cpp Outdated
Comment threadsrc/coreclr/gc/gcbridge.cpp

@filipnavarafilipnavara left a comment

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.

Generally LGTM and makes it much easier to reason about the code, just one nit about the data structure layout.

@lateralusXlateralusX left a comment

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.

LGTM!

@BrzVlad

Copy link
Copy Markdown
MemberAuthor

/ba-g android infra issue

@BrzVlad
BrzVlad merged commit fe17c2c into dotnet:mainNov 5, 2025
144 of 149 checks passed
BrzVlad added a commit to BrzVlad/runtime that referenced this pull request Nov 10, 2025
A color (SCC) that isn't containing any bridge objects is made visible
to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge
processing, we need to build the callback to pass for .net android.
During this stage, we reduce from the full set of SCCs to SCCs that
should be visible to client (containing bridge objects or satisfying the
above condition). If an SCC has an xref to a color that is not visible
to client, we need to do a recursive traversal to find all neighbors
that are visible to client. The problem is that this process can end up
making an SCC no longer visible to client, leading to inconsistencies in
the computation. Consider a color(C1) that has a neighbor that is not
visible to client(C2). In this final stage, we compute the neighbors of
C1 by traversing recursively through the neighbors of C2. If C2 ends up
pointing to colors that were already neighbors of C1, then, following
this computation, C1 would end up with fewer xrefs_out, making the color
no longer visible to client. This make future checks incorrect,
resulting in building incorrect graph for client.
This scenario seems rare in practice, we should have gotten way more
reports otherwise. We fix this by pinning the visible_to_client property
for a color once it first satisfies it, so it will no longer matter how
many actual xrefs the color has.
Fixes assertions like:
```
* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met
```
@srxqds

Copy link
Copy Markdown
Contributor

Does the release/9.0 branch not have this impact?

@BrzVlad

Copy link
Copy Markdown
MemberAuthor

For .net9 I've backported only #121376. Rather that disabling tarjan gc bridge, on .net9 users can set MONO_GC_PARAMS=disable-non-bridge-scc

steveisok pushed a commit that referenced this pull request Nov 10, 2025
…121483)
Backport #121247 and
#121243 to release/10.0.
This fixes assertions like
```
* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met
```
## Customer Impact
- [x] Customer reported
- [ ] Found internally
Some applications on maui-android can randomly crash during GC, when
using the default gc bridge (the tarjan bridge). We've had a few fixes
for the tarjan bridge merged a few months ago, but there is still this
one issue. The workaround used by customers is to fallback to an older
GC bridge which has worse performance. For some this performance impact
is not acceptable. This backport also fixes the same issue in the
CoreCLR gcbridge implementation. CoreCLR doesn't have a fallback GC
bridge implementation, so this fix is essential for the successful use
of CoreCLR/NativeAOT on android, at least for some customers.
## Regression
- [ ] Yes
- [x] No
## Testing
Tested on our own gc bridge tests, with scenario causing the issue.
## Risk
The GC bridge is a sensitive area and fixes here typically have some
carried risk. This fix however is quite straightforward, it simply pins
the value of a property inside an SCC node, rather than having it
recomputed with unstable value that was leading to problems. No changes
are done to the core algorithm. Low risk.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Dec 11, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@BrzVlad@srxqds@filipnavara@lateralusX
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[mono][sgen] Make color visible to client permanent - #121247

Merged
BrzVlad merged 4 commits into
dotnet:mainfrom
BrzVlad:fix-gcbridge-non-bridge-scc
Nov 5, 2025
Merged

[mono][sgen] Make color visible to client permanent#121247
BrzVlad merged 4 commits into
dotnet:mainfrom
BrzVlad:fix-gcbridge-non-bridge-scc

Conversation

@BrzVlad

Copy link
Copy Markdown
Member

A color (SCC) that isn't containing any bridge objects is made visible to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge processing, we need to build the callback to pass for .net android. During this stage, we reduce from the full set of SCCs to SCCs that should be visible to client (containing bridge objects or satisfying the above condition). If an SCC has an xref to a color that is not visible to client, we need to do a recursive traversal to find all neighbors that are visible to client. The problem is that this process can end up making an SCC no longer visible to client, leading to inconsistencies in the computation. Consider a color(C1) that has a neighbor that is not visible to client(C2). In this final stage, we compute the neighbors of C1 by traversing recursively through the neighbors of C2. If C2 ends up pointing to colors that were already neighbors of C1, then, following this computation, C1 would end up with fewer xrefs_out, making the color no longer visible to client. This make future checks incorrect, resulting in building incorrect graph for client.

This scenario seems rare in practice, we should have gotten way more reports otherwise. We fix this by pinning the visible_to_client property for a color once it first satisfies it, so it will no longer matter how many actual xrefs the color has.

Fixes assertions like:

* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met

A color (SCC) that isn't containing any bridge objects is made visible to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge processing, we need to build the callback to pass for .net android. During this stage, we reduce from the full set of SCCs to SCCs that should be visible to client (containing bridge objects or satisfying the above condition). If an SCC has an xref to a color that is not visible to client, we need to do a recursive traversal to find all neighbors that are visible to client. The problem is that this process can end up making an SCC no longer visible to client, leading to inconsistencies in the computation. Consider a color(C1) that has a neighbor that is not visible to client(C2). In this final stage, we compute the neighbors of C1 by traversing recursively through the neighbors of C2. If C2 ends up pointing to colors that were already neighbors of C1, then, following this computation, C1 would end up with fewer xrefs_out, making the color no longer visible to client. This make future checks incorrect, resulting in building incorrect graph for client.
This scenario seems rare in practice, we should have gotten way more reports otherwise. We fix this by pinning the visible_to_client property for a color once it first satisfies it, so it will no longer matter how many actual xrefs the color has.
CopilotAI review requested due to automatic review settings October 31, 2025 14:29
@github-actionsgithub-actionsBot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Oct 31, 2025
@BrzVlad

Copy link
Copy Markdown
MemberAuthor
graph TD;
BL0-->NBMID;
BL1-->NBMID;
BL2-->NBMID;
BL3-->NBMID;
BL4-->NBMID;
BL5-->NBMID;
BL6-->NBMID;
BL7-->NBMID;
NBMID-->BR0;
NBMID-->BR1;
NBMID-->BR2;
NBMID-->BR3;
NBMID-->BR4;
NBMID-->BR5;
NBMID-->NBR7;
NBMID-->BR6;
NBR7-->BR5;
NBR7-->BR6;
Loading

Consider the following graph, that is identical to the one from the added testcase. B prefix is for bridge objects, NB is for normal objects. Every object is an SCC in this scenario. The optimization passing SCCs containing non bridge objects is meant to prevent the addition of excessive links on the java side. This graph leads to the addition of 8+7 = 15 refs on java. If we didn't allow to pass NBMID SCC over to the java side, we would need to add 7 refs for each one of the BLx objects, totalling 56 refs!

NBMID is initially considered to be a visible to client color, because it has 8 xrefs_in and 8 xrefs_out (8x8 > 60). However, when computing the final xrefs for this color, NBR7 is not included (because it is a color that doesn't contain any bridges and it doesn't have enough links). It will instead be traversed, ending up with BR5 and BR6 xrefs that were already present. This means that NBMID will only have 7 xrefs_out and it would no longer satisfy the condition of being a color visible to client.

@BrzVladBrzVlad added area-GC-mono and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Oct 31, 2025
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @BrzVlad
See info in area-owners.md if you want to be subscribed.

CopilotAI 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 fixes a bug in the GC bridge processing where a color's visibility to the client could change during processing, causing incorrect behavior. The fix introduces a visibleToClient flag that pins a color as visible once detected, preventing it from becoming invisible later even if it loses xrefs.

Key changes:

  • Added a visibleToClient flag to ColorData structures in both CoreCLR and Mono runtimes
  • Modified ColorVisibleToClient/color_visible_to_client functions to cache visibility status
  • Added a test case BridgelessHeavyColorChanging to verify the fix using inline arrays

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/gc/gcbridge.cppAdded visibleToClient flag to ColorData and updated ColorVisibleToClient to cache visibility determination
src/mono/mono/metadata/sgen-tarjan-bridge.cAdded visible_to_client flag to ColorData and updated color_visible_to_client to cache visibility determination
src/tests/GC/Features/Bridge/Bridge.csAdded InlineData struct, updated NonBridge14 to use it, and added BridgelessHeavyColorChanging test method

Comment threadsrc/coreclr/gc/gcbridge.cpp Outdated
Comment threadsrc/coreclr/gc/gcbridge.cpp

@filipnavarafilipnavara left a comment

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.

Generally LGTM and makes it much easier to reason about the code, just one nit about the data structure layout.

@lateralusXlateralusX left a comment

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.

LGTM!

@BrzVlad

Copy link
Copy Markdown
MemberAuthor

/ba-g android infra issue

@BrzVlad
BrzVlad merged commit fe17c2c into dotnet:mainNov 5, 2025
144 of 149 checks passed
BrzVlad added a commit to BrzVlad/runtime that referenced this pull request Nov 10, 2025
A color (SCC) that isn't containing any bridge objects is made visible
to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge
processing, we need to build the callback to pass for .net android.
During this stage, we reduce from the full set of SCCs to SCCs that
should be visible to client (containing bridge objects or satisfying the
above condition). If an SCC has an xref to a color that is not visible
to client, we need to do a recursive traversal to find all neighbors
that are visible to client. The problem is that this process can end up
making an SCC no longer visible to client, leading to inconsistencies in
the computation. Consider a color(C1) that has a neighbor that is not
visible to client(C2). In this final stage, we compute the neighbors of
C1 by traversing recursively through the neighbors of C2. If C2 ends up
pointing to colors that were already neighbors of C1, then, following
this computation, C1 would end up with fewer xrefs_out, making the color
no longer visible to client. This make future checks incorrect,
resulting in building incorrect graph for client.
This scenario seems rare in practice, we should have gotten way more
reports otherwise. We fix this by pinning the visible_to_client property
for a color once it first satisfies it, so it will no longer matter how
many actual xrefs the color has.
Fixes assertions like:
```
* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met
```
@srxqds

Copy link
Copy Markdown
Contributor

Does the release/9.0 branch not have this impact?

@BrzVlad

Copy link
Copy Markdown
MemberAuthor

For .net9 I've backported only #121376. Rather that disabling tarjan gc bridge, on .net9 users can set MONO_GC_PARAMS=disable-non-bridge-scc

steveisok pushed a commit that referenced this pull request Nov 10, 2025
…121483)
Backport #121247 and
#121243 to release/10.0.
This fixes assertions like
```
* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met
```
## Customer Impact
- [x] Customer reported
- [ ] Found internally
Some applications on maui-android can randomly crash during GC, when
using the default gc bridge (the tarjan bridge). We've had a few fixes
for the tarjan bridge merged a few months ago, but there is still this
one issue. The workaround used by customers is to fallback to an older
GC bridge which has worse performance. For some this performance impact
is not acceptable. This backport also fixes the same issue in the
CoreCLR gcbridge implementation. CoreCLR doesn't have a fallback GC
bridge implementation, so this fix is essential for the successful use
of CoreCLR/NativeAOT on android, at least for some customers.
## Regression
- [ ] Yes
- [x] No
## Testing
Tested on our own gc bridge tests, with scenario causing the issue.
## Risk
The GC bridge is a sensitive area and fixes here typically have some
carried risk. This fix however is quite straightforward, it simply pins
the value of a property inside an SCC node, rather than having it
recomputed with unstable value that was leading to problems. No changes
are done to the core algorithm. Low risk.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Dec 11, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@BrzVlad@srxqds@filipnavara@lateralusX
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

[mono][sgen] Make color visible to client permanent - #121247

Merged
BrzVlad merged 4 commits into
dotnet:mainfrom
BrzVlad:fix-gcbridge-non-bridge-scc
Nov 5, 2025
Merged

[mono][sgen] Make color visible to client permanent#121247
BrzVlad merged 4 commits into
dotnet:mainfrom
BrzVlad:fix-gcbridge-non-bridge-scc

Conversation

@BrzVlad

Copy link
Copy Markdown
Member

A color (SCC) that isn't containing any bridge objects is made visible to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge processing, we need to build the callback to pass for .net android. During this stage, we reduce from the full set of SCCs to SCCs that should be visible to client (containing bridge objects or satisfying the above condition). If an SCC has an xref to a color that is not visible to client, we need to do a recursive traversal to find all neighbors that are visible to client. The problem is that this process can end up making an SCC no longer visible to client, leading to inconsistencies in the computation. Consider a color(C1) that has a neighbor that is not visible to client(C2). In this final stage, we compute the neighbors of C1 by traversing recursively through the neighbors of C2. If C2 ends up pointing to colors that were already neighbors of C1, then, following this computation, C1 would end up with fewer xrefs_out, making the color no longer visible to client. This make future checks incorrect, resulting in building incorrect graph for client.

This scenario seems rare in practice, we should have gotten way more reports otherwise. We fix this by pinning the visible_to_client property for a color once it first satisfies it, so it will no longer matter how many actual xrefs the color has.

Fixes assertions like:

* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met

A color (SCC) that isn't containing any bridge objects is made visible to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge processing, we need to build the callback to pass for .net android. During this stage, we reduce from the full set of SCCs to SCCs that should be visible to client (containing bridge objects or satisfying the above condition). If an SCC has an xref to a color that is not visible to client, we need to do a recursive traversal to find all neighbors that are visible to client. The problem is that this process can end up making an SCC no longer visible to client, leading to inconsistencies in the computation. Consider a color(C1) that has a neighbor that is not visible to client(C2). In this final stage, we compute the neighbors of C1 by traversing recursively through the neighbors of C2. If C2 ends up pointing to colors that were already neighbors of C1, then, following this computation, C1 would end up with fewer xrefs_out, making the color no longer visible to client. This make future checks incorrect, resulting in building incorrect graph for client.
This scenario seems rare in practice, we should have gotten way more reports otherwise. We fix this by pinning the visible_to_client property for a color once it first satisfies it, so it will no longer matter how many actual xrefs the color has.
CopilotAI review requested due to automatic review settings October 31, 2025 14:29
@github-actionsgithub-actionsBot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Oct 31, 2025
@BrzVlad

Copy link
Copy Markdown
MemberAuthor
graph TD;
BL0-->NBMID;
BL1-->NBMID;
BL2-->NBMID;
BL3-->NBMID;
BL4-->NBMID;
BL5-->NBMID;
BL6-->NBMID;
BL7-->NBMID;
NBMID-->BR0;
NBMID-->BR1;
NBMID-->BR2;
NBMID-->BR3;
NBMID-->BR4;
NBMID-->BR5;
NBMID-->NBR7;
NBMID-->BR6;
NBR7-->BR5;
NBR7-->BR6;
Loading

Consider the following graph, that is identical to the one from the added testcase. B prefix is for bridge objects, NB is for normal objects. Every object is an SCC in this scenario. The optimization passing SCCs containing non bridge objects is meant to prevent the addition of excessive links on the java side. This graph leads to the addition of 8+7 = 15 refs on java. If we didn't allow to pass NBMID SCC over to the java side, we would need to add 7 refs for each one of the BLx objects, totalling 56 refs!

NBMID is initially considered to be a visible to client color, because it has 8 xrefs_in and 8 xrefs_out (8x8 > 60). However, when computing the final xrefs for this color, NBR7 is not included (because it is a color that doesn't contain any bridges and it doesn't have enough links). It will instead be traversed, ending up with BR5 and BR6 xrefs that were already present. This means that NBMID will only have 7 xrefs_out and it would no longer satisfy the condition of being a color visible to client.

@BrzVladBrzVlad added area-GC-mono and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Oct 31, 2025
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @BrzVlad
See info in area-owners.md if you want to be subscribed.

CopilotAI 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 fixes a bug in the GC bridge processing where a color's visibility to the client could change during processing, causing incorrect behavior. The fix introduces a visibleToClient flag that pins a color as visible once detected, preventing it from becoming invisible later even if it loses xrefs.

Key changes:

  • Added a visibleToClient flag to ColorData structures in both CoreCLR and Mono runtimes
  • Modified ColorVisibleToClient/color_visible_to_client functions to cache visibility status
  • Added a test case BridgelessHeavyColorChanging to verify the fix using inline arrays

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/gc/gcbridge.cppAdded visibleToClient flag to ColorData and updated ColorVisibleToClient to cache visibility determination
src/mono/mono/metadata/sgen-tarjan-bridge.cAdded visible_to_client flag to ColorData and updated color_visible_to_client to cache visibility determination
src/tests/GC/Features/Bridge/Bridge.csAdded InlineData struct, updated NonBridge14 to use it, and added BridgelessHeavyColorChanging test method

Comment threadsrc/coreclr/gc/gcbridge.cpp Outdated
Comment threadsrc/coreclr/gc/gcbridge.cpp

@filipnavarafilipnavara left a comment

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.

Generally LGTM and makes it much easier to reason about the code, just one nit about the data structure layout.

@lateralusXlateralusX left a comment

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.

LGTM!

@BrzVlad

Copy link
Copy Markdown
MemberAuthor

/ba-g android infra issue

@BrzVlad
BrzVlad merged commit fe17c2c into dotnet:mainNov 5, 2025
144 of 149 checks passed
BrzVlad added a commit to BrzVlad/runtime that referenced this pull request Nov 10, 2025
A color (SCC) that isn't containing any bridge objects is made visible
to client if xrefs_in * xrefs_out is greater than 60. Later on in bridge
processing, we need to build the callback to pass for .net android.
During this stage, we reduce from the full set of SCCs to SCCs that
should be visible to client (containing bridge objects or satisfying the
above condition). If an SCC has an xref to a color that is not visible
to client, we need to do a recursive traversal to find all neighbors
that are visible to client. The problem is that this process can end up
making an SCC no longer visible to client, leading to inconsistencies in
the computation. Consider a color(C1) that has a neighbor that is not
visible to client(C2). In this final stage, we compute the neighbors of
C1 by traversing recursively through the neighbors of C2. If C2 ends up
pointing to colors that were already neighbors of C1, then, following
this computation, C1 would end up with fewer xrefs_out, making the color
no longer visible to client. This make future checks incorrect,
resulting in building incorrect graph for client.
This scenario seems rare in practice, we should have gotten way more
reports otherwise. We fix this by pinning the visible_to_client property
for a color once it first satisfies it, so it will no longer matter how
many actual xrefs the color has.
Fixes assertions like:
```
* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met
```
@srxqds

Copy link
Copy Markdown
Contributor

Does the release/9.0 branch not have this impact?

@BrzVlad

Copy link
Copy Markdown
MemberAuthor

For .net9 I've backported only #121376. Rather that disabling tarjan gc bridge, on .net9 users can set MONO_GC_PARAMS=disable-non-bridge-scc

steveisok pushed a commit that referenced this pull request Nov 10, 2025
…121483)
Backport #121247 and
#121243 to release/10.0.
This fixes assertions like
```
* Assertion at /home/vbrezae/Xamarin/repos/runtime/src/mono/mono/metadata/sgen-tarjan-bridge.c:1151, condition `color_visible_to_client (cd)' not met
```
## Customer Impact
- [x] Customer reported
- [ ] Found internally
Some applications on maui-android can randomly crash during GC, when
using the default gc bridge (the tarjan bridge). We've had a few fixes
for the tarjan bridge merged a few months ago, but there is still this
one issue. The workaround used by customers is to fallback to an older
GC bridge which has worse performance. For some this performance impact
is not acceptable. This backport also fixes the same issue in the
CoreCLR gcbridge implementation. CoreCLR doesn't have a fallback GC
bridge implementation, so this fix is essential for the successful use
of CoreCLR/NativeAOT on android, at least for some customers.
## Regression
- [ ] Yes
- [x] No
## Testing
Tested on our own gc bridge tests, with scenario causing the issue.
## Risk
The GC bridge is a sensitive area and fixes here typically have some
carried risk. This fix however is quite straightforward, it simply pins
the value of a property inside an SCC node, rather than having it
recomputed with unstable value that was leading to problems. No changes
are done to the core algorithm. Low risk.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Dec 11, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@BrzVlad@srxqds@filipnavara@lateralusX