File: Compiler\DependencyAnalysis\MethodCodeNode.cs
Web Access
Project: src\src\runtime\src\coreclr\tools\aot\ILCompiler.RyuJit\ILCompiler.RyuJit.csproj (ILCompiler.RyuJit)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;

using Internal.IL;
using Internal.Text;
using Internal.TypeSystem;

using CombinedDependencyList = System.Collections.Generic.List<ILCompiler.DependencyAnalysisFramework.DependencyNodeCore<ILCompiler.DependencyAnalysis.NodeFactory>.CombinedDependencyListEntry>;

namespace ILCompiler.DependencyAnalysis
{
    [DebuggerTypeProxy(typeof(MethodCodeNodeDebugView))]
    public class MethodCodeNode : ObjectNode, IMethodBodyNode, INodeWithCodeInfo, INodeWithDebugInfo, ISpecialUnboxThunkNode, IMethodCodeNodeWithTypeSignature
    {
        private MethodDesc _method;
        private ObjectData _methodCode;
        private FrameInfo[] _frameInfos;
        private byte[] _gcInfo;
        private MethodExceptionHandlingInfoNode _ehInfo;
        private DebugLocInfo[] _debugLocInfos;
        private DebugVarInfo[] _debugVarInfos;
        private DebugEHClauseInfo[] _debugEHClauseInfos;
        private DependencyList _nonRelocationDependencies;
        private MethodDebugInformation _debugInfo;
        private TypeDesc[] _localTypes;

        public MethodCodeNode(MethodDesc method)
        {
            Debug.Assert(!method.IsAbstract);
            Debug.Assert(!method.IsGenericMethodDefinition && !method.OwningType.IsGenericDefinition);
            Debug.Assert(method.GetCanonMethodTarget(CanonicalFormKind.Specific) == method);
            _method = method;
        }

        public void SetCode(ObjectData data)
        {
            Debug.Assert(_methodCode == null);
            _methodCode = data;
        }

        public MethodDesc Method =>  _method;

        protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler);

        public override ObjectNodeSection GetSection(NodeFactory factory)
        {
            return factory.Target.IsWindows ?
                ObjectNodeSection.ManagedCodeWindowsContentSection : ObjectNodeSection.ManagedCodeUnixContentSection;
        }

        public override bool StaticDependenciesAreComputed => _methodCode != null;

        public override bool InterestingForDynamicDependencyAnalysis => _method.HasInstantiation || _method.OwningType.HasInstantiation;

        public virtual void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
        {
            sb.Append(nameMangler.GetMangledMethodName(_method));
        }
        public int Offset => 0;
        public override bool IsShareable => _method is InstantiatedMethod || EETypeNode.IsTypeNodeShareable(_method.OwningType);

        public override bool HasConditionalStaticDependencies => CodeBasedDependencyAlgorithm.HasConditionalDependenciesDueToMethodCodePresence(_method);

        public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory factory)
        {
            CombinedDependencyList dependencies = null;
            CodeBasedDependencyAlgorithm.AddConditionalDependenciesDueToMethodCodePresence(ref dependencies, factory, _method);
            return dependencies ?? (IEnumerable<CombinedDependencyListEntry>)Array.Empty<CombinedDependencyListEntry>();
        }

        protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory)
        {
            DependencyList dependencies = _nonRelocationDependencies != null ? new DependencyList(_nonRelocationDependencies) : null;

            TypeDesc owningType = _method.OwningType;
            if (factory.PreinitializationManager.HasEagerStaticConstructor(owningType))
            {
                dependencies ??= new DependencyList();
                dependencies.Add(factory.EagerCctorIndirection(owningType.GetStaticConstructor()), "Eager .cctor");
            }

            if (_ehInfo != null)
            {
                dependencies ??= new DependencyList();
                dependencies.Add(_ehInfo, "Exception handling information");
            }

            if (MethodAssociatedDataNode.MethodHasAssociatedData(this))
            {
                dependencies ??= new DependencyList();
                dependencies.Add(new DependencyListEntry(factory.MethodAssociatedData(this), "Method associated data"));
            }

            return dependencies;
        }

        public override ObjectData GetData(NodeFactory factory, bool relocsOnly)
        {
            return _methodCode;
        }

        public bool IsSpecialUnboxingThunk => ((CompilerTypeSystemContext)Method.Context).IsSpecialUnboxingThunk(_method);

        public ISymbolNode GetUnboxingThunkTarget(NodeFactory factory)
        {
            Debug.Assert(IsSpecialUnboxingThunk);

            MethodDesc nonUnboxingMethod = ((CompilerTypeSystemContext)Method.Context).GetTargetOfSpecialUnboxingThunk(_method);
            return factory.MethodEntrypoint(nonUnboxingMethod, false);
        }

        public FrameInfo[] FrameInfos => _frameInfos;
        public byte[] GCInfo => _gcInfo;
        public MethodExceptionHandlingInfoNode EHInfo => _ehInfo;

        public ISymbolNode GetAssociatedDataNode(NodeFactory factory)
        {
            if (MethodAssociatedDataNode.MethodHasAssociatedData(this))
                return factory.MethodAssociatedData(this);

            return null;
        }

        public void InitializeFrameInfos(FrameInfo[] frameInfos)
        {
            Debug.Assert(_frameInfos == null);
            _frameInfos = frameInfos;
        }

        public void InitializeGCInfo(byte[] gcInfo)
        {
            Debug.Assert(_gcInfo == null);
            _gcInfo = gcInfo;
        }

        public void InitializeEHInfo(ObjectData ehInfo)
        {
            Debug.Assert(_ehInfo == null);
            if (ehInfo != null)
                _ehInfo = new MethodExceptionHandlingInfoNode(_method, ehInfo);
        }

        public DebugLocInfo[] DebugLocInfos => _debugLocInfos;
        public DebugVarInfo[] DebugVarInfos => _debugVarInfos;
        public DebugEHClauseInfo[] DebugEHClauseInfos => _debugEHClauseInfos;

        public bool IsStateMachineMoveNextMethod => _debugInfo.IsStateMachineMoveNextMethod;

        public void InitializeDebugLocInfos(DebugLocInfo[] debugLocInfos)
        {
            Debug.Assert(_debugLocInfos == null);
            _debugLocInfos = debugLocInfos;
        }

        public void InitializeDebugVarInfos(DebugVarInfo[] debugVarInfos)
        {
            Debug.Assert(_debugVarInfos == null);
            _debugVarInfos = debugVarInfos;
        }

        public void InitializeDebugInfo(MethodDebugInformation debugInfo)
        {
            Debug.Assert(_debugInfo == null);
            _debugInfo = debugInfo;
        }

        public void InitializeLocalTypes(TypeDesc[] localTypes)
        {
            Debug.Assert(_localTypes == null);
            _localTypes = localTypes;
        }

        public void InitializeDebugEHClauseInfos(DebugEHClauseInfo[] debugEHClauseInfos)
        {
            Debug.Assert(_debugEHClauseInfos == null);
            _debugEHClauseInfos = debugEHClauseInfos;
        }

        public IEnumerable<DebugVarInfoMetadata> GetDebugVars()
        {
            MethodSignature sig = _method.Signature;
            int offset = sig.IsStatic ? 0 : 1;

            var parameterNames = new string[sig.Length + offset];
            int i = 0;
            foreach (var paramName in _debugInfo.GetParameterNames())
            {
                parameterNames[i] = paramName;
                i++;
            }

            string[] localNames;
            if (_localTypes.Length > 0)
            {
                localNames = new string[_localTypes.Length];
                var localVariables = _debugInfo.GetLocalVariables();
                if (localVariables != null)
                {
                    foreach (var local in localVariables)
                    {
                        if (!local.CompilerGenerated && local.Slot < localNames.Length)
                            localNames[local.Slot] = local.Name;
                    }
                }
            }
            else
            {
                localNames = Array.Empty<string>();
            }

            foreach (var varInfo in _debugVarInfos)
            {
                if (varInfo.VarNumber < parameterNames.Length)
                {
                    // This is a parameter
                    TypeDesc varType;
                    if (!sig.IsStatic && varInfo.VarNumber == 0)
                    {
                        varType = _method.OwningType.IsValueType ?
                            _method.OwningType.MakeByRefType() :
                            _method.OwningType;
                    }
                    else
                    {
                        varType = _method.Signature[(int)varInfo.VarNumber - offset];
                    }

                    string name = parameterNames[varInfo.VarNumber];
                    if (name == null)
                        continue;

                    yield return new DebugVarInfoMetadata(name, varType, isParameter: true, varInfo);
                }
                else
                {
                    // This is a local
                    int localNumber = (int)varInfo.VarNumber - sig.Length - offset;
                    string name = localNames[localNumber];
                    if (name == null)
                        continue;

                    yield return new DebugVarInfoMetadata(name, _localTypes[localNumber], isParameter: false, varInfo);
                }
            }
        }

        public void InitializeNonRelocationDependencies(DependencyList dependencies)
        {
            _nonRelocationDependencies = dependencies;
        }

        public IEnumerable<NativeSequencePoint> GetNativeSequencePoints()
        {
            // This can be null if we're not generating debug info
            if (_debugLocInfos == null)
                yield break;

            var sequencePoints = new (string Document, int LineNumber)[_debugLocInfos.Length * 4 /* chosen empirically */];
            try
            {
                foreach (var sequencePoint in _debugInfo.GetSequencePoints())
                {
                    int offset = sequencePoint.Offset;
                    if (offset >= sequencePoints.Length)
                    {
                        int newLength = Math.Max(2 * sequencePoints.Length, sequencePoint.Offset + 1);
                        Array.Resize(ref sequencePoints, newLength);
                    }
                    sequencePoints[offset] = (sequencePoint.Document, sequencePoint.LineNumber);
                }

                // Propagate last known document/line number forward to enable correct mapping when IL offsets decrease at higher native offsets
                for (int i = 1; i < sequencePoints.Length; i++)
                {
                    if (sequencePoints[i].Document == null && sequencePoints[i - 1].Document != null)
                    {
                        sequencePoints[i] = (sequencePoints[i - 1].Document, sequencePoints[i - 1].LineNumber);
                    }
                }
            }
            catch (BadImageFormatException)
            {
                // Roslyn had a bug where it was generating bad sequence points:
                // https://github.com/dotnet/roslyn/issues/20118
                // Do not crash the compiler.
                yield break;
            }

            int previousNativeOffset = -1;
            string previousDocument = null;
            int previousLineNumber = -1;
            // OffsetMapping is sorted in order of increasing native offset (but not necessarily by IL offset)
            foreach (var nativeMapping in _debugLocInfos)
            {
                if (nativeMapping.NativeOffset == previousNativeOffset)
                    continue;

                var sequencePoint = sequencePoints[Math.Min(nativeMapping.ILOffset, sequencePoints.Length - 1)];
                // Emit sequence point if its line number or document differ from the previous one.
                // See WalkILOffsetsCallback in src/coreclr/vm/debugdebugger.cpp for more details.
                if (sequencePoint.Document != null && (sequencePoint.Document != previousDocument || sequencePoint.LineNumber != previousLineNumber))
                {
                    yield return new NativeSequencePoint(
                        nativeMapping.NativeOffset,
                        sequencePoint.Document,
                        sequencePoint.LineNumber);
                    previousNativeOffset = nativeMapping.NativeOffset;
                    previousDocument = sequencePoint.Document;
                    previousLineNumber = sequencePoint.LineNumber;
                }
            }
        }

        public override int ClassCode => 788492407;

        public override int CompareToImpl(ISortableNode other, CompilerComparer comparer)
        {
            return comparer.Compare(_method, ((MethodCodeNode)other)._method);
        }

        public override string ToString()
        {
            return _method.ToString();
        }

        internal sealed class MethodCodeNodeDebugView
        {
            private readonly MethodCodeNode _node;

            public MethodCodeNodeDebugView(MethodCodeNode node)
            {
                _node = node;
            }

            public MethodDesc Method => _node.Method;

            public string Disassembly
            {
                get
                {
                    var sb = new StringBuilder();
                    sb.Append("// ");
                    sb.AppendLine(_node.Method.ToString());
                    if (_node.StaticDependenciesAreComputed)
                    {
                        var d = Disassembler.Disassemble(
                            _node.Method.Context.Target.Architecture,
                            _node._methodCode.Data,
                            _node._methodCode.Relocs);
                        sb.Append(d);
                    }
                    else
                    {
                        sb.Append("// Not compiled yet.");
                    }

                    return sb.ToString();
                }
            }
        }
    }

    public readonly struct DebugLocInfo
    {
        public readonly int NativeOffset;
        public readonly int ILOffset;

        public DebugLocInfo(int nativeOffset, int ilOffset)
            => (NativeOffset, ILOffset) = (nativeOffset, ilOffset);
    }
}