File: Compiler\DependencyAnalysis\ReadyToRun\DebugInfoTableNode.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.IO;

using Internal.JitInterface;
using Internal.NativeFormat;
using Internal.Text;
using Internal.TypeSystem;

namespace ILCompiler.DependencyAnalysis.ReadyToRun
{
    /// <summary>
    /// DebugInfoTableNode emits the data structures required to support managed code debugging
    /// for ready to run code. See https://github.com/dotnet/runtime/blob/main/src/coreclr/inc/cordebuginfo.h
    /// for the types this information is based on. We store two tables of information in an
    /// image:
    ///
    /// Location Information
    ///
    ///     In order to provide source line debugging, generated ready-to-run code is mapped
    ///     back to the original IL instruction. This maps to OffsetMapping in cordebuginfo.h.
    ///
    ///     A row in the table looks like this:
    ///
    ///     Native offset | IL Offset | Flags
    ///
    ///     Native offset starts at 0 for the beginning of a method. The rows are stored in
    ///     ascending order of offset and use a delta encoding. That is, each successive row
    ///     stores the difference between the previous native offset and the new one to save
    ///     encoding space.
    ///
    ///     Flags stores values useful to the debugger. For example, if the offset is a
    ///     call site, or the stack is empty at that point. These are used for edit and
    ///     continue. It maps to SourceTypes in cordebuginfo.h.
    ///
    /// Variable Information
    ///
    ///     The debugger needs to know, for a given native code range, where the memory
    ///     backing each variable is stored. That can be in a register, on the stack, or maybe
    ///     even split between a combination of registers and the stack. This maps to
    ///     NativeVarInfo in cordebuginfo.h.
    ///
    ///     A row in the table looks like this:
    ///
    ///     Start Offset | End Offset | Variable Number | Location
    ///
    ///     Start Offset and End Offset are offsets within the generated native code and are
    ///     used to capture the range of instructions during which the variable is stored
    ///     in the same location.
    ///
    ///     Variable Number is the index of each variable in the original IL code.
    ///
    ///     Location is a variant data structure specifying what memory is backing the variable
    ///     for a given native code range. This is a specific register, or a stack offset. This
    ///     maps to VarLoc in cordebuginfo.h.
    ///
    /// </summary>
    public class DebugInfoTableNode : HeaderTableNode
    {
        public override int ClassCode => 1000735112;

        public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
        {
            sb.Append(nameMangler.CompilationUnitPrefix);
            sb.Append("__ReadyToRunDebugInfoTable"u8);
        }

        public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
        {
            // This node does not trigger generation of other nodes.
            if (relocsOnly)
                return new ObjectData(Array.Empty<byte>(), Array.Empty<Relocation>(), 1, new ISymbolDefinitionNode[] { this });

            NativeWriter writer = new NativeWriter();
            Section section = writer.NewSection();
            VertexArray vertexArray = new VertexArray(section);
            section.Place(vertexArray);

            Dictionary<byte[], BlobVertex> blobCache = new Dictionary<byte[], BlobVertex>(ByteArrayComparer.Instance);

            foreach (MethodWithGCInfo method in factory.EnumerateCompiledMethods())
            {
                MemoryStream methodDebugBlob = new MemoryStream();

                byte[] bounds = method.DebugLocInfos;
                byte[] vars = method.DebugVarInfos;

                NibbleWriter nibbleWriter = new NibbleWriter();
                uint boundsLength = (uint)(bounds?.Length ?? 0);
                bool isFatHeader = boundsLength == DebugInfoFat;

                if (isFatHeader)
                {
                    nibbleWriter.WriteUInt(DebugInfoFat);
                    nibbleWriter.WriteUInt(boundsLength);
                    nibbleWriter.WriteUInt((uint)(vars?.Length ?? 0));
                    nibbleWriter.WriteUInt(0); // cbUninstrumentedBounds
                    nibbleWriter.WriteUInt(0); // cbPatchpointInfo
                    nibbleWriter.WriteUInt(0); // cbRichDebugInfo
                    nibbleWriter.WriteUInt(0); // cbAsyncInfo
                }
                else
                {
                    nibbleWriter.WriteUInt(boundsLength);
                    nibbleWriter.WriteUInt((uint)(vars?.Length ?? 0));
                }

                byte[] header = nibbleWriter.ToArray();
                methodDebugBlob.Write(header, 0, header.Length);

                if (bounds?.Length > 0)
                {
                    methodDebugBlob.Write(bounds, 0, bounds.Length);
                }

                if (vars?.Length > 0)
                {
                    methodDebugBlob.Write(vars, 0, vars.Length);
                }

                byte[] debugBlobArrayKey = methodDebugBlob.ToArray();
                if (!blobCache.TryGetValue(debugBlobArrayKey, out BlobVertex debugBlob))
                {
                    debugBlob = new BlobVertex(methodDebugBlob.ToArray());
                    blobCache.Add(debugBlobArrayKey, debugBlob);
                }
                vertexArray.Set(factory.RuntimeFunctionsTable.GetIndex(method), new DebugInfoVertex(debugBlob));
            }

            vertexArray.ExpandLayout();

            MemoryStream writerContent = new MemoryStream();
            writer.Save(writerContent);

            return new ObjectData(
                data: writerContent.ToArray(),
                relocs: null,
                alignment: 8,
                definedSymbols: new ISymbolDefinitionNode[] { this });
        }

        public static byte[] CreateBoundsBlobForMethod(OffsetMapping[] offsetMapping)
        {
            if (offsetMapping == null || offsetMapping.Length == 0)
                return null;

            uint previousNativeOffset = 0;
            uint maxNativeDelta = 0;
            uint maxILValue = 0;
            foreach (var locInfo in offsetMapping)
            {
                maxNativeDelta = Math.Max(maxNativeDelta, locInfo.nativeOffset - previousNativeOffset);
                maxILValue = Math.Max(maxILValue, locInfo.ilOffset + 3);
                previousNativeOffset = locInfo.nativeOffset;
            }

            int bitWidthForNativeDelta = Math.Max(1, 32 - System.Numerics.BitOperations.LeadingZeroCount(maxNativeDelta));
            int bitWidthForILOffset = Math.Max(1, 32 - System.Numerics.BitOperations.LeadingZeroCount(maxILValue));

            int bitWidthReportForNativeDelta = bitWidthForNativeDelta - 1;
            int bitWidthReportForILOffset = bitWidthForILOffset - 1;
            NibbleWriter writer2 = new NibbleWriter();
            writer2.WriteUInt((uint)offsetMapping.Length); // We need the total count
            writer2.WriteUInt((uint)bitWidthReportForNativeDelta); // Number of bits needed for native deltas
            writer2.WriteUInt((uint)bitWidthReportForILOffset); // How many bits needed for IL offsets
            const int BitsForSourceType = 3;
            int bitWidth = bitWidthForNativeDelta +
                          bitWidthForILOffset +
                          BitsForSourceType;
            int totalBits = bitWidth * offsetMapping.Length;
            int bytesNeededForArray = (totalBits + 7) / 8;

            byte bitsInProgress = 0;
            byte bitsInProgressCount = 0;

            List<byte> boundsBlob = new List<byte>(writer2.ToArray());

            for (uint i = 0; i < offsetMapping.Length; i++)
            {
                var bound = offsetMapping[i];

                uint prevNativeOffset = 0;
                if (i > 0)
                {
                    prevNativeOffset = offsetMapping[i - 1].nativeOffset;
                }
                uint nativeOffsetDelta = bound.nativeOffset - prevNativeOffset;

                uint sourceBits = 0;
                if ((bound.source & SourceTypes.CALL_INSTRUCTION) != 0)
                    sourceBits |= 1;
                if ((bound.source & SourceTypes.STACK_EMPTY) != 0)
                    sourceBits |= 2;
                if ((bound.source & SourceTypes.ASYNC) != 0)
                    sourceBits |= 4;

                if ((bound.source & ~(SourceTypes.CALL_INSTRUCTION | SourceTypes.STACK_EMPTY | SourceTypes.ASYNC)) != 0)
                    throw new InternalCompilerErrorException("Unknown source type " + (uint)bound.source);

                if ((sourceBits & ~((1u << BitsForSourceType) - 1)) != 0)
                    throw new InternalCompilerErrorException("Unencodable source type " + sourceBits + " (for " + (uint)bound.source + ")");

                ulong mappingDataEncoded = (ulong)sourceBits |
                    ((ulong)nativeOffsetDelta << BitsForSourceType) |
                    ((ulong)((int)bound.ilOffset - (int)MappingTypes.EPILOG) << (BitsForSourceType + bitWidthForNativeDelta));

                for (byte bitsToWrite = (byte)bitWidth; bitsToWrite > 0;)
                {
                    // Figure out next bits to write if we need to combine with a previous byte.
                    if (bitsInProgressCount > 0)
                    {
                        byte bitsToAddFromNewEncoding = (byte)(8 - bitsInProgressCount);
                        byte bitsToAddOnToInProgress = (byte)((mappingDataEncoded & ((((uint)1) << bitsToAddFromNewEncoding) - 1)) << bitsInProgressCount);
                        boundsBlob.Add((byte)(bitsToAddOnToInProgress | bitsInProgress));
                        mappingDataEncoded >>= bitsToAddFromNewEncoding;
                        bitsToWrite -= bitsToAddFromNewEncoding;
                        bitsInProgressCount = 0;
                    }
                    else if (bitsToWrite >= 8)
                    {
                        boundsBlob.Add((byte)mappingDataEncoded);
                        mappingDataEncoded >>= 8;
                        bitsToWrite -= 8;
                    }
                    else
                    {
                        bitsInProgress = (byte)mappingDataEncoded;
                        bitsInProgressCount = bitsToWrite;
                        bitsToWrite = 0;
                    }
                }
            }
            if (bitsInProgressCount > 0)
            {
                Debug.Assert(bitsInProgressCount < 8);
                boundsBlob.Add(bitsInProgress);
            }
            return boundsBlob.ToArray();
        }

        public static byte[] CreateVarBlobForMethod(NativeVarInfo[] varInfos, TargetDetails target)
        {
            if (varInfos == null || varInfos.Length == 0)
                return null;

            bool isX86 = target.Architecture == TargetArchitecture.X86;

            NibbleWriter writer = new NibbleWriter();
            writer.WriteUInt((uint)varInfos.Length);

            foreach (var nativeVarInfo in varInfos)
            {
                writer.WriteUInt(nativeVarInfo.startOffset);
                writer.WriteUInt(nativeVarInfo.endOffset - nativeVarInfo.startOffset);
                writer.WriteUInt((uint)(nativeVarInfo.varNumber - (int)ILNum.MAX_ILNUM));

                VarLocType varLocType = nativeVarInfo.varLoc.LocationType;

                writer.WriteUInt((uint)varLocType);

                switch (varLocType)
                {
                    case VarLocType.VLT_REG:
                    case VarLocType.VLT_REG_FP:
                    case VarLocType.VLT_REG_BYREF:
                        writer.WriteUInt((uint)nativeVarInfo.varLoc.B);
                        break;
                    case VarLocType.VLT_STK:
                    case VarLocType.VLT_STK_BYREF:
                        writer.WriteUInt((uint)nativeVarInfo.varLoc.B);
                        WriteEncodedStackOffset(writer, nativeVarInfo.varLoc.C, assume4ByteAligned : isX86);
                        break;
                    case VarLocType.VLT_REG_REG:
                        writer.WriteUInt((uint)nativeVarInfo.varLoc.B);
                        writer.WriteUInt((uint)nativeVarInfo.varLoc.C);
                        break;
                    case VarLocType.VLT_REG_STK:
                        writer.WriteUInt((uint)nativeVarInfo.varLoc.B);
                        writer.WriteUInt((uint)nativeVarInfo.varLoc.C);
                        WriteEncodedStackOffset(writer, nativeVarInfo.varLoc.D, assume4ByteAligned : isX86);
                        break;
                    case VarLocType.VLT_STK_REG:
                        WriteEncodedStackOffset(writer, nativeVarInfo.varLoc.B, assume4ByteAligned : isX86);
                        writer.WriteUInt((uint)nativeVarInfo.varLoc.C);
                        writer.WriteUInt((uint)nativeVarInfo.varLoc.D);
                        break;
                    case VarLocType.VLT_STK2:
                        writer.WriteUInt((uint)nativeVarInfo.varLoc.B);
                        WriteEncodedStackOffset(writer, nativeVarInfo.varLoc.C, assume4ByteAligned : isX86);
                        break;
                    case VarLocType.VLT_FPSTK:
                        writer.WriteUInt((uint)nativeVarInfo.varLoc.B);
                        break;
                    case VarLocType.VLT_FIXED_VA:
                        writer.WriteUInt((uint)nativeVarInfo.varLoc.B);
                        break;
                    default:
                        throw new BadImageFormatException("Unexpected var loc type");
                }

                static void WriteEncodedStackOffset(NibbleWriter _writer, int offset, bool assume4ByteAligned)
                {
                    if (assume4ByteAligned)
                    {
                        Debug.Assert(offset % 4 == 0);
                        _writer.WriteInt(offset / 4);
                    }
                    else
                    {
                        _writer.WriteInt(offset);
                    }
                }
            }

            return writer.ToArray();
        }

        private const int DebugInfoFat = 0;
    }
}