From d4aebdda580cce078f3658b55c18914027e30fa9 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sun, 29 Mar 2026 20:03:36 +0200 Subject: [PATCH 1/3] Remove unsafe code from BlobUtilities.cs - Replace Unsafe.WriteUnaligned with BinaryPrimitives.Write*LittleEndian/BigEndian - Remove #if NET/#else blocks from WriteDouble, WriteSingle, WriteGuid - Use BitConverter.DoubleToUInt64Bits/SingleToUInt32Bits and Guid.TryWriteBytes unconditionally - Remove redundant WriteByte extension method - Add Polyfills.cs with C# 14 extensions for non-.NETCoreApp targets (BitConverter.SingleToUInt32Bits, DoubleToUInt64Bits, Guid.TryWriteBytes) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System.Reflection.Metadata.csproj | 1 + .../Internal/Utilities/BlobUtilities.cs | 78 ++----------------- .../Internal/Utilities/Polyfills.cs | 55 +++++++++++++ .../System/Reflection/Metadata/BlobBuilder.cs | 3 +- 4 files changed, 65 insertions(+), 72 deletions(-) create mode 100644 src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/Polyfills.cs 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 7f38ee4629f8ac..242a4c1f6a6f54 100644 --- a/src/libraries/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj +++ b/src/libraries/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj @@ -93,6 +93,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/BlobUtilities.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/BlobUtilities.cs index 56d5dacd85d352..f705ac97d924b9 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/BlobUtilities.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/BlobUtilities.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.Reflection.Internal; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace System.Reflection { @@ -14,54 +13,29 @@ internal static class BlobUtilities public static void WriteBytes(this byte[] buffer, int start, byte value, int byteCount) { Debug.Assert(buffer.Length > 0); - new Span(buffer, start, byteCount).Fill(value); } - public static void WriteDouble(this byte[] buffer, int start, double value) - { -#if NET + public static void WriteDouble(this byte[] buffer, int start, double value) => WriteUInt64(buffer, start, BitConverter.DoubleToUInt64Bits(value)); -#else - unsafe - { - WriteUInt64(buffer, start, *(ulong*)&value); - } -#endif - } - public static void WriteSingle(this byte[] buffer, int start, float value) - { -#if NET + public static void WriteSingle(this byte[] buffer, int start, float value) => WriteUInt32(buffer, start, BitConverter.SingleToUInt32Bits(value)); -#else - unsafe - { - WriteUInt32(buffer, start, *(uint*)&value); - } -#endif - } - - public static void WriteByte(this byte[] buffer, int start, byte value) - { - // Perf: The compiler emits a check when pinning the buffer. It's thus not worth doing so. - buffer[start] = value; - } public static void WriteUInt16(this byte[] buffer, int start, ushort value) => - Unsafe.WriteUnaligned(ref buffer[start], !BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(value) : value); + BinaryPrimitives.WriteUInt16LittleEndian(buffer.AsSpan(start), value); public static void WriteUInt16BE(this byte[] buffer, int start, ushort value) => - Unsafe.WriteUnaligned(ref buffer[start], BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(value) : value); + BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(start), value); public static void WriteUInt32BE(this byte[] buffer, int start, uint value) => - Unsafe.WriteUnaligned(ref buffer[start], BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(value) : value); + BinaryPrimitives.WriteUInt32BigEndian(buffer.AsSpan(start), value); public static void WriteUInt32(this byte[] buffer, int start, uint value) => - Unsafe.WriteUnaligned(ref buffer[start], !BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(value) : value); + BinaryPrimitives.WriteUInt32LittleEndian(buffer.AsSpan(start), value); public static void WriteUInt64(this byte[] buffer, int start, ulong value) => - Unsafe.WriteUnaligned(ref buffer[start], !BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(value) : value); + BinaryPrimitives.WriteUInt64LittleEndian(buffer.AsSpan(start), value); public const int SizeOfSerializedDecimal = sizeof(byte) + 3 * sizeof(uint); @@ -72,7 +46,7 @@ public static void WriteDecimal(this byte[] buffer, int start, decimal value) uint low, mid, high; value.GetBits(out isNegative, out scale, out low, out mid, out high); - WriteByte(buffer, start, (byte)(scale | (isNegative ? 0x80 : 0x00))); + buffer[start] = (byte)(scale | (isNegative ? 0x80 : 0x00)); WriteUInt32(buffer, start + 1, low); WriteUInt32(buffer, start + 5, mid); WriteUInt32(buffer, start + 9, high); @@ -82,45 +56,9 @@ public static void WriteDecimal(this byte[] buffer, int start, decimal value) public static void WriteGuid(this byte[] buffer, int start, Guid value) { -#if NET bool written = value.TryWriteBytes(buffer.AsSpan(start)); // This function is not public, callers have to ensure that enough space is available. Debug.Assert(written); -#else - unsafe - { - fixed (byte* dst = &buffer[start]) - { - byte* src = (byte*)&value; - - uint a = *(uint*)(src + 0); - unchecked - { - dst[0] = (byte)a; - dst[1] = (byte)(a >> 8); - dst[2] = (byte)(a >> 16); - dst[3] = (byte)(a >> 24); - - ushort b = *(ushort*)(src + 4); - dst[4] = (byte)b; - dst[5] = (byte)(b >> 8); - - ushort c = *(ushort*)(src + 6); - dst[6] = (byte)c; - dst[7] = (byte)(c >> 8); - } - - dst[8] = src[8]; - dst[9] = src[9]; - dst[10] = src[10]; - dst[11] = src[11]; - dst[12] = src[12]; - dst[13] = src[13]; - dst[14] = src[14]; - dst[15] = src[15]; - } - } -#endif } public static unsafe void WriteUTF8(this byte[] buffer, int start, char* charPtr, int charCount, int byteCount, bool allowUnpairedSurrogates) diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/Polyfills.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/Polyfills.cs new file mode 100644 index 00000000000000..d5a77e8e531d6e --- /dev/null +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/Polyfills.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System +{ + internal static class BitConverterPolyfills + { + extension(BitConverter) + { + public static uint SingleToUInt32Bits(float value) + { + unsafe + { + return *(uint*)&value; + } + } + + public static ulong DoubleToUInt64Bits(double value) + { + unsafe + { + return *(ulong*)&value; + } + } + } + } + + internal static class GuidPolyfills + { + extension(Guid self) + { + public bool TryWriteBytes(Span destination) + { + if (destination.Length < 16) + return false; + + ref Guid selfRef = ref Unsafe.AsRef(in self); + if (BitConverter.IsLittleEndian) + { + MemoryMarshal.Write(destination, ref selfRef); + } + else + { + // slower path for BigEndian: ToByteArray always returns LE layout + self.ToByteArray().AsSpan().CopyTo(destination); + } + + return true; + } + } + } +} 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 97967bba080068..e92c1bba21837a 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 @@ -821,8 +821,7 @@ public void WriteBoolean(bool value) /// Builder is not writable, it has been linked with another one. public void WriteByte(byte value) { - int start = ReserveBytesPrimitive(sizeof(byte)); - _buffer.WriteByte(start, value); + _buffer[ReserveBytesPrimitive(sizeof(byte))] = value; } /// Builder is not writable, it has been linked with another one. From 0bc623f88d3231e2734409055e8b4229e31586f7 Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Sun, 29 Mar 2026 20:24:44 +0200 Subject: [PATCH 2/3] Update Polyfills.cs --- .../src/System/Reflection/Internal/Utilities/Polyfills.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/Polyfills.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/Polyfills.cs index d5a77e8e531d6e..8c91c9ff15f59e 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/Polyfills.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/Polyfills.cs @@ -44,7 +44,7 @@ public bool TryWriteBytes(Span destination) } else { - // slower path for BigEndian: ToByteArray always returns LE layout + // slower path for BigEndian self.ToByteArray().AsSpan().CopyTo(destination); } From 1802a8c86812656d7565d5d3d1e694edc28fd044 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sun, 29 Mar 2026 21:30:00 +0200 Subject: [PATCH 3/3] Fix BlobBuilder.WriteByte: evaluate _buffer after ReserveBytesPrimitive ReserveBytesPrimitive can reallocate _buffer when expanding to a new chunk. Combining into _buffer[ReserveBytesPrimitive(...)] evaluates _buffer before the call, writing to the stale buffer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Reflection/Metadata/BlobBuilder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 e92c1bba21837a..9228bc45f4567a 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 @@ -821,7 +821,8 @@ public void WriteBoolean(bool value) /// Builder is not writable, it has been linked with another one. public void WriteByte(byte value) { - _buffer[ReserveBytesPrimitive(sizeof(byte))] = value; + int start = ReserveBytesPrimitive(sizeof(byte)); + _buffer[start] = value; } /// Builder is not writable, it has been linked with another one.