File: Compiler\DependencyAnalysis\ReadyToRun\MethodGCInfoNode.cs
Web Access
Project: src\src\runtime\src\coreclr\tools\aot\ILCompiler.ReadyToRun\ILCompiler.ReadyToRun.csproj (ILCompiler.ReadyToRun)
// 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.Linq;
using Internal.Text;
using Internal.TypeSystem;

namespace ILCompiler.DependencyAnalysis.ReadyToRun
{
    public class MethodGCInfoNode : EmbeddedObjectNode
    {
        private readonly MethodWithGCInfo _methodNode;

        public MethodGCInfoNode(MethodWithGCInfo methodNode)
        {
            _methodNode = methodNode;
        }

        public override bool StaticDependenciesAreComputed => true;

        public override int ClassCode => 892356612;

        public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
        {
            sb.Append("MethodGCInfoNode->"u8);
            _methodNode.AppendMangledName(nameMangler, sb);
        }

        protected override void OnMarked(NodeFactory factory)
        {
            if (factory.RuntimeFunctionsGCInfo.Deduplicator == null)
            {
                factory.RuntimeFunctionsGCInfo.Deduplicator = new HashSet<MethodGCInfoNode>(new MethodGCInfoNodeDeduplicatingComparer(factory));
            }
            factory.RuntimeFunctionsGCInfo.AddEmbeddedObject(this);
        }

        public int[] CalculateFuncletOffsets(NodeFactory factory)
        {
            int coldCodeUnwindInfoCount = 0;
            if (_methodNode.ColdCodeNode != null)
            {
                coldCodeUnwindInfoCount = _methodNode.ColdFrameInfos.Length;
            }

            int[] offsets = new int[_methodNode.FrameInfos.Length + coldCodeUnwindInfoCount];
            if (!factory.RuntimeFunctionsGCInfo.Deduplicator.TryGetValue(this, out var deduplicatedResult))
            {
                throw new Exception("Did not properly initialize deduplicator");
            }

            int offset = deduplicatedResult.OffsetFromBeginningOfArray;
            for (int frameInfoIndex = 0; frameInfoIndex < deduplicatedResult._methodNode.FrameInfos.Length; frameInfoIndex++)
            {
                offsets[frameInfoIndex] = offset;
                offset += deduplicatedResult._methodNode.FrameInfos[frameInfoIndex].BlobData.Length;
                offset += (-offset & 3); // 4-alignment for the personality routine
                if (factory.Target.Architecture != TargetArchitecture.X86)
                {
                    offset += sizeof(uint); // personality routine
                }
                if (frameInfoIndex == 0 && deduplicatedResult._methodNode.GCInfo != null)
                {
                    offset += deduplicatedResult._methodNode.GCInfo.Length;
                    offset += (-offset & 3); // 4-alignment after GC info in 1st funclet
                }
            }

            if (_methodNode.ColdCodeNode != null)
            {
                // TODO: Take a look at deduplicatedResult
                for (int frameInfoIndex = 0; frameInfoIndex < _methodNode.ColdFrameInfos.Length; frameInfoIndex++)
                {
                    offsets[frameInfoIndex + _methodNode.FrameInfos.Length] = offset;
                    byte[] blobData = _methodNode.ColdFrameInfos[frameInfoIndex].BlobData;
                    if (blobData != null)
                    {
                        offset += blobData.Length;
                        offset += (-offset & 3); // 4-alignment for the personality routine
                        if (factory.Target.Architecture != TargetArchitecture.X86)
                        {
                            offset += sizeof(uint); // personality routine
                        }
                    }
                    else
                    {
                        // TODO: Update/Verify this amount
                        offset += 16;
                    }
                }
            }

            return offsets;
        }

        struct GCInfoComponent : IEquatable<GCInfoComponent>
        {
            public GCInfoComponent(byte[] bytes)
            {
                Debug.Assert(bytes != null);
                Bytes = bytes;
                Symbol = null;
                SymbolDelta = 0;
            }

            public GCInfoComponent(ISymbolNode symbol, int symbolDelta)
            {
                Debug.Assert(symbol != null);
                Bytes = null;
                Symbol = symbol;
                SymbolDelta = symbolDelta;
            }

            public readonly byte[] Bytes;
            public readonly ISymbolNode Symbol;
            public readonly int SymbolDelta;

            public override int GetHashCode()
            {
                HashCode hashCode = new HashCode();
                if (Bytes != null)
                {
                    foreach (byte b in Bytes)
                        hashCode.Add(b);
                }
                else
                {
                    hashCode.Add(Symbol);
                    hashCode.Add(SymbolDelta);
                }
                return hashCode.ToHashCode();
            }

            public override bool Equals(object obj)
            {
                return obj is GCInfoComponent other && other.Equals(this);
            }

            public bool Equals(GCInfoComponent other)
            {
                if (Bytes != null)
                {
                    if (other.Bytes == null)
                        return false;
                    
                    return Bytes.SequenceEqual(other.Bytes);
                }
                else
                {
                    return Symbol == other.Symbol && SymbolDelta == other.SymbolDelta;
                }
            }
        }

        private IEnumerable<GCInfoComponent> EncodeDataCore(NodeFactory factory)
        {
            TargetArchitecture targetArch = factory.Target.Architecture;

            int numHotFrameInfos = _methodNode.FrameInfos.Length;
            int numFrameInfos = numHotFrameInfos;
            if (_methodNode.ColdCodeNode != null)
            {
                numFrameInfos += _methodNode.ColdFrameInfos.Length;
            }

            const byte UNW_FLAG_EHANDLER = 1;
            const byte UNW_FLAG_UHANDLER = 2;
            const byte UNW_FLAG_CHAININFO = 4;
            const byte FlagsShift = 3;
            
            for (int frameInfoIndex = 0; frameInfoIndex < numFrameInfos; frameInfoIndex++)
            {
                FrameInfo frameInfo = (frameInfoIndex >= numHotFrameInfos) ?
                    _methodNode.ColdFrameInfos[frameInfoIndex - numHotFrameInfos] :
                    _methodNode.FrameInfos[frameInfoIndex];
                byte[] unwindInfo = frameInfo.BlobData;
                if (unwindInfo == null)
                {
                    // Chain unwind info
                    byte[] header = new byte[4];
                    int i = 0;
                    header[i++] = 1 + (UNW_FLAG_CHAININFO << FlagsShift); // Version = 1, UNW_FLAG_CHAININFO
                    header[i++] = 0; // SizeOfProlog = 0
                    header[i++] = 0; // CountOfCode = 0
                    header[i++] = _methodNode.FrameInfos[0].BlobData[3]; // Copying frame and frame offset from main function
                    yield return new GCInfoComponent(header);
                    yield return new GCInfoComponent(_methodNode, 0);
                    yield return new GCInfoComponent(_methodNode, _methodNode.Size);
                    // TODO: Is this correct? 
                    yield return new GCInfoComponent(factory.RuntimeFunctionsGCInfo, this.OffsetFromBeginningOfArray);
                }
                else
                {
                    if (targetArch == TargetArchitecture.X64)
                    {
                        // On Amd64, patch the first byte of the unwind info by setting the flags to EHANDLER | UHANDLER
                        // as that's what CoreCLR does (zapcode.cpp, ZapUnwindData::Save).
                        unwindInfo[0] |= (byte)((UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER) << FlagsShift);
                    }
                    else if ((targetArch == TargetArchitecture.ARM) || (targetArch == TargetArchitecture.ARM64) || (targetArch == TargetArchitecture.LoongArch64) || (targetArch == TargetArchitecture.RiscV64))
                    {
                        // Set the 'X' bit to indicate that there is a personality routine associated with this method
                        unwindInfo[2] |= 1 << 4;
                    }

                    yield return new GCInfoComponent(unwindInfo);

                    if ((targetArch != TargetArchitecture.X86) && (targetArch != TargetArchitecture.Wasm32))
                    {
                        bool isFilterFunclet = (frameInfo.Flags & FrameInfoFlags.Filter) != 0;
                        ISymbolNode personalityRoutine = (isFilterFunclet ? factory.FilterFuncletPersonalityRoutine : factory.PersonalityRoutine);
                        yield return new GCInfoComponent(personalityRoutine, factory.Target.CodeDelta);
                    }

                    if (frameInfoIndex == 0 && _methodNode.GCInfo != null)
                    {
                        yield return new GCInfoComponent(_methodNode.GCInfo);
                    }
                }
            }
        }

        class MethodGCInfoNodeDeduplicatingComparer : IEqualityComparer<MethodGCInfoNode>
        {
            public MethodGCInfoNodeDeduplicatingComparer(NodeFactory factory)
            {
                _factory = factory;
            }

            NodeFactory _factory;
            public bool Equals(MethodGCInfoNode a, MethodGCInfoNode b)
            {
                return a.EncodeDataCore(_factory).SequenceEqual(b.EncodeDataCore(_factory));
            }
            public int GetHashCode(MethodGCInfoNode node)
            {
                HashCode hashcode = new HashCode();
                foreach (var item in node.EncodeDataCore(_factory))
                {
                    hashcode.Add(item);
                }
                return hashcode.ToHashCode();
            }
        }

        public override void EncodeData(ref ObjectDataBuilder dataBuilder, NodeFactory factory, bool relocsOnly)
        {
            if (relocsOnly)
            {
                return;
            }

            bool isFound = factory.RuntimeFunctionsGCInfo.Deduplicator.TryGetValue(this, out var found);

            if (isFound && (found != this))
            {
                return;
            }

            factory.RuntimeFunctionsGCInfo.Deduplicator.Add(this);

            foreach (var item in EncodeDataCore(factory))
            {
                if (item.Bytes != null)
                {
                    dataBuilder.EmitBytes(item.Bytes);
                    // Maintain 4-alignment for the next unwind / GC info block
                    int align4Pad = -item.Bytes.Length & 3;
                    dataBuilder.EmitZeros(align4Pad);
                }
                else
                {
                    dataBuilder.EmitReloc(item.Symbol, RelocType.IMAGE_REL_BASED_ADDR32NB, item.SymbolDelta);
                }
            }
        }

        protected override string GetName(NodeFactory context)
        {
            Utf8StringBuilder sb = new Utf8StringBuilder();
            sb.Append("MethodGCInfo->"u8);
            _methodNode.AppendMangledName(context.NameMangler, sb);
            return sb.ToString();
        }

        public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory context) => null;

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