File: Compiler\ObjectWriter\UnixObjectWriter.Aot.cs
Web Access
Project: src\src\runtime\src\coreclr\tools\aot\ILCompiler.Compiler\ILCompiler.Compiler.csproj (ILCompiler.Compiler)
// 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.IO;
using System.Linq;
using System.Buffers.Binary;
using ILCompiler.DependencyAnalysis;
using Internal.Text;
using Internal.TypeSystem;
using Internal.TypeSystem.TypesDebugInfo;

using Debug = System.Diagnostics.Debug;

namespace ILCompiler.ObjectWriter
{
    /// <summary>
    /// Base implementation for ELF and Mach-O object file format writers. Implements
    /// the common code for DWARF debugging and exception handling information.
    /// </summary>
    internal abstract partial class UnixObjectWriter : ObjectWriter
    {
        // Exception handling sections
        private SectionWriter _lsdaSectionWriter;
        private int _ehFrameSectionIndex;
        private DwarfCie _dwarfCie;
        private DwarfEhFrame _dwarfEhFrame;

        protected int EhFrameSectionIndex => _ehFrameSectionIndex;

        // Debugging
        private DwarfBuilder _dwarfBuilder;

        private static readonly ObjectNodeSection DebugInfoSection = new ObjectNodeSection(".debug_info", SectionType.Debug);
        private static readonly ObjectNodeSection DebugStringSection = new ObjectNodeSection(".debug_str", SectionType.Debug);
        private static readonly ObjectNodeSection DebugAbbrevSection = new ObjectNodeSection(".debug_abbrev", SectionType.Debug);
        private static readonly ObjectNodeSection DebugLocSection = new ObjectNodeSection(".debug_loc", SectionType.Debug);
        private static readonly ObjectNodeSection DebugRangesSection = new ObjectNodeSection(".debug_ranges", SectionType.Debug);
        private static readonly ObjectNodeSection DebugLineSection = new ObjectNodeSection(".debug_line", SectionType.Debug);
        private static readonly ObjectNodeSection DebugARangesSection = new ObjectNodeSection(".debug_aranges", SectionType.Debug);

        private protected virtual bool UseFrameNames => false;

        private protected virtual bool EmitCompactUnwinding(Utf8String startSymbolName, ulong length, Utf8String lsdaSymbolName, byte[] blob) => false;

        private protected override void CreateEhSections()
        {
            SectionWriter ehFrameSectionWriter;

            // Create sections for exception handling
            _lsdaSectionWriter = GetOrCreateSection(LsdaSection);
            ehFrameSectionWriter = GetOrCreateSection(EhFrameSection);
            _lsdaSectionWriter.EmitAlignment(8);
            ehFrameSectionWriter.EmitAlignment(8);
            _ehFrameSectionIndex = ehFrameSectionWriter.SectionIndex;

            // We always use the same CIE in DWARF EH frames, so create and emit it now
            bool is64Bit = _nodeFactory.Target.Architecture switch
            {
                TargetArchitecture.X86 => false,
                TargetArchitecture.ARM => false,
                _ => true
            };
            _dwarfCie = new DwarfCie(_nodeFactory.Target.Architecture);
            _dwarfEhFrame = new DwarfEhFrame(ehFrameSectionWriter, is64Bit);
            _dwarfEhFrame.AddCie(_dwarfCie);
        }

        private protected void EmitLsda(
            INodeWithCodeInfo nodeWithCodeInfo,
            FrameInfo[] frameInfos,
            int frameInfoIndex,
            SectionWriter lsdaSectionWriter,
            ref long mainLsdaOffset)
        {
            FrameInfo frameInfo = frameInfos[frameInfoIndex];
            FrameInfoFlags flags = frameInfo.Flags;

            if (frameInfoIndex != 0)
            {
                lsdaSectionWriter.WriteByte((byte)flags);
                lsdaSectionWriter.WriteLittleEndian<int>((int)(mainLsdaOffset - lsdaSectionWriter.Position));
                // Emit relative offset from the main function
                lsdaSectionWriter.WriteLittleEndian<uint>((uint)(frameInfo.StartOffset - frameInfos[0].StartOffset));
            }
            else
            {
                MethodExceptionHandlingInfoNode ehInfo = nodeWithCodeInfo.EHInfo;
                ISymbolNode associatedDataNode = nodeWithCodeInfo.GetAssociatedDataNode(_nodeFactory) as ISymbolNode;

                flags |= ehInfo is not null ? FrameInfoFlags.HasEHInfo : 0;
                flags |= associatedDataNode is not null ? FrameInfoFlags.HasAssociatedData : 0;

                mainLsdaOffset = lsdaSectionWriter.Position;
                lsdaSectionWriter.WriteByte((byte)flags);

                if (associatedDataNode is not null)
                {
                    Utf8String symbolName = GetMangledName(associatedDataNode);
                    lsdaSectionWriter.EmitSymbolReference(RelocType.IMAGE_REL_BASED_RELPTR32, symbolName, 0);
                }

                if (ehInfo is not null)
                {
                    Utf8String symbolName = GetMangledName(ehInfo);
                    lsdaSectionWriter.EmitSymbolReference(RelocType.IMAGE_REL_BASED_RELPTR32, symbolName, 0);
                }

                if (nodeWithCodeInfo.GCInfo is not null)
                {
                    lsdaSectionWriter.Write(nodeWithCodeInfo.GCInfo);
                }
            }
        }

        private sealed class LsdaCache
        {
            private sealed class LsdaComparer : IEqualityComparer<INodeWithCodeInfo>
            {
                public static readonly LsdaComparer Instance = new LsdaComparer();

                public bool Equals(INodeWithCodeInfo x, INodeWithCodeInfo y)
                {
                    Debug.Assert(IsCacheable(x));
                    Debug.Assert(IsCacheable(y));
                    ReadOnlySpan<byte> xGc = x.GCInfo;
                    ReadOnlySpan<byte> yGc = y.GCInfo;
                    if (!xGc.SequenceEqual(yGc))
                        return false;

                    ReadOnlySpan<FrameInfo> xFrames = x.FrameInfos;
                    ReadOnlySpan<FrameInfo> yFrames = y.FrameInfos;
                    return xFrames.SequenceEqual(yFrames);
                }

                public int GetHashCode(INodeWithCodeInfo obj)
                {
                    Debug.Assert(IsCacheable(obj));
                    HashCode hash = default;
                    hash.AddBytes(obj.GCInfo);
                    foreach (FrameInfo f in obj.FrameInfos)
                        hash.Add(f.GetHashCode());
                    return hash.ToHashCode();
                }
            }

            private Dictionary<INodeWithCodeInfo, Utf8String[]> _lsdas = new Dictionary<INodeWithCodeInfo, Utf8String[]>(LsdaComparer.Instance);

            public static bool IsCacheable(INodeWithCodeInfo nodeWithCodeInfo)
                => nodeWithCodeInfo.EHInfo == null && !MethodAssociatedDataNode.MethodHasAssociatedData((IMethodNode)nodeWithCodeInfo);

            public Utf8String[] FindCachedLsda(INodeWithCodeInfo nodeWithCodeInfo)
            {
                Debug.Assert(IsCacheable(nodeWithCodeInfo));
                return _lsdas.GetValueOrDefault(nodeWithCodeInfo);
            }

            public void AddLsdaToCache(INodeWithCodeInfo nodeWithCodeInfo, Utf8String[] symbols)
            {
                Debug.Assert(IsCacheable(nodeWithCodeInfo));
                _lsdas.Add(nodeWithCodeInfo, symbols);
            }
        }

        private readonly LsdaCache _lsdaCache = new LsdaCache();

        private protected override void EmitUnwindInfo(
            SectionWriter sectionWriter,
            INodeWithCodeInfo nodeWithCodeInfo,
            Utf8String currentSymbolName)
        {
            if (nodeWithCodeInfo.FrameInfos is FrameInfo[] frameInfos &&
                nodeWithCodeInfo is ISymbolDefinitionNode)
            {
                bool useFrameNames = UseFrameNames;
                SectionWriter lsdaSectionWriter;

                Utf8String[] newLsdaSymbols = null;
                Utf8String[] emittedLsdaSymbols = null;
                if (ShouldShareSymbol((ObjectNode)nodeWithCodeInfo))
                {
                    lsdaSectionWriter = GetOrCreateSection(LsdaSection, currentSymbolName, Utf8String.Concat("_lsda0"u8, currentSymbolName.AsSpan()));
                }
                else
                {
                    lsdaSectionWriter = _lsdaSectionWriter;
                    if (LsdaCache.IsCacheable(nodeWithCodeInfo) && !useFrameNames)
                    {
                        emittedLsdaSymbols = _lsdaCache.FindCachedLsda(nodeWithCodeInfo);
                        if (emittedLsdaSymbols == null)
                            newLsdaSymbols = new Utf8String[frameInfos.Length];
                    }
                }

                long mainLsdaOffset = 0;
                Span<byte> i_str = stackalloc byte[16];
                for (int i = 0; i < frameInfos.Length; i++)
                {
                    FrameInfo frameInfo = frameInfos[i];

                    int start = frameInfo.StartOffset;
                    int end = frameInfo.EndOffset;
                    byte[] blob = frameInfo.BlobData;

                    Utf8String lsdaSymbolName;
                    if (emittedLsdaSymbols != null)
                    {
                        lsdaSymbolName = emittedLsdaSymbols[i];
                    }
                    else
                    {
                        lsdaSymbolName = _utf8StringBuilder.Clear().Append("_lsda"u8).Append(FormatUtf8Int(i_str, i)).Append(currentSymbolName).ToUtf8String();
                        if (newLsdaSymbols != null)
                            newLsdaSymbols[i] = lsdaSymbolName;
                        lsdaSectionWriter.EmitSymbolDefinition(lsdaSymbolName);
                        EmitLsda(nodeWithCodeInfo, frameInfos, i, _lsdaSectionWriter, ref mainLsdaOffset);
                    }

                    Utf8String framSymbolName = _utf8StringBuilder.Clear().Append("_fram"u8).Append(FormatUtf8Int(i_str, i)).Append(currentSymbolName).ToUtf8String();
                    if (useFrameNames && start != 0)
                    {
                        sectionWriter.EmitSymbolDefinition(framSymbolName, start);
                    }

                    Utf8String startSymbolName = useFrameNames && start != 0 ? framSymbolName : currentSymbolName;
                    ulong length = (ulong)(end - start);
                    if (!EmitCompactUnwinding(startSymbolName, length, lsdaSymbolName, blob))
                    {
                        var fde = new DwarfFde(
                            _dwarfCie,
                            blob,
                            pcStartSymbolName: startSymbolName,
                            pcStartSymbolOffset: useFrameNames ? 0 : start,
                            pcLength: (ulong)(end - start),
                            lsdaSymbolName,
                            personalitySymbolName: default);
                        _dwarfEhFrame.AddFde(fde);
                    }
                }

                if (newLsdaSymbols != null)
                    _lsdaCache.AddLsdaToCache(nodeWithCodeInfo, newLsdaSymbols);
            }
        }

        private protected override void EmitDebugFunctionInfo(
            uint methodTypeIndex,
            Utf8String methodName,
            SymbolDefinition methodSymbol,
            INodeWithDebugInfo debugNode,
            bool hasSequencePoints)
        {
            DebugEHClauseInfo[] clauses = null;

            if (debugNode is INodeWithCodeInfo nodeWithCodeInfo)
            {
                clauses = nodeWithCodeInfo.DebugEHClauseInfos;
            }

            if (_sections[methodSymbol.SectionIndex] is UnixSectionDefinition section)
            {
                _dwarfBuilder.EmitSubprogramInfo(
                    methodName,
                    section.SymbolName,
                    methodSymbol.Value,
                    methodSymbol.Size,
                    methodTypeIndex,
                    debugNode.GetDebugVars().Select(debugVar => (debugVar, GetVarTypeIndex(debugNode.IsStateMachineMoveNextMethod, debugVar))),
                    clauses ?? []);

                if (hasSequencePoints)
                {
                    _dwarfBuilder.EmitLineInfo(
                        methodSymbol.SectionIndex,
                        section.SymbolName,
                        methodSymbol.Value,
                        debugNode.GetNativeSequencePoints());
                }
            }
        }

        private protected override void EmitDebugSections(IDictionary<Utf8String, SymbolDefinition> definedSymbols)
        {
            foreach (UnixSectionDefinition section in _sections)
            {
                if (section is not null)
                {
                    _dwarfBuilder.EmitSectionInfo(section.SymbolName, (ulong)section.SectionStream.Length);
                }
            }

            SectionWriter infoSectionWriter = GetOrCreateSection(DebugInfoSection);
            SectionWriter stringSectionWriter = GetOrCreateSection(DebugStringSection);
            SectionWriter abbrevSectionWriter = GetOrCreateSection(DebugAbbrevSection);
            SectionWriter locSectionWriter = GetOrCreateSection(DebugLocSection);
            SectionWriter rangeSectionWriter = GetOrCreateSection(DebugRangesSection);
            SectionWriter lineSectionWriter = GetOrCreateSection(DebugLineSection);
            SectionWriter arangeSectionWriter = GetOrCreateSection(DebugARangesSection);

            _dwarfBuilder.Write(
                infoSectionWriter,
                stringSectionWriter,
                abbrevSectionWriter,
                locSectionWriter,
                rangeSectionWriter,
                lineSectionWriter,
                arangeSectionWriter,
                symbolName =>
                {
                    if (definedSymbols.TryGetValue(ExternCName(symbolName), out SymbolDefinition symbolDef) &&
                        _sections[symbolDef.SectionIndex] is UnixSectionDefinition section)
                    {
                        return (section.SymbolName, symbolDef.Value);
                    }
                    return (default, 0);
                });
        }

        private protected override ITypesDebugInfoWriter CreateDebugInfoBuilder()
        {
            return _dwarfBuilder = new DwarfBuilder(
                _nodeFactory.NameMangler,
                _nodeFactory.Target,
                _options.HasFlag(ObjectWritingOptions.UseDwarf5));
        }

    }
}