diff --git a/src/libraries/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj b/src/libraries/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj
index 6d42a53f7c5a7c..ad67c99d457252 100644
--- a/src/libraries/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj
+++ b/src/libraries/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj
@@ -45,7 +45,6 @@ The System.Reflection.Metadata library is built-in as part of the shared framewo
-
@@ -60,6 +59,7 @@ The System.Reflection.Metadata library is built-in as part of the shared framewo
+
diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/ByteSequenceComparer.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/ByteSequenceComparer.cs
deleted file mode 100644
index 2ddd1806da2f3f..00000000000000
--- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/ByteSequenceComparer.cs
+++ /dev/null
@@ -1,68 +0,0 @@
-// 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.Collections.Immutable;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-
-namespace System.Reflection.Internal
-{
- internal sealed class ByteSequenceComparer : IEqualityComparer, IEqualityComparer>
- {
- internal static readonly ByteSequenceComparer Instance = new ByteSequenceComparer();
-
- private ByteSequenceComparer()
- {
- }
-
- internal static bool Equals(ImmutableArray x, ImmutableArray y)
- {
- return x.AsSpan().SequenceEqual(y.AsSpan());
- }
-
- internal static bool Equals(byte[] left, int leftStart, byte[] right, int rightStart, int length)
- {
- return left.AsSpan(leftStart, length).SequenceEqual(right.AsSpan(rightStart, length));
- }
-
- internal static bool Equals(byte[]? left, byte[]? right)
- {
- return left.AsSpan().SequenceEqual(right.AsSpan());
- }
-
- // Both hash computations below use the FNV-1a algorithm (http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function).
-
- internal static int GetHashCode(byte[] x)
- {
- Debug.Assert(x != null);
- return Hash.GetFNVHashCode(x);
- }
-
- internal static int GetHashCode(ImmutableArray x)
- {
- Debug.Assert(!x.IsDefault);
- return Hash.GetFNVHashCode(x.AsSpan());
- }
-
- bool IEqualityComparer.Equals(byte[]? x, byte[]? y)
- {
- return Equals(x, y);
- }
-
- int IEqualityComparer.GetHashCode(byte[] x)
- {
- return GetHashCode(x);
- }
-
- bool IEqualityComparer>.Equals(ImmutableArray x, ImmutableArray y)
- {
- return Equals(x, y);
- }
-
- int IEqualityComparer>.GetHashCode(ImmutableArray x)
- {
- return GetHashCode(x);
- }
- }
-}
diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobBuilder.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobBuilder.cs
index 8debffc0e94761..98bda9ebddd8e7 100644
--- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobBuilder.cs
+++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobBuilder.cs
@@ -50,6 +50,7 @@ public partial class BlobBuilder
private bool IsHead => (_length & IsFrozenMask) == 0;
private int Length => (int)(_length & ~IsFrozenMask);
private uint FrozenLength => _length | IsFrozenMask;
+ private Span Span => _buffer.AsSpan(0, Length);
public BlobBuilder(int capacity = DefaultChunkSize)
{
@@ -234,7 +235,7 @@ public bool ContentEquals(BlobBuilder other)
var right = rightEnumerator.Current;
int minLength = Math.Min(left.Length - leftStart, right.Length - rightStart);
- if (!ByteSequenceComparer.Equals(left._buffer, leftStart, right._buffer, rightStart, minLength))
+ if (!left._buffer.AsSpan(leftStart, minLength).SequenceEqual(right._buffer.AsSpan(rightStart, minLength)))
{
return false;
}
@@ -318,6 +319,19 @@ public ImmutableArray ToImmutableArray(int start, int byteCount)
return ImmutableByteArrayInterop.DangerousCreateFromUnderlyingArray(ref array);
}
+ internal bool TryGetSpan(out ReadOnlySpan buffer)
+ {
+ if (_nextOrPrevious == this)
+ {
+ // If the blob builder has one chunk, we can just return it and avoid copies.
+ buffer = Span;
+ return true;
+ }
+
+ buffer = default;
+ return false;
+ }
+
/// is null.
/// Content is not available, the builder has been linked with another one.
public void WriteContentTo(Stream destination)
@@ -344,7 +358,7 @@ public void WriteContentTo(ref BlobWriter destination)
foreach (var chunk in GetChunks())
{
- destination.WriteBytes(chunk._buffer.AsSpan(0, chunk.Length));
+ destination.WriteBytes(chunk.Span);
}
}
@@ -359,7 +373,7 @@ public void WriteContentTo(BlobBuilder destination)
foreach (var chunk in GetChunks())
{
- destination.WriteBytes(chunk._buffer.AsSpan(0, chunk.Length));
+ destination.WriteBytes(chunk.Span);
}
}
diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriter.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriter.cs
index fdd73e33ba289a..a9bd1288111556 100644
--- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriter.cs
+++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriter.cs
@@ -54,7 +54,7 @@ public BlobWriter(byte[] buffer, int start, int count)
///
public bool ContentEquals(BlobWriter other)
{
- return Length == other.Length && ByteSequenceComparer.Equals(_buffer, _start, other._buffer, other._start, Length);
+ return Length == other.Length && _buffer.AsSpan(_start, Length).SequenceEqual(other._buffer.AsSpan(other._start, other.Length));
}
public int Offset
diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/BlobDictionary.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/BlobDictionary.cs
new file mode 100644
index 00000000000000..0fa272a27062b1
--- /dev/null
+++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/BlobDictionary.cs
@@ -0,0 +1,109 @@
+// 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.Collections.Immutable;
+using System.Diagnostics;
+using System.Reflection.Internal;
+#if NET
+using System.Runtime.InteropServices;
+#endif
+
+namespace System.Reflection.Metadata.Ecma335
+{
+ [DebuggerDisplay("Count = {Count}")]
+ internal readonly struct BlobDictionary
+ {
+ private readonly Dictionary, BlobHandle>> _dictionary;
+
+ // A simple LCG. Constants taken from
+ // https://github.com/imneme/pcg-c/blob/83252d9c23df9c82ecb42210afed61a7b42402d7/include/pcg_variants.h#L276-L284
+ private static int GetNextDictionaryKey(int dictionaryKey) =>
+ (int)((uint)dictionaryKey * 747796405 + 2891336453);
+
+#if NET
+ private unsafe ref KeyValuePair, BlobHandle> GetValueRefOrAddDefault(ReadOnlySpan key, out bool exists)
+ {
+ int dictionaryKey = Hash.GetFNVHashCode(key);
+ while (true)
+ {
+ ref var entry = ref CollectionsMarshal.GetValueRefOrAddDefault(_dictionary, dictionaryKey, out exists);
+ if (!exists || entry.Key.AsSpan().SequenceEqual(key))
+ {
+#pragma warning disable CS9082 // Local is returned by reference but was initialized to a value that cannot be returned by reference
+ // In .NET 6 the assembly of GetValueRefOrAddDefault was compiled with earlier ref safety rules
+ // and caused an error, which was turned into a warning because of unsafe and was suppressed.
+ return ref entry;
+#pragma warning restore CS9082
+ }
+ dictionaryKey = GetNextDictionaryKey(dictionaryKey);
+ }
+ }
+
+ public BlobHandle GetOrAdd(ReadOnlySpan key, ImmutableArray immutableKey, BlobHandle value, out bool exists)
+ {
+ ref var entry = ref GetValueRefOrAddDefault(key, out exists);
+ if (exists)
+ {
+ return entry.Value;
+ }
+
+ // If we are given an immutable array, do not allocate a new one.
+ if (immutableKey.IsDefault)
+ {
+ immutableKey = key.ToImmutableArray();
+ }
+ else
+ {
+ Debug.Assert(immutableKey.AsSpan().SequenceEqual(key));
+ }
+
+ entry = new(immutableKey, value);
+ return value;
+ }
+#else
+ public BlobHandle GetOrAdd(ReadOnlySpan key, ImmutableArray immutableKey, BlobHandle value, out bool exists)
+ {
+ int dictionarykey = Hash.GetFNVHashCode(key);
+ KeyValuePair, BlobHandle> entry;
+ while (true)
+ {
+ if (!(exists = _dictionary.TryGetValue(dictionarykey, out entry))
+ || entry.Key.AsSpan().SequenceEqual(key))
+ {
+ break;
+ }
+ dictionarykey = GetNextDictionaryKey(dictionarykey);
+ }
+
+ if (exists)
+ {
+ return entry.Value;
+ }
+
+ // If we are given an immutable array, do not allocate a new one.
+ if (immutableKey.IsDefault)
+ {
+ immutableKey = key.ToImmutableArray();
+ }
+ else
+ {
+ Debug.Assert(immutableKey.AsSpan().SequenceEqual(key));
+ }
+
+ _dictionary.Add(dictionarykey, new(immutableKey, value));
+ return value;
+ }
+#endif
+
+ public BlobDictionary(int capacity = 0)
+ {
+ _dictionary = new(capacity);
+ }
+
+ public int Count => _dictionary.Count;
+
+ public Dictionary, BlobHandle>>.Enumerator GetEnumerator() =>
+ _dictionary.GetEnumerator();
+ }
+}
diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataBuilder.Heaps.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataBuilder.Heaps.cs
index a58bf9f1f7883d..142ea5f67c6f99 100644
--- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataBuilder.Heaps.cs
+++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataBuilder.Heaps.cs
@@ -3,10 +3,8 @@
using System.Collections.Generic;
using System.Collections.Immutable;
-using System.Diagnostics;
using System.Reflection.Internal;
-using System.Runtime.CompilerServices;
-using System.Text;
+using System.Runtime.InteropServices;
namespace System.Reflection.Metadata.Ecma335
{
@@ -44,7 +42,7 @@ internal void SetCapacity(int capacity)
private int _stringHeapCapacity = 4 * 1024;
// #Blob heap
- private readonly Dictionary, BlobHandle> _blobs = new Dictionary, BlobHandle>(1024, ByteSequenceComparer.Instance);
+ private readonly BlobDictionary _blobs = new BlobDictionary(1024);
private readonly int _blobHeapStartOffset;
private int _blobHeapSize;
@@ -118,7 +116,7 @@ public MetadataBuilder(
// beginning of the delta blob.
_userStringBuilder.WriteByte(0);
- _blobs.Add(ImmutableArray.Empty, default(BlobHandle));
+ _blobs.GetOrAdd(ReadOnlySpan.Empty, ImmutableArray.Empty, default, out _);
_blobHeapSize = 1;
// When EnC delta is applied #US, #String and #Blob heaps are appended.
@@ -193,7 +191,11 @@ public BlobHandle GetOrAddBlob(BlobBuilder value)
Throw.ArgumentNull(nameof(value));
}
- // TODO: avoid making a copy if the blob exists in the index
+ if (value.TryGetSpan(out ReadOnlySpan buffer))
+ {
+ return GetOrAddBlob(buffer);
+ }
+
return GetOrAddBlob(value.ToImmutableArray());
}
@@ -210,8 +212,19 @@ public BlobHandle GetOrAddBlob(byte[] value)
Throw.ArgumentNull(nameof(value));
}
- // TODO: avoid making a copy if the blob exists in the index
- return GetOrAddBlob(ImmutableArray.Create(value));
+ return GetOrAddBlob(new ReadOnlySpan(value));
+ }
+
+ private BlobHandle GetOrAddBlob(ReadOnlySpan value, ImmutableArray immutableValue = default)
+ {
+ BlobHandle nextHandle = BlobHandle.FromOffset(_blobHeapStartOffset + _blobHeapSize);
+ BlobHandle handle = _blobs.GetOrAdd(value, immutableValue, nextHandle, out bool exists);
+ if (!exists)
+ {
+ _blobHeapSize += BlobWriterImpl.GetCompressedIntegerSize(value.Length) + value.Length;
+ }
+
+ return handle;
}
///
@@ -227,16 +240,7 @@ public BlobHandle GetOrAddBlob(ImmutableArray value)
Throw.ArgumentNull(nameof(value));
}
- BlobHandle handle;
- if (!_blobs.TryGetValue(value, out handle))
- {
- handle = BlobHandle.FromOffset(_blobHeapStartOffset + _blobHeapSize);
- _blobs.Add(value, handle);
-
- _blobHeapSize += BlobWriterImpl.GetCompressedIntegerSize(value.Length) + value.Length;
- }
-
- return handle;
+ return GetOrAddBlob(value.AsSpan(), value);
}
///
@@ -267,6 +271,16 @@ public unsafe BlobHandle GetOrAddConstantBlob(object? value)
/// is null.
public BlobHandle GetOrAddBlobUTF16(string value)
{
+ if (value is null)
+ {
+ Throw.ArgumentNull(nameof(value));
+ }
+
+ if (BitConverter.IsLittleEndian)
+ {
+ return GetOrAddBlob(MemoryMarshal.AsBytes(value.AsSpan()));
+ }
+
var builder = PooledBlobBuilder.GetInstance();
builder.WriteUTF16(value);
var handle = GetOrAddBlob(builder);
@@ -611,8 +625,8 @@ private void WriteAlignedBlobHeap(BlobBuilder builder)
int startOffset = _blobHeapStartOffset;
foreach (var entry in _blobs)
{
- int heapOffset = entry.Value.GetHeapOffset();
- var blob = entry.Key;
+ int heapOffset = entry.Value.Value.GetHeapOffset();
+ var blob = entry.Value.Key;
writer.Offset = (heapOffset == 0) ? 0 : heapOffset - startOffset;
writer.WriteCompressedInteger(blob.Length);
diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/BlobTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/BlobTests.cs
index cefd6e109d4e3b..6c05f36d046c9f 100644
--- a/src/libraries/System.Reflection.Metadata/tests/Metadata/BlobTests.cs
+++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/BlobTests.cs
@@ -61,7 +61,7 @@ private void TestContentEquals(byte[] left, byte[] right)
var builder2 = new BlobBuilder(0);
builder2.WriteBytes(right);
- bool expected = ByteSequenceComparer.Equals(left, right);
+ bool expected = left.AsSpan().SequenceEqual(right.AsSpan());
Assert.Equal(expected, builder1.ContentEquals(builder2));
}