File: System\Reflection\Metadata\Ecma335\MetadataSizes.cs
Web Access
Project: src\src\libraries\System.Reflection.Metadata\src\System.Reflection.Metadata.csproj (System.Reflection.Metadata)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Immutable;
using System.Diagnostics;
using System.Reflection.Internal;
 
namespace System.Reflection.Metadata.Ecma335
{
    /// <summary>
    /// Provides information on sizes of various metadata structures.
    /// </summary>
    public sealed class MetadataSizes
    {
        private const int StreamAlignment = 4;
 
        // Call the length of the string (including the terminator) m (we require m <= 255);
        internal const int MaxMetadataVersionByteCount = 0xff - 1;
 
        internal readonly int MetadataVersionPaddedLength;
 
        internal const ulong SortedTypeSystemTables =
            1UL << (int)TableIndex.InterfaceImpl |
            1UL << (int)TableIndex.Constant |
            1UL << (int)TableIndex.CustomAttribute |
            1UL << (int)TableIndex.FieldMarshal |
            1UL << (int)TableIndex.DeclSecurity |
            1UL << (int)TableIndex.ClassLayout |
            1UL << (int)TableIndex.FieldLayout |
            1UL << (int)TableIndex.MethodSemantics |
            1UL << (int)TableIndex.MethodImpl |
            1UL << (int)TableIndex.ImplMap |
            1UL << (int)TableIndex.FieldRva |
            1UL << (int)TableIndex.NestedClass |
            1UL << (int)TableIndex.GenericParam |
            1UL << (int)TableIndex.GenericParamConstraint;
 
        internal const ulong SortedDebugTables =
            1UL << (int)TableIndex.LocalScope |
            1UL << (int)TableIndex.StateMachineMethod |
            1UL << (int)TableIndex.CustomDebugInformation;
 
        internal readonly bool IsEncDelta;
        internal readonly bool IsCompressed;
 
        internal readonly bool BlobReferenceIsSmall;
        internal readonly bool StringReferenceIsSmall;
        internal readonly bool GuidReferenceIsSmall;
        internal readonly bool CustomAttributeTypeCodedIndexIsSmall;
        internal readonly bool DeclSecurityCodedIndexIsSmall;
        internal readonly bool EventDefReferenceIsSmall;
        internal readonly bool FieldDefReferenceIsSmall;
        internal readonly bool GenericParamReferenceIsSmall;
        internal readonly bool HasConstantCodedIndexIsSmall;
        internal readonly bool HasCustomAttributeCodedIndexIsSmall;
        internal readonly bool HasFieldMarshalCodedIndexIsSmall;
        internal readonly bool HasSemanticsCodedIndexIsSmall;
        internal readonly bool ImplementationCodedIndexIsSmall;
        internal readonly bool MemberForwardedCodedIndexIsSmall;
        internal readonly bool MemberRefParentCodedIndexIsSmall;
        internal readonly bool MethodDefReferenceIsSmall;
        internal readonly bool MethodDefOrRefCodedIndexIsSmall;
        internal readonly bool ModuleRefReferenceIsSmall;
        internal readonly bool ParameterReferenceIsSmall;
        internal readonly bool PropertyDefReferenceIsSmall;
        internal readonly bool ResolutionScopeCodedIndexIsSmall;
        internal readonly bool TypeDefReferenceIsSmall;
        internal readonly bool TypeDefOrRefCodedIndexIsSmall;
        internal readonly bool TypeOrMethodDefCodedIndexIsSmall;
 
        internal readonly bool DocumentReferenceIsSmall;
        internal readonly bool LocalVariableReferenceIsSmall;
        internal readonly bool LocalConstantReferenceIsSmall;
        internal readonly bool ImportScopeReferenceIsSmall;
        internal readonly bool HasCustomDebugInformationCodedIndexIsSmall;
 
        /// <summary>
        /// Exact (unaligned) heap sizes.
        /// </summary>
        /// <remarks>Use <see cref="GetAlignedHeapSize(HeapIndex)"/> to get an aligned heap size.</remarks>
        public ImmutableArray<int> HeapSizes { get; }
 
        /// <summary>
        /// Table row counts.
        /// </summary>
        public ImmutableArray<int> RowCounts { get; }
 
        /// <summary>
        /// External table row counts.
        /// </summary>
        public ImmutableArray<int> ExternalRowCounts { get; }
 
        /// <summary>
        /// Non-empty tables that are emitted into the metadata table stream.
        /// </summary>
        internal readonly ulong PresentTablesMask;
 
        /// <summary>
        /// Non-empty tables stored in an external metadata table stream that might be referenced from the metadata table stream being emitted.
        /// </summary>
        internal readonly ulong ExternalTablesMask;
 
        /// <summary>
        /// Overall size of metadata stream storage (stream headers, table stream, heaps, additional streams).
        /// Aligned to <see cref="StreamAlignment"/>.
        /// </summary>
        internal readonly int MetadataStreamStorageSize;
 
        /// <summary>
        /// The size of metadata stream (#- or #~). Aligned.
        /// Aligned to <see cref="StreamAlignment"/>.
        /// </summary>
        internal readonly int MetadataTableStreamSize;
 
        /// <summary>
        /// The size of #Pdb stream. Aligned.
        /// </summary>
        internal readonly int StandalonePdbStreamSize;
 
        internal MetadataSizes(
            ImmutableArray<int> rowCounts,
            ImmutableArray<int> externalRowCounts,
            ImmutableArray<int> heapSizes,
            int metadataVersionByteCount,
            bool isStandaloneDebugMetadata)
        {
            Debug.Assert(rowCounts.Length == MetadataTokens.TableCount);
            Debug.Assert(externalRowCounts.Length == MetadataTokens.TableCount);
            Debug.Assert(heapSizes.Length == MetadataTokens.HeapCount);
 
            RowCounts = rowCounts;
            ExternalRowCounts = externalRowCounts;
            HeapSizes = heapSizes;
 
            // +1 for NUL terminator
            MetadataVersionPaddedLength = BitArithmetic.Align(metadataVersionByteCount + 1, 4);
 
            PresentTablesMask = ComputeNonEmptyTableMask(rowCounts);
            ExternalTablesMask = ComputeNonEmptyTableMask(externalRowCounts);
 
            // Auto-detect EnC delta from presence of EnC tables.
            // EnC delta tables are stored as uncompressed metadata table stream, other metadata use compressed stream.
            // We could support uncompress non-EnC metadata in future if we needed to.
            bool isEncDelta = IsPresent(TableIndex.EncLog) || IsPresent(TableIndex.EncMap);
            bool isCompressed = !isEncDelta;
 
            IsEncDelta = isEncDelta;
            IsCompressed = isCompressed;
 
            BlobReferenceIsSmall = isCompressed && heapSizes[(int)HeapIndex.Blob] <= ushort.MaxValue;
            StringReferenceIsSmall = isCompressed && heapSizes[(int)HeapIndex.String] <= ushort.MaxValue;
            GuidReferenceIsSmall = isCompressed && heapSizes[(int)HeapIndex.Guid] <= ushort.MaxValue;
 
            // table can either be present or external, it can't be both:
            Debug.Assert((PresentTablesMask & ExternalTablesMask) == 0);
 
            CustomAttributeTypeCodedIndexIsSmall = IsReferenceSmall(3, TableIndex.MethodDef, TableIndex.MemberRef);
            DeclSecurityCodedIndexIsSmall = IsReferenceSmall(2, TableIndex.MethodDef, TableIndex.TypeDef);
            EventDefReferenceIsSmall = IsReferenceSmall(0, TableIndex.Event);
            FieldDefReferenceIsSmall = IsReferenceSmall(0, TableIndex.Field);
            GenericParamReferenceIsSmall = IsReferenceSmall(0, TableIndex.GenericParam);
            HasConstantCodedIndexIsSmall = IsReferenceSmall(2, TableIndex.Field, TableIndex.Param, TableIndex.Property);
 
            HasCustomAttributeCodedIndexIsSmall = IsReferenceSmall(5,
                TableIndex.MethodDef,
                TableIndex.Field,
                TableIndex.TypeRef,
                TableIndex.TypeDef,
                TableIndex.Param,
                TableIndex.InterfaceImpl,
                TableIndex.MemberRef,
                TableIndex.Module,
                TableIndex.DeclSecurity,
                TableIndex.Property,
                TableIndex.Event,
                TableIndex.StandAloneSig,
                TableIndex.ModuleRef,
                TableIndex.TypeSpec,
                TableIndex.Assembly,
                TableIndex.AssemblyRef,
                TableIndex.File,
                TableIndex.ExportedType,
                TableIndex.ManifestResource,
                TableIndex.GenericParam,
                TableIndex.GenericParamConstraint,
                TableIndex.MethodSpec);
 
            HasFieldMarshalCodedIndexIsSmall = IsReferenceSmall(1, TableIndex.Field, TableIndex.Param);
            HasSemanticsCodedIndexIsSmall = IsReferenceSmall(1, TableIndex.Event, TableIndex.Property);
            ImplementationCodedIndexIsSmall = IsReferenceSmall(2, TableIndex.File, TableIndex.AssemblyRef, TableIndex.ExportedType);
            MemberForwardedCodedIndexIsSmall = IsReferenceSmall(1, TableIndex.Field, TableIndex.MethodDef);
            MemberRefParentCodedIndexIsSmall = IsReferenceSmall(3, TableIndex.TypeDef, TableIndex.TypeRef, TableIndex.ModuleRef, TableIndex.MethodDef, TableIndex.TypeSpec);
            MethodDefReferenceIsSmall = IsReferenceSmall(0, TableIndex.MethodDef);
            MethodDefOrRefCodedIndexIsSmall = IsReferenceSmall(1, TableIndex.MethodDef, TableIndex.MemberRef);
            ModuleRefReferenceIsSmall = IsReferenceSmall(0, TableIndex.ModuleRef);
            ParameterReferenceIsSmall = IsReferenceSmall(0, TableIndex.Param);
            PropertyDefReferenceIsSmall = IsReferenceSmall(0, TableIndex.Property);
            ResolutionScopeCodedIndexIsSmall = IsReferenceSmall(2, TableIndex.Module, TableIndex.ModuleRef, TableIndex.AssemblyRef, TableIndex.TypeRef);
            TypeDefReferenceIsSmall = IsReferenceSmall(0, TableIndex.TypeDef);
            TypeDefOrRefCodedIndexIsSmall = IsReferenceSmall(2, TableIndex.TypeDef, TableIndex.TypeRef, TableIndex.TypeSpec);
            TypeOrMethodDefCodedIndexIsSmall = IsReferenceSmall(1, TableIndex.TypeDef, TableIndex.MethodDef);
 
            DocumentReferenceIsSmall = IsReferenceSmall(0, TableIndex.Document);
            LocalVariableReferenceIsSmall = IsReferenceSmall(0, TableIndex.LocalVariable);
            LocalConstantReferenceIsSmall = IsReferenceSmall(0, TableIndex.LocalConstant);
            ImportScopeReferenceIsSmall = IsReferenceSmall(0, TableIndex.ImportScope);
 
            HasCustomDebugInformationCodedIndexIsSmall = IsReferenceSmall(5,
                TableIndex.MethodDef,
                TableIndex.Field,
                TableIndex.TypeRef,
                TableIndex.TypeDef,
                TableIndex.Param,
                TableIndex.InterfaceImpl,
                TableIndex.MemberRef,
                TableIndex.Module,
                TableIndex.DeclSecurity,
                TableIndex.Property,
                TableIndex.Event,
                TableIndex.StandAloneSig,
                TableIndex.ModuleRef,
                TableIndex.TypeSpec,
                TableIndex.Assembly,
                TableIndex.AssemblyRef,
                TableIndex.File,
                TableIndex.ExportedType,
                TableIndex.ManifestResource,
                TableIndex.GenericParam,
                TableIndex.GenericParamConstraint,
                TableIndex.MethodSpec,
                TableIndex.Document,
                TableIndex.LocalScope,
                TableIndex.LocalVariable,
                TableIndex.LocalConstant,
                TableIndex.ImportScope);
 
            int size = CalculateTableStreamHeaderSize();
 
            const byte small = 2;
            const byte large = 4;
 
            byte blobReferenceSize = BlobReferenceIsSmall ? small : large;
            byte stringReferenceSize = StringReferenceIsSmall ? small : large;
            byte guidReferenceSize = GuidReferenceIsSmall ? small : large;
            byte customAttributeTypeCodedIndexSize = CustomAttributeTypeCodedIndexIsSmall ? small : large;
            byte declSecurityCodedIndexSize = DeclSecurityCodedIndexIsSmall ? small : large;
            byte eventDefReferenceSize = EventDefReferenceIsSmall ? small : large;
            byte fieldDefReferenceSize = FieldDefReferenceIsSmall ? small : large;
            byte genericParamReferenceSize = GenericParamReferenceIsSmall ? small : large;
            byte hasConstantCodedIndexSize = HasConstantCodedIndexIsSmall ? small : large;
            byte hasCustomAttributeCodedIndexSize = HasCustomAttributeCodedIndexIsSmall ? small : large;
            byte hasFieldMarshalCodedIndexSize = HasFieldMarshalCodedIndexIsSmall ? small : large;
            byte hasSemanticsCodedIndexSize = HasSemanticsCodedIndexIsSmall ? small : large;
            byte implementationCodedIndexSize = ImplementationCodedIndexIsSmall ? small : large;
            byte memberForwardedCodedIndexSize = MemberForwardedCodedIndexIsSmall ? small : large;
            byte memberRefParentCodedIndexSize = MemberRefParentCodedIndexIsSmall ? small : large;
            byte methodDefReferenceSize = MethodDefReferenceIsSmall ? small : large;
            byte methodDefOrRefCodedIndexSize = MethodDefOrRefCodedIndexIsSmall ? small : large;
            byte moduleRefReferenceSize = ModuleRefReferenceIsSmall ? small : large;
            byte parameterReferenceSize = ParameterReferenceIsSmall ? small : large;
            byte propertyDefReferenceSize = PropertyDefReferenceIsSmall ? small : large;
            byte resolutionScopeCodedIndexSize = ResolutionScopeCodedIndexIsSmall ? small : large;
            byte typeDefReferenceSize = TypeDefReferenceIsSmall ? small : large;
            byte typeDefOrRefCodedIndexSize = TypeDefOrRefCodedIndexIsSmall ? small : large;
            byte typeOrMethodDefCodedIndexSize = TypeOrMethodDefCodedIndexIsSmall ? small : large;
            byte documentReferenceSize = DocumentReferenceIsSmall ? small : large;
            byte localVariableReferenceSize = LocalVariableReferenceIsSmall ? small : large;
            byte localConstantReferenceSize = LocalConstantReferenceIsSmall ? small : large;
            byte importScopeReferenceSize = ImportScopeReferenceIsSmall ? small : large;
            byte hasCustomDebugInformationCodedIndexSize = HasCustomDebugInformationCodedIndexIsSmall ? small : large;
 
            size += GetTableSize(TableIndex.Module, 2 + 3 * guidReferenceSize + stringReferenceSize);
            size += GetTableSize(TableIndex.TypeRef, resolutionScopeCodedIndexSize + stringReferenceSize + stringReferenceSize);
            size += GetTableSize(TableIndex.TypeDef, 4 + stringReferenceSize + stringReferenceSize + typeDefOrRefCodedIndexSize + fieldDefReferenceSize + methodDefReferenceSize);
            Debug.Assert(rowCounts[(int)TableIndex.FieldPtr] == 0);
            size += GetTableSize(TableIndex.Field, 2 + stringReferenceSize + blobReferenceSize);
            Debug.Assert(rowCounts[(int)TableIndex.MethodPtr] == 0);
            size += GetTableSize(TableIndex.MethodDef, 8 + stringReferenceSize + blobReferenceSize + parameterReferenceSize);
            Debug.Assert(rowCounts[(int)TableIndex.ParamPtr] == 0);
            size += GetTableSize(TableIndex.Param, 4 + stringReferenceSize);
            size += GetTableSize(TableIndex.InterfaceImpl, typeDefReferenceSize + typeDefOrRefCodedIndexSize);
            size += GetTableSize(TableIndex.MemberRef, memberRefParentCodedIndexSize + stringReferenceSize + blobReferenceSize);
            size += GetTableSize(TableIndex.Constant, 2 + hasConstantCodedIndexSize + blobReferenceSize);
            size += GetTableSize(TableIndex.CustomAttribute, hasCustomAttributeCodedIndexSize + customAttributeTypeCodedIndexSize + blobReferenceSize);
            size += GetTableSize(TableIndex.FieldMarshal, hasFieldMarshalCodedIndexSize + blobReferenceSize);
            size += GetTableSize(TableIndex.DeclSecurity, 2 + declSecurityCodedIndexSize + blobReferenceSize);
            size += GetTableSize(TableIndex.ClassLayout, 6 + typeDefReferenceSize);
            size += GetTableSize(TableIndex.FieldLayout, 4 + fieldDefReferenceSize);
            size += GetTableSize(TableIndex.StandAloneSig, blobReferenceSize);
            size += GetTableSize(TableIndex.EventMap, typeDefReferenceSize + eventDefReferenceSize);
            Debug.Assert(rowCounts[(int)TableIndex.EventPtr] == 0);
            size += GetTableSize(TableIndex.Event, 2 + stringReferenceSize + typeDefOrRefCodedIndexSize);
            size += GetTableSize(TableIndex.PropertyMap, typeDefReferenceSize + propertyDefReferenceSize);
            Debug.Assert(rowCounts[(int)TableIndex.PropertyPtr] == 0);
            size += GetTableSize(TableIndex.Property, 2 + stringReferenceSize + blobReferenceSize);
            size += GetTableSize(TableIndex.MethodSemantics, 2 + methodDefReferenceSize + hasSemanticsCodedIndexSize);
            size += GetTableSize(TableIndex.MethodImpl, typeDefReferenceSize + methodDefOrRefCodedIndexSize + methodDefOrRefCodedIndexSize);
            size += GetTableSize(TableIndex.ModuleRef, stringReferenceSize);
            size += GetTableSize(TableIndex.TypeSpec, blobReferenceSize);
            size += GetTableSize(TableIndex.ImplMap, 2 + memberForwardedCodedIndexSize + stringReferenceSize + moduleRefReferenceSize);
            size += GetTableSize(TableIndex.FieldRva, 4 + fieldDefReferenceSize);
            size += GetTableSize(TableIndex.EncLog, 8);
            size += GetTableSize(TableIndex.EncMap, 4);
            size += GetTableSize(TableIndex.Assembly, 16 + blobReferenceSize + stringReferenceSize + stringReferenceSize);
            Debug.Assert(rowCounts[(int)TableIndex.AssemblyProcessor] == 0);
            Debug.Assert(rowCounts[(int)TableIndex.AssemblyOS] == 0);
            size += GetTableSize(TableIndex.AssemblyRef, 12 + blobReferenceSize + stringReferenceSize + stringReferenceSize + blobReferenceSize);
            Debug.Assert(rowCounts[(int)TableIndex.AssemblyRefProcessor] == 0);
            Debug.Assert(rowCounts[(int)TableIndex.AssemblyRefOS] == 0);
            size += GetTableSize(TableIndex.File, 4 + stringReferenceSize + blobReferenceSize);
            size += GetTableSize(TableIndex.ExportedType, 8 + stringReferenceSize + stringReferenceSize + implementationCodedIndexSize);
            size += GetTableSize(TableIndex.ManifestResource, 8 + stringReferenceSize + implementationCodedIndexSize);
            size += GetTableSize(TableIndex.NestedClass, typeDefReferenceSize + typeDefReferenceSize);
            size += GetTableSize(TableIndex.GenericParam, 4 + typeOrMethodDefCodedIndexSize + stringReferenceSize);
            size += GetTableSize(TableIndex.MethodSpec, methodDefOrRefCodedIndexSize + blobReferenceSize);
            size += GetTableSize(TableIndex.GenericParamConstraint, genericParamReferenceSize + typeDefOrRefCodedIndexSize);
 
            size += GetTableSize(TableIndex.Document, blobReferenceSize + guidReferenceSize + blobReferenceSize + guidReferenceSize);
            size += GetTableSize(TableIndex.MethodDebugInformation, documentReferenceSize + blobReferenceSize);
            size += GetTableSize(TableIndex.LocalScope, methodDefReferenceSize + importScopeReferenceSize + localVariableReferenceSize + localConstantReferenceSize + 4 + 4);
            size += GetTableSize(TableIndex.LocalVariable, 2 + 2 + stringReferenceSize);
            size += GetTableSize(TableIndex.LocalConstant, stringReferenceSize + blobReferenceSize);
            size += GetTableSize(TableIndex.ImportScope, importScopeReferenceSize + blobReferenceSize);
            size += GetTableSize(TableIndex.StateMachineMethod, methodDefReferenceSize + methodDefReferenceSize);
            size += GetTableSize(TableIndex.CustomDebugInformation, hasCustomDebugInformationCodedIndexSize + guidReferenceSize + blobReferenceSize);
 
            // +1 for terminating 0 byte
            size = BitArithmetic.Align(size + 1, StreamAlignment);
 
            MetadataTableStreamSize = size;
 
            size += GetAlignedHeapSize(HeapIndex.String);
            size += GetAlignedHeapSize(HeapIndex.UserString);
            size += GetAlignedHeapSize(HeapIndex.Guid);
            size += GetAlignedHeapSize(HeapIndex.Blob);
 
            StandalonePdbStreamSize = isStandaloneDebugMetadata ? CalculateStandalonePdbStreamSize() : 0;
            size += StandalonePdbStreamSize;
 
            MetadataStreamStorageSize = size;
        }
 
        internal bool IsStandaloneDebugMetadata => StandalonePdbStreamSize > 0;
 
        internal bool IsPresent(TableIndex table) => (PresentTablesMask & (1UL << (int)table)) != 0;
 
        /// <summary>
        /// Metadata header size.
        /// Includes:
        /// - metadata storage signature
        /// - storage header
        /// - stream headers
        /// </summary>
        internal int MetadataHeaderSize
        {
            get
            {
                const int RegularStreamHeaderSizes = 76;
                const int EncDeltaMarkerStreamHeaderSize = 16;
                const int StandalonePdbStreamHeaderSize = 16;
 
                Debug.Assert(RegularStreamHeaderSizes ==
                    GetMetadataStreamHeaderSize("#~") +
                    GetMetadataStreamHeaderSize("#Strings") +
                    GetMetadataStreamHeaderSize("#US") +
                    GetMetadataStreamHeaderSize("#GUID") +
                    GetMetadataStreamHeaderSize("#Blob"));
 
                Debug.Assert(EncDeltaMarkerStreamHeaderSize == GetMetadataStreamHeaderSize("#JTD"));
                Debug.Assert(StandalonePdbStreamHeaderSize == GetMetadataStreamHeaderSize("#Pdb"));
 
                return
                    sizeof(uint) +                 // signature
                    sizeof(ushort) +               // major version
                    sizeof(ushort) +               // minor version
                    sizeof(uint) +                 // reserved
                    sizeof(uint) +                 // padded metadata version length
                    MetadataVersionPaddedLength +  // metadata version
                    sizeof(ushort) +               // storage header: reserved
                    sizeof(ushort) +               // stream count
                    (IsStandaloneDebugMetadata ? StandalonePdbStreamHeaderSize : 0) +
                    RegularStreamHeaderSizes +
                    (IsEncDelta ? EncDeltaMarkerStreamHeaderSize : 0);
            }
        }
 
        internal static int GetMetadataStreamHeaderSize(string streamName)
        {
            return
                sizeof(int) + // offset
                sizeof(int) + // size
                BitArithmetic.Align(streamName.Length + 1, 4); // zero-terminated name, padding
        }
 
        /// <summary>
        /// Total size of metadata (header and all streams).
        /// </summary>
        internal int MetadataSize => MetadataHeaderSize + MetadataStreamStorageSize;
 
        /// <summary>
        /// Returns aligned size of the specified heap.
        /// </summary>
        public int GetAlignedHeapSize(HeapIndex index)
        {
            int i = (int)index;
            if (i < 0 || i > HeapSizes.Length)
            {
                Throw.ArgumentOutOfRange(nameof(index));
            }
 
            return BitArithmetic.Align(HeapSizes[i], StreamAlignment);
        }
 
        internal int CalculateTableStreamHeaderSize()
        {
            int result = sizeof(int) +        // Reserved
                         sizeof(short) +      // Version (major, minor)
                         sizeof(byte) +       // Heap index sizes
                         sizeof(byte) +       // Bit width of RowId
                         sizeof(long) +       // Valid table mask
                         sizeof(long);        // Sorted table mask
 
            // present table row counts
            for (int i = 0; i < RowCounts.Length; i++)
            {
                if (((1UL << i) & PresentTablesMask) != 0)
                {
                    result += sizeof(int);
                }
            }
 
            return result;
        }
 
        internal const int PdbIdSize = 20;
 
        internal int CalculateStandalonePdbStreamSize()
        {
            int result =
                PdbIdSize +                                                         // PDB ID
                sizeof(int) +                                                       // EntryPoint
                sizeof(long) +                                                      // ReferencedTypeSystemTables
                BitArithmetic.CountBits(ExternalTablesMask) * sizeof(int); // External row counts
 
            Debug.Assert(result % StreamAlignment == 0);
            return result;
        }
 
        private static ulong ComputeNonEmptyTableMask(ImmutableArray<int> rowCounts)
        {
            ulong mask = 0;
            for (int i = 0; i < rowCounts.Length; i++)
            {
                if (rowCounts[i] > 0)
                {
                    mask |= (1UL << i);
                }
            }
 
            return mask;
        }
 
        private int GetTableSize(TableIndex index, int rowSize)
        {
            return RowCounts[(int)index] * rowSize;
        }
 
        private bool IsReferenceSmall(int tagBitSize, params TableIndex[] tables)
        {
            const int smallBitCount = 16;
            return IsCompressed && ReferenceFits(smallBitCount - tagBitSize, tables);
        }
 
        private bool ReferenceFits(int bitCount, TableIndex[] tables)
        {
            int maxIndex = (1 << bitCount) - 1;
            foreach (TableIndex table in tables)
            {
                // table can be either local or external, but not both:
                Debug.Assert(RowCounts[(int)table] == 0 || ExternalRowCounts[(int)table] == 0);
 
                if (RowCounts[(int)table] + ExternalRowCounts[(int)table] > maxIndex)
                {
                    return false;
                }
            }
 
            return true;
        }
    }
}