﻿// 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 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;
            }
        }
    }
}
