File: PEWriter\MetadataWriter.PortablePdb.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// 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.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Debugging;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.DiaSymReader;
using Roslyn.Utilities;
 
namespace Microsoft.Cci
{
    internal partial class MetadataWriter
    {
        /// <summary>
        /// Import scopes are associated with binders (in C#) and thus multiple instances might be created for a single set of imports.
        /// We consider scopes with the same parent and the same imports the same.
        /// Internal for testing.
        /// </summary>
        internal sealed class ImportScopeEqualityComparer : IEqualityComparer<IImportScope>
        {
            private readonly EmitContext _context;
 
            public ImportScopeEqualityComparer(EmitContext context)
            {
                _context = context;
            }
 
            public bool Equals(IImportScope x, IImportScope y)
            {
                return (object)x == y ||
                       x != null && y != null && Equals(x.Parent, y.Parent) && x.GetUsedNamespaces(_context).SequenceEqual(y.GetUsedNamespaces(_context));
            }
 
            public int GetHashCode(IImportScope obj)
            {
                return Hash.Combine(Hash.CombineValues(obj.GetUsedNamespaces(_context)), obj.Parent != null ? GetHashCode(obj.Parent) : 0);
            }
        }
 
        private readonly Dictionary<DebugSourceDocument, DocumentHandle> _documentIndex = new Dictionary<DebugSourceDocument, DocumentHandle>();
        private readonly Dictionary<IImportScope, ImportScopeHandle> _scopeIndex;
 
        private void SerializeMethodDebugInfo(IMethodBody bodyOpt, int methodRid, int aggregateMethodRid, StandaloneSignatureHandle localSignatureHandleOpt, ref LocalVariableHandle lastLocalVariableHandle, ref LocalConstantHandle lastLocalConstantHandle)
        {
            if (bodyOpt == null)
            {
                _debugMetadataOpt.AddMethodDebugInformation(document: default, sequencePoints: default);
                return;
            }
 
            bool isKickoffMethod = bodyOpt.StateMachineTypeName != null;
            bool emitAllDebugInfo = isKickoffMethod || !bodyOpt.SequencePoints.IsEmpty;
 
            var methodHandle = MetadataTokens.MethodDefinitionHandle(methodRid);
 
            // documents & sequence points:
            BlobHandle sequencePointsBlob = SerializeSequencePoints(localSignatureHandleOpt, bodyOpt.SequencePoints, _documentIndex, out var singleDocumentHandle);
            _debugMetadataOpt.AddMethodDebugInformation(document: singleDocumentHandle, sequencePoints: sequencePointsBlob);
 
            if (emitAllDebugInfo)
            {
                var bodyImportScope = bodyOpt.ImportScope;
                var importScopeHandle = (bodyImportScope != null) ? GetImportScopeIndex(bodyImportScope, _scopeIndex) : default;
 
                // Unlike native PDB we don't emit an empty root scope.
                // scopes are already ordered by StartOffset ascending then by EndOffset descending (the longest scope first).
 
                if (bodyOpt.LocalScopes.Length == 0)
                {
                    // TODO: the compiler should produce a scope for each debuggable method rather then adding one here
                    _debugMetadataOpt.AddLocalScope(
                        method: methodHandle,
                        importScope: importScopeHandle,
                        variableList: NextHandle(lastLocalVariableHandle),
                        constantList: NextHandle(lastLocalConstantHandle),
                        startOffset: 0,
                        length: bodyOpt.IL.Length);
                }
                else
                {
                    foreach (LocalScope scope in bodyOpt.LocalScopes)
                    {
                        _debugMetadataOpt.AddLocalScope(
                            method: methodHandle,
                            importScope: importScopeHandle,
                            variableList: NextHandle(lastLocalVariableHandle),
                            constantList: NextHandle(lastLocalConstantHandle),
                            startOffset: scope.StartOffset,
                            length: scope.Length);
 
                        foreach (ILocalDefinition local in scope.Variables)
                        {
                            Debug.Assert(local.SlotIndex >= 0);
 
                            lastLocalVariableHandle = _debugMetadataOpt.AddLocalVariable(
                                attributes: local.PdbAttributes,
                                index: local.SlotIndex,
                                name: _debugMetadataOpt.GetOrAddString(local.Name));
 
                            SerializeLocalInfo(local, lastLocalVariableHandle);
                        }
 
                        foreach (ILocalDefinition constant in scope.Constants)
                        {
                            var mdConstant = constant.CompileTimeValue;
                            Debug.Assert(mdConstant != null);
 
                            lastLocalConstantHandle = _debugMetadataOpt.AddLocalConstant(
                                name: _debugMetadataOpt.GetOrAddString(constant.Name),
                                signature: SerializeLocalConstantSignature(constant));
 
                            SerializeLocalInfo(constant, lastLocalConstantHandle);
                        }
                    }
                }
 
                var moveNextBodyInfo = bodyOpt.MoveNextBodyInfo;
                if (moveNextBodyInfo != null)
                {
                    _debugMetadataOpt.AddStateMachineMethod(
                        moveNextMethod: methodHandle,
                        kickoffMethod: GetMethodDefinitionHandle(moveNextBodyInfo.KickoffMethod));
 
                    if (moveNextBodyInfo is AsyncMoveNextBodyDebugInfo asyncInfo)
                    {
                        SerializeAsyncMethodSteppingInfo(asyncInfo, methodHandle, aggregateMethodRid);
                    }
                }
 
                if (bodyOpt.IsPrimaryConstructor)
                {
                    _debugMetadataOpt.AddCustomDebugInformation(
                        parent: methodHandle,
                        kind: _debugMetadataOpt.GetOrAddGuid(PortableCustomDebugInfoKinds.PrimaryConstructorInformationBlob),
                        value: default(BlobHandle));
                }
 
                SerializeStateMachineLocalScopes(bodyOpt, methodHandle);
            }
 
            // Emit EnC info for all methods even if they do not have sequence points.
            // The information facilitates reusing lambdas and closures. The reuse is important for runtimes that can't add new members (e.g. Mono).
            // EnC delta doesn't need this information - we use information recorded by previous generation emit.
            if (Context.Module.CommonCompilation.Options.EnableEditAndContinue && IsFullMetadata)
            {
                SerializeEncMethodDebugInformation(bodyOpt, methodHandle);
            }
        }
 
        private static LocalVariableHandle NextHandle(LocalVariableHandle handle) =>
            MetadataTokens.LocalVariableHandle(MetadataTokens.GetRowNumber(handle) + 1);
 
        private static LocalConstantHandle NextHandle(LocalConstantHandle handle) =>
            MetadataTokens.LocalConstantHandle(MetadataTokens.GetRowNumber(handle) + 1);
 
        private BlobHandle SerializeLocalConstantSignature(ILocalDefinition localConstant)
        {
            var builder = new BlobBuilder();
 
            // TODO: BlobEncoder.LocalConstantSignature
 
            // CustomMod*
            var encoder = new CustomModifiersEncoder(builder);
            SerializeCustomModifiers(encoder, localConstant.CustomModifiers);
 
            var type = localConstant.Type;
            var typeCode = type.TypeCode;
 
            object value = localConstant.CompileTimeValue.Value;
 
            // PrimitiveConstant or EnumConstant
            if (value is decimal)
            {
                builder.WriteByte((byte)SignatureTypeKind.ValueType);
                builder.WriteCompressedInteger(CodedIndex.TypeDefOrRefOrSpec(GetTypeHandle(type)));
 
                builder.WriteDecimal((decimal)value);
            }
            else if (value is DateTime)
            {
                builder.WriteByte((byte)SignatureTypeKind.ValueType);
                builder.WriteCompressedInteger(CodedIndex.TypeDefOrRefOrSpec(GetTypeHandle(type)));
 
                builder.WriteDateTime((DateTime)value);
            }
            else if (typeCode == PrimitiveTypeCode.String)
            {
                builder.WriteByte((byte)ConstantTypeCode.String);
                if (value == null)
                {
                    builder.WriteByte(0xff);
                }
                else
                {
                    builder.WriteUTF16((string)value);
                }
            }
            else if (value != null)
            {
                // TypeCode
                builder.WriteByte((byte)GetConstantTypeCode(value));
 
                // Value
                builder.WriteConstant(value);
 
                // EnumType
                if (type.IsEnum)
                {
                    builder.WriteCompressedInteger(CodedIndex.TypeDefOrRefOrSpec(GetTypeHandle(type)));
                }
            }
            else if (this.module.IsPlatformType(type, PlatformType.SystemObject))
            {
                builder.WriteByte((byte)SignatureTypeCode.Object);
            }
            else
            {
                builder.WriteByte((byte)(type.IsValueType ? SignatureTypeKind.ValueType : SignatureTypeKind.Class));
                builder.WriteCompressedInteger(CodedIndex.TypeDefOrRefOrSpec(GetTypeHandle(type)));
            }
 
            return _debugMetadataOpt.GetOrAddBlob(builder);
        }
 
        private static SignatureTypeCode GetConstantTypeCode(object value)
        {
            if (value == null)
            {
                // The encoding of Type for the nullref value for FieldInit is ELEMENT_TYPE_CLASS with a Value of a zero.
                return (SignatureTypeCode)SignatureTypeKind.Class;
            }
 
            Debug.Assert(!value.GetType().GetTypeInfo().IsEnum);
 
            // Perf: Note that JIT optimizes each expression val.GetType() == typeof(T) to a single register comparison.
            // Also the checks are sorted by commonality of the checked types.
 
            if (value.GetType() == typeof(int))
            {
                return SignatureTypeCode.Int32;
            }
 
            if (value.GetType() == typeof(string))
            {
                return SignatureTypeCode.String;
            }
 
            if (value.GetType() == typeof(bool))
            {
                return SignatureTypeCode.Boolean;
            }
 
            if (value.GetType() == typeof(char))
            {
                return SignatureTypeCode.Char;
            }
 
            if (value.GetType() == typeof(byte))
            {
                return SignatureTypeCode.Byte;
            }
 
            if (value.GetType() == typeof(long))
            {
                return SignatureTypeCode.Int64;
            }
 
            if (value.GetType() == typeof(double))
            {
                return SignatureTypeCode.Double;
            }
 
            if (value.GetType() == typeof(short))
            {
                return SignatureTypeCode.Int16;
            }
 
            if (value.GetType() == typeof(ushort))
            {
                return SignatureTypeCode.UInt16;
            }
 
            if (value.GetType() == typeof(uint))
            {
                return SignatureTypeCode.UInt32;
            }
 
            if (value.GetType() == typeof(sbyte))
            {
                return SignatureTypeCode.SByte;
            }
 
            if (value.GetType() == typeof(ulong))
            {
                return SignatureTypeCode.UInt64;
            }
 
            if (value.GetType() == typeof(float))
            {
                return SignatureTypeCode.Single;
            }
 
            throw ExceptionUtilities.Unreachable();
        }
 
        #region ImportScope
 
        private static readonly ImportScopeHandle ModuleImportScopeHandle = MetadataTokens.ImportScopeHandle(1);
 
        private void SerializeImport(BlobBuilder writer, AssemblyReferenceAlias alias)
        {
            // <import> ::= AliasAssemblyReference <alias> <target-assembly>
            writer.WriteByte((byte)ImportDefinitionKind.AliasAssemblyReference);
            writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(_debugMetadataOpt.GetOrAddBlobUTF8(alias.Name)));
            writer.WriteCompressedInteger(MetadataTokens.GetRowNumber(GetOrAddAssemblyReferenceHandle(alias.Assembly)));
        }
 
        private void SerializeImport(BlobBuilder writer, UsedNamespaceOrType import)
        {
            if (import.TargetXmlNamespaceOpt != null)
            {
                Debug.Assert(import.TargetNamespaceOpt == null);
                Debug.Assert(import.TargetAssemblyOpt == null);
                Debug.Assert(import.TargetTypeOpt == null);
 
                // <import> ::= ImportXmlNamespace <alias> <target-namespace>
                writer.WriteByte((byte)ImportDefinitionKind.ImportXmlNamespace);
                writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(_debugMetadataOpt.GetOrAddBlobUTF8(import.AliasOpt)));
                writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(_debugMetadataOpt.GetOrAddBlobUTF8(import.TargetXmlNamespaceOpt)));
            }
            else if (import.TargetTypeOpt != null)
            {
                Debug.Assert(import.TargetNamespaceOpt == null);
                Debug.Assert(import.TargetAssemblyOpt == null);
 
                if (import.AliasOpt != null)
                {
                    // <import> ::= AliasType <alias> <target-type>
                    writer.WriteByte((byte)ImportDefinitionKind.AliasType);
                    writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(_debugMetadataOpt.GetOrAddBlobUTF8(import.AliasOpt)));
                }
                else
                {
                    // <import> ::= ImportType <target-type>
                    writer.WriteByte((byte)ImportDefinitionKind.ImportType);
                }
 
                writer.WriteCompressedInteger(CodedIndex.TypeDefOrRefOrSpec(GetTypeHandle(import.TargetTypeOpt))); // TODO: index in release build
            }
            else if (import.TargetNamespaceOpt != null)
            {
                if (import.TargetAssemblyOpt != null)
                {
                    if (import.AliasOpt != null)
                    {
                        // <import> ::= AliasAssemblyNamespace <alias> <target-assembly> <target-namespace>
                        writer.WriteByte((byte)ImportDefinitionKind.AliasAssemblyNamespace);
                        writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(_debugMetadataOpt.GetOrAddBlobUTF8(import.AliasOpt)));
                    }
                    else
                    {
                        // <import> ::= ImportAssemblyNamespace <target-assembly> <target-namespace>
                        writer.WriteByte((byte)ImportDefinitionKind.ImportAssemblyNamespace);
                    }
 
                    writer.WriteCompressedInteger(MetadataTokens.GetRowNumber(GetAssemblyReferenceHandle(import.TargetAssemblyOpt)));
                }
                else
                {
                    if (import.AliasOpt != null)
                    {
                        // <import> ::= AliasNamespace <alias> <target-namespace>
                        writer.WriteByte((byte)ImportDefinitionKind.AliasNamespace);
                        writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(_debugMetadataOpt.GetOrAddBlobUTF8(import.AliasOpt)));
                    }
                    else
                    {
                        // <import> ::= ImportNamespace <target-namespace>
                        writer.WriteByte((byte)ImportDefinitionKind.ImportNamespace);
                    }
                }
 
                // TODO: cache?
                string namespaceName = TypeNameSerializer.BuildQualifiedNamespaceName(import.TargetNamespaceOpt);
                writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(_debugMetadataOpt.GetOrAddBlobUTF8(namespaceName)));
            }
            else
            {
                // <import> ::= ImportReferenceAlias <alias>
                Debug.Assert(import.AliasOpt != null);
                Debug.Assert(import.TargetAssemblyOpt == null);
 
                writer.WriteByte((byte)ImportDefinitionKind.ImportAssemblyReferenceAlias);
                writer.WriteCompressedInteger(MetadataTokens.GetHeapOffset(_debugMetadataOpt.GetOrAddBlobUTF8(import.AliasOpt)));
            }
        }
 
        private void DefineModuleImportScope()
        {
            // module-level import scope:
            var writer = new BlobBuilder();
 
            SerializeModuleDefaultNamespace();
 
            foreach (AssemblyReferenceAlias alias in module.GetAssemblyReferenceAliases(Context))
            {
                SerializeImport(writer, alias);
            }
 
            foreach (UsedNamespaceOrType import in module.GetImports())
            {
                SerializeImport(writer, import);
            }
 
            var rid = _debugMetadataOpt.AddImportScope(
                parentScope: default(ImportScopeHandle),
                imports: _debugMetadataOpt.GetOrAddBlob(writer));
 
            Debug.Assert(rid == ModuleImportScopeHandle);
        }
 
        private ImportScopeHandle GetImportScopeIndex(IImportScope scope, Dictionary<IImportScope, ImportScopeHandle> scopeIndex)
        {
            ImportScopeHandle scopeHandle;
            if (scopeIndex.TryGetValue(scope, out scopeHandle))
            {
                // scope is already indexed:
                return scopeHandle;
            }
 
            var parent = scope.Parent;
            var parentScopeHandle = (parent != null) ? GetImportScopeIndex(scope.Parent, scopeIndex) : ModuleImportScopeHandle;
 
            var result = _debugMetadataOpt.AddImportScope(
                parentScope: parentScopeHandle,
                imports: SerializeImportsBlob(scope));
 
            scopeIndex.Add(scope, result);
            return result;
        }
 
        private BlobHandle SerializeImportsBlob(IImportScope scope)
        {
            var writer = new BlobBuilder();
 
            foreach (UsedNamespaceOrType import in scope.GetUsedNamespaces(Context))
            {
                SerializeImport(writer, import);
            }
 
            return _debugMetadataOpt.GetOrAddBlob(writer);
        }
 
        private void SerializeModuleDefaultNamespace()
        {
            // C#: DefaultNamespace is null.
            // VB: DefaultNamespace is non-null.
            if (module.DefaultNamespace == null)
            {
                return;
            }
 
            _debugMetadataOpt.AddCustomDebugInformation(
                parent: EntityHandle.ModuleDefinition,
                kind: _debugMetadataOpt.GetOrAddGuid(PortableCustomDebugInfoKinds.DefaultNamespace),
                value: _debugMetadataOpt.GetOrAddBlobUTF8(module.DefaultNamespace));
        }
 
        #endregion
 
        #region Locals
 
        private void SerializeLocalInfo(ILocalDefinition local, EntityHandle parent)
        {
            var dynamicFlags = local.DynamicTransformFlags;
            if (!dynamicFlags.IsEmpty)
            {
                var value = SerializeBitVector(dynamicFlags);
 
                _debugMetadataOpt.AddCustomDebugInformation(
                    parent: parent,
                    kind: _debugMetadataOpt.GetOrAddGuid(PortableCustomDebugInfoKinds.DynamicLocalVariables),
                    value: _debugMetadataOpt.GetOrAddBlob(value));
            }
 
            var tupleElementNames = local.TupleElementNames;
            if (!tupleElementNames.IsEmpty)
            {
                var builder = new BlobBuilder();
                SerializeTupleElementNames(builder, tupleElementNames);
 
                _debugMetadataOpt.AddCustomDebugInformation(
                    parent: parent,
                    kind: _debugMetadataOpt.GetOrAddGuid(PortableCustomDebugInfoKinds.TupleElementNames),
                    value: _debugMetadataOpt.GetOrAddBlob(builder));
            }
        }
 
        private static ImmutableArray<byte> SerializeBitVector(ImmutableArray<bool> vector)
        {
            var builder = ArrayBuilder<byte>.GetInstance();
 
            int b = 0;
            int shift = 0;
            for (int i = 0; i < vector.Length; i++)
            {
                if (vector[i])
                {
                    b |= 1 << shift;
                }
 
                if (shift == 7)
                {
                    builder.Add((byte)b);
                    b = 0;
                    shift = 0;
                }
                else
                {
                    shift++;
                }
            }
 
            if (b != 0)
            {
                builder.Add((byte)b);
            }
            else
            {
                // trim trailing zeros:
                int lastNonZero = builder.Count - 1;
                while (builder[lastNonZero] == 0)
                {
                    lastNonZero--;
                }
 
                builder.Clip(lastNonZero + 1);
            }
 
            return builder.ToImmutableAndFree();
        }
 
        private static void SerializeTupleElementNames(BlobBuilder builder, ImmutableArray<string> names)
        {
            foreach (var name in names)
            {
                WriteUtf8String(builder, name ?? string.Empty);
            }
        }
 
        /// <summary>
        /// Write string as UTF-8 with null terminator.
        /// </summary>
        private static void WriteUtf8String(BlobBuilder builder, string str)
        {
            builder.WriteUTF8(str);
            builder.WriteByte(0);
        }
 
        #endregion
 
        #region State Machines
 
        private void SerializeAsyncMethodSteppingInfo(AsyncMoveNextBodyDebugInfo asyncInfo, MethodDefinitionHandle moveNextMethod, int aggregateMethodDefRid)
        {
            Debug.Assert(asyncInfo.ResumeOffsets.Length == asyncInfo.YieldOffsets.Length);
            Debug.Assert(asyncInfo.CatchHandlerOffset >= -1);
 
            var writer = new BlobBuilder();
 
            writer.WriteUInt32((uint)((long)asyncInfo.CatchHandlerOffset + 1));
 
            for (int i = 0; i < asyncInfo.ResumeOffsets.Length; i++)
            {
                writer.WriteUInt32((uint)asyncInfo.YieldOffsets[i]);
                writer.WriteUInt32((uint)asyncInfo.ResumeOffsets[i]);
                writer.WriteCompressedInteger(aggregateMethodDefRid);
            }
 
            _debugMetadataOpt.AddCustomDebugInformation(
                parent: moveNextMethod,
                kind: _debugMetadataOpt.GetOrAddGuid(PortableCustomDebugInfoKinds.AsyncMethodSteppingInformationBlob),
                value: _debugMetadataOpt.GetOrAddBlob(writer));
        }
 
        private void SerializeStateMachineLocalScopes(IMethodBody methodBody, MethodDefinitionHandle method)
        {
            var scopes = methodBody.StateMachineHoistedLocalScopes;
            if (scopes.IsDefaultOrEmpty)
            {
                return;
            }
 
            var writer = new BlobBuilder();
 
            foreach (var scope in scopes)
            {
                writer.WriteUInt32((uint)scope.StartOffset);
                writer.WriteUInt32((uint)scope.Length);
            }
 
            _debugMetadataOpt.AddCustomDebugInformation(
                parent: method,
                kind: _debugMetadataOpt.GetOrAddGuid(PortableCustomDebugInfoKinds.StateMachineHoistedLocalScopes),
                value: _debugMetadataOpt.GetOrAddBlob(writer));
        }
 
        #endregion
 
        #region Sequence Points
 
        private BlobHandle SerializeSequencePoints(
            StandaloneSignatureHandle localSignatureHandleOpt,
            ImmutableArray<SequencePoint> sequencePoints,
            Dictionary<DebugSourceDocument, DocumentHandle> documentIndex,
            out DocumentHandle singleDocumentHandle)
        {
            if (sequencePoints.Length == 0)
            {
                singleDocumentHandle = default(DocumentHandle);
                return default(BlobHandle);
            }
 
            var writer = PooledBlobBuilder.GetInstance();
 
            int previousNonHiddenStartLine = -1;
            int previousNonHiddenStartColumn = -1;
 
            // header:
            writer.WriteCompressedInteger(MetadataTokens.GetRowNumber(localSignatureHandleOpt));
 
            var previousDocument = TryGetSingleDocument(sequencePoints);
            singleDocumentHandle = (previousDocument != null) ? GetOrAddDocument(previousDocument, documentIndex) : default(DocumentHandle);
 
            for (int i = 0; i < sequencePoints.Length; i++)
            {
                var currentDocument = sequencePoints[i].Document;
                if (previousDocument != currentDocument)
                {
                    var documentHandle = GetOrAddDocument(currentDocument, documentIndex);
 
                    // optional document in header or document record:
                    if (previousDocument != null)
                    {
                        writer.WriteCompressedInteger(0);
                    }
 
                    writer.WriteCompressedInteger(MetadataTokens.GetRowNumber(documentHandle));
                    previousDocument = currentDocument;
                }
 
                // delta IL offset:
                if (i > 0)
                {
                    writer.WriteCompressedInteger(sequencePoints[i].Offset - sequencePoints[i - 1].Offset);
                }
                else
                {
                    writer.WriteCompressedInteger(sequencePoints[i].Offset);
                }
 
                if (sequencePoints[i].IsHidden)
                {
                    writer.WriteInt16(0);
                    continue;
                }
 
                // Delta Lines & Columns:
                SerializeDeltaLinesAndColumns(writer, sequencePoints[i]);
 
                // delta Start Lines & Columns:
                if (previousNonHiddenStartLine < 0)
                {
                    Debug.Assert(previousNonHiddenStartColumn < 0);
                    writer.WriteCompressedInteger(sequencePoints[i].StartLine);
                    writer.WriteCompressedInteger(sequencePoints[i].StartColumn);
                }
                else
                {
                    writer.WriteCompressedSignedInteger(sequencePoints[i].StartLine - previousNonHiddenStartLine);
                    writer.WriteCompressedSignedInteger(sequencePoints[i].StartColumn - previousNonHiddenStartColumn);
                }
 
                previousNonHiddenStartLine = sequencePoints[i].StartLine;
                previousNonHiddenStartColumn = sequencePoints[i].StartColumn;
            }
 
            return _debugMetadataOpt.GetOrAddBlobAndFree(writer);
        }
 
        private static DebugSourceDocument TryGetSingleDocument(ImmutableArray<SequencePoint> sequencePoints)
        {
            DebugSourceDocument singleDocument = sequencePoints[0].Document;
            for (int i = 1; i < sequencePoints.Length; i++)
            {
                if (sequencePoints[i].Document != singleDocument)
                {
                    return null;
                }
            }
 
            return singleDocument;
        }
 
        private void SerializeDeltaLinesAndColumns(BlobBuilder writer, SequencePoint sequencePoint)
        {
            int deltaLines = sequencePoint.EndLine - sequencePoint.StartLine;
            int deltaColumns = sequencePoint.EndColumn - sequencePoint.StartColumn;
 
            // only hidden sequence points have zero width
            Debug.Assert(deltaLines != 0 || deltaColumns != 0 || sequencePoint.IsHidden);
 
            writer.WriteCompressedInteger(deltaLines);
 
            if (deltaLines == 0)
            {
                writer.WriteCompressedInteger(deltaColumns);
            }
            else
            {
                writer.WriteCompressedSignedInteger(deltaColumns);
            }
        }
 
        #endregion
 
        #region Documents
 
        private DocumentHandle GetOrAddDocument(DebugSourceDocument document, Dictionary<DebugSourceDocument, DocumentHandle> index)
        {
            if (index.TryGetValue(document, out var documentHandle))
            {
                return documentHandle;
            }
 
            return AddDocument(document, index);
        }
 
        private DocumentHandle AddDocument(DebugSourceDocument document, Dictionary<DebugSourceDocument, DocumentHandle> index)
        {
            DocumentHandle documentHandle;
            DebugSourceInfo info = document.GetSourceInfo();
 
            var name = document.Location;
            if (_usingNonSourceDocumentNameEnumerator)
            {
                var result = _nonSourceDocumentNameEnumerator.MoveNext();
                Debug.Assert(result);
                name = _nonSourceDocumentNameEnumerator.Current;
            }
 
            documentHandle = _debugMetadataOpt.AddDocument(
                name: _debugMetadataOpt.GetOrAddDocumentName(name),
                hashAlgorithm: info.Checksum.IsDefault ? default(GuidHandle) : _debugMetadataOpt.GetOrAddGuid(info.ChecksumAlgorithmId),
                hash: info.Checksum.IsDefault ? default(BlobHandle) : _debugMetadataOpt.GetOrAddBlob(info.Checksum),
                language: _debugMetadataOpt.GetOrAddGuid(document.Language));
 
            index.Add(document, documentHandle);
 
            if (info.EmbeddedTextBlob != null)
            {
                _debugMetadataOpt.AddCustomDebugInformation(
                    parent: documentHandle,
                    kind: _debugMetadataOpt.GetOrAddGuid(PortableCustomDebugInfoKinds.EmbeddedSource),
                    value: _debugMetadataOpt.GetOrAddBlob(info.EmbeddedTextBlob));
            }
 
            return documentHandle;
        }
 
        /// <summary>
        /// Add document entries for all debug documents that do not yet have an entry.
        /// </summary>
        /// <remarks>
        /// This is done after serializing method debug info to ensure that we embed all requested
        /// text even if there are no corresponding sequence points.
        /// </remarks>
        public void AddRemainingDebugDocuments(IReadOnlyDictionary<string, DebugSourceDocument> documents)
        {
            foreach (var kvp in documents
                .Where(kvp => !_documentIndex.ContainsKey(kvp.Value))
                .OrderBy(kvp => kvp.Key))
            {
                AddDocument(kvp.Value, _documentIndex);
            }
        }
 
        #endregion
 
        #region Edit and Continue
 
        private void SerializeEncMethodDebugInformation(IMethodBody methodBody, MethodDefinitionHandle method)
        {
            var encInfo = GetEncMethodDebugInfo(methodBody);
 
            if (!encInfo.LocalSlots.IsDefaultOrEmpty)
            {
                var writer = PooledBlobBuilder.GetInstance();
 
                encInfo.SerializeLocalSlots(writer);
 
                _debugMetadataOpt.AddCustomDebugInformation(
                    parent: method,
                    kind: _debugMetadataOpt.GetOrAddGuid(PortableCustomDebugInfoKinds.EncLocalSlotMap),
                    value: _debugMetadataOpt.GetOrAddBlobAndFree(writer));
            }
 
            if (!encInfo.Lambdas.IsDefaultOrEmpty)
            {
                var writer = PooledBlobBuilder.GetInstance();
 
                encInfo.SerializeLambdaMap(writer);
 
                _debugMetadataOpt.AddCustomDebugInformation(
                    parent: method,
                    kind: _debugMetadataOpt.GetOrAddGuid(PortableCustomDebugInfoKinds.EncLambdaAndClosureMap),
                    value: _debugMetadataOpt.GetOrAddBlobAndFree(writer));
            }
 
            if (!encInfo.StateMachineStates.IsDefaultOrEmpty)
            {
                var writer = PooledBlobBuilder.GetInstance();
 
                encInfo.SerializeStateMachineStates(writer);
 
                _debugMetadataOpt.AddCustomDebugInformation(
                    parent: method,
                    kind: _debugMetadataOpt.GetOrAddGuid(PortableCustomDebugInfoKinds.EncStateMachineStateMap),
                    value: _debugMetadataOpt.GetOrAddBlobAndFree(writer));
            }
        }
 
        #endregion
 
        private void EmbedSourceLink(Stream stream)
        {
            byte[] bytes;
 
            try
            {
                bytes = stream.ReadAllBytes();
            }
            catch (Exception e) when (!(e is OperationCanceledException))
            {
                throw new SymUnmanagedWriterException(e.Message, e);
            }
 
            _debugMetadataOpt.AddCustomDebugInformation(
                parent: EntityHandle.ModuleDefinition,
                kind: _debugMetadataOpt.GetOrAddGuid(PortableCustomDebugInfoKinds.SourceLink),
                value: _debugMetadataOpt.GetOrAddBlob(bytes));
        }
 
        ///<summary>The version of the compilation options schema to be written to the PDB.</summary>
        private const int CompilationOptionsSchemaVersion = 2;
 
        /// <summary>
        /// Capture the set of compilation options to allow a compilation 
        /// to be reconstructed from the pdb
        /// </summary>
        private void EmbedCompilationOptions(CommonPEModuleBuilder module)
        {
            var builder = new BlobBuilder();
 
            if (this.Context.RebuildData is { } rebuildData)
            {
                var reader = rebuildData.OptionsBlobReader;
                builder.WriteBytes(reader.ReadBytes(reader.RemainingBytes));
            }
            else
            {
                var compilerVersion = typeof(Compilation).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
                WriteValue(CompilationOptionNames.CompilationOptionsVersion, CompilationOptionsSchemaVersion.ToString());
                WriteValue(CompilationOptionNames.CompilerVersion, compilerVersion);
 
                WriteValue(CompilationOptionNames.Language, module.CommonCompilation.Options.Language);
                WriteValue(CompilationOptionNames.SourceFileCount, module.CommonCompilation.SyntaxTrees.Count().ToString());
                WriteValue(CompilationOptionNames.OutputKind, module.OutputKind.ToString());
 
                if (module.EmitOptions.FallbackSourceFileEncoding != null)
                {
                    WriteValue(CompilationOptionNames.FallbackEncoding, module.EmitOptions.FallbackSourceFileEncoding.WebName);
                }
 
                if (module.EmitOptions.DefaultSourceFileEncoding != null)
                {
                    WriteValue(CompilationOptionNames.DefaultEncoding, module.EmitOptions.DefaultSourceFileEncoding.WebName);
                }
 
                int portabilityPolicy = 0;
                if (module.CommonCompilation.Options.AssemblyIdentityComparer is DesktopAssemblyIdentityComparer identityComparer)
                {
                    portabilityPolicy |= identityComparer.PortabilityPolicy.SuppressSilverlightLibraryAssembliesPortability ? 0b1 : 0;
                    portabilityPolicy |= identityComparer.PortabilityPolicy.SuppressSilverlightPlatformAssembliesPortability ? 0b10 : 0;
                }
 
                if (portabilityPolicy != 0)
                {
                    WriteValue(CompilationOptionNames.PortabilityPolicy, portabilityPolicy.ToString());
                }
 
                var optimizationLevel = module.CommonCompilation.Options.OptimizationLevel;
                var debugPlusMode = module.CommonCompilation.Options.DebugPlusMode;
                if ((optimizationLevel, debugPlusMode) != OptimizationLevelFacts.DefaultValues)
                {
                    WriteValue(CompilationOptionNames.Optimization, optimizationLevel.ToPdbSerializedString(debugPlusMode));
                }
 
                var platform = module.CommonCompilation.Options.Platform;
                WriteValue(CompilationOptionNames.Platform, platform.ToString());
 
                var runtimeVersion = typeof(object).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
                WriteValue(CompilationOptionNames.RuntimeVersion, runtimeVersion);
 
                module.CommonCompilation.SerializePdbEmbeddedCompilationOptions(builder);
            }
 
            _debugMetadataOpt.AddCustomDebugInformation(
                parent: EntityHandle.ModuleDefinition,
                kind: _debugMetadataOpt.GetOrAddGuid(PortableCustomDebugInfoKinds.CompilationOptions),
                value: _debugMetadataOpt.GetOrAddBlob(builder));
 
            void WriteValue(string key, string value)
            {
                builder.WriteUTF8(key);
                builder.WriteByte(0);
                builder.WriteUTF8(value);
                builder.WriteByte(0);
            }
        }
 
        /// <summary>
        /// Writes information about metadata references to the pdb so the same
        /// reference can be found on sourcelink to create the compilation again
        /// </summary>
        private void EmbedMetadataReferenceInformation(CommonPEModuleBuilder module)
        {
            var builder = new BlobBuilder();
 
            // Order of information
            // File name (null terminated string): A.exe
            // Extern Alias (null terminated string): a1,a2,a3
            // MetadataImageKind (byte)
            // EmbedInteropTypes (boolean)
            // COFF header Timestamp field (4 byte int)
            // COFF header SizeOfImage field (4 byte int)
            // MVID (Guid, 24 bytes)
            var referenceManager = module.CommonCompilation.GetBoundReferenceManager();
            foreach (var pair in referenceManager.GetReferencedAssemblyAliases())
            {
                if (referenceManager.GetMetadataReference(pair.AssemblySymbol) is PortableExecutableReference { FilePath: { } } portableReference)
                {
                    var fileName = PathUtilities.GetFileName(portableReference.FilePath);
                    var peReader = pair.AssemblySymbol.GetISymbol() is IAssemblySymbol assemblySymbol
                        ? assemblySymbol.GetMetadata().GetAssembly().ManifestModule.PEReaderOpt
                        : null;
 
                    // Don't write before checking that we can get a peReader for the metadata reference
                    if (peReader is null)
                        continue;
 
                    // Write file name first
                    builder.WriteUTF8(fileName);
 
                    // Make sure to add null terminator
                    builder.WriteByte(0);
 
                    // Extern alias
                    if (pair.Aliases.Length > 0)
                        builder.WriteUTF8(string.Join(",", pair.Aliases.OrderBy(StringComparer.Ordinal)));
 
                    // Always null terminate the extern alias list
                    builder.WriteByte(0);
 
                    byte kindAndEmbedInteropTypes = (byte)(portableReference.Properties.EmbedInteropTypes
                        ? 0b10
                        : 0b0);
 
                    kindAndEmbedInteropTypes |= portableReference.Properties.Kind switch
                    {
                        MetadataImageKind.Assembly => 1,
                        MetadataImageKind.Module => 0,
                        _ => throw ExceptionUtilities.UnexpectedValue(portableReference.Properties.Kind)
                    };
 
                    builder.WriteByte(kindAndEmbedInteropTypes);
                    builder.WriteInt32(peReader.PEHeaders.CoffHeader.TimeDateStamp);
                    builder.WriteInt32(peReader.PEHeaders.PEHeader.SizeOfImage);
 
                    var metadataReader = peReader.GetMetadataReader();
                    var moduleDefinition = metadataReader.GetModuleDefinition();
                    builder.WriteGuid(metadataReader.GetGuid(moduleDefinition.Mvid));
                }
            }
 
            _debugMetadataOpt.AddCustomDebugInformation(
                parent: EntityHandle.ModuleDefinition,
                kind: _debugMetadataOpt.GetOrAddGuid(PortableCustomDebugInfoKinds.CompilationMetadataReferences),
                value: _debugMetadataOpt.GetOrAddBlob(builder));
        }
 
        private void EmbedTypeDefinitionDocumentInformation(CommonPEModuleBuilder module)
        {
            var builder = new BlobBuilder();
 
            foreach (var (definition, documents) in module.GetTypeToDebugDocumentMap(Context))
            {
                foreach (var document in documents)
                {
                    var handle = GetOrAddDocument(document, _documentIndex);
                    builder.WriteCompressedInteger(MetadataTokens.GetRowNumber(handle));
                }
                _debugMetadataOpt.AddCustomDebugInformation(
                    parent: GetTypeDefinitionHandle(definition),
                    kind: _debugMetadataOpt.GetOrAddGuid(PortableCustomDebugInfoKinds.TypeDefinitionDocuments),
                    value: _debugMetadataOpt.GetOrAddBlob(builder));
                builder.Clear();
            }
        }
 
    }
}