File: CodeGen\LocalScopeManager.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// 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.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Metadata;
using Microsoft.CodeAnalysis.Debugging;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
using Cci = Microsoft.Cci;
 
namespace Microsoft.CodeAnalysis.CodeGen
{
    internal partial class ILBuilder
    {
        private sealed class LocalScopeManager
        {
            private readonly LocalScopeInfo _rootScope;
            private readonly Stack<ScopeInfo> _scopes;
            private ExceptionHandlerScope _enclosingExceptionHandler;
 
            internal LocalScopeManager()
            {
                _rootScope = new LocalScopeInfo();
                _scopes = new Stack<ScopeInfo>(1);
                _scopes.Push(_rootScope);
            }
 
            private ScopeInfo CurrentScope => _scopes.Peek();
 
            internal ScopeInfo OpenScope(ScopeType scopeType, Cci.ITypeReference exceptionType)
            {
                var scope = CurrentScope.OpenScope(scopeType, exceptionType, _enclosingExceptionHandler);
                _scopes.Push(scope);
 
                if (scope.IsExceptionHandler)
                {
                    _enclosingExceptionHandler = (ExceptionHandlerScope)scope;
                }
 
                Debug.Assert(_enclosingExceptionHandler == GetEnclosingExceptionHandler());
                return scope;
            }
 
            internal void FinishFilterCondition(ILBuilder builder)
            {
                CurrentScope.FinishFilterCondition(builder);
            }
 
            internal void ClosingScope(ILBuilder builder)
            {
                CurrentScope.ClosingScope(builder);
            }
 
            internal void CloseScope(ILBuilder builder)
            {
                var scope = _scopes.Pop();
                scope.CloseScope(builder);
 
                if (scope.IsExceptionHandler)
                {
                    _enclosingExceptionHandler = GetEnclosingExceptionHandler();
                }
 
                Debug.Assert(_enclosingExceptionHandler == GetEnclosingExceptionHandler());
            }
 
            internal ExceptionHandlerScope EnclosingExceptionHandler => _enclosingExceptionHandler;
 
            private ExceptionHandlerScope GetEnclosingExceptionHandler()
            {
                foreach (var scope in _scopes)
                {
                    switch (scope.Type)
                    {
                        case ScopeType.Try:
                        case ScopeType.Catch:
                        case ScopeType.Filter:
                        case ScopeType.Finally:
                        case ScopeType.Fault:
                            return (ExceptionHandlerScope)scope;
                    }
                }
                return null;
            }
 
            internal BasicBlock CreateBlock(ILBuilder builder)
            {
                var scope = (LocalScopeInfo)CurrentScope;
                return scope.CreateBlock(builder);
            }
 
            internal SwitchBlock CreateSwitchBlock(ILBuilder builder)
            {
                var scope = (LocalScopeInfo)CurrentScope;
                return scope.CreateSwitchBlock(builder);
            }
 
            internal void AddLocal(LocalDefinition variable)
            {
                var scope = (LocalScopeInfo)CurrentScope;
                scope.AddLocal(variable);
            }
 
            internal void AddLocalConstant(LocalConstantDefinition constant)
            {
                var scope = (LocalScopeInfo)CurrentScope;
                scope.AddLocalConstant(constant);
            }
 
            /// <summary>
            /// Gets all scopes that contain variables.
            /// </summary>
            internal ImmutableArray<Cci.LocalScope> GetAllScopesWithLocals()
            {
                var result = ArrayBuilder<Cci.LocalScope>.GetInstance();
                ScopeBounds rootBounds = _rootScope.GetLocalScopes(result);
 
                int expectedRootScopeLength = rootBounds.End - rootBounds.Begin;
 
                // Add root scope if it was not already added.
                // we add it even if it does not contain any locals
                if (result.Count > 0 && result[result.Count - 1].Length != expectedRootScopeLength)
                {
                    result.Add(new Cci.LocalScope(
                        0,
                        expectedRootScopeLength,
                        ImmutableArray<Cci.ILocalDefinition>.Empty,
                        ImmutableArray<Cci.ILocalDefinition>.Empty));
                }
 
                // scopes should be sorted by position and size
                result.Sort(ScopeComparer.Instance);
 
                return result.ToImmutableAndFree();
            }
 
            /// <summary>
            /// Returns an ExceptionHandlerRegion for each exception handler clause
            /// beneath the root scope. Each ExceptionHandlerRegion indicates the type
            /// of clause (catch or finally) and the bounds of the try block and clause block.
            /// </summary>
            internal ImmutableArray<Cci.ExceptionHandlerRegion> GetExceptionHandlerRegions()
            {
                var result = ArrayBuilder<Cci.ExceptionHandlerRegion>.GetInstance();
                _rootScope.GetExceptionHandlerRegions(result);
                return result.ToImmutableAndFree();
            }
 
            internal ImmutableArray<StateMachineHoistedLocalScope> GetHoistedLocalScopes()
            {
                var result = ArrayBuilder<StateMachineHoistedLocalScope>.GetInstance();
                _rootScope.GetHoistedLocalScopes(result);
                return result.ToImmutableAndFree();
            }
 
            internal void AddUserHoistedLocal(int slotIndex)
            {
                var scope = (LocalScopeInfo)CurrentScope;
                scope.AddUserHoistedLocal(slotIndex);
            }
 
            internal void FreeBasicBlocks()
            {
                _rootScope.FreeBasicBlocks();
            }
 
            internal bool PossiblyDefinedOutsideOfTry(LocalDefinition local)
            {
                foreach (var s in _scopes)
                {
                    if (s.ContainsLocal(local))
                    {
                        return false;
                    }
 
                    if (s.Type == ScopeType.Try)
                    {
                        return true;
                    }
                }
 
                // not recorded in scopes, could be a temp
                // we cannot tell anything.
                return true;
            }
        }
 
        /// <summary>
        /// Base class for IL scopes where a scope contains IL blocks and other nested
        /// scopes. A scope may represent a scope for variable declarations, an exception
        /// handler clause, or an entire exception handler (multiple clauses).
        /// </summary>
        internal abstract class ScopeInfo
        {
            public abstract ScopeType Type { get; }
 
            public virtual ScopeInfo OpenScope(ScopeType scopeType,
                Microsoft.Cci.ITypeReference exceptionType,
                ExceptionHandlerScope currentHandler)
            {
                if (scopeType == ScopeType.TryCatchFinally)
                {
                    return new ExceptionHandlerContainerScope(currentHandler);
                }
                else
                {
                    Debug.Assert(scopeType == ScopeType.Variable || scopeType == ScopeType.StateMachineVariable);
                    return new LocalScopeInfo();
                }
            }
 
            public virtual void ClosingScope(ILBuilder builder)
            {
            }
 
            public virtual void CloseScope(ILBuilder builder)
            {
            }
 
            public virtual void FinishFilterCondition(ILBuilder builder)
            {
                throw ExceptionUtilities.Unreachable();
            }
 
            public bool IsExceptionHandler
            {
                get
                {
                    switch (this.Type)
                    {
                        case ScopeType.Try:
                        case ScopeType.Catch:
                        case ScopeType.Filter:
                        case ScopeType.Finally:
                        case ScopeType.Fault:
                            return true;
                        default:
                            return false;
                    }
                }
            }
 
            internal abstract void GetExceptionHandlerRegions(ArrayBuilder<Cci.ExceptionHandlerRegion> regions);
 
            /// <summary>
            /// Recursively calculates the start and end of the given scope.
            /// Only scopes with locals are actually dumped to the list.
            /// </summary>
            internal abstract ScopeBounds GetLocalScopes(ArrayBuilder<Cci.LocalScope> result);
 
            protected static ScopeBounds GetLocalScopes<TScopeInfo>(ArrayBuilder<Cci.LocalScope> result, ImmutableArray<TScopeInfo>.Builder scopes)
                where TScopeInfo : ScopeInfo
            {
                Debug.Assert(scopes.Count > 0);
 
                int begin = int.MaxValue;
                int end = 0;
 
                foreach (var scope in scopes)
                {
                    ScopeBounds bounds = scope.GetLocalScopes(result);
                    begin = Math.Min(begin, bounds.Begin);
                    end = Math.Max(end, bounds.End);
                }
 
                return new ScopeBounds(begin, end);
            }
 
            /// <summary>
            /// Recursively calculates the start and end of the given scope.
            /// Only scopes with locals are actually dumped to the list.
            /// </summary>
            internal abstract ScopeBounds GetHoistedLocalScopes(ArrayBuilder<StateMachineHoistedLocalScope> result);
 
            protected static ScopeBounds GetHoistedLocalScopes<TScopeInfo>(ArrayBuilder<StateMachineHoistedLocalScope> result, ImmutableArray<TScopeInfo>.Builder scopes)
                where TScopeInfo : ScopeInfo
            {
                Debug.Assert(scopes.Count > 0);
 
                int begin = int.MaxValue;
                int end = 0;
 
                foreach (var scope in scopes)
                {
                    ScopeBounds bounds = scope.GetHoistedLocalScopes(result);
                    begin = Math.Min(begin, bounds.Begin);
                    end = Math.Max(end, bounds.End);
                }
 
                return new ScopeBounds(begin, end);
            }
 
            /// <summary>
            /// Free any basic blocks owned by this scope or sub-scopes.
            /// </summary>
            public abstract void FreeBasicBlocks();
 
            internal virtual bool ContainsLocal(LocalDefinition local) => false;
        }
 
        /// <summary>
        /// Class that collects content of the scope (blocks, nested scopes, variables etc).
        /// There is one for every opened scope.
        /// </summary>
        internal class LocalScopeInfo : ScopeInfo
        {
            private ImmutableArray<LocalDefinition>.Builder _localVariables;
            private ImmutableArray<LocalConstantDefinition>.Builder _localConstants;
            private ImmutableArray<int>.Builder _stateMachineUserHoistedLocalSlotIndices;
 
            // Nested scopes and blocks are not relevant for PDB. 
            // We need these only to figure scope bounds.
            private ImmutableArray<ScopeInfo>.Builder _nestedScopes;
            protected ImmutableArray<BasicBlock>.Builder Blocks;
 
            public override ScopeType Type => ScopeType.Variable;
 
            public override ScopeInfo OpenScope(
                ScopeType scopeType,
                Cci.ITypeReference exceptionType,
                ExceptionHandlerScope currentExceptionHandler)
            {
                var scope = base.OpenScope(scopeType, exceptionType, currentExceptionHandler);
                if (_nestedScopes == null)
                {
                    _nestedScopes = ImmutableArray.CreateBuilder<ScopeInfo>(1);
                }
                _nestedScopes.Add(scope);
                return scope;
            }
 
            internal void AddLocal(LocalDefinition variable)
            {
                if (_localVariables == null)
                {
                    _localVariables = ImmutableArray.CreateBuilder<LocalDefinition>(1);
                }
 
                Debug.Assert(variable.Name != null);
 
                _localVariables.Add(variable);
            }
 
            internal void AddLocalConstant(LocalConstantDefinition constant)
            {
                if (_localConstants == null)
                {
                    _localConstants = ImmutableArray.CreateBuilder<LocalConstantDefinition>(1);
                }
 
                Debug.Assert(constant.Name != null);
 
                _localConstants.Add(constant);
            }
 
            internal void AddUserHoistedLocal(int slotIndex)
            {
                if (_stateMachineUserHoistedLocalSlotIndices == null)
                {
                    _stateMachineUserHoistedLocalSlotIndices = ImmutableArray.CreateBuilder<int>(1);
                }
 
                Debug.Assert(slotIndex >= 0);
                _stateMachineUserHoistedLocalSlotIndices.Add(slotIndex);
            }
 
            internal override bool ContainsLocal(LocalDefinition local)
            {
                var locals = _localVariables;
                return locals != null && locals.Contains(local);
            }
 
            public virtual BasicBlock CreateBlock(ILBuilder builder)
            {
                var enclosingHandler = builder.EnclosingExceptionHandler;
                var block = enclosingHandler == null ?
                    AllocatePooledBlock(builder) :
                    new BasicBlockWithHandlerScope(builder, enclosingHandler);
 
                AddBlock(block);
                return block;
            }
 
            private static BasicBlock AllocatePooledBlock(ILBuilder builder)
            {
                var block = BasicBlock.Pool.Allocate();
                block.Initialize(builder);
                return block;
            }
 
            public SwitchBlock CreateSwitchBlock(ILBuilder builder)
            {
                var block = new SwitchBlock(builder, builder.EnclosingExceptionHandler);
                AddBlock(block);
                return block;
            }
 
            protected void AddBlock(BasicBlock block)
            {
                if (Blocks == null)
                {
                    Blocks = ImmutableArray.CreateBuilder<BasicBlock>(4);
                }
 
                Blocks.Add(block);
            }
 
            internal override void GetExceptionHandlerRegions(ArrayBuilder<Cci.ExceptionHandlerRegion> regions)
            {
                if (_nestedScopes != null)
                {
                    for (int i = 0, cnt = _nestedScopes.Count; i < cnt; i++)
                    {
                        _nestedScopes[i].GetExceptionHandlerRegions(regions);
                    }
                }
            }
 
            internal override ScopeBounds GetLocalScopes(ArrayBuilder<Cci.LocalScope> result)
            {
                int begin = int.MaxValue;
                int end = 0;
 
                // It may seem overkill to scan all blocks, 
                // but blocks may be reordered so we cannot be sure which ones are first/last.
                if (Blocks != null)
                {
                    for (int i = 0; i < Blocks.Count; i++)
                    {
                        var block = Blocks[i];
 
                        if (block.Reachability != Reachability.NotReachable)
                        {
                            begin = Math.Min(begin, block.Start);
                            end = Math.Max(end, block.Start + block.TotalSize);
                        }
                    }
                }
 
                // if there are nested scopes, dump them too
                // also may need to adjust current scope bounds.
                if (_nestedScopes != null)
                {
                    ScopeBounds nestedBounds = GetLocalScopes(result, _nestedScopes);
                    begin = Math.Min(begin, nestedBounds.Begin);
                    end = Math.Max(end, nestedBounds.End);
                }
 
                // we are not interested in scopes with no variables or no code in them.
                if ((_localVariables != null || _localConstants != null) && end > begin)
                {
                    var newScope = new Cci.LocalScope(
                        begin,
                        end,
                        _localConstants.AsImmutableOrEmpty<Cci.ILocalDefinition>(),
                        _localVariables.AsImmutableOrEmpty<Cci.ILocalDefinition>());
 
                    result.Add(newScope);
                }
 
                return new ScopeBounds(begin, end);
            }
 
            internal override ScopeBounds GetHoistedLocalScopes(ArrayBuilder<StateMachineHoistedLocalScope> result)
            {
                int begin = int.MaxValue;
                int end = 0;
 
                // It may seem overkill to scan all blocks, 
                // but blocks may be reordered so we cannot be sure which ones are first/last.
                if (Blocks != null)
                {
                    for (int i = 0; i < Blocks.Count; i++)
                    {
                        var block = Blocks[i];
 
                        if (block.Reachability != Reachability.NotReachable)
                        {
                            begin = Math.Min(begin, block.Start);
                            end = Math.Max(end, block.Start + block.TotalSize);
                        }
                    }
                }
 
                // if there are nested scopes, dump them too
                // also may need to adjust current scope bounds.
                if (_nestedScopes != null)
                {
                    ScopeBounds nestedBounds = GetHoistedLocalScopes(result, _nestedScopes);
                    begin = Math.Min(begin, nestedBounds.Begin);
                    end = Math.Max(end, nestedBounds.End);
                }
 
                // we are not interested in scopes with no variables or no code in them.
                if (_stateMachineUserHoistedLocalSlotIndices != null && end > begin)
                {
                    var newScope = new StateMachineHoistedLocalScope(begin, end);
 
                    foreach (var slotIndex in _stateMachineUserHoistedLocalSlotIndices)
                    {
                        while (result.Count <= slotIndex)
                        {
                            result.Add(default(StateMachineHoistedLocalScope));
                        }
 
                        result[slotIndex] = newScope;
                    }
                }
 
                return new ScopeBounds(begin, end);
            }
 
            public override void FreeBasicBlocks()
            {
                if (Blocks != null)
                {
                    for (int i = 0, cnt = Blocks.Count; i < cnt; i++)
                    {
                        Blocks[i].Free();
                    }
                }
 
                if (_nestedScopes != null)
                {
                    for (int i = 0, cnt = _nestedScopes.Count; i < cnt; i++)
                    {
                        _nestedScopes[i].FreeBasicBlocks();
                    }
                }
            }
        }
 
        /// <summary>
        /// A scope for a single try, catch, or finally clause. If the clause
        /// is a catch clause, ExceptionType will be set.
        /// </summary>
        internal sealed class ExceptionHandlerScope : LocalScopeInfo
        {
            private readonly ExceptionHandlerContainerScope _containingScope;
            private readonly ScopeType _type;
            private readonly Microsoft.Cci.ITypeReference _exceptionType;
 
            private BasicBlock _lastFilterConditionBlock;
 
            // branches may become "blocked by finally" if finally does not terminate (throws or contains infinite loop)
            // we cannot guarantee that the original label will be emitted (it might be unreachable).
            // on the other hand, it does not matter what blocked branches target as long as it is still blocked by same finally
            // so we provide this "special" block that is located right after finally that any blocked branch can safely target
            // We do guarantee that special block will be emitted as long as something uses it as a target of a branch.
            private object _blockedByFinallyDestination;
 
            public ExceptionHandlerScope(ExceptionHandlerContainerScope containingScope, ScopeType type, Microsoft.Cci.ITypeReference exceptionType)
            {
                Debug.Assert((type == ScopeType.Try) || (type == ScopeType.Catch) || (type == ScopeType.Filter) || (type == ScopeType.Finally) || (type == ScopeType.Fault));
                Debug.Assert((type == ScopeType.Catch) == (exceptionType != null));
 
                _containingScope = containingScope;
                _type = type;
                _exceptionType = exceptionType;
            }
 
            public ExceptionHandlerContainerScope ContainingExceptionScope => _containingScope;
 
            public override ScopeType Type => _type;
 
            public Microsoft.Cci.ITypeReference ExceptionType => _exceptionType;
 
            // pessimistically sets destination for blocked branches.
            // called when finally block is inserted in the outer TryFinally scope.
            // reachability analysis will clear the label as son as it proves
            // that finally is not blocking.
            public void SetBlockedByFinallyDestination(object label)
            {
                _blockedByFinallyDestination = label;
            }
 
            // if current finally does not terminate, this is where 
            // branches going through it should be retargeted.
            // Otherwise returns null.
            public object BlockedByFinallyDestination => _blockedByFinallyDestination;
 
            // Called when finally is determined to be non-blocking
            public void UnblockFinally()
            {
                _blockedByFinallyDestination = null;
            }
 
            public int FilterHandlerStart
                => _lastFilterConditionBlock.Start + _lastFilterConditionBlock.TotalSize;
 
            public override void FinishFilterCondition(ILBuilder builder)
            {
                Debug.Assert(_type == ScopeType.Filter);
                Debug.Assert(_lastFilterConditionBlock == null);
 
                _lastFilterConditionBlock = builder.FinishFilterCondition();
            }
 
            public BasicBlock LastFilterConditionBlock => _lastFilterConditionBlock;
 
            public override void ClosingScope(ILBuilder builder)
            {
                switch (_type)
                {
                    case ScopeType.Finally:
                    case ScopeType.Fault:
                        // Emit endfinally|endfault - they are the same opcode.
                        builder.EmitEndFinally();
                        break;
 
                    default:
                        // Emit branch to label after exception handler.
                        // ("br" will be rewritten as "leave" later by ILBuilder.)
                        var endLabel = _containingScope.EndLabel;
                        Debug.Assert(endLabel != null);
 
                        builder.EmitBranch(ILOpCode.Br, endLabel);
                        break;
                }
            }
 
            public override void CloseScope(ILBuilder builder)
            {
                Debug.Assert(LeaderBlock != null);
            }
 
            public override BasicBlock CreateBlock(ILBuilder builder)
            {
                Debug.Assert(builder.EnclosingExceptionHandler == this);
                var block = (Blocks == null) ?
                    new ExceptionHandlerLeaderBlock(builder, this, this.GetLeaderBlockType()) :
                    new BasicBlockWithHandlerScope(builder, this);
 
                AddBlock(block);
                return block;
            }
 
            public ExceptionHandlerLeaderBlock LeaderBlock => (ExceptionHandlerLeaderBlock)Blocks?[0];
 
            private BlockType GetLeaderBlockType()
            {
                switch (_type)
                {
                    case ScopeType.Try:
                        return BlockType.Try;
                    case ScopeType.Catch:
                        return BlockType.Catch;
                    case ScopeType.Filter:
                        return BlockType.Filter;
                    case ScopeType.Finally:
                        return BlockType.Finally;
                    default:
                        return BlockType.Fault;
                }
            }
 
            public override void FreeBasicBlocks()
            {
                base.FreeBasicBlocks();
            }
        }
 
        /// <summary>
        /// A scope for an entire exception handler (a try block with either several
        /// catches or a finally block). Unlike other scopes, this scope contains
        /// nested scopes only, no IL blocks (although nested ExceptionHandlerScopes
        /// for the clauses will contain IL blocks).
        /// </summary>
        internal sealed class ExceptionHandlerContainerScope : ScopeInfo
        {
            private readonly ImmutableArray<ExceptionHandlerScope>.Builder _handlers;
            private readonly object _endLabel;
            private readonly ExceptionHandlerScope _containingHandler;
 
            public ExceptionHandlerContainerScope(ExceptionHandlerScope containingHandler)
            {
                _handlers = ImmutableArray.CreateBuilder<ExceptionHandlerScope>(2);
                _containingHandler = containingHandler;
                _endLabel = new object();
            }
 
            public ExceptionHandlerScope ContainingHandler => _containingHandler;
 
            public object EndLabel => _endLabel;
 
            public override ScopeType Type => ScopeType.TryCatchFinally;
 
            public override ScopeInfo OpenScope(ScopeType scopeType,
                Microsoft.Cci.ITypeReference exceptionType,
                ExceptionHandlerScope currentExceptionHandler)
            {
                Debug.Assert(((_handlers.Count == 0) && (scopeType == ScopeType.Try)) ||
                    ((_handlers.Count > 0) && ((scopeType == ScopeType.Catch) || (scopeType == ScopeType.Filter) || (scopeType == ScopeType.Finally) || (scopeType == ScopeType.Fault))));
 
                Debug.Assert(currentExceptionHandler == _containingHandler);
 
                var handler = new ExceptionHandlerScope(this, scopeType, exceptionType);
                _handlers.Add(handler);
                return handler;
            }
 
            public override void CloseScope(ILBuilder builder)
            {
                Debug.Assert(_handlers.Count > 1);
 
                // Fix up the NextExceptionHandler reference of each leader block.
                var tryScope = _handlers[0];
                var previousBlock = tryScope.LeaderBlock;
 
                for (int i = 1; i < _handlers.Count; i++)
                {
                    var handlerScope = _handlers[i];
                    var nextBlock = handlerScope.LeaderBlock;
 
                    previousBlock.NextExceptionHandler = nextBlock;
                    previousBlock = nextBlock;
                }
 
                // Generate label for try/catch "leave" target.
                builder.MarkLabel(_endLabel);
 
                // hide the following code, since it could be reached through the label above.
                builder.DefineHiddenSequencePoint();
 
                Debug.Assert(builder._currentBlock == builder._labelInfos[_endLabel].bb);
 
                if (_handlers[1].Type == ScopeType.Finally)
                {
                    // Generate "nop" branch to itself. If this block is unreachable
                    // (because the finally block does not complete), the "nop" will be
                    // replaced by Br_s. On the other hand, if this block is reachable,
                    // the "nop" will be skipped so any "leave" instructions jumping
                    // to this block will jump to the next instead.
                    builder.EmitBranch(ILOpCode.Nop, _endLabel);
 
                    _handlers[1].SetBlockedByFinallyDestination(_endLabel);
                }
            }
 
            internal override void GetExceptionHandlerRegions(ArrayBuilder<Cci.ExceptionHandlerRegion> regions)
            {
                Debug.Assert(_handlers.Count > 1);
 
                ExceptionHandlerScope tryScope = null;
                ScopeBounds tryBounds = new ScopeBounds();
 
                foreach (var handlerScope in _handlers)
                {
                    // Partition I, section 12.4.2.5:
                    // The ordering of the exception clauses in the Exception Handler Table is important. If handlers are nested, 
                    // the most deeply nested try blocks shall come before the try blocks that enclose them.
                    //
                    // so we collect the inner regions first.
                    handlerScope.GetExceptionHandlerRegions(regions);
 
                    var handlerBounds = GetBounds(handlerScope);
 
                    if (tryScope == null)
                    {
                        // the first scope that we see should be Try.
                        Debug.Assert(handlerScope.Type == ScopeType.Try);
 
                        tryScope = handlerScope;
                        tryBounds = handlerBounds;
 
                        var reachability = tryScope.LeaderBlock.Reachability;
                        Debug.Assert((reachability == Reachability.Reachable) || (reachability == Reachability.NotReachable));
 
                        // All handler blocks should have same reachability.
                        Debug.Assert(_handlers.All(h => (h.LeaderBlock.Reachability == reachability)));
 
                        if (reachability != Reachability.Reachable)
                        {
                            return;
                        }
                    }
                    else
                    {
                        Cci.ExceptionHandlerRegion region;
                        switch (handlerScope.Type)
                        {
                            case ScopeType.Finally:
                                region = new Cci.ExceptionHandlerRegionFinally(tryBounds.Begin, tryBounds.End, handlerBounds.Begin, handlerBounds.End);
                                break;
 
                            case ScopeType.Fault:
                                region = new Cci.ExceptionHandlerRegionFault(tryBounds.Begin, tryBounds.End, handlerBounds.Begin, handlerBounds.End);
                                break;
 
                            case ScopeType.Catch:
                                region = new Cci.ExceptionHandlerRegionCatch(tryBounds.Begin, tryBounds.End, handlerBounds.Begin, handlerBounds.End, handlerScope.ExceptionType);
                                break;
 
                            case ScopeType.Filter:
                                region = new Cci.ExceptionHandlerRegionFilter(tryBounds.Begin, tryBounds.End, handlerScope.FilterHandlerStart, handlerBounds.End, handlerBounds.Begin);
                                break;
 
                            default:
                                throw ExceptionUtilities.UnexpectedValue(handlerScope.Type);
                        }
 
                        regions.Add(region);
                    }
                }
            }
 
            internal override ScopeBounds GetLocalScopes(ArrayBuilder<Cci.LocalScope> scopesWithVariables)
                => GetLocalScopes(scopesWithVariables, _handlers);
 
            internal override ScopeBounds GetHoistedLocalScopes(ArrayBuilder<StateMachineHoistedLocalScope> result)
                => GetHoistedLocalScopes(result, _handlers);
 
            private static ScopeBounds GetBounds(ExceptionHandlerScope scope)
            {
                var scopes = ArrayBuilder<Cci.LocalScope>.GetInstance();
                var result = scope.GetLocalScopes(scopes);
                scopes.Free();
                return result;
            }
 
            public override void FreeBasicBlocks()
            {
                // No basic blocks owned directly here.
 
                foreach (var scope in _handlers)
                {
                    scope.FreeBasicBlocks();
                }
            }
 
            internal bool FinallyOnly()
            {
                var curScope = this;
                do
                {
                    var handlers = curScope._handlers;
                    // handler[0] is always the try
                    // if we have a finally, then we do not have any catches and 
                    // the finally is as handlers[1]
                    if (handlers.Count != 2 || handlers[1].Type != ScopeType.Finally)
                    {
                        return false;
                    }
 
                    curScope = curScope._containingHandler?.ContainingExceptionScope;
                }
                while (curScope != null);
 
                return true;
            }
        }
 
        internal readonly struct ScopeBounds
        {
            internal readonly int Begin; // inclusive
            internal readonly int End;   // exclusive
 
            internal ScopeBounds(int begin, int end)
            {
                Debug.Assert(begin >= 0 && end >= 0);
                this.Begin = begin;
                this.End = end;
            }
        }
 
        /// <summary>
        /// Compares scopes by their start (ascending) and then size (descending).
        /// </summary>
        private sealed class ScopeComparer : IComparer<Cci.LocalScope>
        {
            public static readonly ScopeComparer Instance = new ScopeComparer();
 
            private ScopeComparer() { }
 
            public int Compare(Cci.LocalScope x, Cci.LocalScope y)
            {
                var result = x.StartOffset.CompareTo(y.StartOffset);
                return (result == 0) ? y.EndOffset.CompareTo(x.EndOffset) : result;
            }
        }
    }
}