diff --git a/src/jit/liveness.cpp b/src/jit/liveness.cpp index a4ef0557825d..3e66d02cfeca 100644 --- a/src/jit/liveness.cpp +++ b/src/jit/liveness.cpp @@ -1104,6 +1104,42 @@ void Compiler::fgExtendDbgLifetimes() #endif // DEBUG } +//------------------------------------------------------------------------ +// fgGetHandlerLiveVars: determine set of locals live because of implicit +// exception flow from a block. +// +// Arguments: +// block - the block in question +// +// Returns: +// Additional set of locals to be considered live throughout the block. +// +// Notes: +// Assumes caller has screened candidate blocks to only those with +// exception flow, via `ehBlockHasExnFlowDsc`. +// +// Exception flow can arise because of a newly raised exception (for +// blocks within try regions) or because of an actively propagating exception +// (for filter blocks). This flow effectively creates additional successor +// edges in the flow graph that the jit does not model. This method computes +// the net contribution from all the missing successor edges. +// +// For example, with the following C# source, during EH processing of the throw, +// the outer filter will execute in pass1, before the inner handler executes +// in pass2, and so the filter blocks should show the inner handler's local is live. +// +// try +// { +// using (AllocateObject()) // ==> try-finally; handler calls Dispose +// { +// throw new Exception(); +// } +// } +// catch (Exception e1) when (IsExpectedException(e1)) +// { +// Console.WriteLine("In catch 1"); +// } + VARSET_VALRET_TP Compiler::fgGetHandlerLiveVars(BasicBlock* block) { noway_assert(block); @@ -1115,7 +1151,6 @@ VARSET_VALRET_TP Compiler::fgGetHandlerLiveVars(BasicBlock* block) do { /* Either we enter the filter first or the catch/finally */ - if (HBtab->HasFilter()) { VarSetOps::UnionD(this, liveVars, HBtab->ebdFilter->bbLiveIn); @@ -1147,6 +1182,72 @@ VARSET_VALRET_TP Compiler::fgGetHandlerLiveVars(BasicBlock* block) } while (true); + // If this block is within a filter, we also need to report as live + // any vars live into enclosed finally or fault handlers, since the + // filter will run during the first EH pass, and enclosed or enclosing + // handlers will run during the second EH pass. So all these handlers + // are "exception flow" successors of the filter. + // + // Note we are relying on ehBlockHasExnFlowDsc to return true + // for any filter block that we should examine here. + if (block->hasHndIndex()) + { + const unsigned thisHndIndex = block->getHndIndex(); + EHblkDsc* enclosingHBtab = ehGetDsc(thisHndIndex); + + if (enclosingHBtab->InFilterRegionBBRange(block)) + { + assert(enclosingHBtab->HasFilter()); + + // Search the EH table for enclosed regions. + // + // All the enclosed regions will be lower numbered and + // immediately prior to and contiguous with the enclosing + // region in the EH tab. + unsigned index = thisHndIndex; + + while (index > 0) + { + index--; + unsigned enclosingIndex = ehGetEnclosingTryIndex(index); + bool isEnclosed = false; + + // To verify this is an enclosed region, search up + // through the enclosing regions until we find the + // region associated with the filter. + while (enclosingIndex != EHblkDsc::NO_ENCLOSING_INDEX) + { + if (enclosingIndex == thisHndIndex) + { + isEnclosed = true; + break; + } + + enclosingIndex = ehGetEnclosingTryIndex(enclosingIndex); + } + + // If we found an enclosed region, check if the region + // is a try fault or try finally, and if so, add any + // locals live into the enclosed region's handler into this + // block's live-in set. + if (isEnclosed) + { + EHblkDsc* enclosedHBtab = ehGetDsc(index); + + if (enclosedHBtab->HasFinallyOrFaultHandler()) + { + VarSetOps::UnionD(this, liveVars, enclosedHBtab->ebdHndBeg->bbLiveIn); + } + } + // Once we run across a non-enclosed region, we can stop searching. + else + { + break; + } + } + } + } + return liveVars; } @@ -1219,14 +1320,17 @@ class LiveVarAnalysis // since (without proof otherwise) the use and def may touch different memory at run-time. m_memoryLiveIn = m_memoryLiveOut | block->bbMemoryUse; - /* Can exceptions from this block be handled (in this function)? */ - + // Does this block have implicit exception flow to a filter or handler? + // If so, include the effects of that flow. if (m_compiler->ehBlockHasExnFlowDsc(block)) { const VARSET_TP& liveVars(m_compiler->fgGetHandlerLiveVars(block)); - VarSetOps::UnionD(m_compiler, m_liveIn, liveVars); VarSetOps::UnionD(m_compiler, m_liveOut, liveVars); + + // Implicit eh edges can induce loop-like behavior, + // so make sure we iterate to closure. + m_hasPossibleBackEdge = true; } /* Has there been any change in either live set? */ @@ -3093,7 +3197,6 @@ void Compiler::fgInterBlockLocalVarLiveness() if (block->bbCatchTyp != BBCT_NONE) { /* Note the set of variables live on entry to exception handler */ - VarSetOps::UnionD(this, exceptVars, block->bbLiveIn); } diff --git a/tests/src/JIT/Regression/JitBlue/GitHub_22820/GitHub_22820.cs b/tests/src/JIT/Regression/JitBlue/GitHub_22820/GitHub_22820.cs new file mode 100644 index 000000000000..95121ab73360 --- /dev/null +++ b/tests/src/JIT/Regression/JitBlue/GitHub_22820/GitHub_22820.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +// Repro for issue 22820. On x86 we need to report enclosed handler +// live-in locals as live into any enclosing filter. +// +// Run with optimized codegen and COMPlus_GCStress=0x4 + +class DisposableObject : IDisposable +{ + public void Dispose() + { + Console.WriteLine("In dispose"); + } +} + +class Program +{ + public static bool IsExpectedException(Exception e) + { + Console.WriteLine("In filter"); + GC.Collect(); + return e is OperationCanceledException; + } + + public static IDisposable AllocateObject() + { + return new DisposableObject(); + } + + static int Main(string[] args) + { + int result = 0; + + try + { + try + { + using (AllocateObject()) + { + throw new Exception(); + } + } + catch (Exception e1) when (IsExpectedException(e1)) + { + Console.WriteLine("In catch 1"); + } + } + catch (Exception e2) + { + Console.WriteLine("In catch 2"); + result = 100; + } + + return result; + } +} diff --git a/tests/src/JIT/Regression/JitBlue/GitHub_22820/GitHub_22820.csproj b/tests/src/JIT/Regression/JitBlue/GitHub_22820/GitHub_22820.csproj new file mode 100644 index 000000000000..42f8a01f3919 --- /dev/null +++ b/tests/src/JIT/Regression/JitBlue/GitHub_22820/GitHub_22820.csproj @@ -0,0 +1,33 @@ + + + + + Debug + AnyCPU + 2.0 + {2649FAFE-07BF-4F93-8120-BA9A69285ABB} + Exe + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + ..\..\ + + + + + + None + True + + + + False + + + + + + + + + + + \ No newline at end of file