File: src\Dependencies\CodeAnalysis.Debugging\CustomDebugInfoEncoder.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
 
namespace Microsoft.CodeAnalysis.Debugging
{
    internal struct CustomDebugInfoEncoder
    {
        public BlobBuilder Builder { get; }
 
        private readonly Blob _recordCountFixup;
        private int _recordCount;
 
        public CustomDebugInfoEncoder(BlobBuilder builder)
        {
            Debug.Assert(builder.Count == 0);
 
            Builder = builder;
            _recordCount = 0;
 
            // header:
            builder.WriteByte(CustomDebugInfoConstants.Version);
 
            // reserve byte for record count:
            _recordCountFixup = builder.ReserveBytes(1);
 
            // alignment:
            builder.WriteInt16(0);
        }
 
        public readonly int RecordCount => _recordCount;
 
        /// <exception cref="InvalidOperationException">More than <see cref="byte.MaxValue"/> records added.</exception>
        public readonly byte[] ToArray()
        {
            if (_recordCount == 0)
            {
                return null;
            }
 
            Debug.Assert(_recordCount <= byte.MaxValue);
            new BlobWriter(_recordCountFixup).WriteByte((byte)_recordCount);
            return Builder.ToArray();
        }
 
        public void AddStateMachineTypeName(string typeName)
        {
            Debug.Assert(typeName != null);
 
            AddRecord(
                CustomDebugInfoKind.StateMachineTypeName,
                typeName,
                (name, builder) =>
                {
                    builder.WriteUTF16(name);
                    builder.WriteInt16(0);
                });
        }
 
        public void AddForwardMethodInfo(MethodDefinitionHandle methodHandle)
        {
            AddRecord(
                CustomDebugInfoKind.ForwardMethodInfo,
                methodHandle,
                (mh, builder) => builder.WriteInt32(MetadataTokens.GetToken(mh)));
        }
 
        public void AddForwardModuleInfo(MethodDefinitionHandle methodHandle)
        {
            AddRecord(
                CustomDebugInfoKind.ForwardModuleInfo,
                methodHandle,
                (mh, builder) => builder.WriteInt32(MetadataTokens.GetToken(mh)));
        }
 
        public void AddUsingGroups(IReadOnlyCollection<int> groupSizes)
        {
            Debug.Assert(groupSizes.Count <= ushort.MaxValue);
 
            // This originally wrote (uint)12, (ushort)1, (ushort)0 in the
            // case where usingCounts was empty, but I'm not sure why.
            if (groupSizes.Count == 0)
            {
                return;
            }
 
            AddRecord(
                CustomDebugInfoKind.UsingGroups,
                groupSizes,
                (uc, builder) =>
                {
                    builder.WriteUInt16((ushort)uc.Count);
                    foreach (var usingCount in uc)
                    {
                        Debug.Assert(usingCount <= ushort.MaxValue);
                        builder.WriteUInt16((ushort)usingCount);
                    }
                });
        }
 
        public void AddStateMachineHoistedLocalScopes(ImmutableArray<StateMachineHoistedLocalScope> scopes)
        {
            if (scopes.IsDefaultOrEmpty)
            {
                return;
            }
 
            AddRecord(
                CustomDebugInfoKind.StateMachineHoistedLocalScopes,
                scopes,
                (s, builder) =>
                {
                    builder.WriteInt32(s.Length);
                    foreach (var scope in s)
                    {
                        if (scope.IsDefault)
                        {
                            builder.WriteInt32(0);
                            builder.WriteInt32(0);
                        }
                        else
                        {
                            // Dev12 C# emits end-inclusive range
                            builder.WriteInt32(scope.StartOffset);
                            builder.WriteInt32(scope.EndOffset - 1);
                        }
                    }
                });
        }
 
        internal const int DynamicAttributeSize = 64;
        internal const int IdentifierSize = 64;
 
        public void AddDynamicLocals(IReadOnlyCollection<(string LocalName, byte[] Flags, int Count, int SlotIndex)> dynamicLocals)
        {
            Debug.Assert(dynamicLocals != null);
 
            AddRecord(
                CustomDebugInfoKind.DynamicLocals,
                dynamicLocals,
                (infos, builder) =>
                {
                    builder.WriteInt32(infos.Count);
 
                    foreach (var info in infos)
                    {
                        Debug.Assert(info.Flags.Length <= DynamicAttributeSize);
                        Debug.Assert(info.LocalName.Length <= IdentifierSize);
 
                        builder.WriteBytes(info.Flags);
                        builder.WriteBytes(0, sizeof(byte) * (DynamicAttributeSize - info.Flags.Length));
                        builder.WriteInt32(info.Count);
                        builder.WriteInt32(info.SlotIndex);
                        builder.WriteUTF16(info.LocalName);
                        builder.WriteBytes(0, sizeof(char) * (IdentifierSize - info.LocalName.Length));
                    }
                });
        }
 
        public void AddTupleElementNames(IReadOnlyCollection<(string LocalName, int SlotIndex, int ScopeStart, int ScopeEnd, ImmutableArray<string> Names)> tupleLocals)
        {
            Debug.Assert(tupleLocals != null);
 
            AddRecord(
                CustomDebugInfoKind.TupleElementNames,
                tupleLocals,
                (infos, builder) =>
                {
                    Debug.Assert(infos.Count > 0);
 
                    builder.WriteInt32(infos.Count);
                    foreach (var info in infos)
                    {
                        // Constants have slot index -1 and scope specified,
                        // variables have a slot index specified and no scope.
                        Debug.Assert((info.SlotIndex == -1) ^ (info.ScopeStart == 0 && info.ScopeEnd == 0));
 
                        builder.WriteInt32(info.Names.Length);
                        foreach (var name in info.Names)
                        {
                            if (name != null)
                            {
                                builder.WriteUTF8(name);
                            }
 
                            builder.WriteByte(0);
                        }
 
                        builder.WriteInt32(info.SlotIndex);
                        builder.WriteInt32(info.ScopeStart);
                        builder.WriteInt32(info.ScopeEnd);
                        if (info.LocalName != null)
                        {
                            builder.WriteUTF8(info.LocalName);
                        }
 
                        builder.WriteByte(0);
                    }
                });
        }
 
        public void AddRecord<T>(
            CustomDebugInfoKind kind,
            T debugInfo,
            Action<T, BlobBuilder> recordSerializer)
        {
            var startOffset = Builder.Count;
            Builder.WriteByte(CustomDebugInfoConstants.Version);
            Builder.WriteByte((byte)kind);
            Builder.WriteByte(0);
 
            // alignment size and length (will be patched)
            var alignmentSizeAndLengthWriter = new BlobWriter(Builder.ReserveBytes(sizeof(byte) + sizeof(uint)));
 
            recordSerializer(debugInfo, Builder);
 
            var length = Builder.Count - startOffset;
            var alignedLength = 4 * ((length + 3) / 4);
            var alignmentSize = (byte)(alignedLength - length);
            Builder.WriteBytes(0, alignmentSize);
 
            // Fill in alignment size and length. 
            // For backward compat, alignment size should only be emitted for records introduced since Roslyn. 
            alignmentSizeAndLengthWriter.WriteByte((kind > CustomDebugInfoKind.DynamicLocals) ? alignmentSize : (byte)0);
            alignmentSizeAndLengthWriter.WriteUInt32((uint)alignedLength);
 
            _recordCount++;
        }
    }
}