diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataAggregator.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataAggregator.cs index 602bf71a233068..824d7a5838d674 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataAggregator.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataAggregator.cs @@ -10,7 +10,8 @@ namespace System.Reflection.Metadata.Ecma335 public sealed class MetadataAggregator { // For each heap handle and each delta contains aggregate heap lengths. - // heapSizes[heap kind][reader index] == Sum { 0..index | reader[i].XxxHeap.Block.Length } + // For GUIDs: heapSizes[heap kind][reader index] == reader[reader index].GuidHeap.Block.Length + // For all other heaps: heapSizes[heap kind][reader index] == Sum { 0..reader index | reader[reader index].XxxHeap.Block.Length } private readonly ImmutableArray> _heapSizes; private readonly ImmutableArray> _rowCounts; @@ -153,7 +154,7 @@ private static ImmutableArray> CalculateHeapSizes( userStringSizes[r + 1] = userStringSizes[r] + deltaReaders[r].GetHeapSize(HeapIndex.UserString); stringSizes[r + 1] = stringSizes[r] + deltaReaders[r].GetHeapSize(HeapIndex.String); blobSizes[r + 1] = blobSizes[r] + deltaReaders[r].GetHeapSize(HeapIndex.Blob); - guidSizes[r + 1] = guidSizes[r] + deltaReaders[r].GetHeapSize(HeapIndex.Guid) / guidSize; + guidSizes[r + 1] = deltaReaders[r].GetHeapSize(HeapIndex.Guid) / guidSize; } return ImmutableArray.Create( diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/Ecma335/MetadataAggregatorTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/Ecma335/MetadataAggregatorTests.cs index 6d52f318fd7439..c4467b99336674 100644 --- a/src/libraries/System.Reflection.Metadata/tests/Metadata/Ecma335/MetadataAggregatorTests.cs +++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/Ecma335/MetadataAggregatorTests.cs @@ -1,8 +1,12 @@ // 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.Generic; using System.Reflection.Internal; +using System.Reflection.Metadata.Tests; +using System.Reflection.PortableExecutable; using System.Runtime.InteropServices; +using System.Text; using Xunit; using RowCounts = System.Reflection.Metadata.Ecma335.MetadataAggregator.RowCounts; @@ -175,6 +179,69 @@ public void HeapSizes() TestGenerationHandle(aggregator, MetadataTokens.GuidHandle(3), expectedHandle: MetadataTokens.GuidHandle(3), expectedGeneration: 4); AssertExtensions.Throws("handle", () => TestGenerationHandle(aggregator, MetadataTokens.StringHandle(22), expectedHandle: MetadataTokens.StringHandle(0), expectedGeneration: 0)); + // Sizes are not cumulative for GUIDs. They represent the number of available GUIDs. + AssertExtensions.Throws("handle", () => TestGenerationHandle(aggregator, MetadataTokens.GuidHandle(4), expectedHandle: MetadataTokens.StringHandle(0), expectedGeneration: 0)); + } + + [Fact] + public void HeapSize_GuidHeapSizes() + { + MetadataReader baseReader = MetadataReaderTests.GetMetadataReader(NetModule.AppCS); + var bytes = CreateMinimalReaderMetadataAsBytes(Guid.NewGuid()); + unsafe + { + fixed (byte* pointer = bytes) + { + MetadataReader mr1 = CreateMinimalReaderWithGuid(pointer, bytes.Length); + MetadataReader mr2 = CreateMinimalReaderWithGuid(pointer, bytes.Length); + IReadOnlyList metadataReaders = new List { mr1, mr2 }; + + var aggregator = new MetadataAggregator(baseReader, metadataReaders); + + var guidSizeReader0 = baseReader.GetHeapSize(HeapIndex.Guid) / 16; + Assert.Equal(1, guidSizeReader0); + var guidSizeReader1 = mr1.GetHeapSize(HeapIndex.Guid) / 16; + Assert.Equal(1, guidSizeReader1); + var guidSizeReader2 = mr2.GetHeapSize(HeapIndex.Guid) / 16; + Assert.Equal(1, guidSizeReader2); + + TestGenerationHandle(aggregator, MetadataTokens.GuidHandle(1), expectedHandle: MetadataTokens.GuidHandle(1), expectedGeneration: 0); + + // GUID-heap allocation shouldn't be cumulative, since GUIDs are copied among generations. + // The delta-reader above need indeed a single GUID allocation in each gen. + AssertExtensions.Throws("handle", () => TestGenerationHandle(aggregator, MetadataTokens.GuidHandle(2), expectedHandle: MetadataTokens.GuidHandle(2), expectedGeneration: 0)); + } + } + } + + private static byte[] CreateMinimalReaderMetadataAsBytes(Guid mvid) + { + var builder = new MetadataBuilder(); + GuidHandle mvidHandle = builder.GetOrAddGuid(mvid); + + // module-row name is mandatory + StringHandle name = builder.GetOrAddString("TestModule"); + builder.AddModule( + generation: 0, + moduleName: name, + mvid: mvidHandle, + encId: default, + encBaseId: default); + + // Let's serialize metadata + var root = new MetadataRootBuilder(builder); + var bb = new BlobBuilder(); + root.Serialize(bb, methodBodyStreamRva: 0, mappedFieldDataStreamRva: 0); + return bb.ToArray(); + } + + private static unsafe MetadataReader CreateMinimalReaderWithGuid(byte* pointer, int length) + { + var reader = new MetadataReader(pointer, length, MetadataReaderOptions.None); + // to avoid minimal flag exception. + reader.TableRowCounts[(int)TableIndex.EncMap] = 1; + reader.IsMinimalDelta = true; + return reader; } } }