Fix GC hole with filter liveness - #89228
Conversation
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch Issue DetailsExecution following a In this case, after Code to handle this case was introduced with dotnet/coreclr#23044, however it didn't handle the case where a This change callers of Fixes #88168
|
Execution following a `filter` clause can be to finally/fault clauses nested within
the `try` region that the `filter` protects. E.g.,
```
try {
try {
} finally {
// 1
}
} filter {
// 2
} filter-handler {
}
```
In this case, after `2` executes, `1` might execute. This is a consequence
of the 2-pass EH model. This means that GC variables in `1` must be kept alive
when `2` is executed, in case a GC occurs during the execution of `2`.
Code to handle this case was introduced with dotnet/coreclr#23044,
however it didn't handle the case where a `filter` is a top-level EH construct
(and not nested within another `try`).
This change callers of `fgGetHandlerLiveVars` to always call it (not just
when `ehBlockHasExnFlowDsc()` is true) so the code that handles the filter case
has a chance to run. To make this a little more efficient, the VARSET_TP that will
be filled in is expected to be pre-allocated and passed in.
Fixes dotnet#88168
|
I'm fine with this fix as is since it just builds on what's there, but note that there are broader issues when we don't model these successors in the usual way, and special case it in liveness: see e.g. #86538 (comment). Ideally we would fix this by fixing the BB successor visitor code. I tried to do that here: https://github.com/dotnet/runtime/compare/main...jakobbotsch:fix-86538?expand=1 There were two problems:
Anyway, as mentioned, I'm ok with this fix, but maybe you have some ideas on the above? [edit] The TP impact looks unexpectedly significant |
|
Diffs -- a few diffs with extended variable lifetimes, including the function identified in the bug ( TP looks pretty bad, though: up to +0.98%. |
|
The particular case is the following context in the current win-x64 coreclr_tests.run collection: ****** START compiling Microsoft.Build.Execution.BuildManager+<>c__DisplayClass100_0:<IssueBuildRequestForBuildSubmission>g__IssueBuildSubmissionToSchedulerImpl|1(Microsoft.Build.Execution.BuildSubmission,bool):this (MethodHash=4030165b)It has an EH table that looks like the following when we build SSA: *************** Exception Handling table
index eTry, eHnd
0 :: 3 - Try at BB03..BB15 [010..095), Fault at BB19..BB21 [095..09F)
1 :: 3 - Try at BB40..BB43 [118..176), Finally at BB22..BB24 [176..180)
2 :: 3 - Try at BB47..BB47 [199..1CD), Finally at BB25..BB27 [1CD..1D7)
3 :: - Try at BB02..BB17 [002..0A4), Filter at BB28..BB31 [0A4..0C4), Handler at BB32..BB50 [0C4..1D9)Note that EH0 is enclosed by EH3, but EH1 and EH2 aren't and cause the search to exit before edit: Perhaps the logic should actually be using |
|
Another case is ****** START compiling System.Net.Http.Http2Connection+Http2Stream+<SendDataAsync>d__81:MoveNext():this (MethodHash=e59e56f7)from win-x64 libraries_tests.pmi. It has an EH table that looks like: *************** Exception Handling table
index eTry, eHnd
0 :: 3 - Try at BB15..BB31 [07B..111), Fault at BB105..BB108 [111..121)
1 :: 3 - Try at BB67..BB72 [1A9..1C3), Fault at BB109..BB112 [1C3..1D3)
2 :: 4 3 - Try at BB122..BB125 [2C6..2F8), Finally at BB113..BB116 [2F8..308)
3 :: 4 - Try at BB11..BB91 [055..290), Filter at BB117..BB120 [290..2BA), Handler at BB121..BB128 [2BA..30A)
4 :: 5 - Try at BB10..BB91 [055..30C), Finally at BB129..BB136 [30C..31C)
5 :: - Try at BB02..BB102 [00E..31E), Handler at BB137..BB137 [31E..343)EH3 has successors due to EH0 and EH1, but stops the backwards iteration when it gets to EH2. |
|
I pushed a commit to my branch linked above that implements the |
|
@BruceForstall I've submitted #89275 that folds this change in and fixes both #86538, #88168, the issue I have identified above (by switching to |
Yes, this makes sense. All the EH clauses nested within the |
|
Subsumed by #89275 |
Execution following a
filterclause can be to finally/fault clauses nested within thetryregion that thefilterprotects. E.g.,In this case, after
2executes,1might execute if the exception came from0. This is a consequence of the 2-pass EH model. This means that GC variables in1must be kept alive when2is executed, in case a GC occurs during the execution of2.Code to handle this case was introduced with dotnet/coreclr#23044, however it didn't handle the case where a
filteris a top-level EH construct (and not nested within anothertry).This change callers of
fgGetHandlerLiveVars(and it's virtual copyPromotionLiveness::AddHandlerLiveVars()) to always call it (not just whenehBlockHasExnFlowDsc()is true) so the code that handles the filter case has a chance to run. To make this a little more efficient, the VARSET_TP that will be filled in is expected to be pre-allocated and passed in.Fixes #88168