Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<ImmutableArray<int>> _heapSizes;

private readonly ImmutableArray<ImmutableArray<RowCounts>> _rowCounts;
Expand Down Expand Up @@ -153,7 +154,7 @@ private static ImmutableArray<ImmutableArray<int>> 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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

return ImmutableArray.Create(
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -175,6 +179,69 @@ public void HeapSizes()
TestGenerationHandle(aggregator, MetadataTokens.GuidHandle(3), expectedHandle: MetadataTokens.GuidHandle(3), expectedGeneration: 4);

AssertExtensions.Throws<ArgumentException>("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<ArgumentException>("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<MetadataReader> metadataReaders = new List<MetadataReader> { 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<ArgumentException>("handle", () => TestGenerationHandle(aggregator, MetadataTokens.GuidHandle(2), expectedHandle: MetadataTokens.GuidHandle(2), expectedGeneration: 0));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be able to read 3 guids since there is one in baseline, and one in each delta, no?

Copy link
Author

@fabrimaz fabrimaz Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tmat in a real case yes, not in this test though.
Our new implementation removes cumulative sum and relies on each delta-reader GUID heap size, relying on deltareaders the copying of previous GUIDs and adding of existing new ones.

With new implementation, each generation here allocates a single GUID, hence only that one at index 1 could be read.
Tried to allocate more GUIDs on manually created delta-readers but couldn't find a working example.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With cumulative it was
Gen0: #1
Gen1: #1 #2
Gen2: #1 #2 #3

With new implementation
Gen0: #1
Gen1: #1
Gen2: #1

It's ok because all existing GUIDs are actually copied in next generation of metadata reader normally.

}
}
}

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;
}
}
}