From 19e3d13051fa9e2a3c516aec2c3f45fb4a5593ae Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sun, 29 Mar 2026 00:21:55 +0100 Subject: [PATCH 1/7] remove unsafe code --- .../Formats/Cbor/CborHelpers.netstandard.cs | 30 ++------- .../IO/Hashing/Crc32ParameterSet.WellKnown.cs | 67 +++++++------------ .../Internal/Utilities/BlobUtilities.cs | 15 ++--- 3 files changed, 35 insertions(+), 77 deletions(-) diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netstandard.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netstandard.cs index adb2eb362c0308..c5e14b7f612251 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netstandard.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netstandard.cs @@ -92,25 +92,13 @@ public static string BuildStringFromIndefiniteLengthTextString(int lengt [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ushort ReadHalfBigEndian(ReadOnlySpan source) { - ushort value = BitConverter.IsLittleEndian ? - BinaryPrimitives.ReverseEndianness(MemoryMarshal.Read(source)) : - MemoryMarshal.Read(source); - - return value; + return BinaryPrimitives.ReadUInt16BigEndian(source); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteHalfBigEndian(Span destination, ushort value) { - if (BitConverter.IsLittleEndian) - { - ushort tmp = BinaryPrimitives.ReverseEndianness(value); - MemoryMarshal.Write(destination, ref tmp); - } - else - { - MemoryMarshal.Write(destination, ref value); - } + BinaryPrimitives.WriteUInt16BigEndian(destination, value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -138,23 +126,13 @@ public static void WriteSingleBigEndian(Span destination, float value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double ReadDoubleBigEndian(ReadOnlySpan source) { - return BitConverter.IsLittleEndian ? - BitConverter.Int64BitsToDouble(BinaryPrimitives.ReverseEndianness(MemoryMarshal.Read(source))) : - MemoryMarshal.Read(source); + return BitConverter.Int64BitsToDouble(BinaryPrimitives.ReadInt64BigEndian(source)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteDoubleBigEndian(Span destination, double value) { - if (BitConverter.IsLittleEndian) - { - long tmp = BinaryPrimitives.ReverseEndianness(BitConverter.DoubleToInt64Bits(value)); - MemoryMarshal.Write(destination, ref tmp); - } - else - { - MemoryMarshal.Write(destination, ref value); - } + BinaryPrimitives.WriteInt64BigEndian(destination, BitConverter.DoubleToInt64Bits(value)); } internal static uint SingleToUInt32Bits(float value) diff --git a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs index b48bffa843d4c4..f7593a958b55ae 100644 --- a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs @@ -1,9 +1,8 @@ // 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; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace System.IO.Hashing { @@ -133,24 +132,17 @@ private static uint UpdateScalarArm64(uint crc, ReadOnlySpan source) Debug.Assert(System.Runtime.Intrinsics.Arm.Crc32.Arm64.IsSupported, "ARM CRC support is required."); // Compute in 8 byte chunks - if (source.Length >= sizeof(ulong)) - { - ref byte ptr = ref MemoryMarshal.GetReference(source); - - // Exclude trailing bytes not a multiple of 8 - int longLength = source.Length & ~0x7; + int longLength = source.Length & ~0x7; - for (int i = 0; i < longLength; i += sizeof(ulong)) - { - crc = System.Runtime.Intrinsics.Arm.Crc32.Arm64.ComputeCrc32( - crc, - Unsafe.ReadUnaligned(ref Unsafe.Add(ref ptr, i))); - } - source = source.Slice(longLength); + for (int i = 0; i < longLength; i += sizeof(ulong)) + { + crc = System.Runtime.Intrinsics.Arm.Crc32.Arm64.ComputeCrc32( + crc, + BinaryPrimitives.ReadUInt64LittleEndian(source.Slice(i))); } // Compute remaining bytes - for (int i = 0; i < source.Length; i++) + for (int i = longLength; i < source.Length; i++) { crc = System.Runtime.Intrinsics.Arm.Crc32.ComputeCrc32(crc, source[i]); } @@ -163,25 +155,17 @@ private static uint UpdateScalarArm(uint crc, ReadOnlySpan source) Debug.Assert(System.Runtime.Intrinsics.Arm.Crc32.IsSupported, "ARM CRC support is required."); // Compute in 4 byte chunks - if (source.Length >= sizeof(uint)) - { - ref byte ptr = ref MemoryMarshal.GetReference(source); - - // Exclude trailing bytes not a multiple of 4 - int intLength = source.Length & ~0x3; + int intLength = source.Length & ~0x3; - for (int i = 0; i < intLength; i += sizeof(uint)) - { - crc = System.Runtime.Intrinsics.Arm.Crc32.ComputeCrc32( - crc, - Unsafe.ReadUnaligned(ref Unsafe.Add(ref ptr, i))); - } - - source = source.Slice(intLength); + for (int i = 0; i < intLength; i += sizeof(uint)) + { + crc = System.Runtime.Intrinsics.Arm.Crc32.ComputeCrc32( + crc, + BinaryPrimitives.ReadUInt32LittleEndian(source.Slice(i))); } // Compute remaining bytes - for (int i = 0; i < source.Length; i++) + for (int i = intLength; i < source.Length; i++) { crc = System.Runtime.Intrinsics.Arm.Crc32.ComputeCrc32(crc, source[i]); } @@ -211,29 +195,29 @@ private static uint UpdateIntrinsic(uint crc, ReadOnlySpan source) { if (System.Runtime.Intrinsics.X86.Sse42.X64.IsSupported) { - ReadOnlySpan ulongData = MemoryMarshal.Cast(source); + int longLength = source.Length & ~0x7; ulong crc64 = crc; - foreach (ulong value in ulongData) + for (int i = 0; i < longLength; i += sizeof(ulong)) { - crc64 = System.Runtime.Intrinsics.X86.Sse42.X64.Crc32(crc64, value); + crc64 = System.Runtime.Intrinsics.X86.Sse42.X64.Crc32(crc64, BinaryPrimitives.ReadUInt64LittleEndian(source.Slice(i))); } crc = (uint)crc64; - source = source.Slice(ulongData.Length * sizeof(ulong)); + source = source.Slice(longLength); } - ReadOnlySpan uintData = MemoryMarshal.Cast(source); + int intLength = source.Length & ~0x3; - foreach (uint value in uintData) + for (int i = 0; i < intLength; i += sizeof(uint)) { - crc = System.Runtime.Intrinsics.X86.Sse42.Crc32(crc, value); + crc = System.Runtime.Intrinsics.X86.Sse42.Crc32(crc, BinaryPrimitives.ReadUInt32LittleEndian(source.Slice(i))); } // SSE 4.2 defines a ushort version as well, but that will only save us one byte, // so not worth the branch and cast. - ReadOnlySpan remainingBytes = source.Slice(uintData.Length * sizeof(uint)); + ReadOnlySpan remainingBytes = source.Slice(intLength); foreach (byte value in remainingBytes) { @@ -243,7 +227,6 @@ private static uint UpdateIntrinsic(uint crc, ReadOnlySpan source) else { Debug.Assert(System.Runtime.Intrinsics.Arm.Crc32.IsSupported); - ref byte ptr = ref MemoryMarshal.GetReference(source); int offset = 0; if (System.Runtime.Intrinsics.Arm.Crc32.Arm64.IsSupported) @@ -254,7 +237,7 @@ private static uint UpdateIntrinsic(uint crc, ReadOnlySpan source) { crc = System.Runtime.Intrinsics.Arm.Crc32.Arm64.ComputeCrc32C( crc, - Unsafe.ReadUnaligned(ref Unsafe.Add(ref ptr, offset))); + BinaryPrimitives.ReadUInt64LittleEndian(source.Slice(offset))); } } @@ -264,7 +247,7 @@ private static uint UpdateIntrinsic(uint crc, ReadOnlySpan source) { crc = System.Runtime.Intrinsics.Arm.Crc32.ComputeCrc32C( crc, - Unsafe.ReadUnaligned(ref Unsafe.Add(ref ptr, offset))); + BinaryPrimitives.ReadUInt32LittleEndian(source.Slice(offset))); } ReadOnlySpan remainingBytes = source.Slice(offset); 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..a2340cb5ad3fcf 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 @@ -23,10 +23,7 @@ public static void WriteDouble(this byte[] buffer, int start, double value) #if NET WriteUInt64(buffer, start, BitConverter.DoubleToUInt64Bits(value)); #else - unsafe - { - WriteUInt64(buffer, start, *(ulong*)&value); - } + WriteUInt64(buffer, start, unchecked((ulong)BitConverter.DoubleToInt64Bits(value))); #endif } @@ -49,19 +46,19 @@ public static void WriteByte(this byte[] buffer, int start, byte 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); From 406a1856b32969940652bf2892b10494cded1bf4 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sun, 29 Mar 2026 15:43:51 +0200 Subject: [PATCH 2/7] Address PR feedback: revert Crc32, inline BinaryPrimitives calls - Revert Crc32ParameterSet.WellKnown.cs changes (bounds check concern) - BlobUtilities: use BitConverter.DoubleToInt64Bits on all TFMs - CborHelpers: remove ReadDoubleBigEndian/WriteDoubleBigEndian wrappers, inline BinaryPrimitives calls at call sites with #if NET polyfill. Remove WriteHalfBigEndian wrapper, call BinaryPrimitives directly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Formats/Cbor/CborHelpers.netcoreapp.cs | 7 -- .../Formats/Cbor/CborHelpers.netstandard.cs | 18 ----- .../Formats/Cbor/Reader/CborReader.Simple.cs | 6 +- .../Formats/Cbor/Writer/CborWriter.Simple.cs | 7 +- .../Writer/CborWriter.Simple.netstandard.cs | 2 +- .../IO/Hashing/Crc32ParameterSet.WellKnown.cs | 67 ++++++++++++------- .../Internal/Utilities/BlobUtilities.cs | 4 -- 7 files changed, 54 insertions(+), 57 deletions(-) diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netcoreapp.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netcoreapp.cs index 95dc9bde882ba4..7d0f4e59059963 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netcoreapp.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netcoreapp.cs @@ -32,16 +32,9 @@ public static Half ReadHalfBigEndian(ReadOnlySpan source) public static unsafe float ReadSingleBigEndian(ReadOnlySpan source) => BinaryPrimitives.ReadSingleBigEndian(source); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static double ReadDoubleBigEndian(ReadOnlySpan source) - => BinaryPrimitives.ReadDoubleBigEndian(source); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteSingleBigEndian(Span destination, float value) => BinaryPrimitives.WriteSingleBigEndian(destination, value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void WriteDoubleBigEndian(Span destination, double value) - => BinaryPrimitives.WriteDoubleBigEndian(destination, value); } } diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netstandard.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netstandard.cs index c5e14b7f612251..8dacfaad01b6d5 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netstandard.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netstandard.cs @@ -95,12 +95,6 @@ public static ushort ReadHalfBigEndian(ReadOnlySpan source) return BinaryPrimitives.ReadUInt16BigEndian(source); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteHalfBigEndian(Span destination, ushort value) - { - BinaryPrimitives.WriteUInt16BigEndian(destination, value); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float ReadSingleBigEndian(ReadOnlySpan source) { @@ -123,18 +117,6 @@ public static void WriteSingleBigEndian(Span destination, float value) } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static double ReadDoubleBigEndian(ReadOnlySpan source) - { - return BitConverter.Int64BitsToDouble(BinaryPrimitives.ReadInt64BigEndian(source)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteDoubleBigEndian(Span destination, double value) - { - BinaryPrimitives.WriteInt64BigEndian(destination, BitConverter.DoubleToInt64Bits(value)); - } - internal static uint SingleToUInt32Bits(float value) => (uint)SingleToInt32Bits(value); diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Simple.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Simple.cs index 228ae0bb78d9d4..31105d05cc9653 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Simple.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Simple.cs @@ -84,7 +84,11 @@ public double ReadDouble() case CborAdditionalInfo.Additional64BitData: EnsureReadCapacity(buffer, 1 + sizeof(double)); - result = CborHelpers.ReadDoubleBigEndian(buffer.Slice(1)); +#if NET + result = BinaryPrimitives.ReadDoubleBigEndian(buffer.Slice(1)); +#else + result = BitConverter.Int64BitsToDouble(BinaryPrimitives.ReadInt64BigEndian(buffer.Slice(1))); +#endif AdvanceBuffer(1 + sizeof(double)); AdvanceDataItemCounters(); return result; diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Simple.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Simple.cs index 1b7e8dd00074be..b150473c10e182 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Simple.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Simple.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.Runtime.CompilerServices; namespace System.Formats.Cbor @@ -72,7 +73,11 @@ private void WriteDoubleCore(double value) { EnsureWriteCapacity(1 + sizeof(double)); WriteInitialByte(new CborInitialByte(CborMajorType.Simple, CborAdditionalInfo.Additional64BitData)); - CborHelpers.WriteDoubleBigEndian(_buffer.AsSpan(_offset), value); +#if NET + BinaryPrimitives.WriteDoubleBigEndian(_buffer.AsSpan(_offset), value); +#else + BinaryPrimitives.WriteInt64BigEndian(_buffer.AsSpan(_offset), BitConverter.DoubleToInt64Bits(value)); +#endif _offset += sizeof(double); AdvanceDataItemCounters(); } diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Simple.netstandard.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Simple.netstandard.cs index f257ce1f4b4e02..fd2e89c4151b81 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Simple.netstandard.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Simple.netstandard.cs @@ -27,7 +27,7 @@ private void WriteHalf(ushort value) } else { - CborHelpers.WriteHalfBigEndian(_buffer.AsSpan(_offset), value); + BinaryPrimitives.WriteUInt16BigEndian(_buffer.AsSpan(_offset), value); } _offset += sizeof(ushort); AdvanceDataItemCounters(); diff --git a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs index f7593a958b55ae..b48bffa843d4c4 100644 --- a/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs +++ b/src/libraries/System.IO.Hashing/src/System/IO/Hashing/Crc32ParameterSet.WellKnown.cs @@ -1,8 +1,9 @@ // 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; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace System.IO.Hashing { @@ -132,17 +133,24 @@ private static uint UpdateScalarArm64(uint crc, ReadOnlySpan source) Debug.Assert(System.Runtime.Intrinsics.Arm.Crc32.Arm64.IsSupported, "ARM CRC support is required."); // Compute in 8 byte chunks - int longLength = source.Length & ~0x7; - - for (int i = 0; i < longLength; i += sizeof(ulong)) + if (source.Length >= sizeof(ulong)) { - crc = System.Runtime.Intrinsics.Arm.Crc32.Arm64.ComputeCrc32( - crc, - BinaryPrimitives.ReadUInt64LittleEndian(source.Slice(i))); + ref byte ptr = ref MemoryMarshal.GetReference(source); + + // Exclude trailing bytes not a multiple of 8 + int longLength = source.Length & ~0x7; + + for (int i = 0; i < longLength; i += sizeof(ulong)) + { + crc = System.Runtime.Intrinsics.Arm.Crc32.Arm64.ComputeCrc32( + crc, + Unsafe.ReadUnaligned(ref Unsafe.Add(ref ptr, i))); + } + source = source.Slice(longLength); } // Compute remaining bytes - for (int i = longLength; i < source.Length; i++) + for (int i = 0; i < source.Length; i++) { crc = System.Runtime.Intrinsics.Arm.Crc32.ComputeCrc32(crc, source[i]); } @@ -155,17 +163,25 @@ private static uint UpdateScalarArm(uint crc, ReadOnlySpan source) Debug.Assert(System.Runtime.Intrinsics.Arm.Crc32.IsSupported, "ARM CRC support is required."); // Compute in 4 byte chunks - int intLength = source.Length & ~0x3; - - for (int i = 0; i < intLength; i += sizeof(uint)) + if (source.Length >= sizeof(uint)) { - crc = System.Runtime.Intrinsics.Arm.Crc32.ComputeCrc32( - crc, - BinaryPrimitives.ReadUInt32LittleEndian(source.Slice(i))); + ref byte ptr = ref MemoryMarshal.GetReference(source); + + // Exclude trailing bytes not a multiple of 4 + int intLength = source.Length & ~0x3; + + for (int i = 0; i < intLength; i += sizeof(uint)) + { + crc = System.Runtime.Intrinsics.Arm.Crc32.ComputeCrc32( + crc, + Unsafe.ReadUnaligned(ref Unsafe.Add(ref ptr, i))); + } + + source = source.Slice(intLength); } // Compute remaining bytes - for (int i = intLength; i < source.Length; i++) + for (int i = 0; i < source.Length; i++) { crc = System.Runtime.Intrinsics.Arm.Crc32.ComputeCrc32(crc, source[i]); } @@ -195,29 +211,29 @@ private static uint UpdateIntrinsic(uint crc, ReadOnlySpan source) { if (System.Runtime.Intrinsics.X86.Sse42.X64.IsSupported) { - int longLength = source.Length & ~0x7; + ReadOnlySpan ulongData = MemoryMarshal.Cast(source); ulong crc64 = crc; - for (int i = 0; i < longLength; i += sizeof(ulong)) + foreach (ulong value in ulongData) { - crc64 = System.Runtime.Intrinsics.X86.Sse42.X64.Crc32(crc64, BinaryPrimitives.ReadUInt64LittleEndian(source.Slice(i))); + crc64 = System.Runtime.Intrinsics.X86.Sse42.X64.Crc32(crc64, value); } crc = (uint)crc64; - source = source.Slice(longLength); + source = source.Slice(ulongData.Length * sizeof(ulong)); } - int intLength = source.Length & ~0x3; + ReadOnlySpan uintData = MemoryMarshal.Cast(source); - for (int i = 0; i < intLength; i += sizeof(uint)) + foreach (uint value in uintData) { - crc = System.Runtime.Intrinsics.X86.Sse42.Crc32(crc, BinaryPrimitives.ReadUInt32LittleEndian(source.Slice(i))); + crc = System.Runtime.Intrinsics.X86.Sse42.Crc32(crc, value); } // SSE 4.2 defines a ushort version as well, but that will only save us one byte, // so not worth the branch and cast. - ReadOnlySpan remainingBytes = source.Slice(intLength); + ReadOnlySpan remainingBytes = source.Slice(uintData.Length * sizeof(uint)); foreach (byte value in remainingBytes) { @@ -227,6 +243,7 @@ private static uint UpdateIntrinsic(uint crc, ReadOnlySpan source) else { Debug.Assert(System.Runtime.Intrinsics.Arm.Crc32.IsSupported); + ref byte ptr = ref MemoryMarshal.GetReference(source); int offset = 0; if (System.Runtime.Intrinsics.Arm.Crc32.Arm64.IsSupported) @@ -237,7 +254,7 @@ private static uint UpdateIntrinsic(uint crc, ReadOnlySpan source) { crc = System.Runtime.Intrinsics.Arm.Crc32.Arm64.ComputeCrc32C( crc, - BinaryPrimitives.ReadUInt64LittleEndian(source.Slice(offset))); + Unsafe.ReadUnaligned(ref Unsafe.Add(ref ptr, offset))); } } @@ -247,7 +264,7 @@ private static uint UpdateIntrinsic(uint crc, ReadOnlySpan source) { crc = System.Runtime.Intrinsics.Arm.Crc32.ComputeCrc32C( crc, - BinaryPrimitives.ReadUInt32LittleEndian(source.Slice(offset))); + Unsafe.ReadUnaligned(ref Unsafe.Add(ref ptr, offset))); } ReadOnlySpan remainingBytes = source.Slice(offset); 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 a2340cb5ad3fcf..0953fbbd8fe53e 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 @@ -20,11 +20,7 @@ public static void WriteBytes(this byte[] buffer, int start, byte value, int byt public static void WriteDouble(this byte[] buffer, int start, double value) { -#if NET - WriteUInt64(buffer, start, BitConverter.DoubleToUInt64Bits(value)); -#else WriteUInt64(buffer, start, unchecked((ulong)BitConverter.DoubleToInt64Bits(value))); -#endif } public static void WriteSingle(this byte[] buffer, int start, float value) From 032535a80027e5cb04c662d1f51d249de02ed441 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sun, 29 Mar 2026 15:58:57 +0200 Subject: [PATCH 3/7] Simplify: use common BinaryPrimitives path for double, remove #if NET Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Formats/Cbor/Reader/CborReader.Simple.cs | 4 ---- .../src/System/Formats/Cbor/Writer/CborWriter.Simple.cs | 4 ---- 2 files changed, 8 deletions(-) diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Simple.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Simple.cs index 31105d05cc9653..405880371fb190 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Simple.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Simple.cs @@ -84,11 +84,7 @@ public double ReadDouble() case CborAdditionalInfo.Additional64BitData: EnsureReadCapacity(buffer, 1 + sizeof(double)); -#if NET - result = BinaryPrimitives.ReadDoubleBigEndian(buffer.Slice(1)); -#else result = BitConverter.Int64BitsToDouble(BinaryPrimitives.ReadInt64BigEndian(buffer.Slice(1))); -#endif AdvanceBuffer(1 + sizeof(double)); AdvanceDataItemCounters(); return result; diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Simple.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Simple.cs index b150473c10e182..66507b9f6dd69b 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Simple.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Simple.cs @@ -73,11 +73,7 @@ private void WriteDoubleCore(double value) { EnsureWriteCapacity(1 + sizeof(double)); WriteInitialByte(new CborInitialByte(CborMajorType.Simple, CborAdditionalInfo.Additional64BitData)); -#if NET - BinaryPrimitives.WriteDoubleBigEndian(_buffer.AsSpan(_offset), value); -#else BinaryPrimitives.WriteInt64BigEndian(_buffer.AsSpan(_offset), BitConverter.DoubleToInt64Bits(value)); -#endif _offset += sizeof(double); AdvanceDataItemCounters(); } From 2ae30d34601496f6e0beb9c7fb253a1185e1bfe7 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sun, 29 Mar 2026 16:52:22 +0200 Subject: [PATCH 4/7] Use extension methods to polyfill BinaryPrimitives for netstandard Address jkotas feedback: convert CborHelpers Single/Double wrappers to extension(BinaryPrimitives) polyfills so callers can use BinaryPrimitives.Read/WriteSingle/DoubleBigEndian directly on all TFMs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Formats/Cbor/CborHelpers.netcoreapp.cs | 9 --- .../Formats/Cbor/CborHelpers.netstandard.cs | 62 ++++++++++++------- .../Formats/Cbor/Reader/CborReader.Simple.cs | 6 +- .../Formats/Cbor/Writer/CborWriter.Simple.cs | 4 +- 4 files changed, 45 insertions(+), 36 deletions(-) diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netcoreapp.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netcoreapp.cs index 7d0f4e59059963..f7815761b06e8e 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netcoreapp.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netcoreapp.cs @@ -27,14 +27,5 @@ public static string BuildStringFromIndefiniteLengthTextString(int lengt [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Half ReadHalfBigEndian(ReadOnlySpan source) => BinaryPrimitives.ReadHalfBigEndian(source); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe float ReadSingleBigEndian(ReadOnlySpan source) - => BinaryPrimitives.ReadSingleBigEndian(source); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void WriteSingleBigEndian(Span destination, float value) - => BinaryPrimitives.WriteSingleBigEndian(destination, value); - } } diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netstandard.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netstandard.cs index 8dacfaad01b6d5..073445cd551787 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netstandard.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netstandard.cs @@ -95,28 +95,6 @@ public static ushort ReadHalfBigEndian(ReadOnlySpan source) return BinaryPrimitives.ReadUInt16BigEndian(source); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float ReadSingleBigEndian(ReadOnlySpan source) - { - return BitConverter.IsLittleEndian ? - Int32BitsToSingle(BinaryPrimitives.ReverseEndianness(MemoryMarshal.Read(source))) : - MemoryMarshal.Read(source); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteSingleBigEndian(Span destination, float value) - { - if (BitConverter.IsLittleEndian) - { - int tmp = BinaryPrimitives.ReverseEndianness(SingleToInt32Bits(value)); - MemoryMarshal.Write(destination, ref tmp); - } - else - { - MemoryMarshal.Write(destination, ref value); - } - } - internal static uint SingleToUInt32Bits(float value) => (uint)SingleToInt32Bits(value); @@ -130,6 +108,46 @@ internal static unsafe float Int32BitsToSingle(int value) => *((float*)&value); } + internal static class BinaryPrimitivesPolyfills + { + extension(BinaryPrimitives) + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double ReadDoubleBigEndian(ReadOnlySpan source) + { + return BitConverter.Int64BitsToDouble(BinaryPrimitives.ReadInt64BigEndian(source)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteDoubleBigEndian(Span destination, double value) + { + BinaryPrimitives.WriteInt64BigEndian(destination, BitConverter.DoubleToInt64Bits(value)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ReadSingleBigEndian(ReadOnlySpan source) + { + return BitConverter.IsLittleEndian ? + CborHelpers.Int32BitsToSingle(BinaryPrimitives.ReverseEndianness(MemoryMarshal.Read(source))) : + MemoryMarshal.Read(source); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteSingleBigEndian(Span destination, float value) + { + if (BitConverter.IsLittleEndian) + { + int tmp = BinaryPrimitives.ReverseEndianness(CborHelpers.SingleToInt32Bits(value)); + MemoryMarshal.Write(destination, ref tmp); + } + else + { + MemoryMarshal.Write(destination, ref value); + } + } + } + } + internal static class StackExtensions { public static bool TryPop(this Stack stack, [MaybeNullWhen(false)] out T result) diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Simple.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Simple.cs index 405880371fb190..ec115bc980b555 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Simple.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Simple.cs @@ -36,7 +36,7 @@ public float ReadSingle() case CborAdditionalInfo.Additional32BitData: EnsureReadCapacity(buffer, 1 + sizeof(float)); - result = CborHelpers.ReadSingleBigEndian(buffer.Slice(1)); + result = BinaryPrimitives.ReadSingleBigEndian(buffer.Slice(1)); AdvanceBuffer(1 + sizeof(float)); AdvanceDataItemCounters(); return result; @@ -77,14 +77,14 @@ public double ReadDouble() case CborAdditionalInfo.Additional32BitData: EnsureReadCapacity(buffer, 1 + sizeof(float)); - result = CborHelpers.ReadSingleBigEndian(buffer.Slice(1)); + result = BinaryPrimitives.ReadSingleBigEndian(buffer.Slice(1)); AdvanceBuffer(1 + sizeof(float)); AdvanceDataItemCounters(); return result; case CborAdditionalInfo.Additional64BitData: EnsureReadCapacity(buffer, 1 + sizeof(double)); - result = BitConverter.Int64BitsToDouble(BinaryPrimitives.ReadInt64BigEndian(buffer.Slice(1))); + result = BinaryPrimitives.ReadDoubleBigEndian(buffer.Slice(1)); AdvanceBuffer(1 + sizeof(double)); AdvanceDataItemCounters(); return result; diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Simple.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Simple.cs index 66507b9f6dd69b..72345928dc2d0f 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Simple.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Simple.cs @@ -64,7 +64,7 @@ private void WriteSingleCore(float value) { EnsureWriteCapacity(1 + sizeof(float)); WriteInitialByte(new CborInitialByte(CborMajorType.Simple, CborAdditionalInfo.Additional32BitData)); - CborHelpers.WriteSingleBigEndian(_buffer.AsSpan(_offset), value); + BinaryPrimitives.WriteSingleBigEndian(_buffer.AsSpan(_offset), value); _offset += sizeof(float); AdvanceDataItemCounters(); } @@ -73,7 +73,7 @@ private void WriteDoubleCore(double value) { EnsureWriteCapacity(1 + sizeof(double)); WriteInitialByte(new CborInitialByte(CborMajorType.Simple, CborAdditionalInfo.Additional64BitData)); - BinaryPrimitives.WriteInt64BigEndian(_buffer.AsSpan(_offset), BitConverter.DoubleToInt64Bits(value)); + BinaryPrimitives.WriteDoubleBigEndian(_buffer.AsSpan(_offset), value); _offset += sizeof(double); AdvanceDataItemCounters(); } From 80713771ed1f43ffad41b5f95986e74c2fef51c2 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sun, 29 Mar 2026 17:23:16 +0200 Subject: [PATCH 5/7] Address feedback: simplify Single/Double polyfills, use BinaryPrimitives directly - CborHelpers: simplify ReadSingleBigEndian/WriteSingleBigEndian extensions to use ReadInt32BigEndian/WriteInt32BigEndian with inlined unsafe casts, removing MemoryMarshal.Read/Write and ReverseEndianness - BlobUtilities: use BinaryPrimitives.WriteSingle/DoubleLittleEndian on NET, keep existing fallback on netstandard2.0/net462 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Formats/Cbor/CborHelpers.netstandard.cs | 21 ++++++------------- .../Internal/Utilities/BlobUtilities.cs | 6 +++++- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netstandard.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netstandard.cs index 073445cd551787..ddf12e9c56df04 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netstandard.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netstandard.cs @@ -96,7 +96,7 @@ public static ushort ReadHalfBigEndian(ReadOnlySpan source) } internal static uint SingleToUInt32Bits(float value) - => (uint)SingleToInt32Bits(value); + => unchecked((uint)SingleToInt32Bits(value)); internal static unsafe int SingleToInt32Bits(float value) => *((int*)&value); @@ -125,25 +125,16 @@ public static void WriteDoubleBigEndian(Span destination, double value) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float ReadSingleBigEndian(ReadOnlySpan source) + public static unsafe float ReadSingleBigEndian(ReadOnlySpan source) { - return BitConverter.IsLittleEndian ? - CborHelpers.Int32BitsToSingle(BinaryPrimitives.ReverseEndianness(MemoryMarshal.Read(source))) : - MemoryMarshal.Read(source); + int intValue = BinaryPrimitives.ReadInt32BigEndian(source); + return *((float*)&intValue); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteSingleBigEndian(Span destination, float value) + public static unsafe void WriteSingleBigEndian(Span destination, float value) { - if (BitConverter.IsLittleEndian) - { - int tmp = BinaryPrimitives.ReverseEndianness(CborHelpers.SingleToInt32Bits(value)); - MemoryMarshal.Write(destination, ref tmp); - } - else - { - MemoryMarshal.Write(destination, ref value); - } + BinaryPrimitives.WriteInt32BigEndian(destination, *((int*)&value)); } } } 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 0953fbbd8fe53e..212c42c17ef952 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 @@ -20,13 +20,17 @@ public static void WriteBytes(this byte[] buffer, int start, byte value, int byt public static void WriteDouble(this byte[] buffer, int start, double value) { +#if NET + BinaryPrimitives.WriteDoubleLittleEndian(buffer.AsSpan(start), value); +#else WriteUInt64(buffer, start, unchecked((ulong)BitConverter.DoubleToInt64Bits(value))); +#endif } public static void WriteSingle(this byte[] buffer, int start, float value) { #if NET - WriteUInt32(buffer, start, BitConverter.SingleToUInt32Bits(value)); + BinaryPrimitives.WriteSingleLittleEndian(buffer.AsSpan(start), value); #else unsafe { From bc5870b0ebef4662955997d8e94b7fda40852f61 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sun, 29 Mar 2026 18:02:39 +0200 Subject: [PATCH 6/7] Delete CborHelpers.netcoreapp.cs, merge into single CborHelpers.cs Move all TFM-specific CborHelpers methods into one file with #if NET guards. Delete the netcoreapp-only partial class file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System.Formats.Cbor.csproj | 3 +- ...rHelpers.netstandard.cs => CborHelpers.cs} | 33 ++++++++++++++++++- .../Formats/Cbor/CborHelpers.netcoreapp.cs | 31 ----------------- 3 files changed, 33 insertions(+), 34 deletions(-) rename src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/{CborHelpers.netstandard.cs => CborHelpers.cs} (82%) delete mode 100644 src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netcoreapp.cs diff --git a/src/libraries/System.Formats.Cbor/src/System.Formats.Cbor.csproj b/src/libraries/System.Formats.Cbor/src/System.Formats.Cbor.csproj index 0a0b502728f9c4..cba3c974bf0abd 100644 --- a/src/libraries/System.Formats.Cbor/src/System.Formats.Cbor.csproj +++ b/src/libraries/System.Formats.Cbor/src/System.Formats.Cbor.csproj @@ -36,17 +36,16 @@ System.Formats.Cbor.CborWriter + - - diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netstandard.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.cs similarity index 82% rename from src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netstandard.cs rename to src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.cs index ddf12e9c56df04..6e79ebbfd9721f 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netstandard.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.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; using System.Buffers.Binary; using System.Collections.Generic; using System.Diagnostics; @@ -11,12 +12,20 @@ namespace System.Formats.Cbor { - internal static partial class CborHelpers + internal static class CborHelpers { +#if NET + public static readonly DateTimeOffset UnixEpoch = DateTimeOffset.UnixEpoch; +#else private const long UnixEpochTicks = 719162L /*Number of days from 1/1/0001 to 12/31/1969*/ * 10000 * 1000 * 60 * 60 * 24; /* Ticks per day.*/ public static readonly DateTimeOffset UnixEpoch = new DateTimeOffset(UnixEpochTicks, TimeSpan.Zero); +#endif +#if NET + public static BigInteger CreateBigIntegerFromUnsignedBigEndianBytes(byte[] bytes) + => new BigInteger(bytes, isUnsigned: true, isBigEndian: true); +#else public static BigInteger CreateBigIntegerFromUnsignedBigEndianBytes(byte[] bigEndianBytes) { if (bigEndianBytes.Length == 0) @@ -44,7 +53,12 @@ public static BigInteger CreateBigIntegerFromUnsignedBigEndianBytes(byte[] bigEn return new BigInteger(temp); } +#endif +#if NET + public static byte[] CreateUnsignedBigEndianBytesFromBigInteger(BigInteger value) + => value.ToByteArray(isUnsigned: true, isBigEndian: true); +#else public static byte[] CreateUnsignedBigEndianBytesFromBigInteger(BigInteger value) { byte[] littleEndianBytes = value.ToByteArray(); @@ -74,12 +88,22 @@ public static byte[] CreateUnsignedBigEndianBytesFromBigInteger(BigInteger value return start == 0 ? littleEndianBytes : bytesAsSpan.Slice(start).ToArray(); } +#endif +#if NET + public static void GetBitsFromDecimal(decimal d, Span destination) + => decimal.GetBits(d, destination); +#else public static void GetBitsFromDecimal(decimal d, Span destination) { decimal.GetBits(d).CopyTo(destination); } +#endif +#if NET + public static string BuildStringFromIndefiniteLengthTextString(int length, TState state, SpanAction action) + => string.Create(length, state, action); +#else public delegate void SpanAction(Span span, TArg arg); public static string BuildStringFromIndefiniteLengthTextString(int length, TState state, SpanAction action) @@ -88,12 +112,19 @@ public static string BuildStringFromIndefiniteLengthTextString(int lengt action(arr, state); return new string(arr); } +#endif +#if NET + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Half ReadHalfBigEndian(ReadOnlySpan source) + => BinaryPrimitives.ReadHalfBigEndian(source); +#else [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ushort ReadHalfBigEndian(ReadOnlySpan source) { return BinaryPrimitives.ReadUInt16BigEndian(source); } +#endif internal static uint SingleToUInt32Bits(float value) => unchecked((uint)SingleToInt32Bits(value)); diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netcoreapp.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netcoreapp.cs deleted file mode 100644 index f7815761b06e8e..00000000000000 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netcoreapp.cs +++ /dev/null @@ -1,31 +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.Buffers; -using System.Buffers.Binary; -using System.Numerics; -using System.Runtime.CompilerServices; - -namespace System.Formats.Cbor -{ - internal static partial class CborHelpers - { - public static readonly DateTimeOffset UnixEpoch = DateTimeOffset.UnixEpoch; - - public static BigInteger CreateBigIntegerFromUnsignedBigEndianBytes(byte[] bytes) - => new BigInteger(bytes, isUnsigned: true, isBigEndian: true); - - public static byte[] CreateUnsignedBigEndianBytesFromBigInteger(BigInteger value) - => value.ToByteArray(isUnsigned: true, isBigEndian: true); - - public static void GetBitsFromDecimal(decimal d, Span destination) - => decimal.GetBits(d, destination); - - public static string BuildStringFromIndefiniteLengthTextString(int length, TState state, SpanAction action) - => string.Create(length, state, action); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Half ReadHalfBigEndian(ReadOnlySpan source) - => BinaryPrimitives.ReadHalfBigEndian(source); - } -} From 86719d2a9100c186016631ccea66042c2490adee Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sun, 29 Mar 2026 18:28:15 +0200 Subject: [PATCH 7/7] Convert all CborHelpers polyfills: BitConverter extensions, use modern APIs on NET - Add extension(BitConverter) polyfills for SingleToInt32Bits, Int32BitsToSingle, SingleToUInt32Bits, UInt32BitsToSingle on netstandard - Update HalfHelpers and CborWriter.Simple to use BitConverter.* directly - Keep CborHelpers wrappers for UnixEpoch, BigInteger, decimal.GetBits, string.Create (different signatures per TFM, not pure polyfills) - Guard netstandard-only polyfills with #if !NET, add NET versions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Formats/Cbor/CborHelpers.cs | 32 ++++++++++++------- .../Formats/Cbor/HalfHelpers.netstandard.cs | 10 +++--- .../Writer/CborWriter.Simple.netstandard.cs | 4 +-- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.cs index 6e79ebbfd9721f..bcfdd1e76526c7 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.cs @@ -125,20 +125,9 @@ public static ushort ReadHalfBigEndian(ReadOnlySpan source) return BinaryPrimitives.ReadUInt16BigEndian(source); } #endif - - internal static uint SingleToUInt32Bits(float value) - => unchecked((uint)SingleToInt32Bits(value)); - - internal static unsafe int SingleToInt32Bits(float value) - => *((int*)&value); - - internal static float UInt32BitsToSingle(uint value) - => Int32BitsToSingle((int)value); - - internal static unsafe float Int32BitsToSingle(int value) - => *((float*)&value); } +#if !NET internal static class BinaryPrimitivesPolyfills { extension(BinaryPrimitives) @@ -170,6 +159,25 @@ public static unsafe void WriteSingleBigEndian(Span destination, float val } } + internal static class BitConverterPolyfills + { + extension(BitConverter) + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe int SingleToInt32Bits(float value) => *((int*)&value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe float Int32BitsToSingle(int value) => *((float*)&value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint SingleToUInt32Bits(float value) => unchecked((uint)BitConverter.SingleToInt32Bits(value)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float UInt32BitsToSingle(uint value) => BitConverter.Int32BitsToSingle((int)value); + } + } +#endif + internal static class StackExtensions { public static bool TryPop(this Stack stack, [MaybeNullWhen(false)] out T result) diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/HalfHelpers.netstandard.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/HalfHelpers.netstandard.cs index c4d191be9ad54c..890fa148e9fd79 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/HalfHelpers.netstandard.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/HalfHelpers.netstandard.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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; @@ -53,7 +53,7 @@ public static float HalfToFloat(ushort value) { if (sig == 0) { - return CborHelpers.UInt32BitsToSingle(sign ? FloatSignMask : 0); // Positive / Negative zero + return BitConverter.UInt32BitsToSingle(sign ? FloatSignMask : 0); // Positive / Negative zero } (exp, sig) = NormSubnormalF16Sig(sig); exp -= 1; @@ -62,7 +62,7 @@ public static float HalfToFloat(ushort value) return CreateSingle(sign, (byte)(exp + 0x70), sig << 13); static float CreateSingle(bool sign, byte exp, uint sig) - => CborHelpers.Int32BitsToSingle((int)(((sign ? 1U : 0U) << FloatSignShift) + ((uint)exp << FloatExponentShift) + sig)); + => BitConverter.Int32BitsToSingle((int)(((sign ? 1U : 0U) << FloatSignShift) + ((uint)exp << FloatExponentShift) + sig)); } public static bool HalfIsNaN(ushort value) @@ -119,7 +119,7 @@ private static float CreateSingleNaN(bool sign, ulong significand) uint signInt = (sign ? 1U : 0U) << FloatSignShift; uint sigInt = (uint)(significand >> 41); - return CborHelpers.UInt32BitsToSingle(signInt | NaNBits | sigInt); + return BitConverter.UInt32BitsToSingle(signInt | NaNBits | sigInt); } #endregion @@ -128,7 +128,7 @@ public static ushort FloatToHalf(float value) { const int SingleMaxExponent = 0xFF; - uint floatInt = CborHelpers.SingleToUInt32Bits(value); + uint floatInt = BitConverter.SingleToUInt32Bits(value); bool sign = (floatInt & FloatSignMask) >> FloatSignShift != 0; int exp = (int)(floatInt & FloatExponentMask) >> FloatExponentShift; uint sig = floatInt & FloatSignificandMask; diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Simple.netstandard.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Simple.netstandard.cs index fd2e89c4151b81..86ccde95d03984 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Simple.netstandard.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Simple.netstandard.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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; @@ -37,7 +37,7 @@ private void WriteHalf(ushort value) internal static bool TryConvertSingleToHalf(float value, out ushort result) { result = HalfHelpers.FloatToHalf(value); - return float.IsNaN(value) || CborHelpers.SingleToInt32Bits(HalfHelpers.HalfToFloat(result)) == CborHelpers.SingleToInt32Bits(value); + return float.IsNaN(value) || BitConverter.SingleToInt32Bits(HalfHelpers.HalfToFloat(result)) == BitConverter.SingleToInt32Bits(value); } } }