File: Compiler\ObjectWriter\Dwarf\DwarfInfo.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.Diagnostics;
using System.Linq;
using ILCompiler.DependencyAnalysis;
using Internal.JitInterface;
using Internal.Text;
using Internal.TypeSystem;
using Internal.TypeSystem.TypesDebugInfo;
using static ILCompiler.ObjectWriter.DwarfNative;

namespace ILCompiler.ObjectWriter
{
    internal abstract class DwarfInfo
    {
        public abstract void Dump(DwarfInfoWriter writer);
    }

    internal sealed class DwarfPrimitiveTypeInfo : DwarfInfo
    {
        private readonly TypeFlags _typeFlags;
        private readonly int _targetPointerSize;

        public DwarfPrimitiveTypeInfo(TypeFlags typeFlags, int targetPointerSize)
        {
            _typeFlags = typeFlags;
            _targetPointerSize = targetPointerSize;
        }

        public byte ByteSize => _typeFlags switch
        {
            TypeFlags.Boolean => 1,
            TypeFlags.Char => 2,
            TypeFlags.SByte => 1,
            TypeFlags.Byte => 1,
            TypeFlags.Int16 => 2,
            TypeFlags.UInt16 => 2,
            TypeFlags.Int32 => 4,
            TypeFlags.UInt32 => 4,
            TypeFlags.Int64 => 8,
            TypeFlags.UInt64 => 8,
            TypeFlags.IntPtr => (byte)_targetPointerSize,
            TypeFlags.UIntPtr => (byte)_targetPointerSize,
            TypeFlags.Single => 4,
            TypeFlags.Double => 8,
            _ => 0,
        };

        public override void Dump(DwarfInfoWriter writer)
        {
            if (_typeFlags == TypeFlags.Void)
            {
                writer.WriteStartDIE(DwarfAbbrev.VoidType);
                writer.WriteStringReference("void");
                writer.WriteEndDIE();
            }
            else
            {
                var (name, encoding, byteSize) = _typeFlags switch
                {
                    TypeFlags.Boolean => ("bool", DW_ATE_boolean, 1),
                    TypeFlags.Char => ("char16_t", DW_ATE_UTF, 2),
                    TypeFlags.SByte => ("sbyte", DW_ATE_signed, 1),
                    TypeFlags.Byte => ("byte", DW_ATE_unsigned, 1),
                    TypeFlags.Int16 => ("short", DW_ATE_signed, 2),
                    TypeFlags.UInt16 => ("ushort", DW_ATE_unsigned, 2),
                    TypeFlags.Int32 => ("int", DW_ATE_signed, 4),
                    TypeFlags.UInt32 => ("uint", DW_ATE_unsigned, 4),
                    TypeFlags.Int64 => ("long", DW_ATE_signed, 8),
                    TypeFlags.UInt64 => ("ulong", DW_ATE_unsigned, 8),
                    TypeFlags.IntPtr => ("nint", DW_ATE_signed, _targetPointerSize),
                    TypeFlags.UIntPtr => ("nuint", DW_ATE_unsigned, _targetPointerSize),
                    TypeFlags.Single => ("float", DW_ATE_float, 4),
                    TypeFlags.Double => ("double", DW_ATE_float, 8),
                    _ => ("", 0, 0),
                };

                writer.WriteStartDIE(DwarfAbbrev.BaseType);
                writer.WriteStringReference(name);
                writer.Write([(byte)encoding, (byte)byteSize]);
                writer.WriteEndDIE();
            }
        }
    }

    internal sealed class DwarfEnumTypeInfo : DwarfInfo
    {
        private readonly EnumTypeDescriptor _typeDescriptor;
        private readonly EnumRecordTypeDescriptor[] _typeRecords;
        private readonly byte _byteSize;

        public DwarfEnumTypeInfo(
            EnumTypeDescriptor typeDescriptor,
            EnumRecordTypeDescriptor[] typeRecords,
            byte byteSize)
        {
            _typeDescriptor = typeDescriptor;
            _typeRecords = typeRecords;
            _byteSize = byteSize;
        }

        public override void Dump(DwarfInfoWriter writer)
        {
            writer.WriteStartDIE(_typeRecords.Length > 0 ? DwarfAbbrev.EnumerationType : DwarfAbbrev.EnumerationTypeNoChildren);
            writer.WriteStringReference(_typeDescriptor.Name);
            writer.WriteInfoReference(_typeDescriptor.ElementType);
            writer.Write([_byteSize]);

            if (_typeRecords.Length > 0)
            {
                DwarfAbbrev abbrev = _byteSize switch {
                    1 => DwarfAbbrev.Enumerator1,
                    2 => DwarfAbbrev.Enumerator2,
                    4 => DwarfAbbrev.Enumerator4,
                    8 => DwarfAbbrev.Enumerator8,
                    _ => throw new NotSupportedException()
                };

                foreach (EnumRecordTypeDescriptor typeRecord in _typeRecords)
                {
                    writer.WriteStartDIE(abbrev);
                    writer.WriteStringReference(typeRecord.Name);
                    switch (_byteSize)
                    {
                        case 1: writer.WriteUInt8((byte)typeRecord.Value); break;
                        case 2: writer.WriteUInt16((ushort)typeRecord.Value); break;
                        case 4: writer.WriteUInt32((uint)typeRecord.Value); break;
                        case 8: writer.WriteUInt64(typeRecord.Value); break;
                    };
                    writer.WriteEndDIE();
                }
            }

            writer.WriteEndDIE();
        }
    }

    internal sealed class DwarfPointerTypeInfo : DwarfInfo
    {
        private PointerTypeDescriptor _typeDescriptor;

        public DwarfPointerTypeInfo(PointerTypeDescriptor typeDescriptor)
        {
            _typeDescriptor = typeDescriptor;
        }

        public override void Dump(DwarfInfoWriter writer)
        {
            writer.WriteStartDIE(_typeDescriptor.IsReference != 0 ? DwarfAbbrev.ReferenceType : DwarfAbbrev.PointerType);
            writer.WriteInfoReference(_typeDescriptor.ElementType);
            writer.Write([_typeDescriptor.Is64Bit != 0 ? (byte)8 : (byte)4]);
            writer.WriteEndDIE();
        }
    }

    internal sealed class DwarfVoidPtrTypeInfo : DwarfInfo
    {
        public override void Dump(DwarfInfoWriter writer)
        {
            writer.WriteStartDIE(DwarfAbbrev.VoidPointerType);
            writer.WriteEndDIE();
        }
    }

    internal sealed class DwarfSimpleArrayTypeInfo : DwarfInfo
    {
        private readonly uint _elementType;
        private readonly ulong _size;

        public DwarfSimpleArrayTypeInfo(uint elementType, ulong size)
        {
            _elementType = elementType;
            _size = size;
        }

        public override void Dump(DwarfInfoWriter writer)
        {
            writer.WriteStartDIE(DwarfAbbrev.ArrayType);

            // DW_AT_type
            writer.WriteInfoReference(_elementType);

            writer.WriteStartDIE(DwarfAbbrev.SubrangeType);
            // DW_AT_upper_bound
            // NOTE: This produces garbage for _size == 0
            writer.WriteULEB128(_size - 1);
            writer.WriteEndDIE();

            writer.WriteEndDIE();
        }
    }

    internal sealed class DwarfMemberFunction
    {
        public Utf8String Name { get; private set; }
        public Utf8String LinkageName { get; set; }
        public MemberFunctionTypeDescriptor Descriptor { get; private set; }
        public uint[] ArgumentTypes { get; private set; }
        public bool IsStatic { get; private set; }
        public long InfoOffset { get; set; }

        public DwarfMemberFunction(
            Utf8String name,
            MemberFunctionTypeDescriptor descriptor,
            uint[] argumentTypes,
            bool isStatic)
        {
            Name = name;
            LinkageName = name;
            Descriptor = descriptor;
            ArgumentTypes = argumentTypes;
            IsStatic = isStatic;
        }
    }

    internal sealed class DwarfClassTypeInfo : DwarfInfo
    {
        private readonly bool _isForwardDecl;
        private readonly ClassTypeDescriptor _typeDescriptor;
        private readonly ClassFieldsTypeDescriptor _classFieldsTypeDescriptor;
        private readonly DataFieldDescriptor[] _fields;
        private readonly List<DwarfStaticVariableInfo> _statics;
        private List<DwarfMemberFunction> _methods;

        public DwarfClassTypeInfo(ClassTypeDescriptor typeDescriptor)
        {
            _isForwardDecl = true;
            _typeDescriptor = typeDescriptor;
        }

        public DwarfClassTypeInfo(
            ClassTypeDescriptor typeDescriptor,
            ClassFieldsTypeDescriptor classFieldsTypeDescriptor,
            DataFieldDescriptor[] fields,
            StaticDataFieldDescriptor[] statics)
        {
            _typeDescriptor = typeDescriptor;
            _classFieldsTypeDescriptor = classFieldsTypeDescriptor;
            _fields = fields;
            if (statics is not null)
            {
                _statics = new(statics.Length);
                foreach (StaticDataFieldDescriptor staticDescriptor in statics)
                {
                    _statics.Add(new DwarfStaticVariableInfo(staticDescriptor));
                }
            }
        }

        public void AddMemberFunction(DwarfMemberFunction memberFunction)
        {
            _methods ??= new();
            _methods.Add(memberFunction);
        }

        private bool HasChildren =>
            _typeDescriptor.BaseClassId != 0 ||
            _fields.Length > 0 ||
            _methods is not null;

        public IReadOnlyList<DwarfStaticVariableInfo> StaticVariables => _statics ?? [];

        public override void Dump(DwarfInfoWriter writer)
        {
            writer.WriteStartDIE(
                _isForwardDecl ? DwarfAbbrev.ClassTypeDecl :
                HasChildren ? DwarfAbbrev.ClassType : DwarfAbbrev.ClassTypeNoChildren);

            // DW_AT_name
            writer.WriteStringReference(_typeDescriptor.Name);

            if (!_isForwardDecl)
            {
                // DW_AT_byte_size
                writer.WriteUInt32((uint)_typeDescriptor.InstanceSize);

                if (_typeDescriptor.BaseClassId != 0)
                {
                    writer.WriteStartDIE(DwarfAbbrev.ClassInheritance);
                    // DW_AT_type
                    writer.WriteInfoReference(_typeDescriptor.BaseClassId);
                    // DW_AT_data_member_location
                    writer.Write([0]);
                    writer.WriteEndDIE();
                }

                int staticIndex = 0;
                foreach (DataFieldDescriptor fieldDescriptor in _fields)
                {
                    if (fieldDescriptor.Offset != 0xFFFFFFFFu)
                    {
                        writer.WriteStartDIE(DwarfAbbrev.ClassMember);
                        // DW_AT_name
                        writer.WriteStringReference(fieldDescriptor.Name);
                        // DW_AT_type
                        writer.WriteInfoReference(fieldDescriptor.FieldTypeIndex);
                        // DW_AT_data_member_location
                        writer.WriteUInt32((uint)fieldDescriptor.Offset);
                        writer.WriteEndDIE();
                    }
                    else
                    {
                        _statics[staticIndex].InfoOffset = writer.Position;
                        writer.WriteStartDIE(DwarfAbbrev.ClassMemberStatic);
                        // DW_AT_name
                        writer.WriteStringReference(fieldDescriptor.Name);
                        // DW_AT_type
                        writer.WriteInfoReference(fieldDescriptor.FieldTypeIndex);
                        staticIndex++;
                        writer.WriteEndDIE();
                    }
                }

                if (_methods is not null)
                {
                    foreach (DwarfMemberFunction method in _methods)
                    {
                        method.InfoOffset = writer.Position;
                        writer.WriteStartDIE(
                            !method.IsStatic ? DwarfAbbrev.SubprogramSpec :
                            method.ArgumentTypes.Length > 0 ? DwarfAbbrev.SubprogramStaticSpec : DwarfAbbrev.SubprogramStaticNoChildrenSpec);
                        // DW_AT_name
                        writer.WriteStringReference(method.Name);
                        // DW_AT_linkage_name
                        writer.WriteStringReference(method.LinkageName);
                        // DW_AT_decl_file, DW_AT_decl_line
                        writer.Write([1, 1]);
                        // DW_AT_type
                        writer.WriteInfoReference(method.Descriptor.ReturnType);

                        if (!method.IsStatic)
                        {
                            // DW_AT_object_pointer
                            writer.WriteInfoAbsReference(writer.Position + sizeof(uint));

                            writer.WriteStartDIE(DwarfAbbrev.FormalParameterThisSpec);
                            // DW_AT_type
                            writer.WriteInfoReference(method.Descriptor.TypeIndexOfThisPointer);
                            writer.WriteEndDIE();
                        }

                        foreach (uint argumentType in method.ArgumentTypes)
                        {
                            writer.WriteStartDIE(DwarfAbbrev.FormalParameterSpec);
                            // DW_AT_type
                            writer.WriteInfoReference(argumentType);
                            writer.WriteEndDIE();
                        }

                        writer.WriteEndDIE();
                    }
                }
            }

            writer.WriteEndDIE();
        }
    }

    internal sealed class DwarfSubprogramInfo : DwarfInfo
    {
        private readonly Utf8String _sectionSymbolName;
        private readonly long _methodAddress;
        private readonly int _methodSize;
        private readonly DwarfMemberFunction _memberFunction;
        private readonly IEnumerable<(DebugVarInfoMetadata, uint)> _debugVars;
        private readonly IEnumerable<DebugEHClauseInfo> _debugEHClauseInfos;
        private readonly bool _isStatic;
        private readonly bool _hasChildren;

        public DwarfSubprogramInfo(
            Utf8String sectionSymbolName,
            long methodAddress,
            int methodSize,
            DwarfMemberFunction memberFunction,
            IEnumerable<(DebugVarInfoMetadata, uint)> debugVars,
            IEnumerable<DebugEHClauseInfo> debugEHClauseInfos)
        {
            _sectionSymbolName = sectionSymbolName;
            _methodAddress = methodAddress;
            _methodSize = methodSize;
            _memberFunction = memberFunction;
            _isStatic = memberFunction.IsStatic;
            _hasChildren = !_isStatic || debugVars.Any() || debugEHClauseInfos.Any();
            _debugVars = debugVars;
            _debugEHClauseInfos = debugEHClauseInfos;
        }

        public override void Dump(DwarfInfoWriter writer)
        {
            writer.WriteStartDIE(
                !_isStatic ? DwarfAbbrev.Subprogram :
                _hasChildren ? DwarfAbbrev.SubprogramStatic : DwarfAbbrev.SubprogramStaticNoChildren);

            // DW_AT_specification
            writer.WriteInfoAbsReference(_memberFunction.InfoOffset);

            // DW_AT_low_pc
            writer.WriteCodeReference(_sectionSymbolName, _methodAddress);

            // DW_AT_high_pc
            writer.WriteAddressSize((ulong)_methodSize);

            // DW_AT_frame_base
            writer.WriteULEB128(1);
            writer.Write([(byte)(DW_OP_reg0 + writer.FrameRegister)]);

            if (!_isStatic)
            {
                // DW_AT_object_pointer
                writer.WriteInfoAbsReference(writer.Position + sizeof(uint));
            }

            /// At the moment, the lexical scope reflects IL, not C#, meaning that
            /// there is only one scope for the whole method. We could be more precise
            /// in the future by pulling the scope information from the PDB.
            foreach ((DebugVarInfoMetadata debugVar, uint typeIndex) in _debugVars)
            {
                bool isThis = debugVar.IsParameter && debugVar.DebugVarInfo.VarNumber == 0 && !_isStatic;
                DumpVar(writer, debugVar, typeIndex, isThis);
            }

            // EH clauses
            foreach (DebugEHClauseInfo clause in _debugEHClauseInfos)
            {
                writer.WriteStartDIE(DwarfAbbrev.TryBlock);
                // DW_AT_low_pc
                writer.WriteCodeReference(_sectionSymbolName, _methodAddress + clause.TryOffset);
                // DW_AT_high_pc
                writer.WriteAddressSize(clause.TryLength);
                writer.WriteEndDIE();

                writer.WriteStartDIE(DwarfAbbrev.CatchBlock);
                // DW_AT_low_pc
                writer.WriteCodeReference(_sectionSymbolName, _methodAddress + clause.HandlerOffset);
                // DW_AT_high_pc
                writer.WriteAddressSize(clause.HandlerLength);
                writer.WriteEndDIE();
            }

            writer.WriteEndDIE();
        }

        private void DumpVar(DwarfInfoWriter writer, DebugVarInfoMetadata metadataInfo, uint typeIndex, bool isThis)
        {
            bool usesDebugLoc = metadataInfo.DebugVarInfo.Ranges.Length != 1;

            if (metadataInfo.IsParameter)
            {
                if (isThis)
                {
                    writer.WriteStartDIE(usesDebugLoc ? DwarfAbbrev.FormalParameterThisLoc : DwarfAbbrev.FormalParameterThis);
                }
                else
                {
                    writer.WriteStartDIE(usesDebugLoc ? DwarfAbbrev.FormalParameterLoc : DwarfAbbrev.FormalParameter);
                }
            }
            else
            {
                writer.WriteStartDIE(usesDebugLoc ? DwarfAbbrev.VariableLoc : DwarfAbbrev.Variable);
            }

            // DW_AT_name
            writer.WriteStringReference(isThis ? "this" : metadataInfo.Name);

            // DW_AT_decl_file, DW_AT_decl_line
            writer.Write([1, 1]);

            // DW_AT_type
            writer.WriteInfoReference(typeIndex);

            // DW_AT_location
            if (usesDebugLoc)
            {
                writer.WriteStartLocationList();
                foreach (var range in metadataInfo.DebugVarInfo.Ranges)
                {
                    var expressionBuilder = writer.GetExpressionBuilder();
                    DumpVarLocation(expressionBuilder, range.VarLoc);
                    writer.WriteLocationListExpression(
                        _sectionSymbolName,
                        _methodAddress + range.StartOffset,
                        _methodAddress + range.EndOffset,
                        expressionBuilder);
                }
                writer.WriteEndLocationList();
            }
            else
            {
                var expressionBuilder = writer.GetExpressionBuilder();
                DumpVarLocation(expressionBuilder, metadataInfo.DebugVarInfo.Ranges[0].VarLoc);
                writer.WriteExpression(expressionBuilder);
            }

            writer.WriteEndDIE();
        }

        private static void DumpVarLocation(DwarfExpressionBuilder e, VarLoc loc)
        {
            switch (loc.LocationType)
            {
                case VarLocType.VLT_REG:
                case VarLocType.VLT_REG_FP:
                    e.OpReg(loc.B);
                    break;
                case VarLocType.VLT_REG_BYREF:
                    e.OpBReg(loc.B);
                    break;
                case VarLocType.VLT_STK:
                case VarLocType.VLT_STK2:
                case VarLocType.VLT_STK_BYREF:
                    e.OpBReg(loc.B, loc.C);
                    if (loc.LocationType == VarLocType.VLT_STK_BYREF)
                    {
                        e.OpDeref();
                    }
                    break;
                case VarLocType.VLT_REG_REG:
                    e.OpReg(loc.C);
                    e.OpPiece();
                    e.OpReg(loc.B);
                    e.OpPiece();
                    break;
                case VarLocType.VLT_REG_STK:
                    e.OpReg(loc.B);
                    e.OpPiece();
                    e.OpBReg(loc.C, loc.D);
                    e.OpPiece();
                    break;
                case VarLocType.VLT_STK_REG:
                    e.OpBReg(loc.B, loc.C);
                    e.OpPiece();
                    e.OpReg(loc.D);
                    e.OpPiece();
                    break;
                case VarLocType.VLT_FPSTK:
                    // On ARM32 this is used to encode actual registers. This may be wrong for x86
                    // which we don't support anyway.
                    e.OpReg(loc.B);
                    break;
                default:
                    // Unsupported
                    Debug.Assert(loc.LocationType != VarLocType.VLT_FIXED_VA);
                    break;
            }
        }
    }

    internal sealed class DwarfStaticVariableInfo
    {
        private readonly StaticDataFieldDescriptor _descriptor;

        public long InfoOffset { get; set; }
        public Utf8String Name => _descriptor.StaticDataName;

        public DwarfStaticVariableInfo(StaticDataFieldDescriptor descriptor)
        {
            _descriptor = descriptor;
        }

        public void Dump(DwarfInfoWriter writer, Utf8String sectionSymbolName, long address)
        {
            writer.WriteStartDIE(DwarfAbbrev.VariableStatic);

            // DW_AT_specification
            writer.WriteInfoAbsReference(InfoOffset);
            // DW_AT_location
            uint length = 1 + (uint)writer.TargetPointerSize; // DW_OP_addr <addr>
            if (_descriptor.IsStaticDataInObject != 0)
                length += 2; // DW_OP_deref, DW_OP_deref
            if (_descriptor.StaticOffset != 0)
                length += 1 + DwarfHelper.SizeOfULEB128(_descriptor.StaticOffset); // DW_OP_plus_uconst <const>
            writer.WriteULEB128(length);
            writer.Write([DW_OP_addr]);
            writer.WriteCodeReference(sectionSymbolName, address);
            if (_descriptor.IsStaticDataInObject != 0)
                writer.Write([DW_OP_deref, DW_OP_deref]);
            if (_descriptor.StaticOffset != 0)
            {
                writer.Write([DW_OP_plus_uconst]);
                writer.WriteULEB128(_descriptor.StaticOffset);
            }

            writer.WriteEndDIE();
        }
    }
}