From 41eee75f3b1fc1640285c23b6a3e1fd9ec15ef35 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sun, 29 Mar 2026 23:38:19 +0200 Subject: [PATCH 1/5] Remove CborHelpers*.cs files Use APIs directly, polyfill for netstandard2.0 via C# 14 extensions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System.Formats.Cbor.csproj | 3 +- .../Formats/Cbor/CborHelpers.netcoreapp.cs | 47 ----- .../Formats/Cbor/CborHelpers.netstandard.cs | 187 ----------------- .../Formats/Cbor/HalfHelpers.netstandard.cs | 8 +- .../src/System/Formats/Cbor/Polyfills.cs | 192 ++++++++++++++++++ .../Formats/Cbor/Reader/CborReader.Simple.cs | 10 +- .../Formats/Cbor/Reader/CborReader.String.cs | 2 +- .../Formats/Cbor/Reader/CborReader.Tag.cs | 29 ++- .../Formats/Cbor/Writer/CborWriter.Simple.cs | 5 +- .../Writer/CborWriter.Simple.netstandard.cs | 4 +- .../Formats/Cbor/Writer/CborWriter.Tag.cs | 8 +- 11 files changed, 238 insertions(+), 257 deletions(-) delete mode 100644 src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netcoreapp.cs delete mode 100644 src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netstandard.cs create mode 100644 src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Polyfills.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..13bf6e627e8daa 100644 --- a/src/libraries/System.Formats.Cbor/src/System.Formats.Cbor.csproj +++ b/src/libraries/System.Formats.Cbor/src/System.Formats.Cbor.csproj @@ -39,14 +39,13 @@ System.Formats.Cbor.CborWriter - - + 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 95dc9bde882ba4..00000000000000 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netcoreapp.cs +++ /dev/null @@ -1,47 +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); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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 deleted file mode 100644 index adb2eb362c0308..00000000000000 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/CborHelpers.netstandard.cs +++ /dev/null @@ -1,187 +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.Binary; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace System.Formats.Cbor -{ - internal static partial class CborHelpers - { - 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); - - public static BigInteger CreateBigIntegerFromUnsignedBigEndianBytes(byte[] bigEndianBytes) - { - if (bigEndianBytes.Length == 0) - { - return new BigInteger(bigEndianBytes); - } - - byte[] temp; - if ((bigEndianBytes[0] & 0x80) != 0) // Is negative? - { - // To prevent positive values from being misinterpreted as negative values, - // you can add a zero-byte value to the most significant side of the array. - // Right in this case as it is Big-endian. - var bytesPlusOne = new byte[bigEndianBytes.Length + 1]; - bigEndianBytes.CopyTo(bytesPlusOne.AsSpan(1)); - temp = bytesPlusOne; - } - else - { - temp = bigEndianBytes; - } - - // Reverse endianness - temp.AsSpan().Reverse(); - - return new BigInteger(temp); - } - - public static byte[] CreateUnsignedBigEndianBytesFromBigInteger(BigInteger value) - { - byte[] littleEndianBytes = value.ToByteArray(); - - if (littleEndianBytes.Length == 1) - { - return littleEndianBytes; - } - - Span bytesAsSpan = littleEndianBytes; - bytesAsSpan.Reverse(); - - int start = 0; - for (int i = 0; i < bytesAsSpan.Length; i++) - { - if (bytesAsSpan[i] == 0x00) - { - start++; - } - else - { - break; - } - } - - Debug.Assert(start <= 1); // If there is a case where we trim more than one byte, we want to add it to our tests. - - return start == 0 ? littleEndianBytes : bytesAsSpan.Slice(start).ToArray(); - } - - public static void GetBitsFromDecimal(decimal d, Span destination) - { - decimal.GetBits(d).CopyTo(destination); - } - - public delegate void SpanAction(Span span, TArg arg); - - public static string BuildStringFromIndefiniteLengthTextString(int length, TState state, SpanAction action) - { - char[] arr = new char[length]; - action(arr, state); - return new string(arr); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort ReadHalfBigEndian(ReadOnlySpan source) - { - ushort value = BitConverter.IsLittleEndian ? - BinaryPrimitives.ReverseEndianness(MemoryMarshal.Read(source)) : - MemoryMarshal.Read(source); - - return value; - } - - [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); - } - } - - [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); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static double ReadDoubleBigEndian(ReadOnlySpan source) - { - return BitConverter.IsLittleEndian ? - BitConverter.Int64BitsToDouble(BinaryPrimitives.ReverseEndianness(MemoryMarshal.Read(source))) : - MemoryMarshal.Read(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); - } - } - - internal static uint SingleToUInt32Bits(float value) - => (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); - } - - internal static class StackExtensions - { - public static bool TryPop(this Stack stack, [MaybeNullWhen(false)] out T result) - { - if (stack.Count > 0) - { - result = stack.Pop(); - return true; - } - - result = default; - return false; - } - } -} 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..d5593d9dc78f02 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 @@ -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/Polyfills.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Polyfills.cs new file mode 100644 index 00000000000000..34b7e35b36c848 --- /dev/null +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Polyfills.cs @@ -0,0 +1,192 @@ +// 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.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System.Formats.Cbor +{ + internal static class DateTimeOffsetPolyfills + { + private const long UnixEpochTicks = 719162L * 10000 * 1000 * 60 * 60 * 24; + + extension(DateTimeOffset) + { + public static DateTimeOffset UnixEpoch => new DateTimeOffset(UnixEpochTicks, TimeSpan.Zero); + } + } + + internal static class BigIntegerPolyfills + { + extension(BigInteger self) + { + public byte[] ToByteArray(bool isUnsigned, bool isBigEndian) + { + byte[] littleEndianBytes = self.ToByteArray(); + + if (littleEndianBytes.Length == 1) + return littleEndianBytes; + + Span bytesAsSpan = littleEndianBytes; + + if (isBigEndian) + bytesAsSpan.Reverse(); + + if (isUnsigned) + { + int start = 0; + for (int i = 0; i < bytesAsSpan.Length; i++) + { + if (bytesAsSpan[i] == 0x00) + start++; + else + break; + } + + if (start > 0) + return bytesAsSpan.Slice(start).ToArray(); + } + + return littleEndianBytes; + } + } + } + + internal static class DecimalPolyfills + { + extension(decimal) + { + public static void GetBits(decimal d, Span destination) + { + decimal.GetBits(d).CopyTo(destination); + } + } + } + + internal static class StringPolyfills + { + internal delegate void SpanAction(Span span, TArg arg); + + extension(string) + { + public static string Create(int length, TState state, SpanAction action) + { + char[] arr = new char[length]; + action(arr, state); + return new string(arr); + } + } + } + + internal static class BinaryPrimitivesPolyfills + { + extension(BinaryPrimitives) + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ReadHalfBigEndian(ReadOnlySpan source) + { + return BitConverter.IsLittleEndian + ? BinaryPrimitives.ReverseEndianness(MemoryMarshal.Read(source)) + : MemoryMarshal.Read(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); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ReadSingleBigEndian(ReadOnlySpan source) + { + return BitConverter.IsLittleEndian + ? BitConverter.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(BitConverter.SingleToInt32Bits(value)); + MemoryMarshal.Write(destination, ref tmp); + } + else + { + MemoryMarshal.Write(destination, ref value); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double ReadDoubleBigEndian(ReadOnlySpan source) + { + return BitConverter.IsLittleEndian + ? BitConverter.Int64BitsToDouble(BinaryPrimitives.ReverseEndianness(MemoryMarshal.Read(source))) + : MemoryMarshal.Read(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); + } + } + } + } + + internal static class BitConverterPolyfills + { + extension(BitConverter) + { + public static unsafe int SingleToInt32Bits(float value) + => *(int*)&value; + + public static unsafe float Int32BitsToSingle(int value) + => *(float*)&value; + + public static uint SingleToUInt32Bits(float value) + => (uint)BitConverter.SingleToInt32Bits(value); + + public static float UInt32BitsToSingle(uint value) + => BitConverter.Int32BitsToSingle((int)value); + } + } + + internal static class StackPolyfills + { + extension(Stack stack) + { + public bool TryPop([MaybeNullWhen(false)] out T result) + { + if (stack.Count > 0) + { + result = stack.Pop(); + return true; + } + + result = default; + return false; + } + } + } +} 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..9eaaafed5b4244 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 @@ -29,14 +29,14 @@ public float ReadSingle() { case CborAdditionalInfo.Additional16BitData: EnsureReadCapacity(buffer, 1 + sizeof(ushort)); - result = HalfHelpers.HalfToFloat(CborHelpers.ReadHalfBigEndian(buffer.Slice(1))); + result = HalfHelpers.HalfToFloat(BinaryPrimitives.ReadHalfBigEndian(buffer.Slice(1))); AdvanceBuffer(1 + sizeof(ushort)); AdvanceDataItemCounters(); return result; 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; @@ -70,21 +70,21 @@ public double ReadDouble() { case CborAdditionalInfo.Additional16BitData: EnsureReadCapacity(buffer, 1 + sizeof(short)); - result = HalfHelpers.HalfToDouble(CborHelpers.ReadHalfBigEndian(buffer.Slice(1))); + result = HalfHelpers.HalfToDouble(BinaryPrimitives.ReadHalfBigEndian(buffer.Slice(1))); AdvanceBuffer(1 + sizeof(short)); AdvanceDataItemCounters(); return result; 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 = CborHelpers.ReadDoubleBigEndian(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/Reader/CborReader.String.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.String.cs index 8ddb9986212974..64dfe490620423 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.String.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.String.cs @@ -387,7 +387,7 @@ private string ReadIndefiniteLengthTextStringConcatenated() } // build the string using range data - string output = CborHelpers.BuildStringFromIndefiniteLengthTextString(concatenatedStringSize, (ranges, _data.Slice(_offset), utf8Encoding), BuildString); + string output = string.Create(concatenatedStringSize, (ranges, _data.Slice(_offset), utf8Encoding), BuildString); AdvanceBuffer(encodingLength); AdvanceDataItemCounters(); diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Tag.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Tag.cs index 2dd4c4d1d9abe1..48c661b54783f8 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Tag.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Tag.cs @@ -125,7 +125,7 @@ public DateTimeOffset ReadUnixTimeSeconds() } TimeSpan timespan = TimeSpan.FromSeconds(seconds); - return CborHelpers.UnixEpoch + timespan; + return DateTimeOffset.UnixEpoch + timespan; default: throw new CborContentException(SR.Cbor_Reader_InvalidUnixTimeEncoding); @@ -175,7 +175,7 @@ public BigInteger ReadBigInteger() } byte[] unsignedBigEndianEncoding = ReadByteString(); - BigInteger unsignedValue = CborHelpers.CreateBigIntegerFromUnsignedBigEndianBytes(unsignedBigEndianEncoding); + BigInteger unsignedValue = CreateBigIntegerFromUnsignedBigEndianBytes(unsignedBigEndianEncoding); return isNegative ? -1 - unsignedValue : unsignedValue; } catch @@ -291,5 +291,30 @@ private CborTag PeekTagCore(out int bytesRead) return result; } + + private static BigInteger CreateBigIntegerFromUnsignedBigEndianBytes(byte[] bigEndianBytes) + { +#if NET + return new BigInteger(bigEndianBytes, isUnsigned: true, isBigEndian: true); +#else + if (bigEndianBytes.Length == 0) + return new BigInteger(bigEndianBytes); + + byte[] temp; + if ((bigEndianBytes[0] & 0x80) != 0) + { + var bytesPlusOne = new byte[bigEndianBytes.Length + 1]; + bigEndianBytes.CopyTo(bytesPlusOne.AsSpan(1)); + temp = bytesPlusOne; + } + else + { + temp = bigEndianBytes; + } + + temp.AsSpan().Reverse(); + return new BigInteger(temp); +#endif + } } } 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..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 @@ -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 @@ -63,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(); } @@ -72,7 +73,7 @@ private void WriteDoubleCore(double value) { EnsureWriteCapacity(1 + sizeof(double)); WriteInitialByte(new CborInitialByte(CborMajorType.Simple, CborAdditionalInfo.Additional64BitData)); - CborHelpers.WriteDoubleBigEndian(_buffer.AsSpan(_offset), value); + BinaryPrimitives.WriteDoubleBigEndian(_buffer.AsSpan(_offset), value); _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..9ca15fc591d0df 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.WriteHalfBigEndian(_buffer.AsSpan(_offset), value); } _offset += sizeof(ushort); AdvanceDataItemCounters(); @@ -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); } } } diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Tag.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Tag.cs index 245c5eb0d3f458..c24ab3227f9150 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Tag.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Writer/CborWriter.Tag.cs @@ -93,7 +93,7 @@ public void WriteBigInteger(BigInteger value) { bool isUnsigned = value.Sign >= 0; BigInteger unsignedValue = isUnsigned ? value : -1 - value; - byte[] unsignedBigEndianEncoding = CborHelpers.CreateUnsignedBigEndianBytesFromBigInteger(unsignedValue); + byte[] unsignedBigEndianEncoding = unsignedValue.ToByteArray(isUnsigned: true, isBigEndian: true); WriteTag(isUnsigned ? CborTag.UnsignedBigNum : CborTag.NegativeBigNum); WriteByteString(unsignedBigEndianEncoding); @@ -147,8 +147,7 @@ internal static class DecimalHelpers public static void Deconstruct(decimal value, out decimal mantissa, out byte scale) { Span buf = stackalloc int[4]; - CborHelpers.GetBitsFromDecimal(value, buf); - + decimal.GetBits(value, buf); int flags = buf[3]; bool isNegative = (flags & SignMask) == SignMask; mantissa = new decimal(lo: buf[0], mid: buf[1], hi: buf[2], isNegative: isNegative, scale: 0); @@ -159,8 +158,7 @@ public static void Deconstruct(decimal value, out decimal mantissa, out byte sca private static decimal ReconstructFromNegativeScale(decimal mantissa, byte scale) { Span buf = stackalloc int[4]; - CborHelpers.GetBitsFromDecimal(mantissa, buf); - + decimal.GetBits(mantissa, buf); int flags = buf[3]; bool isNegative = (flags & SignMask) == SignMask; Debug.Assert((flags & ScaleMask) == 0, "mantissa argument should be integral."); From a6dcb96e2ebb4418119c11ed101e81c4d469f4d9 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sun, 29 Mar 2026 23:53:38 +0200 Subject: [PATCH 2/5] Avoid BigInteger polyfill, use private methods instead Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Formats/Cbor/Reader/CborReader.Tag.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Tag.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Tag.cs index 48c661b54783f8..3735a9c954d50d 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Tag.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Tag.cs @@ -294,9 +294,6 @@ private CborTag PeekTagCore(out int bytesRead) private static BigInteger CreateBigIntegerFromUnsignedBigEndianBytes(byte[] bigEndianBytes) { -#if NET - return new BigInteger(bigEndianBytes, isUnsigned: true, isBigEndian: true); -#else if (bigEndianBytes.Length == 0) return new BigInteger(bigEndianBytes); @@ -314,7 +311,6 @@ private static BigInteger CreateBigIntegerFromUnsignedBigEndianBytes(byte[] bigE temp.AsSpan().Reverse(); return new BigInteger(temp); -#endif } } } From 261b58d4cfee6d6c8b509420c3206f7c86be2a41 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Wed, 1 Apr 2026 03:24:44 +0200 Subject: [PATCH 3/5] Address Jan's feedback: delete HalfHelpers.netcoreapp.cs, move polyfills to Common - Delete HalfHelpers.netcoreapp.cs and use #if NET casts in CborReader.Simple.cs - Move all polyfills from local Polyfills.cs to src/libraries/Common/src/Polyfills/ - Merge BitConverter/DateTimeOffset polyfills with existing common files - Add new common polyfill files: BigInteger, BinaryPrimitives, Decimal, Stack - Add string.Create polyfill to existing StringPolyfills.cs - Fix BigInteger.ToByteArray polyfill: correct trimming for little-endian + isUnsigned Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Polyfills/BigIntegerPolyfills.cs | 49 +++++ .../Polyfills/BinaryPrimitivesPolyfills.cs | 80 ++++++++ .../src/Polyfills/BitConverterPolyfills.cs | 25 ++- .../src/Polyfills/DateTimeOffsetPolyfills.cs | 9 +- .../Common/src/Polyfills/DecimalPolyfills.cs | 16 ++ .../Common/src/Polyfills/StackPolyfills.cs | 25 +++ .../Common/src/System/StringPolyfills.cs | 13 ++ .../src/System.Formats.Cbor.csproj | 16 +- ...fHelpers.netstandard.cs => HalfHelpers.cs} | 10 +- .../Formats/Cbor/HalfHelpers.netcoreapp.cs | 17 -- .../src/System/Formats/Cbor/Polyfills.cs | 192 ------------------ 11 files changed, 228 insertions(+), 224 deletions(-) create mode 100644 src/libraries/Common/src/Polyfills/BigIntegerPolyfills.cs create mode 100644 src/libraries/Common/src/Polyfills/BinaryPrimitivesPolyfills.cs create mode 100644 src/libraries/Common/src/Polyfills/DecimalPolyfills.cs create mode 100644 src/libraries/Common/src/Polyfills/StackPolyfills.cs rename src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/{HalfHelpers.netstandard.cs => HalfHelpers.cs} (97%) delete mode 100644 src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/HalfHelpers.netcoreapp.cs delete mode 100644 src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Polyfills.cs diff --git a/src/libraries/Common/src/Polyfills/BigIntegerPolyfills.cs b/src/libraries/Common/src/Polyfills/BigIntegerPolyfills.cs new file mode 100644 index 00000000000000..1f5fee7e88a281 --- /dev/null +++ b/src/libraries/Common/src/Polyfills/BigIntegerPolyfills.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Numerics; + +/// Provides downlevel polyfills for instance methods on . +internal static class BigIntegerPolyfills +{ + extension(BigInteger self) + { + public byte[] ToByteArray(bool isUnsigned, bool isBigEndian) + { + if (isUnsigned && self.Sign < 0) + throw new OverflowException(); + + byte[] littleEndianBytes = self.ToByteArray(); + + if (!isUnsigned && !isBigEndian) + return littleEndianBytes; + + int length = littleEndianBytes.Length; + + // For unsigned, trim a single most-significant 0x00 sign-extension byte + // from the end of the little-endian array. + if (isUnsigned && length > 1 && littleEndianBytes[length - 1] == 0x00) + length--; + + if (isBigEndian) + { + byte[] result = new byte[length]; + + for (int i = 0; i < length; i++) + { + result[i] = littleEndianBytes[length - 1 - i]; + } + + return result; + } + + if (length == littleEndianBytes.Length) + return littleEndianBytes; + + byte[] trimmed = new byte[length]; + Array.Copy(littleEndianBytes, trimmed, length); + + return trimmed; + } + } +} diff --git a/src/libraries/Common/src/Polyfills/BinaryPrimitivesPolyfills.cs b/src/libraries/Common/src/Polyfills/BinaryPrimitivesPolyfills.cs new file mode 100644 index 00000000000000..502e9b28948539 --- /dev/null +++ b/src/libraries/Common/src/Polyfills/BinaryPrimitivesPolyfills.cs @@ -0,0 +1,80 @@ +// 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.Buffers.Binary; + +/// Provides downlevel polyfills for static methods on . +internal static class BinaryPrimitivesPolyfills +{ + extension(BinaryPrimitives) + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ReadHalfBigEndian(ReadOnlySpan source) + { + return BitConverter.IsLittleEndian + ? BinaryPrimitives.ReverseEndianness(MemoryMarshal.Read(source)) + : MemoryMarshal.Read(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); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ReadSingleBigEndian(ReadOnlySpan source) + { + return BitConverter.IsLittleEndian + ? BitConverter.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(BitConverter.SingleToInt32Bits(value)); + MemoryMarshal.Write(destination, ref tmp); + } + else + { + MemoryMarshal.Write(destination, ref value); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double ReadDoubleBigEndian(ReadOnlySpan source) + { + return BitConverter.IsLittleEndian + ? BitConverter.Int64BitsToDouble(BinaryPrimitives.ReverseEndianness(MemoryMarshal.Read(source))) + : MemoryMarshal.Read(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); + } + } + } +} diff --git a/src/libraries/Common/src/Polyfills/BitConverterPolyfills.cs b/src/libraries/Common/src/Polyfills/BitConverterPolyfills.cs index dd418d32f94406..befcdcd49f5e75 100644 --- a/src/libraries/Common/src/Polyfills/BitConverterPolyfills.cs +++ b/src/libraries/Common/src/Polyfills/BitConverterPolyfills.cs @@ -8,20 +8,29 @@ internal static class BitConverterPolyfills { extension(BitConverter) { + public static int SingleToInt32Bits(float value) + { + unsafe { return *(int*)&value; } + } + + public static float Int32BitsToSingle(int value) + { + unsafe { return *(float*)&value; } + } + public static uint SingleToUInt32Bits(float value) { - unsafe - { - return *(uint*)&value; - } + unsafe { return *(uint*)&value; } + } + + public static float UInt32BitsToSingle(uint value) + { + unsafe { return *(float*)&value; } } public static ulong DoubleToUInt64Bits(double value) { - unsafe - { - return *(ulong*)&value; - } + unsafe { return *(ulong*)&value; } } } } diff --git a/src/libraries/Common/src/Polyfills/DateTimeOffsetPolyfills.cs b/src/libraries/Common/src/Polyfills/DateTimeOffsetPolyfills.cs index 1dcbae615775ad..600e89d4934d81 100644 --- a/src/libraries/Common/src/Polyfills/DateTimeOffsetPolyfills.cs +++ b/src/libraries/Common/src/Polyfills/DateTimeOffsetPolyfills.cs @@ -3,12 +3,19 @@ namespace System; -/// Provides downlevel polyfills for instance members on . +/// Provides downlevel polyfills for members on . internal static class DateTimeOffsetPolyfills { + private const long UnixEpochTicks = 719162L * 10000 * 1000 * 60 * 60 * 24; + extension(DateTimeOffset value) { public int TotalOffsetMinutes => (int)(value.Offset.Ticks / TimeSpan.TicksPerMinute); } + + extension(DateTimeOffset) + { + public static DateTimeOffset UnixEpoch => new DateTimeOffset(UnixEpochTicks, TimeSpan.Zero); + } } diff --git a/src/libraries/Common/src/Polyfills/DecimalPolyfills.cs b/src/libraries/Common/src/Polyfills/DecimalPolyfills.cs new file mode 100644 index 00000000000000..2eef0ab0727297 --- /dev/null +++ b/src/libraries/Common/src/Polyfills/DecimalPolyfills.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System; + +/// Provides downlevel polyfills for static methods on . +internal static class DecimalPolyfills +{ + extension(decimal) + { + public static void GetBits(decimal d, Span destination) + { + decimal.GetBits(d).CopyTo(destination); + } + } +} diff --git a/src/libraries/Common/src/Polyfills/StackPolyfills.cs b/src/libraries/Common/src/Polyfills/StackPolyfills.cs new file mode 100644 index 00000000000000..1f786d4d60cb14 --- /dev/null +++ b/src/libraries/Common/src/Polyfills/StackPolyfills.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace System.Collections.Generic; + +/// Provides downlevel polyfills for instance methods on . +internal static class StackPolyfills +{ + extension(Stack stack) + { + public bool TryPop([MaybeNullWhen(false)] out T result) + { + if (stack.Count > 0) + { + result = stack.Pop(); + return true; + } + + result = default; + return false; + } + } +} diff --git a/src/libraries/Common/src/System/StringPolyfills.cs b/src/libraries/Common/src/System/StringPolyfills.cs index da679b268204f4..08b423087040ff 100644 --- a/src/libraries/Common/src/System/StringPolyfills.cs +++ b/src/libraries/Common/src/System/StringPolyfills.cs @@ -16,6 +16,19 @@ public static bool EndsWith(this string s, char value) => public static bool Contains(this string s, char value) => s.IndexOf(value) >= 0; + + internal delegate void SpanAction(Span span, TArg arg); + + extension(string) + { + public static string Create(int length, TState state, SpanAction action) + { + char[] arr = new char[length]; + action(arr, state); + + return new string(arr); + } + } } } 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 391945a413a074..6988d80473b6a0 100644 --- a/src/libraries/System.Formats.Cbor/src/System.Formats.Cbor.csproj +++ b/src/libraries/System.Formats.Cbor/src/System.Formats.Cbor.csproj @@ -38,19 +38,25 @@ System.Formats.Cbor.CborWriter + + + + - - + - - + + + + + - + 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.cs similarity index 97% rename from src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/HalfHelpers.netstandard.cs rename to src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/HalfHelpers.cs index d5593d9dc78f02..618da7820f635a 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.cs @@ -6,8 +6,15 @@ namespace System.Formats.Cbor { - internal static partial class HalfHelpers + internal static class HalfHelpers { +#if NET + public static float HalfToFloat(Half value) + => (float)value; + + public static double HalfToDouble(Half value) + => (double)value; +#else // Half constants private const ushort HalfExponentMask = 0x7C00; private const ushort HalfExponentShift = 10; @@ -201,5 +208,6 @@ private static ushort RoundPackToHalf(bool sign, short exp, ushort sig) private static uint ShiftRightJam(uint i, int dist) => dist < 31 ? (i >> dist) | (i << (-dist & 31) != 0 ? 1U : 0U) : (i != 0 ? 1U : 0U); #endregion +#endif } } diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/HalfHelpers.netcoreapp.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/HalfHelpers.netcoreapp.cs deleted file mode 100644 index 96514354547b24..00000000000000 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/HalfHelpers.netcoreapp.cs +++ /dev/null @@ -1,17 +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.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace System.Formats.Cbor -{ - internal static partial class HalfHelpers - { - public static unsafe float HalfToFloat(Half value) - => (float)value; - - public static unsafe double HalfToDouble(Half value) - => (double)value; - } -} diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Polyfills.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Polyfills.cs deleted file mode 100644 index 34b7e35b36c848..00000000000000 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Polyfills.cs +++ /dev/null @@ -1,192 +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.Binary; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace System.Formats.Cbor -{ - internal static class DateTimeOffsetPolyfills - { - private const long UnixEpochTicks = 719162L * 10000 * 1000 * 60 * 60 * 24; - - extension(DateTimeOffset) - { - public static DateTimeOffset UnixEpoch => new DateTimeOffset(UnixEpochTicks, TimeSpan.Zero); - } - } - - internal static class BigIntegerPolyfills - { - extension(BigInteger self) - { - public byte[] ToByteArray(bool isUnsigned, bool isBigEndian) - { - byte[] littleEndianBytes = self.ToByteArray(); - - if (littleEndianBytes.Length == 1) - return littleEndianBytes; - - Span bytesAsSpan = littleEndianBytes; - - if (isBigEndian) - bytesAsSpan.Reverse(); - - if (isUnsigned) - { - int start = 0; - for (int i = 0; i < bytesAsSpan.Length; i++) - { - if (bytesAsSpan[i] == 0x00) - start++; - else - break; - } - - if (start > 0) - return bytesAsSpan.Slice(start).ToArray(); - } - - return littleEndianBytes; - } - } - } - - internal static class DecimalPolyfills - { - extension(decimal) - { - public static void GetBits(decimal d, Span destination) - { - decimal.GetBits(d).CopyTo(destination); - } - } - } - - internal static class StringPolyfills - { - internal delegate void SpanAction(Span span, TArg arg); - - extension(string) - { - public static string Create(int length, TState state, SpanAction action) - { - char[] arr = new char[length]; - action(arr, state); - return new string(arr); - } - } - } - - internal static class BinaryPrimitivesPolyfills - { - extension(BinaryPrimitives) - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort ReadHalfBigEndian(ReadOnlySpan source) - { - return BitConverter.IsLittleEndian - ? BinaryPrimitives.ReverseEndianness(MemoryMarshal.Read(source)) - : MemoryMarshal.Read(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); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float ReadSingleBigEndian(ReadOnlySpan source) - { - return BitConverter.IsLittleEndian - ? BitConverter.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(BitConverter.SingleToInt32Bits(value)); - MemoryMarshal.Write(destination, ref tmp); - } - else - { - MemoryMarshal.Write(destination, ref value); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static double ReadDoubleBigEndian(ReadOnlySpan source) - { - return BitConverter.IsLittleEndian - ? BitConverter.Int64BitsToDouble(BinaryPrimitives.ReverseEndianness(MemoryMarshal.Read(source))) - : MemoryMarshal.Read(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); - } - } - } - } - - internal static class BitConverterPolyfills - { - extension(BitConverter) - { - public static unsafe int SingleToInt32Bits(float value) - => *(int*)&value; - - public static unsafe float Int32BitsToSingle(int value) - => *(float*)&value; - - public static uint SingleToUInt32Bits(float value) - => (uint)BitConverter.SingleToInt32Bits(value); - - public static float UInt32BitsToSingle(uint value) - => BitConverter.Int32BitsToSingle((int)value); - } - } - - internal static class StackPolyfills - { - extension(Stack stack) - { - public bool TryPop([MaybeNullWhen(false)] out T result) - { - if (stack.Count > 0) - { - result = stack.Pop(); - return true; - } - - result = default; - return false; - } - } - } -} From bcc9caac9174dc271b3d2c17b846ce80a3bf74e9 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Wed, 1 Apr 2026 03:38:40 +0200 Subject: [PATCH 4/5] Simplify CreateBigIntegerFromUnsignedBigEndianBytes using BigInteger constructor Replace manual byte-reversal logic with new BigInteger(bytes, isUnsigned: true, isBigEndian: true) and add a polyfill for downlevel TFMs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Polyfills/BigIntegerPolyfills.cs | 23 ++++++++++++++++++ .../Formats/Cbor/Reader/CborReader.Tag.cs | 24 ++++--------------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/libraries/Common/src/Polyfills/BigIntegerPolyfills.cs b/src/libraries/Common/src/Polyfills/BigIntegerPolyfills.cs index 1f5fee7e88a281..a8b1c25e00b202 100644 --- a/src/libraries/Common/src/Polyfills/BigIntegerPolyfills.cs +++ b/src/libraries/Common/src/Polyfills/BigIntegerPolyfills.cs @@ -6,6 +6,29 @@ namespace System.Numerics; /// Provides downlevel polyfills for instance methods on . internal static class BigIntegerPolyfills { + public static BigInteger Create(ReadOnlySpan value, bool isUnsigned, bool isBigEndian) + { + if (value.IsEmpty) + return BigInteger.Zero; + + byte[] bytes = value.ToArray(); + + if (isBigEndian) + Array.Reverse(bytes); + + // BigInteger(byte[]) expects little-endian signed (two's complement). + // If the caller says unsigned and the high bit is set, append a 0x00 + // so BigInteger doesn't interpret it as negative. + if (isUnsigned && (bytes[bytes.Length - 1] & 0x80) != 0) + { + byte[] extended = new byte[bytes.Length + 1]; + Array.Copy(bytes, extended, bytes.Length); + bytes = extended; + } + + return new BigInteger(bytes); + } + extension(BigInteger self) { public byte[] ToByteArray(bool isUnsigned, bool isBigEndian) diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Tag.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Tag.cs index 3735a9c954d50d..ca0e052a68dbab 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Tag.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Tag.cs @@ -293,24 +293,10 @@ private CborTag PeekTagCore(out int bytesRead) } private static BigInteger CreateBigIntegerFromUnsignedBigEndianBytes(byte[] bigEndianBytes) - { - if (bigEndianBytes.Length == 0) - return new BigInteger(bigEndianBytes); - - byte[] temp; - if ((bigEndianBytes[0] & 0x80) != 0) - { - var bytesPlusOne = new byte[bigEndianBytes.Length + 1]; - bigEndianBytes.CopyTo(bytesPlusOne.AsSpan(1)); - temp = bytesPlusOne; - } - else - { - temp = bigEndianBytes; - } - - temp.AsSpan().Reverse(); - return new BigInteger(temp); - } +#if NET + => new BigInteger(bigEndianBytes, isUnsigned: true, isBigEndian: true); +#else + => BigIntegerPolyfills.Create(bigEndianBytes, isUnsigned: true, isBigEndian: true); +#endif } } From fed9d48ef517fdc8fff383dd86fb05b07d1c0399 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Wed, 1 Apr 2026 03:42:15 +0200 Subject: [PATCH 5/5] Inline BigInteger polyfill into CborReader instead of shared helper The Create factory method only had one caller, so inline it directly into CreateBigIntegerFromUnsignedBigEndianBytes behind an #if NET guard. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Polyfills/BigIntegerPolyfills.cs | 23 ------------------- .../Formats/Cbor/Reader/CborReader.Tag.cs | 19 ++++++++++++++- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/src/libraries/Common/src/Polyfills/BigIntegerPolyfills.cs b/src/libraries/Common/src/Polyfills/BigIntegerPolyfills.cs index a8b1c25e00b202..1f5fee7e88a281 100644 --- a/src/libraries/Common/src/Polyfills/BigIntegerPolyfills.cs +++ b/src/libraries/Common/src/Polyfills/BigIntegerPolyfills.cs @@ -6,29 +6,6 @@ namespace System.Numerics; /// Provides downlevel polyfills for instance methods on . internal static class BigIntegerPolyfills { - public static BigInteger Create(ReadOnlySpan value, bool isUnsigned, bool isBigEndian) - { - if (value.IsEmpty) - return BigInteger.Zero; - - byte[] bytes = value.ToArray(); - - if (isBigEndian) - Array.Reverse(bytes); - - // BigInteger(byte[]) expects little-endian signed (two's complement). - // If the caller says unsigned and the high bit is set, append a 0x00 - // so BigInteger doesn't interpret it as negative. - if (isUnsigned && (bytes[bytes.Length - 1] & 0x80) != 0) - { - byte[] extended = new byte[bytes.Length + 1]; - Array.Copy(bytes, extended, bytes.Length); - bytes = extended; - } - - return new BigInteger(bytes); - } - extension(BigInteger self) { public byte[] ToByteArray(bool isUnsigned, bool isBigEndian) diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Tag.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Tag.cs index ca0e052a68dbab..5ea7e9950baac9 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Tag.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.Tag.cs @@ -296,7 +296,24 @@ private static BigInteger CreateBigIntegerFromUnsignedBigEndianBytes(byte[] bigE #if NET => new BigInteger(bigEndianBytes, isUnsigned: true, isBigEndian: true); #else - => BigIntegerPolyfills.Create(bigEndianBytes, isUnsigned: true, isBigEndian: true); + { + if (bigEndianBytes.Length == 0) + return BigInteger.Zero; + + byte[] littleEndianBytes = (byte[])bigEndianBytes.Clone(); + Array.Reverse(littleEndianBytes); + + // BigInteger(byte[]) expects little-endian signed (two's complement). + // If the high bit is set, append a 0x00 so BigInteger doesn't interpret it as negative. + if ((littleEndianBytes[littleEndianBytes.Length - 1] & 0x80) != 0) + { + byte[] extended = new byte[littleEndianBytes.Length + 1]; + Array.Copy(littleEndianBytes, extended, littleEndianBytes.Length); + littleEndianBytes = extended; + } + + return new BigInteger(littleEndianBytes); + } #endif } }