File: System\Linq\Expressions\Interpreter\InstructionList.cs
Web Access
Project: src\src\libraries\System.Linq.Expressions\src\System.Linq.Expressions.csproj (System.Linq.Expressions)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
// Enables instruction counting and displaying stats at process exit.
// #define STATS
 
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Dynamic.Utils;
using System.Reflection;
using System.Runtime.CompilerServices;
 
namespace System.Linq.Expressions.Interpreter
{
    [DebuggerTypeProxy(typeof(InstructionArray.DebugView))]
    internal readonly struct InstructionArray
    {
        internal readonly int MaxStackDepth;
        internal readonly int MaxContinuationDepth;
        internal readonly Instruction[] Instructions;
        internal readonly object[]? Objects;
        internal readonly RuntimeLabel[] Labels;
 
        // list of (instruction index, cookie) sorted by instruction index:
        internal readonly List<KeyValuePair<int, object?>>? DebugCookies;
 
        internal InstructionArray(int maxStackDepth, int maxContinuationDepth, Instruction[] instructions,
            object[]? objects, RuntimeLabel[] labels, List<KeyValuePair<int, object?>>? debugCookies)
        {
            MaxStackDepth = maxStackDepth;
            MaxContinuationDepth = maxContinuationDepth;
            Instructions = instructions;
            DebugCookies = debugCookies;
            Objects = objects;
            Labels = labels;
        }
 
        #region Debug View
 
        internal sealed class DebugView
        {
            private readonly InstructionArray _array;
 
            public DebugView(InstructionArray array)
            {
                _array = array;
            }
 
            [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
            public InstructionList.DebugView.InstructionView[]/*!*/ A0 => GetInstructionViews(includeDebugCookies: true);
 
            public InstructionList.DebugView.InstructionView[] GetInstructionViews(bool includeDebugCookies = false)
            {
                return InstructionList.DebugView.GetInstructionViews(
                    _array.Instructions,
                    _array.Objects,
                    (index) => _array.Labels[index].Index,
                    includeDebugCookies ? _array.DebugCookies : null
                );
            }
        }
        #endregion
    }
 
    [DebuggerTypeProxy(typeof(InstructionList.DebugView))]
    internal sealed class InstructionList
    {
        private readonly List<Instruction> _instructions = new List<Instruction>();
        private List<object>? _objects;
 
        private int _currentStackDepth;
        private int _maxStackDepth;
        private int _currentContinuationsDepth;
        private int _maxContinuationDepth;
        private int _runtimeLabelCount;
        private List<BranchLabel>? _labels;
 
#if DEBUG
        // list of (instruction index, cookie) sorted by instruction index:
        private List<KeyValuePair<int, object?>>? _debugCookies;
#endif
        #region Debug View
 
        internal sealed class DebugView
        {
            private readonly InstructionList _list;
 
            public DebugView(InstructionList list)
            {
                ArgumentNullException.ThrowIfNull(list);
                _list = list;
            }
 
            [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
            public InstructionView[]/*!*/ A0 => GetInstructionViews(includeDebugCookies: true);
 
            public InstructionView[] GetInstructionViews(bool includeDebugCookies = false)
            {
                return GetInstructionViews(
                        _list._instructions,
                        _list._objects,
                        (index) => _list._labels![index].TargetIndex,
#if DEBUG
                        includeDebugCookies ? _list._debugCookies :
#endif
                            null
                    );
            }
 
            internal static InstructionView[] GetInstructionViews(IReadOnlyList<Instruction> instructions, IReadOnlyList<object>? objects,
                Func<int, int> labelIndexer, IReadOnlyList<KeyValuePair<int, object?>>? debugCookies)
            {
                var result = new List<InstructionView>();
                int index = 0;
                int stackDepth = 0;
                int continuationsDepth = 0;
 
                IEnumerator<KeyValuePair<int, object?>> cookieEnumerator = (debugCookies ?? Array.Empty<KeyValuePair<int, object?>>()).GetEnumerator();
                bool hasCookie = cookieEnumerator.MoveNext();
 
                for (int i = 0, n = instructions.Count; i < n; i++)
                {
                    Instruction instruction = instructions[i];
 
                    object? cookie = null;
                    while (hasCookie && cookieEnumerator.Current.Key == i)
                    {
                        cookie = cookieEnumerator.Current.Value;
                        hasCookie = cookieEnumerator.MoveNext();
                    }
 
                    int stackDiff = instruction.StackBalance;
                    int contDiff = instruction.ContinuationsBalance;
                    string name = instruction.ToDebugString(i, cookie, labelIndexer, objects);
                    result.Add(new InstructionView(instruction, name, i, stackDepth, continuationsDepth));
 
                    index++;
                    stackDepth += stackDiff;
                    continuationsDepth += contDiff;
                }
                return result.ToArray();
            }
 
            [DebuggerDisplay("{GetValue(),nq}", Name = "{GetName(),nq}", Type = "{GetDisplayType(), nq}")]
            internal readonly struct InstructionView
            {
                private readonly int _index;
                private readonly int _stackDepth;
                private readonly int _continuationsDepth;
                private readonly string _name;
                private readonly Instruction _instruction;
 
                internal string GetName()
                {
                    return _index +
                        (_continuationsDepth == 0 ? "" : " C(" + _continuationsDepth + ")") +
                        (_stackDepth == 0 ? "" : " S(" + _stackDepth + ")");
                }
 
                internal string GetValue()
                {
                    return _name;
                }
 
                internal string GetDisplayType()
                {
                    return _instruction.ContinuationsBalance + "/" + _instruction.StackBalance;
                }
 
                public InstructionView(Instruction instruction, string name, int index, int stackDepth, int continuationsDepth)
                {
                    _instruction = instruction;
                    _name = name;
                    _index = index;
                    _stackDepth = stackDepth;
                    _continuationsDepth = continuationsDepth;
                }
            }
        }
 
        #endregion
 
        #region Core Emit Ops
 
        public void Emit(Instruction instruction)
        {
            _instructions.Add(instruction);
            UpdateStackDepth(instruction);
        }
 
        private void UpdateStackDepth(Instruction instruction)
        {
            Debug.Assert(instruction.ConsumedStack >= 0 && instruction.ProducedStack >= 0 &&
                instruction.ConsumedContinuations >= 0 && instruction.ProducedContinuations >= 0, "bad instruction " + instruction.ToString());
 
            _currentStackDepth -= instruction.ConsumedStack;
            Debug.Assert(_currentStackDepth >= 0, $"negative stack depth {instruction}");
            _currentStackDepth += instruction.ProducedStack;
            if (_currentStackDepth > _maxStackDepth)
            {
                _maxStackDepth = _currentStackDepth;
            }
 
            _currentContinuationsDepth -= instruction.ConsumedContinuations;
            Debug.Assert(_currentContinuationsDepth >= 0, $"negative continuations {instruction}");
            _currentContinuationsDepth += instruction.ProducedContinuations;
            if (_currentContinuationsDepth > _maxContinuationDepth)
            {
                _maxContinuationDepth = _currentContinuationsDepth;
            }
        }
 
        // "Un-emit" the previous instruction.
        // Useful if the instruction was emitted in the calling method, and covers the more usual case.
        // In particular, calling this after an EmitPush() or EmitDup() costs about the same as adding
        // an EmitPop() to undo it at compile time, and leaves a slightly leaner instruction list.
        public void UnEmit()
        {
            Instruction instruction = _instructions[_instructions.Count - 1];
            _instructions.RemoveAt(_instructions.Count - 1);
 
            _currentContinuationsDepth -= instruction.ProducedContinuations;
            _currentContinuationsDepth += instruction.ConsumedContinuations;
            _currentStackDepth -= instruction.ProducedStack;
            _currentStackDepth += instruction.ConsumedStack;
        }
 
#pragma warning disable CA1822
        /// <summary>
        /// Attaches a cookie to the last emitted instruction.
        /// </summary>
        [Conditional("DEBUG")]
        public void SetDebugCookie(object? cookie)
        {
#if DEBUG
            _debugCookies ??= new List<KeyValuePair<int, object?>>();
 
            Debug.Assert(Count > 0);
            _debugCookies.Add(new KeyValuePair<int, object?>(Count - 1, cookie));
#endif
        }
#pragma warning restore CA1822
 
        public int Count => _instructions.Count;
        public int CurrentStackDepth => _currentStackDepth;
        public int CurrentContinuationsDepth => _currentContinuationsDepth;
        public int MaxStackDepth => _maxStackDepth;
 
        internal Instruction GetInstruction(int index) => _instructions[index];
 
#if STATS
        private static Dictionary<string, int> _executedInstructions = new Dictionary<string, int>();
        private static Dictionary<string, Dictionary<object, bool>> _instances = new Dictionary<string, Dictionary<object, bool>>();
 
        static InstructionList()
        {
            AppDomain.CurrentDomain.ProcessExit += new EventHandler((_, __) =>
            {
                PerfTrack.DumpHistogram(_executedInstructions);
                Console.WriteLine("-- Total executed: {0}", _executedInstructions.Values.Aggregate(0, (sum, value) => sum + value));
                Console.WriteLine("-----");
 
                var referenced = new Dictionary<string, int>();
                int total = 0;
                foreach (var entry in _instances)
                {
                    referenced[entry.Key] = entry.Value.Count;
                    total += entry.Value.Count;
                }
 
                PerfTrack.DumpHistogram(referenced);
                Console.WriteLine("-- Total referenced: {0}", total);
                Console.WriteLine("-----");
            });
        }
#endif
        public InstructionArray ToArray()
        {
#if STATS
            lock (_executedInstructions)
            {
                _instructions.ForEach((instr) =>
                {
                    int value = 0;
                    var name = instr.GetType().Name;
                    _executedInstructions.TryGetValue(name, out value);
                    _executedInstructions[name] = value + 1;
 
                    Dictionary<object, bool> dict;
                    if (!_instances.TryGetValue(name, out dict))
                    {
                        _instances[name] = dict = new Dictionary<object, bool>();
                    }
                    dict[instr] = true;
                });
            }
#endif
            return new InstructionArray(
                _maxStackDepth,
                _maxContinuationDepth,
                _instructions.ToArray(),
                _objects?.ToArray(),
                BuildRuntimeLabels(),
#if DEBUG
                _debugCookies
#else
                null
#endif
            );
        }
 
        #endregion
 
        #region Stack Operations
 
        private const int PushIntMinCachedValue = -100;
        private const int PushIntMaxCachedValue = 100;
        private const int CachedObjectCount = 256;
 
        private static Instruction? s_null;
        private static Instruction? s_true;
        private static Instruction? s_false;
        private static Instruction[]? s_Ints;
        private static Instruction[]? s_loadObjectCached;
 
        public void EmitLoad(object? value)
        {
            EmitLoad(value, type: null);
        }
 
        public void EmitLoad(bool value)
        {
            if (value)
            {
                Emit(s_true ??= new LoadObjectInstruction(Utils.BoxedTrue));
            }
            else
            {
                Emit(s_false ??= new LoadObjectInstruction(Utils.BoxedFalse));
            }
        }
 
        public void EmitLoad(object? value, Type? type)
        {
            if (value == null)
            {
                Emit(s_null ??= new LoadObjectInstruction(null));
                return;
            }
 
            if (type == null || type.IsValueType)
            {
                if (value is bool)
                {
                    EmitLoad((bool)value);
                    return;
                }
 
                if (value is int i)
                {
                    if (i >= PushIntMinCachedValue && i <= PushIntMaxCachedValue)
                    {
                        s_Ints ??= new Instruction[PushIntMaxCachedValue - PushIntMinCachedValue + 1];
                        i -= PushIntMinCachedValue;
                        Emit(s_Ints[i] ??= new LoadObjectInstruction(value));
                        return;
                    }
                }
            }
 
            if (_objects == null)
            {
                _objects = new List<object>();
                s_loadObjectCached ??= new Instruction[CachedObjectCount];
            }
 
            if (_objects.Count < s_loadObjectCached!.Length)
            {
                uint index = (uint)_objects.Count;
                _objects.Add(value);
                Emit(s_loadObjectCached[index] ??= new LoadCachedObjectInstruction(index));
            }
            else
            {
                Emit(new LoadObjectInstruction(value));
            }
        }
 
        public void EmitDup()
        {
            Emit(DupInstruction.Instance);
        }
 
        public void EmitPop()
        {
            Emit(PopInstruction.Instance);
        }
 
        #endregion
 
        #region Locals
 
        internal void SwitchToBoxed(int index, int instructionIndex)
        {
            if (_instructions[instructionIndex] is IBoxableInstruction instruction)
            {
                Instruction? newInstruction = instruction.BoxIfIndexMatches(index);
                if (newInstruction != null)
                {
                    _instructions[instructionIndex] = newInstruction;
                }
            }
        }
 
        private const int LocalInstrCacheSize = 64;
 
        private static Instruction[]? s_loadLocal;
        private static Instruction[]? s_loadLocalBoxed;
        private static Instruction[]? s_loadLocalFromClosure;
        private static Instruction[]? s_loadLocalFromClosureBoxed;
        private static Instruction[]? s_assignLocal;
        private static Instruction[]? s_storeLocal;
        private static Instruction[]? s_assignLocalBoxed;
        private static Instruction[]? s_storeLocalBoxed;
        private static Instruction[]? s_assignLocalToClosure;
 
        public void EmitLoadLocal(int index)
        {
            s_loadLocal ??= new Instruction[LocalInstrCacheSize];
 
            if (index < s_loadLocal.Length)
            {
                Emit(s_loadLocal[index] ??= new LoadLocalInstruction(index));
            }
            else
            {
                Emit(new LoadLocalInstruction(index));
            }
        }
 
        public void EmitLoadLocalBoxed(int index)
        {
            Emit(LoadLocalBoxed(index));
        }
 
        internal static Instruction LoadLocalBoxed(int index)
        {
            s_loadLocalBoxed ??= new Instruction[LocalInstrCacheSize];
 
            if (index < s_loadLocalBoxed.Length)
            {
                return s_loadLocalBoxed[index] ??= new LoadLocalBoxedInstruction(index);
            }
            else
            {
                return new LoadLocalBoxedInstruction(index);
            }
        }
 
        public void EmitLoadLocalFromClosure(int index)
        {
            s_loadLocalFromClosure ??= new Instruction[LocalInstrCacheSize];
 
            if (index < s_loadLocalFromClosure.Length)
            {
                Emit(s_loadLocalFromClosure[index] ??= new LoadLocalFromClosureInstruction(index));
            }
            else
            {
                Emit(new LoadLocalFromClosureInstruction(index));
            }
        }
 
        public void EmitLoadLocalFromClosureBoxed(int index)
        {
            s_loadLocalFromClosureBoxed ??= new Instruction[LocalInstrCacheSize];
 
            if (index < s_loadLocalFromClosureBoxed.Length)
            {
                Emit(s_loadLocalFromClosureBoxed[index] ??= new LoadLocalFromClosureBoxedInstruction(index));
            }
            else
            {
                Emit(new LoadLocalFromClosureBoxedInstruction(index));
            }
        }
 
        public void EmitAssignLocal(int index)
        {
            s_assignLocal ??= new Instruction[LocalInstrCacheSize];
 
            if (index < s_assignLocal.Length)
            {
                Emit(s_assignLocal[index] ??= new AssignLocalInstruction(index));
            }
            else
            {
                Emit(new AssignLocalInstruction(index));
            }
        }
 
        public void EmitStoreLocal(int index)
        {
            s_storeLocal ??= new Instruction[LocalInstrCacheSize];
 
            if (index < s_storeLocal.Length)
            {
                Emit(s_storeLocal[index] ??= new StoreLocalInstruction(index));
            }
            else
            {
                Emit(new StoreLocalInstruction(index));
            }
        }
 
        public void EmitAssignLocalBoxed(int index)
        {
            Emit(AssignLocalBoxed(index));
        }
 
        internal static Instruction AssignLocalBoxed(int index)
        {
            s_assignLocalBoxed ??= new Instruction[LocalInstrCacheSize];
 
            if (index < s_assignLocalBoxed.Length)
            {
                return s_assignLocalBoxed[index] ??= new AssignLocalBoxedInstruction(index);
            }
            else
            {
                return new AssignLocalBoxedInstruction(index);
            }
        }
 
        public void EmitStoreLocalBoxed(int index)
        {
            Emit(StoreLocalBoxed(index));
        }
 
        internal static Instruction StoreLocalBoxed(int index)
        {
            s_storeLocalBoxed ??= new Instruction[LocalInstrCacheSize];
 
            if (index < s_storeLocalBoxed.Length)
            {
                return s_storeLocalBoxed[index] ??= new StoreLocalBoxedInstruction(index);
            }
            else
            {
                return new StoreLocalBoxedInstruction(index);
            }
        }
 
        public void EmitAssignLocalToClosure(int index)
        {
            s_assignLocalToClosure ??= new Instruction[LocalInstrCacheSize];
 
            if (index < s_assignLocalToClosure.Length)
            {
                Emit(s_assignLocalToClosure[index] ??= new AssignLocalToClosureInstruction(index));
            }
            else
            {
                Emit(new AssignLocalToClosureInstruction(index));
            }
        }
 
        public void EmitStoreLocalToClosure(int index)
        {
            EmitAssignLocalToClosure(index);
            EmitPop();
        }
 
        public void EmitInitializeLocal(int index, Type type)
        {
            object? value = ScriptingRuntimeHelpers.GetPrimitiveDefaultValue(type);
            if (value != null)
            {
                Emit(new InitializeLocalInstruction.ImmutableValue(index, value));
            }
            else if (type.IsValueType)
            {
                Emit(new InitializeLocalInstruction.MutableValue(index, type));
            }
            else
            {
                Emit(InitReference(index));
            }
        }
 
        internal void EmitInitializeParameter(int index)
        {
            Emit(Parameter(index));
        }
 
        internal static Instruction Parameter(int index)
        {
            return new InitializeLocalInstruction.Parameter(index);
        }
 
        internal static Instruction ParameterBox(int index)
        {
            return new InitializeLocalInstruction.ParameterBox(index);
        }
 
        internal static Instruction InitReference(int index)
        {
            return new InitializeLocalInstruction.Reference(index);
        }
 
        internal static Instruction InitImmutableRefBox(int index)
        {
            return new InitializeLocalInstruction.ImmutableRefBox(index);
        }
 
        public void EmitNewRuntimeVariables(int count)
        {
            Emit(new RuntimeVariablesInstruction(count));
        }
 
        #endregion
 
        #region Array Operations
 
        public void EmitGetArrayItem()
        {
            Emit(GetArrayItemInstruction.Instance);
        }
 
        public void EmitSetArrayItem()
        {
            Emit(SetArrayItemInstruction.Instance);
        }
 
        [RequiresDynamicCode(Expression.NewArrayRequiresDynamicCode)]
        public void EmitNewArray(Type elementType) => Emit(new NewArrayInstruction(elementType));
 
        [RequiresDynamicCode(Expression.NewArrayRequiresDynamicCode)]
        public void EmitNewArrayBounds(Type elementType, int rank)
        {
            Emit(new NewArrayBoundsInstruction(elementType, rank));
        }
 
        [RequiresDynamicCode(Expression.NewArrayRequiresDynamicCode)]
        public void EmitNewArrayInit(Type elementType, int elementCount)
        {
            Emit(new NewArrayInitInstruction(elementType, elementCount));
        }
 
        #endregion
 
        #region Arithmetic Operations
 
        public void EmitAdd(Type type, bool @checked)
        {
            Emit(@checked ? AddOvfInstruction.Create(type) : AddInstruction.Create(type));
        }
 
        public void EmitSub(Type type, bool @checked)
        {
            Emit(@checked ? SubOvfInstruction.Create(type) : SubInstruction.Create(type));
        }
 
        public void EmitMul(Type type, bool @checked)
        {
            Emit(@checked ? MulOvfInstruction.Create(type) : MulInstruction.Create(type));
        }
 
        public void EmitDiv(Type type)
        {
            Emit(DivInstruction.Create(type));
        }
 
        public void EmitModulo(Type type)
        {
            Emit(ModuloInstruction.Create(type));
        }
 
        #endregion
 
        #region Comparisons
 
        public void EmitExclusiveOr(Type type)
        {
            Emit(ExclusiveOrInstruction.Create(type));
        }
 
        public void EmitAnd(Type type)
        {
            Emit(AndInstruction.Create(type));
        }
 
        public void EmitOr(Type type)
        {
            Emit(OrInstruction.Create(type));
        }
 
        public void EmitLeftShift(Type type)
        {
            Emit(LeftShiftInstruction.Create(type));
        }
 
        public void EmitRightShift(Type type)
        {
            Emit(RightShiftInstruction.Create(type));
        }
 
        public void EmitEqual(Type type, bool liftedToNull = false)
        {
            Emit(EqualInstruction.Create(type, liftedToNull));
        }
 
        public void EmitNotEqual(Type type, bool liftedToNull = false)
        {
            Emit(NotEqualInstruction.Create(type, liftedToNull));
        }
 
        public void EmitLessThan(Type type, bool liftedToNull)
        {
            Emit(LessThanInstruction.Create(type, liftedToNull));
        }
 
        public void EmitLessThanOrEqual(Type type, bool liftedToNull)
        {
            Emit(LessThanOrEqualInstruction.Create(type, liftedToNull));
        }
 
        public void EmitGreaterThan(Type type, bool liftedToNull)
        {
            Emit(GreaterThanInstruction.Create(type, liftedToNull));
        }
 
        public void EmitGreaterThanOrEqual(Type type, bool liftedToNull)
        {
            Emit(GreaterThanOrEqualInstruction.Create(type, liftedToNull));
        }
 
        #endregion
 
        #region Conversions
 
        public void EmitNumericConvertChecked(TypeCode from, TypeCode to, bool isLiftedToNull)
        {
            Emit(new NumericConvertInstruction.Checked(from, to, isLiftedToNull));
        }
 
        public void EmitNumericConvertUnchecked(TypeCode from, TypeCode to, bool isLiftedToNull)
        {
            Emit(new NumericConvertInstruction.Unchecked(from, to, isLiftedToNull));
        }
 
        public void EmitConvertToUnderlying(TypeCode to, bool isLiftedToNull)
        {
            Emit(new NumericConvertInstruction.ToUnderlying(to, isLiftedToNull));
        }
 
        public void EmitCast(Type toType)
        {
            Emit(CastInstruction.Create(toType));
        }
 
        public void EmitCastToEnum(Type toType)
        {
            Emit(new CastToEnumInstruction(toType));
        }
 
        public void EmitCastReferenceToEnum(Type toType)
        {
            Debug.Assert(_instructions[_instructions.Count - 1] == NullCheckInstruction.Instance);
            Emit(new CastReferenceToEnumInstruction(toType));
        }
 
        #endregion
 
        #region Boolean Operators
 
        public void EmitNot(Type type)
        {
            Emit(NotInstruction.Create(type));
        }
 
        #endregion
 
        #region Types
 
        public void EmitDefaultValue(Type type)
        {
            Emit(new DefaultValueInstruction(type));
        }
 
        public void EmitNew(ConstructorInfo constructorInfo, ParameterInfo[] parameters)
        {
            Emit(new NewInstruction(constructorInfo, parameters.Length));
        }
 
        public void EmitByRefNew(ConstructorInfo constructorInfo, ParameterInfo[] parameters, ByRefUpdater[] updaters)
        {
            Emit(new ByRefNewInstruction(constructorInfo, parameters.Length, updaters));
        }
 
        internal void EmitCreateDelegate(LightDelegateCreator creator)
        {
            Emit(new CreateDelegateInstruction(creator));
        }
 
        public void EmitTypeEquals()
        {
            Emit(TypeEqualsInstruction.Instance);
        }
 
        public void EmitArrayLength()
        {
            Emit(ArrayLengthInstruction.Instance);
        }
 
        public void EmitNegate(Type type)
        {
            Emit(NegateInstruction.Create(type));
        }
 
        public void EmitNegateChecked(Type type)
        {
            Emit(NegateCheckedInstruction.Create(type));
        }
 
        public void EmitIncrement(Type type)
        {
            Emit(IncrementInstruction.Create(type));
        }
 
        public void EmitDecrement(Type type)
        {
            Emit(DecrementInstruction.Create(type));
        }
 
        public void EmitTypeIs(Type type)
        {
            Emit(new TypeIsInstruction(type));
        }
 
        public void EmitTypeAs(Type type)
        {
            Emit(new TypeAsInstruction(type));
        }
 
        #endregion
 
        #region Fields and Methods
 
        private static readonly Dictionary<FieldInfo, Instruction> s_loadFields = new Dictionary<FieldInfo, Instruction>();
 
        public void EmitLoadField(FieldInfo field)
        {
            Emit(GetLoadField(field));
        }
 
        private static Instruction GetLoadField(FieldInfo field)
        {
            lock (s_loadFields)
            {
                if (!s_loadFields.TryGetValue(field, out Instruction? instruction))
                {
                    if (field.IsStatic)
                    {
                        instruction = new LoadStaticFieldInstruction(field);
                    }
                    else
                    {
                        instruction = new LoadFieldInstruction(field);
                    }
                    s_loadFields.Add(field, instruction);
                }
                return instruction;
            }
        }
 
        public void EmitStoreField(FieldInfo field)
        {
            if (field.IsStatic)
            {
                Emit(new StoreStaticFieldInstruction(field));
            }
            else
            {
                Emit(new StoreFieldInstruction(field));
            }
        }
 
        public void EmitCall(MethodInfo method)
        {
            EmitCall(method, method.GetParametersCached());
        }
 
        public void EmitCall(MethodInfo method, ParameterInfo[] parameters)
        {
            Emit(CallInstruction.Create(method, parameters));
        }
 
        public void EmitByRefCall(MethodInfo method, ParameterInfo[] parameters, ByRefUpdater[] byrefArgs)
        {
            Emit(new ByRefMethodInfoCallInstruction(method, method.IsStatic ? parameters.Length : parameters.Length + 1, byrefArgs));
        }
 
        public void EmitNullableCall(MethodInfo method, ParameterInfo[] parameters)
        {
            Emit(NullableMethodCallInstruction.Create(method.Name, parameters.Length, method));
        }
 
        #endregion
 
        #region Control Flow
 
        private static readonly RuntimeLabel[] s_emptyRuntimeLabels = new RuntimeLabel[] { new RuntimeLabel(Interpreter.RethrowOnReturn, 0, 0) };
 
        private RuntimeLabel[] BuildRuntimeLabels()
        {
            if (_runtimeLabelCount == 0)
            {
                return s_emptyRuntimeLabels;
            }
 
            var result = new RuntimeLabel[_runtimeLabelCount + 1];
            foreach (BranchLabel label in _labels!)
            {
                if (label.HasRuntimeLabel)
                {
                    result[label.LabelIndex] = label.ToRuntimeLabel();
                }
            }
            // "return and rethrow" label:
            result[result.Length - 1] = new RuntimeLabel(Interpreter.RethrowOnReturn, 0, 0);
            return result;
        }
 
        public BranchLabel MakeLabel()
        {
            _labels ??= new List<BranchLabel>();
 
            var label = new BranchLabel();
            _labels.Add(label);
            return label;
        }
 
        internal void FixupBranch(int branchIndex, int offset)
        {
            _instructions[branchIndex] = ((OffsetInstruction)_instructions[branchIndex]).Fixup(offset);
        }
 
        private int EnsureLabelIndex(BranchLabel label)
        {
            if (label.HasRuntimeLabel)
            {
                return label.LabelIndex;
            }
 
            label.LabelIndex = _runtimeLabelCount;
            _runtimeLabelCount++;
            return label.LabelIndex;
        }
 
        public int MarkRuntimeLabel()
        {
            BranchLabel handlerLabel = MakeLabel();
            MarkLabel(handlerLabel);
            return EnsureLabelIndex(handlerLabel);
        }
 
        public void MarkLabel(BranchLabel label)
        {
            label.Mark(this);
        }
 
        public void EmitGoto(BranchLabel label, bool hasResult, bool hasValue, bool labelTargetGetsValue)
        {
            Emit(GotoInstruction.Create(EnsureLabelIndex(label), hasResult, hasValue, labelTargetGetsValue));
        }
 
        private void EmitBranch(OffsetInstruction instruction, BranchLabel label)
        {
            Emit(instruction);
            label.AddBranch(this, Count - 1);
        }
 
        public void EmitBranch(BranchLabel label)
        {
            EmitBranch(new BranchInstruction(), label);
        }
 
        public void EmitBranch(BranchLabel label, bool hasResult, bool hasValue)
        {
            EmitBranch(new BranchInstruction(hasResult, hasValue), label);
        }
 
        public void EmitCoalescingBranch(BranchLabel leftNotNull)
        {
            EmitBranch(new CoalescingBranchInstruction(), leftNotNull);
        }
 
        public void EmitBranchTrue(BranchLabel elseLabel)
        {
            EmitBranch(new BranchTrueInstruction(), elseLabel);
        }
 
        public void EmitBranchFalse(BranchLabel elseLabel)
        {
            EmitBranch(new BranchFalseInstruction(), elseLabel);
        }
 
        public void EmitThrow()
        {
            Emit(ThrowInstruction.Throw);
        }
 
        public void EmitThrowVoid()
        {
            Emit(ThrowInstruction.VoidThrow);
        }
 
        public void EmitRethrow()
        {
            Emit(ThrowInstruction.Rethrow);
        }
 
        public void EmitRethrowVoid()
        {
            Emit(ThrowInstruction.VoidRethrow);
        }
 
        public void EmitEnterTryFinally(BranchLabel finallyStartLabel)
        {
            Emit(EnterTryCatchFinallyInstruction.CreateTryFinally(EnsureLabelIndex(finallyStartLabel)));
        }
 
        public void EmitEnterTryCatch()
        {
            Emit(EnterTryCatchFinallyInstruction.CreateTryCatch());
        }
 
        public EnterTryFaultInstruction EmitEnterTryFault(BranchLabel tryEnd)
        {
            var instruction = new EnterTryFaultInstruction(EnsureLabelIndex(tryEnd));
            Emit(instruction);
            return instruction;
        }
 
        public void EmitEnterFinally(BranchLabel finallyStartLabel)
        {
            Emit(EnterFinallyInstruction.Create(EnsureLabelIndex(finallyStartLabel)));
        }
 
        public void EmitLeaveFinally()
        {
            Emit(LeaveFinallyInstruction.Instance);
        }
 
        public void EmitEnterFault(BranchLabel faultStartLabel)
        {
            Emit(EnterFaultInstruction.Create(EnsureLabelIndex(faultStartLabel)));
        }
 
        public void EmitLeaveFault()
        {
            Emit(LeaveFaultInstruction.Instance);
        }
 
        public void EmitEnterExceptionFilter()
        {
            Emit(EnterExceptionFilterInstruction.Instance);
        }
 
        public void EmitLeaveExceptionFilter()
        {
            Emit(LeaveExceptionFilterInstruction.Instance);
        }
 
        public void EmitEnterExceptionHandlerNonVoid()
        {
            Emit(EnterExceptionHandlerInstruction.NonVoid);
        }
 
        public void EmitEnterExceptionHandlerVoid()
        {
            Emit(EnterExceptionHandlerInstruction.Void);
        }
 
        public void EmitLeaveExceptionHandler(bool hasValue, BranchLabel tryExpressionEndLabel)
        {
            Emit(LeaveExceptionHandlerInstruction.Create(EnsureLabelIndex(tryExpressionEndLabel), hasValue));
        }
 
        public void EmitIntSwitch<T>(Dictionary<T, int> cases) where T : notnull
        {
            Emit(new IntSwitchInstruction<T>(cases));
        }
 
        public void EmitStringSwitch(Dictionary<string, int> cases, StrongBox<int> nullCase)
        {
            Emit(new StringSwitchInstruction(cases, nullCase));
        }
 
        #endregion
    }
}