File: Compiler\ObjectWriter\CodeView\CodeViewTypesBuilder.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.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;

using ILCompiler.DependencyAnalysis;
using Internal.JitInterface;
using Internal.Text;
using Internal.TypeSystem;
using Internal.TypeSystem.TypesDebugInfo;

using static ILCompiler.ObjectWriter.CodeViewNative;
using static ILCompiler.ObjectWriter.CodeViewNative.CodeViewPointer;
using static ILCompiler.ObjectWriter.CodeViewNative.CodeViewPropertyFlags;
using static ILCompiler.ObjectWriter.CodeViewNative.CodeViewType;
using static ILCompiler.ObjectWriter.CodeViewNative.LeafRecordType;

namespace ILCompiler.ObjectWriter
{
    /// <summary>Builder for the CodeView .debug$T section.<summary>
    /// <remarks>
    /// The .debug$T section in CodeView contains type (enum, struct, class)
    /// descriptions. The section is composed of records that are prefixed
    /// with their type and length. Each record is assigned a type index
    /// representing its position in the stream. The type indexes start at
    /// 0x1000 and increase by one for each record. Elementary types, such
    /// as uint, short, or float, have preassigned indexes below 0x1000,
    /// represented by the <see cref="CodeViewType" /> enumeration.
    ///
    /// The maximum record size is limited to sizeof(ushort) due to the
    /// layout of the record header. List records (eg. field list) can
    /// be split into multiple records that are chained together.
    /// </remarks>
    internal sealed class CodeViewTypesBuilder : ITypesDebugInfoWriter
    {
        private readonly NameMangler _nameMangler;
        private readonly SectionWriter _sectionWriter;
        private readonly int _targetPointerSize;

        private readonly uint _classVTableTypeIndex;
        private readonly uint _vfuncTabTypeIndex;
        private readonly List<(Utf8String, uint)> _userDefinedTypes = new();

        private uint _nextTypeIndex = 0x1000;

        public IList<(Utf8String, uint)> UserDefinedTypes => _userDefinedTypes;

        public CodeViewTypesBuilder(NameMangler nameMangler, int targetPointerSize, SectionWriter sectionWriter)
        {
            _nameMangler = nameMangler;
            _sectionWriter = sectionWriter;
            _targetPointerSize = targetPointerSize;

            // Write CodeView version header
            Span<byte> versionBuffer = stackalloc byte[sizeof(uint)];
            BinaryPrimitives.WriteUInt32LittleEndian(versionBuffer, 4);
            _sectionWriter.Write(versionBuffer);

            // We pretend that the MethodTable pointer in System.Object is VTable shape.
            // We use the same "Vtable" for all types because the vtable shape debug
            // record is not expressive enough to capture our vtable shape (where the
            // vtable slots don't start at the beginning of the vtable).
            using (LeafRecordWriter record = StartLeafRecord(LF_VTSHAPE))
            {
                record.Write((ushort)0); // Number of entries in vfunctable
            }
            _classVTableTypeIndex = _nextTypeIndex++;

            using (LeafRecordWriter record = StartLeafRecord(LF_POINTER))
            {
                record.Write(_classVTableTypeIndex);
                record.Write((uint)((targetPointerSize == 8 ? CV_PTR_64 : CV_PTR_NEAR32) | CV_PTR_MODE_LVREF));
            }
            _vfuncTabTypeIndex = _nextTypeIndex++;
        }

        public uint GetPrimitiveTypeIndex(TypeDesc type)
        {
            Debug.Assert(type.IsPrimitive, "it is not a primitive type");
            return GetPrimitiveTypeIndex(type.Category);
        }

        private uint GetPrimitiveTypeIndex(TypeFlags typeFlags)
        {
            // CodeView uses predefined codes for simple types
            return (uint)(typeFlags switch
            {
                TypeFlags.Boolean => T_BOOL08,
                TypeFlags.Char => T_WCHAR,
                TypeFlags.SByte => T_INT1,
                TypeFlags.Byte => T_UINT1,
                TypeFlags.Int16 => T_INT2,
                TypeFlags.UInt16 => T_UINT2,
                TypeFlags.Int32 => T_INT4,
                TypeFlags.UInt32 => T_UINT4,
                TypeFlags.Int64 => T_INT8,
                TypeFlags.UInt64 => T_UINT8,
                TypeFlags.IntPtr => _targetPointerSize == 8 ? T_INT8 : T_INT4,
                TypeFlags.UIntPtr => _targetPointerSize == 8 ? T_UINT8 : T_UINT4,
                TypeFlags.Single => T_REAL32,
                TypeFlags.Double => T_REAL64,
                _ => T_NOTYPE,
            });
        }

        public uint GetPointerTypeIndex(PointerTypeDescriptor pointerDescriptor)
        {
            using (LeafRecordWriter record = StartLeafRecord(LF_POINTER))
            {
                record.Write(pointerDescriptor.ElementType);
                record.Write((uint)(
                    (pointerDescriptor.Is64Bit == 1 ? CV_PTR_64 : CV_PTR_NEAR32) |
                    (pointerDescriptor.IsReference == 1 ? CV_PTR_MODE_LVREF : CV_PTR_MODE_PTR) |
                    (pointerDescriptor.IsConst == 1 ? CV_PTR_IS_CONST : 0)));
            }

            return _nextTypeIndex++;
        }

        public uint GetArrayTypeIndex(
            ClassTypeDescriptor classDescriptor,
            ArrayTypeDescriptor arrayDescriptor)
        {
            uint memberCount = 0;
            uint offset = 0;

            using (LeafRecordWriter arrayRecord = StartLeafRecord(LF_ARRAY))
            {
                arrayRecord.Write(arrayDescriptor.ElementType);
                arrayRecord.Write(T_INT4);
                arrayRecord.Write(arrayDescriptor.Size);
                arrayRecord.Write("");
            }

            uint arrayRecordTypeIndex = _nextTypeIndex++;

            using (LeafRecordWriter fieldListRecord = StartLeafRecord(LF_FIELDLIST))
            {
                if (classDescriptor.BaseClassId != 0)
                {
                    fieldListRecord.StartListEntry(LF_BCLASS);
                    fieldListRecord.Write((ushort)0); // Attributes
                    fieldListRecord.Write(classDescriptor.BaseClassId);
                    fieldListRecord.WriteEncodedInteger(0); // Offset
                    fieldListRecord.EndListEntry();
                    memberCount++;
                    offset += (uint)_targetPointerSize;
                }

                fieldListRecord.StartListEntry(LF_MEMBER);
                fieldListRecord.Write((ushort)CodeViewMemberAccess.CV_public);
                fieldListRecord.Write(T_INT4);
                fieldListRecord.WriteEncodedInteger(offset);
                fieldListRecord.Write("count");
                fieldListRecord.EndListEntry();
                memberCount++;
                offset += (uint)_targetPointerSize;

                if (arrayDescriptor.IsMultiDimensional == 1)
                {
                    for (uint i = 0; i < arrayDescriptor.Rank; ++i)
                    {
                        fieldListRecord.StartListEntry(LF_MEMBER);
                        fieldListRecord.Write((ushort)CodeViewMemberAccess.CV_public);
                        fieldListRecord.Write(T_INT4);
                        fieldListRecord.WriteEncodedInteger(offset);
                        fieldListRecord.Write($"length{i}");
                        fieldListRecord.EndListEntry();
                        memberCount++;
                        offset += 4;
                    }

                    for (uint i = 0; i < arrayDescriptor.Rank; ++i)
                    {
                        fieldListRecord.StartListEntry(LF_MEMBER);
                        fieldListRecord.Write((ushort)CodeViewMemberAccess.CV_public);
                        fieldListRecord.Write(T_INT4);
                        fieldListRecord.WriteEncodedInteger(offset);
                        fieldListRecord.Write($"bounds{i}");
                        fieldListRecord.EndListEntry();
                        memberCount++;
                        offset += 4;
                    }
                }

                fieldListRecord.StartListEntry(LF_MEMBER);
                fieldListRecord.Write((ushort)CodeViewMemberAccess.CV_public);
                fieldListRecord.Write(arrayRecordTypeIndex);
                fieldListRecord.WriteEncodedInteger(offset);
                fieldListRecord.Write("values");
                fieldListRecord.EndListEntry();
                memberCount++;
            }

            uint fieldListTypeIndex = _nextTypeIndex++;

            Debug.Assert(classDescriptor.IsStruct == 0);
            using (LeafRecordWriter record = StartLeafRecord(LF_CLASS))
            {
                Debug.Assert(memberCount <= ushort.MaxValue);
                Debug.Assert(arrayDescriptor.Size <= ushort.MaxValue);
                record.Write((ushort)memberCount); // Number of elements in class
                record.Write((ushort)0);  // Class options (CodeViewPropertyFlags)
                record.Write(fieldListTypeIndex); // Field descriptor index
                record.Write((uint)0); // Derived-from descriptor index
                record.Write((uint)0); // Vtshape descriptor index
                record.Write((ushort)arrayDescriptor.Size); // Size
                record.Write(classDescriptor.Name);
            }

            uint typeIndex = _nextTypeIndex++;
            _userDefinedTypes.Add((classDescriptor.Name, typeIndex));

            return typeIndex;
        }

        public uint GetEnumTypeIndex(
            EnumTypeDescriptor typeDescriptor,
            EnumRecordTypeDescriptor[] typeRecords)
        {
            using (LeafRecordWriter fieldListRecord = StartLeafRecord(LF_FIELDLIST))
            {
                foreach (EnumRecordTypeDescriptor record in typeRecords)
                {
                    fieldListRecord.StartListEntry(LF_ENUMERATE);
                    fieldListRecord.Write((ushort)CodeViewMemberAccess.CV_public);
                    fieldListRecord.WriteEncodedInteger(record.Value);
                    fieldListRecord.Write(record.Name);
                    fieldListRecord.EndListEntry();
                }
            }

            uint fieldListTypeIndex = _nextTypeIndex++;

            using (LeafRecordWriter record = StartLeafRecord(LF_ENUM))
            {
                Debug.Assert(typeRecords.Length <= ushort.MaxValue);
                record.Write((ushort)typeRecords.Length); // Number of elements in class
                record.Write((ushort)0);  // Class options (CodeViewPropertyFlags)
                record.Write(typeDescriptor.ElementType);
                record.Write(fieldListTypeIndex);
                record.Write(typeDescriptor.Name);
            }

            uint typeIndex = _nextTypeIndex++;
            _userDefinedTypes.Add((typeDescriptor.Name, typeIndex));

            return typeIndex;
        }

        public uint GetClassTypeIndex(ClassTypeDescriptor classDescriptor)
        {
            using (LeafRecordWriter record = StartLeafRecord(classDescriptor.IsStruct == 1 ? LF_STRUCTURE : LF_CLASS))
            {
                record.Write((ushort)0); // Number of elements in class
                record.Write((ushort)CV_PROP_FORWARD_REFERENCE); // Class options (CodeViewPropertyFlags)
                record.Write((uint)0); // Field descriptor index
                record.Write((uint)0); // Derived-from descriptor index
                record.Write((uint)0); // Vtshape descriptor index
                record.Write((ushort)0); // Size
                record.Write(classDescriptor.Name);
            }

            return _nextTypeIndex++;
        }

        public uint GetCompleteClassTypeIndex(
            ClassTypeDescriptor classTypeDescriptor,
            ClassFieldsTypeDescriptor classFieldsTypeDescriptor,
            DataFieldDescriptor[] fields,
            StaticDataFieldDescriptor[] statics)
        {
            uint memberCount = 0;

            using (LeafRecordWriter fieldListRecord = StartLeafRecord(LF_FIELDLIST))
            {
                if (classTypeDescriptor.BaseClassId != 0)
                {
                    fieldListRecord.StartListEntry(LF_BCLASS);
                    fieldListRecord.Write((ushort)0); // Attributes
                    fieldListRecord.Write(classTypeDescriptor.BaseClassId);
                    fieldListRecord.WriteEncodedInteger(0); // Offset
                    fieldListRecord.EndListEntry();
                    memberCount++;
                }
                else if (classTypeDescriptor.IsStruct == 0)
                {
                    fieldListRecord.StartListEntry(LF_VFUNCTAB);
                    fieldListRecord.Write((ushort)0); // Padding
                    fieldListRecord.Write(_vfuncTabTypeIndex);
                    fieldListRecord.EndListEntry();
                    memberCount++;
                }

                foreach (DataFieldDescriptor desc in fields)
                {
                    if (desc.Offset == 0xFFFFFFFF)
                    {
                        fieldListRecord.StartListEntry(LF_STMEMBER);
                        fieldListRecord.Write((ushort)CodeViewMemberAccess.CV_public);
                        fieldListRecord.Write(desc.FieldTypeIndex);
                        fieldListRecord.Write(desc.Name);
                        fieldListRecord.EndListEntry();
                    }
                    else
                    {
                        fieldListRecord.StartListEntry(LF_MEMBER);
                        fieldListRecord.Write((ushort)CodeViewMemberAccess.CV_public);
                        fieldListRecord.Write(desc.FieldTypeIndex);
                        fieldListRecord.WriteEncodedInteger(desc.Offset);
                        fieldListRecord.Write(desc.Name);
                        fieldListRecord.EndListEntry();
                    }
                    memberCount++;
                }
            }

            uint fieldListTypeIndex = _nextTypeIndex++;

            using (LeafRecordWriter record = StartLeafRecord(classTypeDescriptor.IsStruct == 1 ? LF_STRUCTURE : LF_CLASS))
            {
                Debug.Assert(memberCount <= ushort.MaxValue);
                record.Write((ushort)memberCount); // Number of elements in class
                record.Write((ushort)0); // Class options (CodeViewPropertyFlags)
                record.Write(fieldListTypeIndex); // Field descriptor index
                record.Write((uint)0); // Derived-from descriptor index
                record.Write((uint)0); // Vtshape descriptor index
                record.WriteEncodedInteger(classFieldsTypeDescriptor.Size); // Size
                record.Write(classTypeDescriptor.Name);
            }

            uint typeIndex = _nextTypeIndex++;
            _userDefinedTypes.Add((classTypeDescriptor.Name, typeIndex));

            return typeIndex;
        }

        public uint GetMemberFunctionTypeIndex(MemberFunctionTypeDescriptor memberDescriptor, uint[] argumentTypes)
        {
            using (LeafRecordWriter fieldListRecord = StartLeafRecord(LF_ARGLIST))
            {
                fieldListRecord.Write((uint)argumentTypes.Length);
                foreach (uint argumentType in argumentTypes)
                {
                    fieldListRecord.Write(argumentType);
                }
            }

            uint argumentListTypeIndex = _nextTypeIndex++;

            using (LeafRecordWriter record = StartLeafRecord(LF_MFUNCTION))
            {
                Debug.Assert(memberDescriptor.NumberOfArguments <= ushort.MaxValue);
                record.Write(memberDescriptor.ReturnType);
                record.Write(memberDescriptor.ContainingClass);
                record.Write(memberDescriptor.TypeIndexOfThisPointer);
                record.Write((byte)memberDescriptor.CallingConvention);
                // TODO: Evaluate if we should mark constructors
                record.Write((byte)0); // Function options (CV_funcattr_t)
                record.Write((ushort)memberDescriptor.NumberOfArguments);
                record.Write(argumentListTypeIndex);
                record.Write((uint)memberDescriptor.ThisAdjust);
            }

            return _nextTypeIndex++;
        }

        public uint GetMemberFunctionId(MemberFunctionIdTypeDescriptor memberIdDescriptor)
        {
            using (LeafRecordWriter record = StartLeafRecord(LF_MFUNC_ID))
            {
                record.Write(memberIdDescriptor.ParentClass);
                record.Write(memberIdDescriptor.MemberFunction);
                record.Write(memberIdDescriptor.Name);
            }

            return _nextTypeIndex++;
        }

        public Utf8String GetMangledName(TypeDesc type)
        {
            return _nameMangler.GetMangledTypeName(type);
        }

        private LeafRecordWriter StartLeafRecord(LeafRecordType leafRecordType)
        {
            return new LeafRecordWriter(this, leafRecordType);
        }

        private ref struct LeafRecordWriter
        {
            private CodeViewTypesBuilder _debugTypesBuilder;
            private LeafRecordType _leafRecordType;
            private ArrayBufferWriter<byte> _bufferWriter;
            private int _lastListMemberStart;

            public LeafRecordWriter(CodeViewTypesBuilder debugTypesBuilder, LeafRecordType leafRecordType)
            {
                _debugTypesBuilder = debugTypesBuilder;
                _leafRecordType = leafRecordType;
                _bufferWriter = new();
                Write((ushort)_leafRecordType);
            }

            public void Dispose()
            {
                int length = sizeof(ushort) + _bufferWriter.WrittenCount;
                int padding = ((length + 3) & ~3) - length;
                Debug.Assert(length <= ushort.MaxValue);
                Span<byte> lengthBuffer = stackalloc byte[sizeof(ushort)];
                BinaryPrimitives.WriteUInt16LittleEndian(lengthBuffer, (ushort)(length + padding - sizeof(ushort)));
                _debugTypesBuilder._sectionWriter.Write(lengthBuffer);
                _debugTypesBuilder._sectionWriter.Write(_bufferWriter.WrittenSpan);
                _debugTypesBuilder._sectionWriter.WritePadding(padding);
                _bufferWriter.Clear();
            }

            public void Write(byte value)
            {
                _bufferWriter.GetSpan(1)[0] = value;
                _bufferWriter.Advance(1);
            }

            public void Write(ushort value)
            {
                BinaryPrimitives.WriteUInt16LittleEndian(_bufferWriter.GetSpan(sizeof(ushort)), value);
                _bufferWriter.Advance(sizeof(ushort));
            }

            public void Write(uint value)
            {
                BinaryPrimitives.WriteUInt32LittleEndian(_bufferWriter.GetSpan(sizeof(uint)), value);
                _bufferWriter.Advance(sizeof(uint));
            }

            public void Write(ulong value)
            {
                BinaryPrimitives.WriteUInt64LittleEndian(_bufferWriter.GetSpan(sizeof(ulong)), value);
                _bufferWriter.Advance(sizeof(ulong));
            }

            public void Write(Utf8String value)
            {
                int byteCount = value.Length + 1;
                value.AsSpan().CopyTo(_bufferWriter.GetSpan(byteCount));
                _bufferWriter.Advance(byteCount);
            }

            public void Write(string value)
            {
                int byteCount = Encoding.UTF8.GetByteCount(value) + 1;
                Encoding.UTF8.GetBytes(value, _bufferWriter.GetSpan(byteCount));
                _bufferWriter.Advance(byteCount);
            }

            public void WriteEncodedInteger(ulong value)
            {
                if (value < (ushort)LF_NUMERIC)
                {
                    Write((ushort)value);
                }
                else if (value <= ushort.MaxValue)
                {
                    Write((ushort)LF_USHORT);
                    Write((ushort)value);
                }
                else if (value <= uint.MaxValue)
                {
                    Write((ushort)LF_ULONG);
                    Write((uint)value);
                }
                else
                {
                    Write((ushort)LF_UQUADWORD);
                    Write(value);
                }
            }

            public void Write(CodeViewType value) => Write((uint)value);

            private void WritePadding()
            {
                int paddingLength = ((_bufferWriter.WrittenCount - 2 + 3) & ~3) - (_bufferWriter.WrittenCount - 2);
                Span<byte> padding = _bufferWriter.GetSpan(paddingLength);
                for (int i = 0; i < paddingLength; i++)
                {
                    padding[i] = (byte)(LF_PAD0 + paddingLength - i);
                }
                _bufferWriter.Advance(paddingLength);
            }

            public void StartListEntry(LeafRecordType recordType)
            {
                Debug.Assert(_leafRecordType == LF_FIELDLIST || _leafRecordType == LF_METHODLIST);

                _lastListMemberStart = _bufferWriter.WrittenCount;
                Write((ushort)recordType);
            }

            public void EndListEntry()
            {
                Debug.Assert(_leafRecordType == LF_FIELDLIST || _leafRecordType == LF_METHODLIST);
                Debug.Assert(_lastListMemberStart > 0);

                WritePadding();

                // If the current list record overflows the maximum list length then emit a
                // LF_FIELDLIST/LF_METHODLIST leaf record now and start a new one. The new one
                // has LF_INDEX as the first element to chain the lists together.
                if (_bufferWriter.WrittenCount > short.MaxValue - sizeof(ushort))
                {
                    // At least one record was already written in the current list.
                    Debug.Assert(_lastListMemberStart > sizeof(ushort));

                    // Flush the current record up to _lastListMemberStart and write LF_INDEX to reference it.
                    int length = sizeof(ushort) + _lastListMemberStart;
                    int padding = ((length + 3) & ~3) - length;
                    _debugTypesBuilder._sectionWriter.WriteLittleEndian<ushort>((ushort)(length + padding - sizeof(ushort)));
                    _debugTypesBuilder._sectionWriter.Write(_bufferWriter.WrittenSpan.Slice(0, _lastListMemberStart));
                    _debugTypesBuilder._sectionWriter.WritePadding(padding);
                    byte[] overflow = _bufferWriter.WrittenSpan.Slice(_lastListMemberStart).ToArray();
                    _bufferWriter.Clear();

                    Write((ushort)_leafRecordType);
                    Write((ushort)LF_INDEX);
                    Write((ushort)0); // Padding
                    Write((uint)_debugTypesBuilder._nextTypeIndex++);
                    overflow.CopyTo(_bufferWriter.GetSpan(overflow.Length));
                    _bufferWriter.Advance(overflow.Length);
                    _lastListMemberStart = _bufferWriter.WrittenCount;
                }
            }
        }
    }
}