File: System\Linq\Expressions\Interpreter\InterpretedFrame.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.
 
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
 
namespace System.Linq.Expressions.Interpreter
{
    internal sealed class InterpretedFrame
    {
        [ThreadStatic]
        private static InterpretedFrame? s_currentFrame;
 
        internal readonly Interpreter Interpreter;
        internal InterpretedFrame? _parent;
 
        private readonly int[]? _continuations;
        private int _continuationIndex;
        private int _pendingContinuation;
        private object? _pendingValue;
 
        public readonly object?[] Data;
 
        public readonly IStrongBox[]? Closure;
 
        public int StackIndex;
        public int InstructionIndex;
 
#if FEATURE_THREAD_ABORT
        // When a ThreadAbortException is raised from interpreted code this is the first frame that caught it.
        // No handlers within this handler re-abort the current thread when left.
        public ExceptionHandler CurrentAbortHandler;
#endif
 
        internal InterpretedFrame(Interpreter interpreter, IStrongBox[]? closure)
        {
            Interpreter = interpreter;
            StackIndex = interpreter.LocalCount;
            Data = new object[StackIndex + interpreter.Instructions.MaxStackDepth];
 
            int c = interpreter.Instructions.MaxContinuationDepth;
            if (c > 0)
            {
                _continuations = new int[c];
            }
 
            Closure = closure;
 
            _pendingContinuation = -1;
            _pendingValue = Interpreter.NoValue;
        }
 
        public DebugInfo? GetDebugInfo(int instructionIndex)
        {
            return DebugInfo.GetMatchingDebugInfo(Interpreter._debugInfos, instructionIndex);
        }
 
        public string? Name => Interpreter.Name;
 
        #region Data Stack Operations
 
        public void Push(object? value)
        {
            Data[StackIndex++] = value;
        }
 
        public void Push(bool value)
        {
            Data[StackIndex++] = value ? Utils.BoxedTrue : Utils.BoxedFalse;
        }
 
        public void Push(int value)
        {
            Data[StackIndex++] = ScriptingRuntimeHelpers.Int32ToObject(value);
        }
 
        public void Push(byte value)
        {
            Data[StackIndex++] = value;
        }
 
        public void Push(sbyte value)
        {
            Data[StackIndex++] = value;
        }
 
        public void Push(short value)
        {
            Data[StackIndex++] = value;
        }
 
        public void Push(ushort value)
        {
            Data[StackIndex++] = value;
        }
 
        public object? Pop()
        {
            return Data[--StackIndex];
        }
 
        internal void SetStackDepth(int depth)
        {
            StackIndex = Interpreter.LocalCount + depth;
        }
 
        public object? Peek()
        {
            return Data[StackIndex - 1];
        }
 
        public void Dup()
        {
            int i = StackIndex;
            Data[i] = Data[i - 1];
            StackIndex = i + 1;
        }
 
        #endregion
 
        #region Stack Trace
 
        public InterpretedFrame? Parent => _parent;
 
        public static bool IsInterpretedFrame(MethodBase method)
        {
            //ArgumentNullException.ThrowIfNull(method);
            return method.DeclaringType == typeof(Interpreter) && method.Name == "Run";
        }
 
        public IEnumerable<InterpretedFrameInfo> GetStackTraceDebugInfo()
        {
            InterpretedFrame? frame = this;
            do
            {
                yield return new InterpretedFrameInfo(frame.Name, frame.GetDebugInfo(frame.InstructionIndex));
                frame = frame.Parent;
            } while (frame != null);
        }
 
        internal void SaveTraceToException(Exception exception)
        {
            exception.Data[typeof(InterpretedFrameInfo)] ??= new List<InterpretedFrameInfo>(GetStackTraceDebugInfo()).ToArray();
        }
 
        public static InterpretedFrameInfo[]? GetExceptionStackTrace(Exception exception)
        {
            return exception.Data[typeof(InterpretedFrameInfo)] as InterpretedFrameInfo[];
        }
 
#if DEBUG
        internal string[] Trace
        {
            get
            {
                var trace = new List<string>();
                InterpretedFrame? frame = this;
                do
                {
                    trace.Add(frame.Name!);
                    frame = frame.Parent;
                } while (frame != null);
                return trace.ToArray();
            }
        }
#endif
 
        internal InterpretedFrame? Enter()
        {
            InterpretedFrame? currentFrame = s_currentFrame;
            s_currentFrame = this;
            return _parent = currentFrame;
        }
 
        internal static void Leave(InterpretedFrame? prevFrame)
        {
            s_currentFrame = prevFrame;
        }
 
        #endregion
 
        #region Continuations
 
        internal bool IsJumpHappened()
        {
            return _pendingContinuation >= 0;
        }
 
        public void RemoveContinuation()
        {
            _continuationIndex--;
        }
 
        public void PushContinuation(int continuation)
        {
            _continuations![_continuationIndex++] = continuation;
        }
 
        public int YieldToCurrentContinuation()
        {
            RuntimeLabel target = Interpreter._labels[_continuations![_continuationIndex - 1]];
            SetStackDepth(target.StackDepth);
            return target.Index - InstructionIndex;
        }
 
        /// <summary>
        /// Get called from the LeaveFinallyInstruction
        /// </summary>
        public int YieldToPendingContinuation()
        {
            Debug.Assert(_pendingContinuation >= 0);
            RuntimeLabel pendingTarget = Interpreter._labels[_pendingContinuation];
 
            // the current continuation might have higher priority (continuationIndex is the depth of the current continuation):
            if (pendingTarget.ContinuationStackDepth < _continuationIndex)
            {
                RuntimeLabel currentTarget = Interpreter._labels[_continuations![_continuationIndex - 1]];
                SetStackDepth(currentTarget.StackDepth);
                return currentTarget.Index - InstructionIndex;
            }
 
            SetStackDepth(pendingTarget.StackDepth);
            if (_pendingValue != Interpreter.NoValue)
            {
                Data[StackIndex - 1] = _pendingValue;
            }
 
            // Set the _pendingContinuation and _pendingValue to the default values if we finally gets to the Goto target
            _pendingContinuation = -1;
            _pendingValue = Interpreter.NoValue;
            return pendingTarget.Index - InstructionIndex;
        }
 
        internal void PushPendingContinuation()
        {
            Push(_pendingContinuation);
            Push(_pendingValue);
 
            _pendingContinuation = -1;
            _pendingValue = Interpreter.NoValue;
        }
 
        internal void PopPendingContinuation()
        {
            _pendingValue = Pop();
            _pendingContinuation = (int)Pop()!;
        }
 
        public int Goto(int labelIndex, object? value, bool gotoExceptionHandler)
        {
            // TODO: we know this at compile time (except for compiled loop):
            RuntimeLabel target = Interpreter._labels[labelIndex];
            Debug.Assert(!gotoExceptionHandler || (gotoExceptionHandler && _continuationIndex == target.ContinuationStackDepth),
                "When it's time to jump to the exception handler, all previous finally blocks should already be processed");
 
            if (_continuationIndex == target.ContinuationStackDepth)
            {
                SetStackDepth(target.StackDepth);
                if (value != Interpreter.NoValue)
                {
                    Data[StackIndex - 1] = value;
                }
                return target.Index - InstructionIndex;
            }
 
            // if we are in the middle of executing jump we forget the previous target and replace it by a new one:
            _pendingContinuation = labelIndex;
            _pendingValue = value;
            return YieldToCurrentContinuation();
        }
 
        #endregion
    }
}