// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Dynamic.Utils;
using System.Globalization;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using static System.Linq.Expressions.CachedReflectionInfo;

namespace System.Linq.Expressions.Compiler
{
    internal sealed partial class LambdaCompiler
    {
        private void EmitBlockExpression(Expression expr, CompilationFlags flags)
        {
            // emit body
            Emit((BlockExpression)expr, UpdateEmitAsTypeFlag(flags, CompilationFlags.EmitAsDefaultType));
        }

        private void Emit(BlockExpression node, CompilationFlags flags)
        {
            int count = node.ExpressionCount;

            if (count == 0)
            {
                return;
            }

            EnterScope(node);

            CompilationFlags emitAs = flags & CompilationFlags.EmitAsTypeMask;

            CompilationFlags tailCall = flags & CompilationFlags.EmitAsTailCallMask;
            for (int index = 0; index < count - 1; index++)
            {
                Expression e = node.GetExpression(index);
                Expression next = node.GetExpression(index + 1);

                CompilationFlags tailCallFlag;
                if (tailCall != CompilationFlags.EmitAsNoTail)
                {
                    var g = next as GotoExpression;
                    if (g != null && (g.Value == null || !Significant(g.Value)) && ReferenceLabel(g.Target).CanReturn)
                    {
                        // Since tail call flags are not passed into EmitTryExpression, CanReturn means the goto will be emitted
                        // as Ret. Therefore we can emit the current expression with tail call.
                        tailCallFlag = CompilationFlags.EmitAsTail;
                    }
                    else
                    {
                        // In the middle of the block.
                        // We may do better here by marking it as Tail if the following expressions are not going to emit any IL.
                        tailCallFlag = CompilationFlags.EmitAsMiddle;
                    }
                }
                else
                {
                    tailCallFlag = CompilationFlags.EmitAsNoTail;
                }

                flags = UpdateEmitAsTailCallFlag(flags, tailCallFlag);
                EmitExpressionAsVoid(e, flags);
            }

            // if the type of Block it means this is not a Comma
            // so we will force the last expression to emit as void.
            // We don't need EmitAsType flag anymore, should only pass
            // the EmitTailCall field in flags to emitting the last expression.
            if (emitAs == CompilationFlags.EmitAsVoidType || node.Type == typeof(void))
            {
                EmitExpressionAsVoid(node.GetExpression(count - 1), tailCall);
            }
            else
            {
                EmitExpressionAsType(node.GetExpression(count - 1), node.Type, tailCall);
            }

            ExitScope(node);
        }

        private void EnterScope(object node)
        {
            if (HasVariables(node) &&
                (_scope.MergedScopes == null || !_scope.MergedScopes.Contains(node)))
            {
                if (!_tree.Scopes.TryGetValue(node, out CompilerScope? scope))
                {
                    //
                    // Very often, we want to compile nodes as reductions
                    // rather than as IL, but usually they need to allocate
                    // some IL locals. To support this, we allow emitting a
                    // BlockExpression that was not bound by VariableBinder.
                    // This works as long as the variables are only used
                    // locally -- i.e. not closed over.
                    //
                    // User-created blocks will never hit this case; only our
                    // internally reduced nodes will.
                    //
                    scope = new CompilerScope(node, false) { NeedsClosure = _scope.NeedsClosure };
                }

                _scope = scope.Enter(this, _scope);
                Debug.Assert(_scope.Node == node);
            }
        }

        private static bool HasVariables(object node)
        {
            var block = node as BlockExpression;
            if (block != null)
            {
                return block.Variables.Count > 0;
            }
            return ((CatchBlock)node).Variable != null;
        }

        private void ExitScope(object node)
        {
            if (_scope.Node == node)
            {
                _scope = _scope.Exit()!;
            }
        }

        private void EmitDefaultExpression(Expression expr)
        {
            var node = (DefaultExpression)expr;
            if (node.Type != typeof(void))
            {
                // emit default(T)
                _ilg.EmitDefault(node.Type, this);
            }
        }

        private void EmitLoopExpression(Expression expr)
        {
            LoopExpression node = (LoopExpression)expr;

            PushLabelBlock(LabelScopeKind.Statement);
            LabelInfo breakTarget = DefineLabel(node.BreakLabel);
            LabelInfo continueTarget = DefineLabel(node.ContinueLabel);

            continueTarget.MarkWithEmptyStack();

            EmitExpressionAsVoid(node.Body);

            _ilg.Emit(OpCodes.Br, continueTarget.Label);

            PopLabelBlock(LabelScopeKind.Statement);

            breakTarget.MarkWithEmptyStack();
        }

        #region SwitchExpression

        private void EmitSwitchExpression(Expression expr, CompilationFlags flags)
        {
            SwitchExpression node = (SwitchExpression)expr;

            if (node.Cases.Count == 0)
            {
                // Emit the switch value in case it has side-effects, but as void
                // since the value is ignored.
                EmitExpressionAsVoid(node.SwitchValue);

                // Now if there is a default body, it happens unconditionally.
                if (node.DefaultBody != null)
                {
                    EmitExpressionAsType(node.DefaultBody, node.Type, flags);
                }
                else
                {
                    // If there are no cases and no default then the type must be void.
                    // Assert that earlier validation caught any exceptions to that.
                    Debug.Assert(node.Type == typeof(void));
                }

                return;
            }

            // Try to emit it as an IL switch. Works for integer types.
            if (TryEmitSwitchInstruction(node, flags))
            {
                return;
            }

            // Try to emit as a hashtable lookup. Works for strings.
            if (TryEmitHashtableSwitch(node, flags))
            {
                return;
            }

            //
            // Fall back to a series of tests. We need to IL gen instead of
            // transform the tree to avoid stack overflow on a big switch.
            //

            ParameterExpression switchValue = Expression.Parameter(node.SwitchValue.Type, "switchValue");
            ParameterExpression testValue = Expression.Parameter(GetTestValueType(node), "testValue");
            _scope.AddLocal(this, switchValue);
            _scope.AddLocal(this, testValue);

            EmitExpression(node.SwitchValue);
            _scope.EmitSet(switchValue);

            // Emit tests
            var labels = new Label[node.Cases.Count];
            var isGoto = new bool[node.Cases.Count];
            for (int i = 0, n = node.Cases.Count; i < n; i++)
            {
                DefineSwitchCaseLabel(node.Cases[i], out labels[i], out isGoto[i]);
                foreach (Expression test in node.Cases[i].TestValues)
                {
                    // Pull the test out into a temp so it runs on the same
                    // stack as the switch. This simplifies spilling.
                    EmitExpression(test);
                    _scope.EmitSet(testValue);
                    Debug.Assert(TypeUtils.AreReferenceAssignable(testValue.Type, test.Type));
                    EmitExpressionAndBranch(true, Expression.Equal(switchValue, testValue, false, node.Comparison), labels[i]);
                }
            }

            // Define labels
            Label end = _ilg.DefineLabel();
            Label @default = (node.DefaultBody == null) ? end : _ilg.DefineLabel();

            // Emit the case and default bodies
            EmitSwitchCases(node, labels, isGoto, @default, end, flags);
        }

        /// <summary>
        /// Gets the common test value type of the SwitchExpression.
        /// </summary>
        private static Type GetTestValueType(SwitchExpression node)
        {
            if (node.Comparison == null)
            {
                // If we have no comparison, all right side types must be the
                // same.
                return node.Cases[0].TestValues[0].Type;
            }

            // Otherwise, get the type from the method.
            Type result = node.Comparison.GetParametersCached()[1].ParameterType.GetNonRefType();
            if (node.IsLifted)
            {
                result = result.GetNullableType();
            }
            return result;
        }

        private sealed class SwitchLabel
        {
            internal readonly decimal Key;
            internal readonly Label Label;

            // Boxed version of Key, preserving the original type.
            internal readonly object Constant;

            internal SwitchLabel(decimal key, object @constant, Label label)
            {
                Key = key;
                Constant = @constant;
                Label = label;
            }
        }

        private sealed class SwitchInfo
        {
            internal readonly SwitchExpression Node;
            internal readonly LocalBuilder Value;
            internal readonly Label Default;
            internal readonly Type Type;
            internal readonly bool IsUnsigned;
            internal readonly bool Is64BitSwitch;

            internal SwitchInfo(SwitchExpression node, LocalBuilder value, Label @default)
            {
                Node = node;
                Value = value;
                Default = @default;
                Type = Node.SwitchValue.Type;
                IsUnsigned = Type.IsUnsigned();
                TypeCode code = Type.GetTypeCode();
                Is64BitSwitch = code == TypeCode.UInt64 || code == TypeCode.Int64;
            }
        }

        private static bool FitsInBucket(List<SwitchLabel> buckets, decimal key, int count)
        {
            Debug.Assert(key > buckets[buckets.Count - 1].Key);
            decimal jumpTableSlots = key - buckets[0].Key + 1;
            if (jumpTableSlots > int.MaxValue)
            {
                return false;
            }
            // density must be > 50%
            return (buckets.Count + count) * 2 > jumpTableSlots;
        }

        private static void MergeBuckets(List<List<SwitchLabel>> buckets)
        {
            while (buckets.Count > 1)
            {
                List<SwitchLabel> first = buckets[buckets.Count - 2];
                List<SwitchLabel> second = buckets[buckets.Count - 1];

                if (!FitsInBucket(first, second[second.Count - 1].Key, second.Count))
                {
                    return;
                }

                // Merge them
                first.AddRange(second);
                buckets.RemoveAt(buckets.Count - 1);
            }
        }

        // Add key to a new or existing bucket
        private static void AddToBuckets(List<List<SwitchLabel>> buckets, SwitchLabel key)
        {
            if (buckets.Count > 0)
            {
                List<SwitchLabel> last = buckets[buckets.Count - 1];
                if (FitsInBucket(last, key.Key, 1))
                {
                    last.Add(key);
                    // we might be able to merge now
                    MergeBuckets(buckets);
                    return;
                }
            }
            // else create a new bucket
            buckets.Add(new List<SwitchLabel> { key });
        }

        // Determines if the type is an integer we can switch on.
        private static bool CanOptimizeSwitchType(Type valueType)
        {
            // enums & char are allowed
            switch (valueType.GetTypeCode())
            {
                case TypeCode.Byte:
                case TypeCode.SByte:
                case TypeCode.Char:
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.UInt16:
                case TypeCode.UInt32:
                case TypeCode.Int64:
                case TypeCode.UInt64:
                    return true;
                default:
                    return false;
            }
        }

        // Tries to emit switch as a jmp table
        private bool TryEmitSwitchInstruction(SwitchExpression node, CompilationFlags flags)
        {
            // If we have a comparison, bail
            if (node.Comparison != null)
            {
                return false;
            }

            // Make sure the switch value type and the right side type
            // are types we can optimize
            Type type = node.SwitchValue.Type;
            if (!CanOptimizeSwitchType(type) ||
                !TypeUtils.AreEquivalent(type, node.Cases[0].TestValues[0].Type))
            {
                return false;
            }

            // Make sure all test values are constant, or we can't emit the
            // jump table.
            if (!node.Cases.All(c => c.TestValues.All(t => t is ConstantExpression)))
            {
                return false;
            }

            //
            // We can emit the optimized switch, let's do it.
            //

            // Build target labels, collect keys.
            var labels = new Label[node.Cases.Count];
            var isGoto = new bool[node.Cases.Count];

            var uniqueKeys = new HashSet<decimal>();
            var keys = new List<SwitchLabel>();
            for (int i = 0; i < node.Cases.Count; i++)
            {
                DefineSwitchCaseLabel(node.Cases[i], out labels[i], out isGoto[i]);

                foreach (ConstantExpression test in node.Cases[i].TestValues)
                {
                    // Guaranteed to work thanks to CanOptimizeSwitchType.
                    //
                    // Use decimal because it can hold Int64 or UInt64 without
                    // precision loss or signed/unsigned conversions.
                    decimal key = ConvertSwitchValue(test.Value);

                    // Only add each key once. If it appears twice, it's
                    // allowed, but can't be reached.
                    if (uniqueKeys.Add(key))
                    {
                        keys.Add(new SwitchLabel(key, test.Value!, labels[i]));
                    }
                }
            }

            // Sort the keys, and group them into buckets.
            keys.Sort((x, y) => Math.Sign(x.Key - y.Key));
            var buckets = new List<List<SwitchLabel>>();
            foreach (SwitchLabel key in keys)
            {
                AddToBuckets(buckets, key);
            }

            // Emit the switchValue
            LocalBuilder value = GetLocal(node.SwitchValue.Type);
            EmitExpression(node.SwitchValue);
            _ilg.Emit(OpCodes.Stloc, value);

            // Create end label, and default label if needed
            Label end = _ilg.DefineLabel();
            Label @default = (node.DefaultBody == null) ? end : _ilg.DefineLabel();

            // Emit the switch
            var info = new SwitchInfo(node, value, @default);
            EmitSwitchBuckets(info, buckets, 0, buckets.Count - 1);

            // Emit the case bodies and default
            EmitSwitchCases(node, labels, isGoto, @default, end, flags);

            FreeLocal(value);
            return true;
        }

        private static decimal ConvertSwitchValue(object? value)
        {
            if (value is char)
            {
                return (int)(char)value;
            }
            return Convert.ToDecimal(value, CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Creates the label for this case.
        /// Optimization: if the body is just a goto, and we can branch
        /// to it, put the goto target directly in the jump table.
        /// </summary>
        private void DefineSwitchCaseLabel(SwitchCase @case, out Label label, out bool isGoto)
        {
            var jump = @case.Body as GotoExpression;
            // if it's a goto with no value
            if (jump != null && jump.Value == null)
            {
                // Reference the label from the switch. This will cause us to
                // analyze the jump target and determine if it is safe.
                LabelInfo jumpInfo = ReferenceLabel(jump.Target);

                // If we have are allowed to emit the "branch" opcode, then we
                // can jump directly there from the switch's jump table.
                // (Otherwise, we need to emit the goto later as a "leave".)
                if (jumpInfo.CanBranch)
                {
                    label = jumpInfo.Label;
                    isGoto = true;
                    return;
                }
            }
            // otherwise, just define a new label
            label = _ilg.DefineLabel();
            isGoto = false;
        }

        private void EmitSwitchCases(SwitchExpression node, Label[] labels, bool[] isGoto, Label @default, Label end, CompilationFlags flags)
        {
            // Jump to default (to handle the fallthrough case)
            _ilg.Emit(OpCodes.Br, @default);

            // Emit the cases
            for (int i = 0, n = node.Cases.Count; i < n; i++)
            {
                // If the body is a goto, we already emitted an optimized
                // branch directly to it. No need to emit anything else.
                if (isGoto[i])
                {
                    continue;
                }

                _ilg.MarkLabel(labels[i]);
                EmitExpressionAsType(node.Cases[i].Body, node.Type, flags);

                // Last case doesn't need branch
                if (node.DefaultBody != null || i < n - 1)
                {
                    if ((flags & CompilationFlags.EmitAsTailCallMask) == CompilationFlags.EmitAsTail)
                    {
                        //The switch case is at the tail of the lambda so
                        //it is safe to emit a Ret.
                        _ilg.Emit(OpCodes.Ret);
                    }
                    else
                    {
                        _ilg.Emit(OpCodes.Br, end);
                    }
                }
            }

            // Default value
            if (node.DefaultBody != null)
            {
                _ilg.MarkLabel(@default);
                EmitExpressionAsType(node.DefaultBody, node.Type, flags);
            }

            _ilg.MarkLabel(end);
        }

        private void EmitSwitchBuckets(SwitchInfo info, List<List<SwitchLabel>> buckets, int first, int last)
        {
            while (true)
            {
                if (first == last)
                {
                    EmitSwitchBucket(info, buckets[first]);
                    return;
                }

                // Split the buckets into two groups, and use an if test to find
                // the right bucket. This ensures we'll only need O(lg(B)) tests
                // where B is the number of buckets
                int mid = (int)(((long)first + last + 1) / 2);

                if (first == mid - 1)
                {
                    EmitSwitchBucket(info, buckets[first]);
                }
                else
                {
                    // If the first half contains more than one, we need to emit an
                    // explicit guard
                    Label secondHalf = _ilg.DefineLabel();
                    _ilg.Emit(OpCodes.Ldloc, info.Value);
                    EmitConstant(buckets[mid - 1][^1].Constant);
                    _ilg.Emit(info.IsUnsigned ? OpCodes.Bgt_Un : OpCodes.Bgt, secondHalf);
                    EmitSwitchBuckets(info, buckets, first, mid - 1);
                    _ilg.MarkLabel(secondHalf);
                }

                first = mid;
            }
        }

        private void EmitSwitchBucket(SwitchInfo info, List<SwitchLabel> bucket)
        {
            // No need for switch if we only have one value
            if (bucket.Count == 1)
            {
                _ilg.Emit(OpCodes.Ldloc, info.Value);
                EmitConstant(bucket[0].Constant);
                _ilg.Emit(OpCodes.Beq, bucket[0].Label);
                return;
            }

            //
            // If we're switching off of Int64/UInt64, we need more guards here
            // because we'll have to narrow the switch value to an Int32, and
            // we can't do that unless the value is in the right range.
            //
            Label? after = null;
            if (info.Is64BitSwitch)
            {
                after = _ilg.DefineLabel();
                _ilg.Emit(OpCodes.Ldloc, info.Value);
                EmitConstant(bucket[^1].Constant);
                _ilg.Emit(info.IsUnsigned ? OpCodes.Bgt_Un : OpCodes.Bgt, after.Value);
                _ilg.Emit(OpCodes.Ldloc, info.Value);
                EmitConstant(bucket[0].Constant);
                _ilg.Emit(info.IsUnsigned ? OpCodes.Blt_Un : OpCodes.Blt, after.Value);
            }

            _ilg.Emit(OpCodes.Ldloc, info.Value);

            // Normalize key
            decimal key = bucket[0].Key;
            if (key != 0)
            {
                EmitConstant(bucket[0].Constant);
                _ilg.Emit(OpCodes.Sub);
            }

            if (info.Is64BitSwitch)
            {
                _ilg.Emit(OpCodes.Conv_I4);
            }

            // Collect labels
            int len = (int)(bucket[bucket.Count - 1].Key - bucket[0].Key + 1);
            Label[] jmpLabels = new Label[len];

            // Initialize all labels to the default
            int slot = 0;
            foreach (SwitchLabel label in bucket)
            {
                while (key++ != label.Key)
                {
                    jmpLabels[slot++] = info.Default;
                }
                jmpLabels[slot++] = label.Label;
            }

            // check we used all keys and filled all slots
            Debug.Assert(key == bucket[bucket.Count - 1].Key + 1);
            Debug.Assert(slot == jmpLabels.Length);

            // Finally, emit the switch instruction
            _ilg.Emit(OpCodes.Switch, jmpLabels);

            if (info.Is64BitSwitch)
            {
                _ilg.MarkLabel(after!.Value);
            }
        }

        private bool TryEmitHashtableSwitch(SwitchExpression node, CompilationFlags flags)
        {
            // If we have a comparison other than string equality, bail
            if (node.Comparison != String_op_Equality_String_String && node.Comparison != String_Equals_String_String)
            {
                return false;
            }

            // All test values must be constant.
            int tests = 0;
            foreach (SwitchCase c in node.Cases)
            {
                foreach (Expression t in c.TestValues)
                {
                    if (!(t is ConstantExpression))
                    {
                        return false;
                    }
                    tests++;
                }
            }

            // Must have >= 7 labels for it to be worth it.
            if (tests < 7)
            {
                return false;
            }

            // If we're in a DynamicMethod, we could just build the dictionary
            // immediately. But that would cause the two code paths to be more
            // different than they really need to be.
            var initializers = new List<ElementInit>(tests);
            var cases = new ArrayBuilder<SwitchCase>(node.Cases.Count);

            int nullCase = -1;
            MethodInfo add = DictionaryOfStringInt32_Add_String_Int32;
            for (int i = 0, n = node.Cases.Count; i < n; i++)
            {
                foreach (ConstantExpression t in node.Cases[i].TestValues)
                {
                    if (t.Value != null)
                    {
                        initializers.Add(Expression.ElementInit(add, new TrueReadOnlyCollection<Expression>(t, Utils.Constant(i))));
                    }
                    else
                    {
                        nullCase = i;
                    }
                }
                cases.UncheckedAdd(Expression.SwitchCase(node.Cases[i].Body, new TrueReadOnlyCollection<Expression>(Utils.Constant(i))));
            }

            // Create the field to hold the lazily initialized dictionary
            MemberExpression dictField = CreateLazyInitializedField<Dictionary<string, int>>("dictionarySwitch");

            // If we happen to initialize it twice (multithreaded case), it's
            // not the end of the world. The C# compiler does better here by
            // emitting a volatile access to the field.
            Expression dictInit = Expression.Condition(
                Expression.Equal(dictField, Expression.Constant(null, dictField.Type)),
                Expression.Assign(
                    dictField,
                    Expression.ListInit(
                        Expression.New(
                            DictionaryOfStringInt32_Ctor_Int32,
                            new TrueReadOnlyCollection<Expression>(
                                Utils.Constant(initializers.Count)
                            )
                        ),
                        initializers
                    )
                ),
                dictField
            );

            //
            // Create a tree like:
            //
            // switchValue = switchValueExpression;
            // if (switchValue == null) {
            //     switchIndex = nullCase;
            // } else {
            //     _dictField ??= new Dictionary<string, int>(count) { { ... }, ... };
            //     if (!_dictField.TryGetValue(switchValue, out switchIndex)) {
            //         switchIndex = -1;
            //     }
            // }
            // switch (switchIndex) {
            //     case 0: ...
            //     case 1: ...
            //     ...
            //     default:
            // }
            //
            ParameterExpression switchValue = Expression.Variable(typeof(string), "switchValue");
            ParameterExpression switchIndex = Expression.Variable(typeof(int), "switchIndex");
            BlockExpression reduced = Expression.Block(
                new TrueReadOnlyCollection<ParameterExpression>(switchIndex, switchValue),
                new TrueReadOnlyCollection<Expression>(
                    Expression.Assign(switchValue, node.SwitchValue),
                    Expression.IfThenElse(
                        Expression.Equal(switchValue, Expression.Constant(null, typeof(string))),
                        Expression.Assign(switchIndex, Utils.Constant(nullCase)),
                        Expression.IfThenElse(
                            CallTryGetValue(dictInit, switchValue, switchIndex),
                            Utils.Empty,
                            Expression.Assign(switchIndex, Utils.Constant(-1))
                        )
                    ),
                    Expression.Switch(node.Type, switchIndex, node.DefaultBody, null, cases.ToReadOnly())
                )
            );

            EmitExpression(reduced, flags);
            return true;
        }

        [DynamicDependency("TryGetValue", typeof(Dictionary<,>))]
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
            Justification = "The method will be preserved by the DynamicDependency.")]
        private static MethodCallExpression CallTryGetValue(Expression dictInit, ParameterExpression switchValue, ParameterExpression switchIndex)
        {
            return Expression.Call(dictInit, "TryGetValue", null, switchValue, switchIndex);
        }

        #endregion

        private void CheckRethrow()
        {
            // Rethrow is only valid inside a catch.
            for (LabelScopeInfo? j = _labelBlock; j != null; j = j.Parent)
            {
                if (j.Kind == LabelScopeKind.Catch)
                {
                    return;
                }
                else if (j.Kind == LabelScopeKind.Finally)
                {
                    // Rethrow from inside finally is not verifiable
                    break;
                }
            }
            throw Error.RethrowRequiresCatch();
        }

        #region TryStatement

        private void CheckTry()
        {
            // Try inside a filter is not verifiable
            for (LabelScopeInfo? j = _labelBlock; j != null; j = j.Parent)
            {
                if (j.Kind == LabelScopeKind.Filter)
                {
                    throw Error.TryNotAllowedInFilter();
                }
            }
        }

        private void EmitSaveExceptionOrPop(CatchBlock cb)
        {
            if (cb.Variable != null)
            {
                // If the variable is present, store the exception
                // in the variable.
                _scope.EmitSet(cb.Variable);
            }
            else
            {
                // Otherwise, pop it off the stack.
                _ilg.Emit(OpCodes.Pop);
            }
        }

        private void EmitTryExpression(Expression expr)
        {
            var node = (TryExpression)expr;

            CheckTry();

            //******************************************************************
            // 1. ENTERING TRY
            //******************************************************************

            PushLabelBlock(LabelScopeKind.Try);
            _ilg.BeginExceptionBlock();

            //******************************************************************
            // 2. Emit the try statement body
            //******************************************************************

            EmitExpression(node.Body);

            Type tryType = node.Type;
            LocalBuilder? value = null;
            if (tryType != typeof(void))
            {
                //store the value of the try body
                value = GetLocal(tryType);
                _ilg.Emit(OpCodes.Stloc, value);
            }
            //******************************************************************
            // 3. Emit the catch blocks
            //******************************************************************

            foreach (CatchBlock cb in node.Handlers)
            {
                PushLabelBlock(LabelScopeKind.Catch);

                // Begin the strongly typed exception block
                if (cb.Filter == null)
                {
                    _ilg.BeginCatchBlock(cb.Test);
                }
                else
                {
                    _ilg.BeginExceptFilterBlock();
                }

                EnterScope(cb);

                EmitCatchStart(cb);

                //
                // Emit the catch block body
                //
                EmitExpression(cb.Body);
                if (tryType != typeof(void))
                {
                    //store the value of the catch block body
                    _ilg.Emit(OpCodes.Stloc, value!);
                }

                ExitScope(cb);

                PopLabelBlock(LabelScopeKind.Catch);
            }

            //******************************************************************
            // 4. Emit the finally block
            //******************************************************************

            if (node.Finally != null || node.Fault != null)
            {
                PushLabelBlock(LabelScopeKind.Finally);

                if (node.Finally != null)
                {
                    _ilg.BeginFinallyBlock();
                }
                else
                {
                    _ilg.BeginFaultBlock();
                }

                // Emit the body
                EmitExpressionAsVoid(node.Finally ?? node.Fault!);

                _ilg.EndExceptionBlock();
                PopLabelBlock(LabelScopeKind.Finally);
            }
            else
            {
                _ilg.EndExceptionBlock();
            }

            if (tryType != typeof(void))
            {
                _ilg.Emit(OpCodes.Ldloc, value!);
                FreeLocal(value!);
            }
            PopLabelBlock(LabelScopeKind.Try);
        }

        /// <summary>
        /// Emits the start of a catch block.  The exception value that is provided by the
        /// CLR is stored in the variable specified by the catch block or popped if no
        /// variable is provided.
        /// </summary>
        private void EmitCatchStart(CatchBlock cb)
        {
            if (cb.Filter == null)
            {
                EmitSaveExceptionOrPop(cb);
                return;
            }

            // emit filter block. Filter blocks are untyped so we need to do
            // the type check ourselves.
            Label endFilter = _ilg.DefineLabel();
            Label rightType = _ilg.DefineLabel();

            // skip if it's not our exception type, but save
            // the exception if it is so it's available to the
            // filter
            _ilg.Emit(OpCodes.Isinst, cb.Test);
            _ilg.Emit(OpCodes.Dup);
            _ilg.Emit(OpCodes.Brtrue, rightType);
            _ilg.Emit(OpCodes.Pop);
            _ilg.Emit(OpCodes.Ldc_I4_0);
            _ilg.Emit(OpCodes.Br, endFilter);

            // it's our type, save it and emit the filter.
            _ilg.MarkLabel(rightType);
            EmitSaveExceptionOrPop(cb);
            PushLabelBlock(LabelScopeKind.Filter);
            EmitExpression(cb.Filter);
            PopLabelBlock(LabelScopeKind.Filter);

            // begin the catch, clear the exception, we've
            // already saved it
            _ilg.MarkLabel(endFilter);
            _ilg.BeginCatchBlock(exceptionType: null);
            _ilg.Emit(OpCodes.Pop);
        }

        #endregion
    }
}
