From 0a4579b5151f6fe1063a9fde0fb545b645511a8a Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Fri, 31 Oct 2025 01:48:29 +0200 Subject: [PATCH 1/6] Deduplicate and optimize `BlobWriterImpl.WriteConstant`. The logic of writing scalar constants was extracted to its own method that writes to a span. This method also got used by `MetadataBuilder.GetOrAddConstantBlob`, avoiding the use of pooled blob builders when writing scalar constants. --- .../Reflection/Metadata/BlobWriterImpl.cs | 150 ++++++++---------- .../Metadata/Ecma335/MetadataBuilder.Heaps.cs | 8 +- 2 files changed, 70 insertions(+), 88 deletions(-) diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriterImpl.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriterImpl.cs index 972def9f6955cb..a7fa067e3435b7 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriterImpl.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriterImpl.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers.Binary; using System.Diagnostics; namespace System.Reflection.Metadata @@ -144,72 +145,102 @@ internal static void WriteCompressedSignedInteger(BlobBuilder writer, int value) } } - internal static void WriteConstant(ref BlobWriter writer, object? value) + /// + /// Writes a scalar (non-string) constant to a span. + /// + /// The span where the content will be encoded. + /// The constant value. + /// The number of bytes that was written. + internal static int WriteScalarConstant(Span bytes, object? value) { if (value == null) { // The encoding of Type for the nullref value for FieldInit is ELEMENT_TYPE_CLASS with a Value of a 32-bit. - writer.WriteUInt32(0); - return; + BinaryPrimitives.WriteUInt32LittleEndian(bytes, 0); + return sizeof(uint); } var type = value.GetType(); - if (type.GetTypeInfo().IsEnum) + if (type.IsEnum) { type = Enum.GetUnderlyingType(type); } if (type == typeof(bool)) { - writer.WriteBoolean((bool)value); + bytes[0] = (byte)((bool)value ? 1 : 0); + return sizeof(bool); } else if (type == typeof(int)) { - writer.WriteInt32((int)value); - } - else if (type == typeof(string)) - { - writer.WriteUTF16((string)value); + BinaryPrimitives.WriteInt32LittleEndian(bytes, (int)value); + return sizeof(int); } else if (type == typeof(byte)) { - writer.WriteByte((byte)value); + bytes[0] = (byte)value; + return sizeof(byte); } else if (type == typeof(char)) { - writer.WriteUInt16((char)value); + BinaryPrimitives.WriteUInt16LittleEndian(bytes, (char)value); + return sizeof(char); } else if (type == typeof(double)) { - writer.WriteDouble((double)value); +#if NET + BinaryPrimitives.WriteDoubleLittleEndian(bytes, (double)value); +#else + double v = (double)value; + unsafe + { + BinaryPrimitives.WriteUInt64LittleEndian(bytes, *(ulong*)(&v)); + } +#endif + return sizeof(double); } else if (type == typeof(short)) { - writer.WriteInt16((short)value); + BinaryPrimitives.WriteInt16LittleEndian(bytes, (short)value); + return sizeof(short); } else if (type == typeof(long)) { - writer.WriteInt64((long)value); + BinaryPrimitives.WriteInt64LittleEndian(bytes, (long)value); + return sizeof(long); } else if (type == typeof(sbyte)) { - writer.WriteSByte((sbyte)value); + bytes[0] = (byte)(sbyte)value; + return sizeof(sbyte); } else if (type == typeof(float)) { - writer.WriteSingle((float)value); +#if NET + BinaryPrimitives.WriteSingleLittleEndian(bytes, (float)value); +#else + float v = (float)value; + unsafe + { + BinaryPrimitives.WriteUInt32LittleEndian(bytes, *(uint*)(&v)); + } +#endif + return sizeof(float); } else if (type == typeof(ushort)) { - writer.WriteUInt16((ushort)value); + BinaryPrimitives.WriteUInt16LittleEndian(bytes, (ushort)value); + return sizeof(ushort); } else if (type == typeof(uint)) { - writer.WriteUInt32((uint)value); + BinaryPrimitives.WriteUInt32LittleEndian(bytes, (uint)value); + return sizeof(uint); } else if (type == typeof(ulong)) { - writer.WriteUInt64((ulong)value); + BinaryPrimitives.WriteUInt64LittleEndian(bytes, (ulong)value); + return sizeof(ulong); } else { @@ -217,77 +248,30 @@ internal static void WriteConstant(ref BlobWriter writer, object? value) } } - internal static void WriteConstant(BlobBuilder writer, object? value) + internal static void WriteConstant(ref BlobWriter writer, object? value) { - if (value == null) + if (value is string s) { - // The encoding of Type for the nullref value for FieldInit is ELEMENT_TYPE_CLASS with a Value of a 32-bit. - writer.WriteUInt32(0); + writer.WriteUTF16(s); return; } - var type = value.GetType(); - if (type.GetTypeInfo().IsEnum) - { - type = Enum.GetUnderlyingType(type); - } + Span bytes = stackalloc byte[sizeof(ulong)]; + int written = WriteScalarConstant(bytes, value); + writer.WriteBytes(bytes.Slice(0, written)); + } - if (type == typeof(bool)) - { - writer.WriteBoolean((bool)value); - } - else if (type == typeof(int)) - { - writer.WriteInt32((int)value); - } - else if (type == typeof(string)) - { - writer.WriteUTF16((string)value); - } - else if (type == typeof(byte)) - { - writer.WriteByte((byte)value); - } - else if (type == typeof(char)) - { - writer.WriteUInt16((char)value); - } - else if (type == typeof(double)) - { - writer.WriteDouble((double)value); - } - else if (type == typeof(short)) - { - writer.WriteInt16((short)value); - } - else if (type == typeof(long)) - { - writer.WriteInt64((long)value); - } - else if (type == typeof(sbyte)) - { - writer.WriteSByte((sbyte)value); - } - else if (type == typeof(float)) - { - writer.WriteSingle((float)value); - } - else if (type == typeof(ushort)) - { - writer.WriteUInt16((ushort)value); - } - else if (type == typeof(uint)) - { - writer.WriteUInt32((uint)value); - } - else if (type == typeof(ulong)) - { - writer.WriteUInt64((ulong)value); - } - else + internal static void WriteConstant(BlobBuilder writer, object? value) + { + if (value is string s) { - throw new ArgumentException(SR.Format(SR.InvalidConstantValueOfType, type)); + writer.WriteUTF16(s); + return; } + + Span bytes = stackalloc byte[sizeof(ulong)]; + int written = WriteScalarConstant(bytes, value); + writer.WriteBytes(bytes.Slice(0, written)); } } } 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 16a2007781507e..70ce8278adfb89 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 @@ -256,11 +256,9 @@ public BlobHandle GetOrAddConstantBlob(object? value) return GetOrAddBlobUTF16(str); } - var builder = PooledBlobBuilder.GetInstance(); - builder.WriteConstant(value); - var result = GetOrAddBlob(builder); - builder.Free(); - return result; + Span buffer = stackalloc byte[sizeof(ulong)]; + int length = BlobWriterImpl.WriteScalarConstant(buffer, value); + return GetOrAddBlob(buffer.Slice(0, length)); } /// From 0635302d5a34f57dc5491f1bd21585402f154a45 Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Tue, 17 Mar 2026 00:31:23 +0200 Subject: [PATCH 2/6] Use constant for maximum size of scalar constant. --- .../src/System/Reflection/Metadata/BlobWriterImpl.cs | 5 +++-- .../Reflection/Metadata/Ecma335/MetadataBuilder.Heaps.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriterImpl.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriterImpl.cs index a7fa067e3435b7..ebbef103680d25 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriterImpl.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriterImpl.cs @@ -13,6 +13,7 @@ internal static class BlobWriterImpl internal const int MaxCompressedIntegerValue = 0x1fffffff; internal const int MinSignedCompressedIntegerValue = unchecked((int)0xF0000000); internal const int MaxSignedCompressedIntegerValue = 0x0FFFFFFF; + internal const int MaxScalarConstantSize = sizeof(ulong); internal static int GetCompressedIntegerSize(int value) { @@ -256,7 +257,7 @@ internal static void WriteConstant(ref BlobWriter writer, object? value) return; } - Span bytes = stackalloc byte[sizeof(ulong)]; + Span bytes = stackalloc byte[MaxScalarConstantSize]; int written = WriteScalarConstant(bytes, value); writer.WriteBytes(bytes.Slice(0, written)); } @@ -269,7 +270,7 @@ internal static void WriteConstant(BlobBuilder writer, object? value) return; } - Span bytes = stackalloc byte[sizeof(ulong)]; + Span bytes = stackalloc byte[MaxScalarConstantSize]; int written = WriteScalarConstant(bytes, value); writer.WriteBytes(bytes.Slice(0, written)); } 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 70ce8278adfb89..ec99c415fe8d3c 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 @@ -256,7 +256,7 @@ public BlobHandle GetOrAddConstantBlob(object? value) return GetOrAddBlobUTF16(str); } - Span buffer = stackalloc byte[sizeof(ulong)]; + Span buffer = stackalloc byte[BlobWriterImpl.MaxScalarConstantSize]; int length = BlobWriterImpl.WriteScalarConstant(buffer, value); return GetOrAddBlob(buffer.Slice(0, length)); } From 3842337bdae46b1844f75259f1bbf3b9247804cf Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Thu, 19 Mar 2026 01:43:47 +0200 Subject: [PATCH 3/6] Temporarily add method in `MetadataBuilder` to clear all blobs. --- .../Reflection/Metadata/Ecma335/BlobDictionary.cs | 2 ++ .../Metadata/Ecma335/MetadataBuilder.Heaps.cs | 13 +++++++++++++ 2 files changed, 15 insertions(+) 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 index 0fa272a27062b1..ca71650fab01c8 100644 --- 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 @@ -103,6 +103,8 @@ public BlobDictionary(int capacity = 0) public int Count => _dictionary.Count; + public void Clear() => _dictionary.Clear(); + 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 ec99c415fe8d3c..72513f2b0ca760 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 @@ -379,6 +379,19 @@ private static char ChooseSeparator(string str) return (count1 >= count2) ? s1 : s2; } + /// + /// Clears all blobs added so far. + /// + /// + /// Temporarily added for use in benchmarks, through UnsafeAccessorAttribute. + /// + private void ClearBlobs() + { + _blobs.Clear(); + _blobs.GetOrAdd([], [], default, out _); + _blobHeapSize = 1; + } + /// /// Adds specified Guid to Guid heap, if it's not there already. /// From 89e2166d08cda64e751243d0b956c4b45430870d Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Thu, 19 Mar 2026 03:43:47 +0200 Subject: [PATCH 4/6] Revert "Temporarily add method in `MetadataBuilder` to clear all blobs." This reverts commit 3842337bdae46b1844f75259f1bbf3b9247804cf. --- .../Reflection/Metadata/Ecma335/BlobDictionary.cs | 2 -- .../Metadata/Ecma335/MetadataBuilder.Heaps.cs | 13 ------------- 2 files changed, 15 deletions(-) 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 index ca71650fab01c8..0fa272a27062b1 100644 --- 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 @@ -103,8 +103,6 @@ public BlobDictionary(int capacity = 0) public int Count => _dictionary.Count; - public void Clear() => _dictionary.Clear(); - 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 72513f2b0ca760..ec99c415fe8d3c 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 @@ -379,19 +379,6 @@ private static char ChooseSeparator(string str) return (count1 >= count2) ? s1 : s2; } - /// - /// Clears all blobs added so far. - /// - /// - /// Temporarily added for use in benchmarks, through UnsafeAccessorAttribute. - /// - private void ClearBlobs() - { - _blobs.Clear(); - _blobs.GetOrAdd([], [], default, out _); - _blobHeapSize = 1; - } - /// /// Adds specified Guid to Guid heap, if it's not there already. /// From b054b306f7dcdbf07e46fec2f406c053af224b9f Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Thu, 19 Mar 2026 03:53:52 +0200 Subject: [PATCH 5/6] Revert changes to `WriteConstant`; only `MetadataBuilder.GetOrAddConstantBlob` is now optimized. --- .../Reflection/Metadata/BlobWriterImpl.cs | 140 ++++++++++++++++-- 1 file changed, 130 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriterImpl.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriterImpl.cs index ebbef103680d25..ab5bf094ab28cf 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriterImpl.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriterImpl.cs @@ -251,28 +251,148 @@ internal static int WriteScalarConstant(Span bytes, object? value) internal static void WriteConstant(ref BlobWriter writer, object? value) { - if (value is string s) + if (value == null) { - writer.WriteUTF16(s); + // The encoding of Type for the nullref value for FieldInit is ELEMENT_TYPE_CLASS with a Value of a 32-bit. + writer.WriteUInt32(0); return; } - Span bytes = stackalloc byte[MaxScalarConstantSize]; - int written = WriteScalarConstant(bytes, value); - writer.WriteBytes(bytes.Slice(0, written)); + var type = value.GetType(); + if (type.GetTypeInfo().IsEnum) + { + type = Enum.GetUnderlyingType(type); + } + + if (type == typeof(bool)) + { + writer.WriteBoolean((bool)value); + } + else if (type == typeof(int)) + { + writer.WriteInt32((int)value); + } + else if (type == typeof(string)) + { + writer.WriteUTF16((string)value); + } + else if (type == typeof(byte)) + { + writer.WriteByte((byte)value); + } + else if (type == typeof(char)) + { + writer.WriteUInt16((char)value); + } + else if (type == typeof(double)) + { + writer.WriteDouble((double)value); + } + else if (type == typeof(short)) + { + writer.WriteInt16((short)value); + } + else if (type == typeof(long)) + { + writer.WriteInt64((long)value); + } + else if (type == typeof(sbyte)) + { + writer.WriteSByte((sbyte)value); + } + else if (type == typeof(float)) + { + writer.WriteSingle((float)value); + } + else if (type == typeof(ushort)) + { + writer.WriteUInt16((ushort)value); + } + else if (type == typeof(uint)) + { + writer.WriteUInt32((uint)value); + } + else if (type == typeof(ulong)) + { + writer.WriteUInt64((ulong)value); + } + else + { + throw new ArgumentException(SR.Format(SR.InvalidConstantValueOfType, type)); + } } internal static void WriteConstant(BlobBuilder writer, object? value) { - if (value is string s) + if (value == null) { - writer.WriteUTF16(s); + // The encoding of Type for the nullref value for FieldInit is ELEMENT_TYPE_CLASS with a Value of a 32-bit. + writer.WriteUInt32(0); return; } - Span bytes = stackalloc byte[MaxScalarConstantSize]; - int written = WriteScalarConstant(bytes, value); - writer.WriteBytes(bytes.Slice(0, written)); + var type = value.GetType(); + if (type.GetTypeInfo().IsEnum) + { + type = Enum.GetUnderlyingType(type); + } + + if (type == typeof(bool)) + { + writer.WriteBoolean((bool)value); + } + else if (type == typeof(int)) + { + writer.WriteInt32((int)value); + } + else if (type == typeof(string)) + { + writer.WriteUTF16((string)value); + } + else if (type == typeof(byte)) + { + writer.WriteByte((byte)value); + } + else if (type == typeof(char)) + { + writer.WriteUInt16((char)value); + } + else if (type == typeof(double)) + { + writer.WriteDouble((double)value); + } + else if (type == typeof(short)) + { + writer.WriteInt16((short)value); + } + else if (type == typeof(long)) + { + writer.WriteInt64((long)value); + } + else if (type == typeof(sbyte)) + { + writer.WriteSByte((sbyte)value); + } + else if (type == typeof(float)) + { + writer.WriteSingle((float)value); + } + else if (type == typeof(ushort)) + { + writer.WriteUInt16((ushort)value); + } + else if (type == typeof(uint)) + { + writer.WriteUInt32((uint)value); + } + else if (type == typeof(ulong)) + { + writer.WriteUInt64((ulong)value); + } + else + { + throw new ArgumentException(SR.Format(SR.InvalidConstantValueOfType, type)); + } } } } From 40944d1de672b38874481fb6b3d09fb91cbd890c Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Thu, 19 Mar 2026 15:47:36 +0200 Subject: [PATCH 6/6] Remove calls to `GetTypeInfo()`. --- .../src/System/Reflection/Metadata/BlobWriterImpl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriterImpl.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriterImpl.cs index ab5bf094ab28cf..5bfb0a8f29d077 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriterImpl.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriterImpl.cs @@ -259,7 +259,7 @@ internal static void WriteConstant(ref BlobWriter writer, object? value) } var type = value.GetType(); - if (type.GetTypeInfo().IsEnum) + if (type.IsEnum) { type = Enum.GetUnderlyingType(type); } @@ -332,7 +332,7 @@ internal static void WriteConstant(BlobBuilder writer, object? value) } var type = value.GetType(); - if (type.GetTypeInfo().IsEnum) + if (type.IsEnum) { type = Enum.GetUnderlyingType(type); }