From aebdc0fd90ab383e4ba05e6befc241838151a438 Mon Sep 17 00:00:00 2001 From: Ahson Ahmed Khan Date: Thu, 12 Oct 2017 16:07:39 -0700 Subject: [PATCH] Add Span Binary Reader/Writer APIs (#24400) * Adding skeleton for Binary APIs. * Adding Binary APIs and tests. * Adding performance tests. * Fix reference and add perf test project to solution. * Addressing feedback from API Review * Renaming Read/Write and verifying T is blittable. * Adding helper which validates that the type T is blittable. * Move Binary APIs from Buffers.dll to Memory.dll. * Fix Write APIs and use correct condition for is partial facade. * Add reference to S.R.Extensions for BitConverter for netstandard1.1 * Adding test for reading ROSpan into a struct with references. --- src/System.Buffers/ref/System.Buffers.cs | 2 +- src/System.Memory/ref/System.Memory.cs | 77 +++ src/System.Memory/src/System.Memory.csproj | 10 +- .../src/System/Buffers/Binary/Reader.cs | 147 ++++++ .../System/Buffers/Binary/ReaderBigEndian.cs | 186 +++++++ .../Buffers/Binary/ReaderLittleEndian.cs | 185 +++++++ .../src/System/Buffers/Binary/Writer.cs | 65 +++ .../System/Buffers/Binary/WriterBigEndian.cs | 173 +++++++ .../Buffers/Binary/WriterLittleEndian.cs | 173 +++++++ .../tests/Binary/BinaryReaderUnitTests.cs | 483 ++++++++++++++++++ .../tests/Binary/BinaryWriterUnitTests.cs | 313 ++++++++++++ .../Perf.Span.BinaryReadAndWrite.cs | 281 ++++++++++ .../System.Memory.Performance.Tests.csproj | 2 + .../tests/System.Memory.Tests.csproj | 6 +- src/System.Memory/tests/TestHelpers.cs | 97 ++++ 15 files changed, 2197 insertions(+), 3 deletions(-) create mode 100644 src/System.Memory/src/System/Buffers/Binary/Reader.cs create mode 100644 src/System.Memory/src/System/Buffers/Binary/ReaderBigEndian.cs create mode 100644 src/System.Memory/src/System/Buffers/Binary/ReaderLittleEndian.cs create mode 100644 src/System.Memory/src/System/Buffers/Binary/Writer.cs create mode 100644 src/System.Memory/src/System/Buffers/Binary/WriterBigEndian.cs create mode 100644 src/System.Memory/src/System/Buffers/Binary/WriterLittleEndian.cs create mode 100644 src/System.Memory/tests/Binary/BinaryReaderUnitTests.cs create mode 100644 src/System.Memory/tests/Binary/BinaryWriterUnitTests.cs create mode 100644 src/System.Memory/tests/Performance/Perf.Span.BinaryReadAndWrite.cs diff --git a/src/System.Buffers/ref/System.Buffers.cs b/src/System.Buffers/ref/System.Buffers.cs index e1d6a23e1b09..62fd63c1456a 100644 --- a/src/System.Buffers/ref/System.Buffers.cs +++ b/src/System.Buffers/ref/System.Buffers.cs @@ -15,4 +15,4 @@ public abstract class ArrayPool public abstract T[] Rent(int minimumLength); public abstract void Return(T[] array, bool clearArray = false); } -} +} \ No newline at end of file diff --git a/src/System.Memory/ref/System.Memory.cs b/src/System.Memory/ref/System.Memory.cs index b38abb407a35..f145efecedc2 100644 --- a/src/System.Memory/ref/System.Memory.cs +++ b/src/System.Memory/ref/System.Memory.cs @@ -202,4 +202,81 @@ public abstract class OwnedMemory : IDisposable, IRetainable public abstract void Retain(); protected internal abstract bool TryGetArray(out ArraySegment arraySegment); } +} + +namespace System.Buffers.Binary +{ + public static class BinaryPrimitives + { + public static sbyte ReverseEndianness(sbyte value) { throw null; } + public static byte ReverseEndianness(byte value) { throw null; } + public static short ReverseEndianness(short value) { throw null; } + public static ushort ReverseEndianness(ushort value) { throw null; } + public static int ReverseEndianness(int value) { throw null; } + public static uint ReverseEndianness(uint value) { throw null; } + public static long ReverseEndianness(long value) { throw null; } + public static ulong ReverseEndianness(ulong value) { throw null; } + + public static T ReadMachineEndian(ReadOnlySpan buffer) where T : struct { throw null; } + public static bool TryReadMachineEndian(ReadOnlySpan buffer, out T value) where T : struct { throw null; } + + public static short ReadInt16LittleEndian(ReadOnlySpan buffer) { throw null; } + public static int ReadInt32LittleEndian(ReadOnlySpan buffer) { throw null; } + public static long ReadInt64LittleEndian(ReadOnlySpan buffer) { throw null; } + public static ushort ReadUInt16LittleEndian(ReadOnlySpan buffer) { throw null; } + public static uint ReadUInt32LittleEndian(ReadOnlySpan buffer) { throw null; } + public static ulong ReadUInt64LittleEndian(ReadOnlySpan buffer) { throw null; } + + public static bool TryReadInt16LittleEndian(ReadOnlySpan buffer, out short value) { throw null; } + public static bool TryReadInt32LittleEndian(ReadOnlySpan buffer, out int value) { throw null; } + public static bool TryReadInt64LittleEndian(ReadOnlySpan buffer, out long value) { throw null; } + public static bool TryReadUInt16LittleEndian(ReadOnlySpan buffer, out ushort value) { throw null; } + public static bool TryReadUInt32LittleEndian(ReadOnlySpan buffer, out uint value) { throw null; } + public static bool TryReadUInt64LittleEndian(ReadOnlySpan buffer, out ulong value) { throw null; } + + public static short ReadInt16BigEndian(ReadOnlySpan buffer) { throw null; } + public static int ReadInt32BigEndian(ReadOnlySpan buffer) { throw null; } + public static long ReadInt64BigEndian(ReadOnlySpan buffer) { throw null; } + public static ushort ReadUInt16BigEndian(ReadOnlySpan buffer) { throw null; } + public static uint ReadUInt32BigEndian(ReadOnlySpan buffer) { throw null; } + public static ulong ReadUInt64BigEndian(ReadOnlySpan buffer) { throw null; } + + public static bool TryReadInt16BigEndian(ReadOnlySpan buffer, out short value) { throw null; } + public static bool TryReadInt32BigEndian(ReadOnlySpan buffer, out int value) { throw null; } + public static bool TryReadInt64BigEndian(ReadOnlySpan buffer, out long value) { throw null; } + public static bool TryReadUInt16BigEndian(ReadOnlySpan buffer, out ushort value) { throw null; } + public static bool TryReadUInt32BigEndian(ReadOnlySpan buffer, out uint value) { throw null; } + public static bool TryReadUInt64BigEndian(ReadOnlySpan buffer, out ulong value) { throw null; } + + public static void WriteMachineEndian(Span buffer, ref T value) where T : struct { throw null; } + public static bool TryWriteMachineEndian(Span buffer, ref T value) where T : struct { throw null; } + + public static void WriteInt16LittleEndian(Span buffer, short value) { throw null; } + public static void WriteInt32LittleEndian(Span buffer, int value) { throw null; } + public static void WriteInt64LittleEndian(Span buffer, long value) { throw null; } + public static void WriteUInt16LittleEndian(Span buffer, ushort value) { throw null; } + public static void WriteUInt32LittleEndian(Span buffer, uint value) { throw null; } + public static void WriteUInt64LittleEndian(Span buffer, ulong value) { throw null; } + + public static bool TryWriteInt16LittleEndian(Span buffer, short value) { throw null; } + public static bool TryWriteInt32LittleEndian(Span buffer, int value) { throw null; } + public static bool TryWriteInt64LittleEndian(Span buffer, long value) { throw null; } + public static bool TryWriteUInt16LittleEndian(Span buffer, ushort value) { throw null; } + public static bool TryWriteUInt32LittleEndian(Span buffer, uint value) { throw null; } + public static bool TryWriteUInt64LittleEndian(Span buffer, ulong value) { throw null; } + + public static void WriteInt16BigEndian(Span buffer, short value) { throw null; } + public static void WriteInt32BigEndian(Span buffer, int value) { throw null; } + public static void WriteInt64BigEndian(Span buffer, long value) { throw null; } + public static void WriteUInt16BigEndian(Span buffer, ushort value) { throw null; } + public static void WriteUInt32BigEndian(Span buffer, uint value) { throw null; } + public static void WriteUInt64BigEndian(Span buffer, ulong value) { throw null; } + + public static bool TryWriteInt16BigEndian(Span buffer, short value) { throw null; } + public static bool TryWriteInt32BigEndian(Span buffer, int value) { throw null; } + public static bool TryWriteInt64BigEndian(Span buffer, long value) { throw null; } + public static bool TryWriteUInt16BigEndian(Span buffer, ushort value) { throw null; } + public static bool TryWriteUInt32BigEndian(Span buffer, uint value) { throw null; } + public static bool TryWriteUInt64BigEndian(Span buffer, ulong value) { throw null; } + } } \ No newline at end of file diff --git a/src/System.Memory/src/System.Memory.csproj b/src/System.Memory/src/System.Memory.csproj index 6caeba7849f4..41e7dd3c3cd0 100644 --- a/src/System.Memory/src/System.Memory.csproj +++ b/src/System.Memory/src/System.Memory.csproj @@ -7,6 +7,7 @@ false $(OutputPath)$(MSBuildProjectName).xml true + $(DefineConstants);IsPartialFacade $(DefineConstants);netcoreapp $(DefineConstants);netstandard11 @@ -24,6 +25,12 @@ + + + + + + @@ -46,10 +53,11 @@ + - + diff --git a/src/System.Memory/src/System/Buffers/Binary/Reader.cs b/src/System.Memory/src/System/Buffers/Binary/Reader.cs new file mode 100644 index 000000000000..c150d510923c --- /dev/null +++ b/src/System.Memory/src/System/Buffers/Binary/Reader.cs @@ -0,0 +1,147 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime; +using System.Runtime.CompilerServices; + +namespace System.Buffers.Binary +{ + /// + /// Reads bytes as primitives with specific endianness + /// + /// + /// For native formats, SpanExtensions.Read<T> should be used. + /// Use these helpers when you need to read specific endinanness. + /// + public static partial class BinaryPrimitives + { + /// + /// This is a no-op and added only for consistency. + /// This allows the caller to read a struct of numeric primitives and reverse each field + /// rather than having to skip sbyte fields. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static sbyte ReverseEndianness(sbyte value) + { + return value; + } + + /// + /// Reverses a primitive value - performs an endianness swap + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short ReverseEndianness(short value) + { + return (short)((value & 0x00FF) << 8 | (value & 0xFF00) >> 8); + } + + /// + /// Reverses a primitive value - performs an endianness swap + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ReverseEndianness(int value) => (int)ReverseEndianness((uint)value); + + /// + /// Reverses a primitive value - performs an endianness swap + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long ReverseEndianness(long value) => (long)ReverseEndianness((ulong)value); + + /// + /// This is a no-op and added only for consistency. + /// This allows the caller to read a struct of numeric primitives and reverse each field + /// rather than having to skip byte fields. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte ReverseEndianness(byte value) + { + return value; + } + + /// + /// Reverses a primitive value - performs an endianness swap + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ReverseEndianness(ushort value) + { + return (ushort)((value & 0x00FFU) << 8 | (value & 0xFF00U) >> 8); + } + + /// + /// Reverses a primitive value - performs an endianness swap + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ReverseEndianness(uint value) + { + value = (value << 16) | (value >> 16); + value = (value & 0x00FF00FF) << 8 | (value & 0xFF00FF00) >> 8; + return value; + } + + /// + /// Reverses a primitive value - performs an endianness swap + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong ReverseEndianness(ulong value) + { + value = (value << 32) | (value >> 32); + value = (value & 0x0000FFFF0000FFFF) << 16 | (value & 0xFFFF0000FFFF0000) >> 16; + value = (value & 0x00FF00FF00FF00FF) << 8 | (value & 0xFF00FF00FF00FF00) >> 8; + return value; + } + + /// + /// Reads a structure of type T out of a read-only span of bytes. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T ReadMachineEndian(ReadOnlySpan buffer) + where T : struct + { +#if IsPartialFacade + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + throw new ArgumentException(SR.Format(SR.Argument_InvalidTypeWithPointersNotSupported, typeof(T))); + } +#else + if (SpanHelpers.IsReferenceOrContainsReferences()) + { + ThrowHelper.ThrowArgumentException_InvalidTypeWithPointersNotSupported(typeof(T)); + } +#endif + if (Unsafe.SizeOf() > buffer.Length) + { + throw new ArgumentOutOfRangeException(); + } + return Unsafe.ReadUnaligned(ref buffer.DangerousGetPinnableReference()); + } + + /// + /// Reads a structure of type T out of a span of bytes. + /// If the span is too small to contain the type T, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryReadMachineEndian(ReadOnlySpan buffer, out T value) + where T : struct + { +#if IsPartialFacade + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + throw new ArgumentException(SR.Format(SR.Argument_InvalidTypeWithPointersNotSupported, typeof(T))); + } +#else + if (SpanHelpers.IsReferenceOrContainsReferences()) + { + ThrowHelper.ThrowArgumentException_InvalidTypeWithPointersNotSupported(typeof(T)); + } +#endif + if (Unsafe.SizeOf() > (uint)buffer.Length) + { + value = default; + return false; + } + value = Unsafe.ReadUnaligned(ref buffer.DangerousGetPinnableReference()); + return true; + } + } +} diff --git a/src/System.Memory/src/System/Buffers/Binary/ReaderBigEndian.cs b/src/System.Memory/src/System/Buffers/Binary/ReaderBigEndian.cs new file mode 100644 index 000000000000..a98d91588c59 --- /dev/null +++ b/src/System.Memory/src/System/Buffers/Binary/ReaderBigEndian.cs @@ -0,0 +1,186 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; + +namespace System.Buffers.Binary +{ + public static partial class BinaryPrimitives + { + /// + /// Reads an Int16 out of a read-only span of bytes as big endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short ReadInt16BigEndian(ReadOnlySpan buffer) + { + short result = ReadMachineEndian(buffer); + if (BitConverter.IsLittleEndian) + { + result = ReverseEndianness(result); + } + return result; + } + + /// + /// Reads an Int32 out of a read-only span of bytes as big endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ReadInt32BigEndian(ReadOnlySpan buffer) + { + int result = ReadMachineEndian(buffer); + if (BitConverter.IsLittleEndian) + { + result = ReverseEndianness(result); + } + return result; + } + + /// + /// Reads an Int64 out of a read-only span of bytes as big endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long ReadInt64BigEndian(ReadOnlySpan buffer) + { + long result = ReadMachineEndian(buffer); + if (BitConverter.IsLittleEndian) + { + result = ReverseEndianness(result); + } + return result; + } + + /// + /// Reads a UInt16 out of a read-only span of bytes as big endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ReadUInt16BigEndian(ReadOnlySpan buffer) + { + ushort result = ReadMachineEndian(buffer); + if (BitConverter.IsLittleEndian) + { + result = ReverseEndianness(result); + } + return result; + } + + /// + /// Reads a UInt32 out of a read-only span of bytes as big endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ReadUInt32BigEndian(ReadOnlySpan buffer) + { + uint result = ReadMachineEndian(buffer); + if (BitConverter.IsLittleEndian) + { + result = ReverseEndianness(result); + } + return result; + } + + /// + /// Reads a UInt64 out of a read-only span of bytes as big endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong ReadUInt64BigEndian(ReadOnlySpan buffer) + { + ulong result = ReadMachineEndian(buffer); + if (BitConverter.IsLittleEndian) + { + result = ReverseEndianness(result); + } + return result; + } + + /// + /// Reads an Int16 out of a read-only span of bytes as big endian. + /// If the span is too small to contain an Int16, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryReadInt16BigEndian(ReadOnlySpan buffer, out short value) + { + bool success = TryReadMachineEndian(buffer, out value); + if (BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return success; + } + + /// + /// Reads an Int32 out of a read-only span of bytes as big endian. + /// If the span is too small to contain an Int32, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryReadInt32BigEndian(ReadOnlySpan buffer, out int value) + { + bool success = TryReadMachineEndian(buffer, out value); + if (BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return success; + } + + /// + /// Reads an Int64 out of a read-only span of bytes as big endian. + /// If the span is too small to contain an Int64, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryReadInt64BigEndian(ReadOnlySpan buffer, out long value) + { + bool success = TryReadMachineEndian(buffer, out value); + if (BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return success; + } + + /// + /// Reads a UInt16 out of a read-only span of bytes as big endian. + /// If the span is too small to contain a UInt16, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryReadUInt16BigEndian(ReadOnlySpan buffer, out ushort value) + { + bool success = TryReadMachineEndian(buffer, out value); + if (BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return success; + } + + /// + /// Reads a UInt32 out of a read-only span of bytes as big endian. + /// If the span is too small to contain a UInt32, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryReadUInt32BigEndian(ReadOnlySpan buffer, out uint value) + { + bool success = TryReadMachineEndian(buffer, out value); + if (BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return success; + } + + /// + /// Reads a UInt64 out of a read-only span of bytes as big endian. + /// If the span is too small to contain a UInt64, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryReadUInt64BigEndian(ReadOnlySpan buffer, out ulong value) + { + bool success = TryReadMachineEndian(buffer, out value); + if (BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return success; + } + } +} + diff --git a/src/System.Memory/src/System/Buffers/Binary/ReaderLittleEndian.cs b/src/System.Memory/src/System/Buffers/Binary/ReaderLittleEndian.cs new file mode 100644 index 000000000000..09dd7d8801d7 --- /dev/null +++ b/src/System.Memory/src/System/Buffers/Binary/ReaderLittleEndian.cs @@ -0,0 +1,185 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; + +namespace System.Buffers.Binary +{ + public static partial class BinaryPrimitives + { + /// + /// Reads an Int16 out of a read-only span of bytes as little endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short ReadInt16LittleEndian(ReadOnlySpan buffer) + { + short result = ReadMachineEndian(buffer); + if (!BitConverter.IsLittleEndian) + { + result = ReverseEndianness(result); + } + return result; + } + + /// + /// Reads an Int32 out of a read-only span of bytes as little endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ReadInt32LittleEndian(ReadOnlySpan buffer) + { + int result = ReadMachineEndian(buffer); + if (!BitConverter.IsLittleEndian) + { + result = ReverseEndianness(result); + } + return result; + } + + /// + /// Reads an Int64 out of a read-only span of bytes as little endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long ReadInt64LittleEndian(ReadOnlySpan buffer) + { + long result = ReadMachineEndian(buffer); + if (!BitConverter.IsLittleEndian) + { + result = ReverseEndianness(result); + } + return result; + } + + /// + /// Reads a UInt16 out of a read-only span of bytes as little endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ReadUInt16LittleEndian(ReadOnlySpan buffer) + { + ushort result = ReadMachineEndian(buffer); + if (!BitConverter.IsLittleEndian) + { + result = ReverseEndianness(result); + } + return result; + } + + /// + /// Reads a UInt32 out of a read-only span of bytes as little endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ReadUInt32LittleEndian(ReadOnlySpan buffer) + { + uint result = ReadMachineEndian(buffer); + if (!BitConverter.IsLittleEndian) + { + result = ReverseEndianness(result); + } + return result; + } + + /// + /// Reads a UInt64 out of a read-only span of bytes as little endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong ReadUInt64LittleEndian(ReadOnlySpan buffer) + { + ulong result = ReadMachineEndian(buffer); + if (!BitConverter.IsLittleEndian) + { + result = ReverseEndianness(result); + } + return result; + } + + /// + /// Reads an Int16 out of a read-only span of bytes as little endian. + /// If the span is too small to contain an Int16, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryReadInt16LittleEndian(ReadOnlySpan buffer, out short value) + { + bool success = TryReadMachineEndian(buffer, out value); + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return success; + } + + /// + /// Reads an Int32 out of a read-only span of bytes as little endian. + /// If the span is too small to contain an Int32, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryReadInt32LittleEndian(ReadOnlySpan buffer, out int value) + { + bool success = TryReadMachineEndian(buffer, out value); + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return success; + } + + /// + /// Reads an Int64 out of a read-only span of bytes as little endian. + /// If the span is too small to contain an Int64, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryReadInt64LittleEndian(ReadOnlySpan buffer, out long value) + { + bool success = TryReadMachineEndian(buffer, out value); + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return success; + } + + /// + /// Reads a UInt16 out of a read-only span of bytes as little endian. + /// If the span is too small to contain a UInt16, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryReadUInt16LittleEndian(ReadOnlySpan buffer, out ushort value) + { + bool success = TryReadMachineEndian(buffer, out value); + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return success; + } + + /// + /// Reads a UInt32 out of a read-only span of bytes as little endian. + /// If the span is too small to contain a UInt32, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryReadUInt32LittleEndian(ReadOnlySpan buffer, out uint value) + { + bool success = TryReadMachineEndian(buffer, out value); + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return success; + } + + /// + /// Reads a UInt64 out of a read-only span of bytes as little endian. + /// If the span is too small to contain a UInt64, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryReadUInt64LittleEndian(ReadOnlySpan buffer, out ulong value) + { + bool success = TryReadMachineEndian(buffer, out value); + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return success; + } + } +} diff --git a/src/System.Memory/src/System/Buffers/Binary/Writer.cs b/src/System.Memory/src/System/Buffers/Binary/Writer.cs new file mode 100644 index 000000000000..6dd14b631831 --- /dev/null +++ b/src/System.Memory/src/System/Buffers/Binary/Writer.cs @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime; +using System.Runtime.CompilerServices; + +namespace System.Buffers.Binary +{ + public static partial class BinaryPrimitives + { + /// + /// Writes a structure of type T into a span of bytes. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteMachineEndian(Span buffer, ref T value) + where T : struct + { +#if IsPartialFacade + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + throw new ArgumentException(SR.Format(SR.Argument_InvalidTypeWithPointersNotSupported, typeof(T))); + } +#else + if (SpanHelpers.IsReferenceOrContainsReferences()) + { + ThrowHelper.ThrowArgumentException_InvalidTypeWithPointersNotSupported(typeof(T)); + } +#endif + if ((uint)Unsafe.SizeOf() > (uint)buffer.Length) + { + throw new ArgumentOutOfRangeException(); + } + Unsafe.WriteUnaligned(ref buffer.DangerousGetPinnableReference(), value); + } + + /// + /// Writes a structure of type T into a span of bytes. + /// If the span is too small to contain the type T, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryWriteMachineEndian(Span buffer, ref T value) + where T : struct + { +#if IsPartialFacade + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + throw new ArgumentException(SR.Format(SR.Argument_InvalidTypeWithPointersNotSupported, typeof(T))); + } +#else + if (SpanHelpers.IsReferenceOrContainsReferences()) + { + ThrowHelper.ThrowArgumentException_InvalidTypeWithPointersNotSupported(typeof(T)); + } +#endif + if (Unsafe.SizeOf() > (uint)buffer.Length) + { + return false; + } + Unsafe.WriteUnaligned(ref buffer.DangerousGetPinnableReference(), value); + return true; + } + } +} + diff --git a/src/System.Memory/src/System/Buffers/Binary/WriterBigEndian.cs b/src/System.Memory/src/System/Buffers/Binary/WriterBigEndian.cs new file mode 100644 index 000000000000..ebfb0968d8e5 --- /dev/null +++ b/src/System.Memory/src/System/Buffers/Binary/WriterBigEndian.cs @@ -0,0 +1,173 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; + +namespace System.Buffers.Binary +{ + public static partial class BinaryPrimitives + { + /// + /// Writes an Int16 into a span of bytes as big endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteInt16BigEndian(Span buffer, short value) + { + if (BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + WriteMachineEndian(buffer, ref value); + } + + /// + /// Writes an Int32 into a span of bytes as big endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteInt32BigEndian(Span buffer, int value) + { + if (BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + WriteMachineEndian(buffer, ref value); + } + + /// + /// Writes an Int64 into a span of bytes as big endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteInt64BigEndian(Span buffer, long value) + { + if (BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + WriteMachineEndian(buffer, ref value); + } + + /// + /// Write a UInt16 into a span of bytes as big endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteUInt16BigEndian(Span buffer, ushort value) + { + if (BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + WriteMachineEndian(buffer, ref value); + } + + /// + /// Write a UInt32 into a span of bytes as big endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteUInt32BigEndian(Span buffer, uint value) + { + if (BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + WriteMachineEndian(buffer, ref value); + } + + /// + /// Write a UInt64 into a span of bytes as big endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteUInt64BigEndian(Span buffer, ulong value) + { + if (BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + WriteMachineEndian(buffer, ref value); + } + + /// + /// Writes an Int16 into a span of bytes as big endian. + /// If the span is too small to contain the value, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryWriteInt16BigEndian(Span buffer, short value) + { + if (BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return TryWriteMachineEndian(buffer, ref value); + } + + /// + /// Writes an Int32 into a span of bytes as big endian. + /// If the span is too small to contain the value, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryWriteInt32BigEndian(Span buffer, int value) + { + if (BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return TryWriteMachineEndian(buffer, ref value); + } + + /// + /// Writes an Int64 into a span of bytes as big endian. + /// If the span is too small to contain the value, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryWriteInt64BigEndian(Span buffer, long value) + { + if (BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return TryWriteMachineEndian(buffer, ref value); + } + + /// + /// Write a UInt16 into a span of bytes as big endian. + /// If the span is too small to contain the value, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryWriteUInt16BigEndian(Span buffer, ushort value) + { + if (BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return TryWriteMachineEndian(buffer, ref value); + } + + /// + /// Write a UInt32 into a span of bytes as big endian. + /// If the span is too small to contain the value, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryWriteUInt32BigEndian(Span buffer, uint value) + { + if (BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return TryWriteMachineEndian(buffer, ref value); + } + + /// + /// Write a UInt64 into a span of bytes as big endian. + /// If the span is too small to contain the value, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryWriteUInt64BigEndian(Span buffer, ulong value) + { + if (BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return TryWriteMachineEndian(buffer, ref value); + } + } +} diff --git a/src/System.Memory/src/System/Buffers/Binary/WriterLittleEndian.cs b/src/System.Memory/src/System/Buffers/Binary/WriterLittleEndian.cs new file mode 100644 index 000000000000..34c317b0f2c3 --- /dev/null +++ b/src/System.Memory/src/System/Buffers/Binary/WriterLittleEndian.cs @@ -0,0 +1,173 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; + +namespace System.Buffers.Binary +{ + public static partial class BinaryPrimitives + { + /// + /// Writes an Int16 into a span of bytes as little endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteInt16LittleEndian(Span buffer, short value) + { + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + WriteMachineEndian(buffer, ref value); + } + + /// + /// Writes an Int32 into a span of bytes as little endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteInt32LittleEndian(Span buffer, int value) + { + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + WriteMachineEndian(buffer, ref value); + } + + /// + /// Writes an Int64 into a span of bytes as little endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteInt64LittleEndian(Span buffer, long value) + { + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + WriteMachineEndian(buffer, ref value); + } + + /// + /// Write a UInt16 into a span of bytes as little endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteUInt16LittleEndian(Span buffer, ushort value) + { + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + WriteMachineEndian(buffer, ref value); + } + + /// + /// Write a UInt32 into a span of bytes as little endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteUInt32LittleEndian(Span buffer, uint value) + { + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + WriteMachineEndian(buffer, ref value); + } + + /// + /// Write a UInt64 into a span of bytes as little endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteUInt64LittleEndian(Span buffer, ulong value) + { + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + WriteMachineEndian(buffer, ref value); + } + + /// + /// Writes an Int16 into a span of bytes as little endian. + /// If the span is too small to contain the value, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryWriteInt16LittleEndian(Span buffer, short value) + { + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return TryWriteMachineEndian(buffer, ref value); + } + + /// + /// Writes an Int32 into a span of bytes as little endian. + /// If the span is too small to contain the value, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryWriteInt32LittleEndian(Span buffer, int value) + { + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return TryWriteMachineEndian(buffer, ref value); + } + + /// + /// Writes an Int64 into a span of bytes as little endian. + /// If the span is too small to contain the value, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryWriteInt64LittleEndian(Span buffer, long value) + { + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return TryWriteMachineEndian(buffer, ref value); + } + + /// + /// Write a UInt16 into a span of bytes as little endian. + /// If the span is too small to contain the value, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryWriteUInt16LittleEndian(Span buffer, ushort value) + { + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return TryWriteMachineEndian(buffer, ref value); + } + + /// + /// Write a UInt32 into a span of bytes as little endian. + /// If the span is too small to contain the value, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryWriteUInt32LittleEndian(Span buffer, uint value) + { + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return TryWriteMachineEndian(buffer, ref value); + } + + /// + /// Write a UInt64 into a span of bytes as little endian. + /// If the span is too small to contain the value, return false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryWriteUInt64LittleEndian(Span buffer, ulong value) + { + if (!BitConverter.IsLittleEndian) + { + value = ReverseEndianness(value); + } + return TryWriteMachineEndian(buffer, ref value); + } + } +} diff --git a/src/System.Memory/tests/Binary/BinaryReaderUnitTests.cs b/src/System.Memory/tests/Binary/BinaryReaderUnitTests.cs new file mode 100644 index 000000000000..5b776244dec2 --- /dev/null +++ b/src/System.Memory/tests/Binary/BinaryReaderUnitTests.cs @@ -0,0 +1,483 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Xunit; + +using static System.Buffers.Binary.BinaryPrimitives; +using static System.TestHelpers; + +namespace System.Buffers.Binary.Tests +{ + public class BinaryReaderUnitTests + { + [Fact] + public void SpanRead() + { + Assert.True(BitConverter.IsLittleEndian); + + ulong value = 0x8877665544332211; // [11 22 33 44 55 66 77 88] + Span span; + unsafe { + span = new Span(&value, 8); + } + + Assert.Equal(0x11, ReadMachineEndian(span)); + Assert.True(TryReadMachineEndian(span, out byte byteValue)); + Assert.Equal(0x11, byteValue); + + Assert.Equal(0x11, ReadMachineEndian(span)); + Assert.True(TryReadMachineEndian(span, out byte sbyteValue)); + Assert.Equal(0x11, byteValue); + + Assert.Equal(0x1122, ReadUInt16BigEndian(span)); + Assert.True(TryReadUInt16BigEndian(span, out ushort ushortValue)); + Assert.Equal(0x1122, ushortValue); + + Assert.Equal(0x2211, ReadUInt16LittleEndian(span)); + Assert.True(TryReadUInt16LittleEndian(span, out ushortValue)); + Assert.Equal(0x2211, ushortValue); + + Assert.Equal(0x1122, ReadInt16BigEndian(span)); + Assert.True(TryReadInt16BigEndian(span, out short shortValue)); + Assert.Equal(0x1122, shortValue); + + Assert.Equal(0x2211, ReadInt16LittleEndian(span)); + Assert.True(TryReadInt16LittleEndian(span, out shortValue)); + Assert.Equal(0x2211, ushortValue); + + Assert.Equal(0x11223344, ReadUInt32BigEndian(span)); + Assert.True(TryReadUInt32BigEndian(span, out uint uintValue)); + Assert.Equal(0x11223344, uintValue); + + Assert.Equal(0x44332211, ReadUInt32LittleEndian(span)); + Assert.True(TryReadUInt32LittleEndian(span, out uintValue)); + Assert.Equal(0x44332211, uintValue); + + Assert.Equal(0x11223344, ReadInt32BigEndian(span)); + Assert.True(TryReadInt32BigEndian(span, out int intValue)); + Assert.Equal(0x11223344, intValue); + + Assert.Equal(0x44332211, ReadInt32LittleEndian(span)); + Assert.True(TryReadInt32LittleEndian(span, out intValue)); + Assert.Equal(0x44332211, intValue); + + Assert.Equal(0x1122334455667788, ReadUInt64BigEndian(span)); + Assert.True(TryReadUInt64BigEndian(span, out ulong ulongValue)); + Assert.Equal(0x1122334455667788, ulongValue); + + Assert.Equal(0x8877665544332211, ReadUInt64LittleEndian(span)); + Assert.True(TryReadUInt64LittleEndian(span, out ulongValue)); + Assert.Equal(0x8877665544332211, ulongValue); + + Assert.Equal(0x1122334455667788, ReadInt64BigEndian(span)); + Assert.True(TryReadInt64BigEndian(span, out long longValue)); + Assert.Equal(0x1122334455667788, longValue); + + Assert.Equal(unchecked((long)0x8877665544332211), ReadInt64LittleEndian(span)); + Assert.True(TryReadInt64LittleEndian(span, out longValue)); + Assert.Equal(unchecked((long)0x8877665544332211), longValue); + } + + [Fact] + public void ReadOnlySpanRead() + { + Assert.True(BitConverter.IsLittleEndian); + + ulong value = 0x8877665544332211; // [11 22 33 44 55 66 77 88] + ReadOnlySpan span; + unsafe { + span = new ReadOnlySpan(&value, 8); + } + + Assert.Equal(0x11, ReadMachineEndian(span)); + Assert.True(TryReadMachineEndian(span, out byte byteValue)); + Assert.Equal(0x11, byteValue); + + Assert.Equal(0x11, ReadMachineEndian(span)); + Assert.True(TryReadMachineEndian(span, out byte sbyteValue)); + Assert.Equal(0x11, byteValue); + + Assert.Equal(0x1122, ReadUInt16BigEndian(span)); + Assert.True(TryReadUInt16BigEndian(span, out ushort ushortValue)); + Assert.Equal(0x1122, ushortValue); + + Assert.Equal(0x2211, ReadUInt16LittleEndian(span)); + Assert.True(TryReadUInt16LittleEndian(span, out ushortValue)); + Assert.Equal(0x2211, ushortValue); + + Assert.Equal(0x1122, ReadInt16BigEndian(span)); + Assert.True(TryReadInt16BigEndian(span, out short shortValue)); + Assert.Equal(0x1122, shortValue); + + Assert.Equal(0x2211, ReadInt16LittleEndian(span)); + Assert.True(TryReadInt16LittleEndian(span, out shortValue)); + Assert.Equal(0x2211, ushortValue); + + Assert.Equal(0x11223344, ReadUInt32BigEndian(span)); + Assert.True(TryReadUInt32BigEndian(span, out uint uintValue)); + Assert.Equal(0x11223344, uintValue); + + Assert.Equal(0x44332211, ReadUInt32LittleEndian(span)); + Assert.True(TryReadUInt32LittleEndian(span, out uintValue)); + Assert.Equal(0x44332211, uintValue); + + Assert.Equal(0x11223344, ReadInt32BigEndian(span)); + Assert.True(TryReadInt32BigEndian(span, out int intValue)); + Assert.Equal(0x11223344, intValue); + + Assert.Equal(0x44332211, ReadInt32LittleEndian(span)); + Assert.True(TryReadInt32LittleEndian(span, out intValue)); + Assert.Equal(0x44332211, intValue); + + Assert.Equal(0x1122334455667788, ReadUInt64BigEndian(span)); + Assert.True(TryReadUInt64BigEndian(span, out ulong ulongValue)); + Assert.Equal(0x1122334455667788, ulongValue); + + Assert.Equal(0x8877665544332211, ReadUInt64LittleEndian(span)); + Assert.True(TryReadUInt64LittleEndian(span, out ulongValue)); + Assert.Equal(0x8877665544332211, ulongValue); + + Assert.Equal(0x1122334455667788, ReadInt64BigEndian(span)); + Assert.True(TryReadInt64BigEndian(span, out long longValue)); + Assert.Equal(0x1122334455667788, longValue); + + Assert.Equal(unchecked((long)0x8877665544332211), ReadInt64LittleEndian(span)); + Assert.True(TryReadInt64LittleEndian(span, out longValue)); + Assert.Equal(unchecked((long)0x8877665544332211), longValue); + } + + [Fact] + public void SpanReadFail() + { + Span span = new byte[] { 1 }; + + Assert.Equal(1, ReadMachineEndian(span)); + Assert.True(TryReadMachineEndian(span, out byte byteValue)); + Assert.Equal(1, byteValue); + + TestHelpers.AssertThrows(span, (_span) => ReadMachineEndian(_span)); + Assert.False(TryReadMachineEndian(span, out short shortValue)); + TestHelpers.AssertThrows(span, (_span) => ReadMachineEndian(_span)); + Assert.False(TryReadMachineEndian(span, out int intValue)); + TestHelpers.AssertThrows(span, (_span) => ReadMachineEndian(_span)); + Assert.False(TryReadMachineEndian(span, out long longValue)); + + TestHelpers.AssertThrows(span, (_span) => ReadMachineEndian(_span)); + Assert.False(TryReadMachineEndian(span, out ushort ushortValue)); + TestHelpers.AssertThrows(span, (_span) => ReadMachineEndian(_span)); + Assert.False(TryReadMachineEndian(span, out uint uintValue)); + TestHelpers.AssertThrows(span, (_span) => ReadMachineEndian(_span)); + Assert.False(TryReadMachineEndian(span, out ulong ulongValue)); + + Span largeSpan = new byte[100]; + TestHelpers.AssertThrows(largeSpan, (_span) => ReadMachineEndian(_span)); + TestHelpers.AssertThrows(largeSpan, (_span) => TryReadMachineEndian(_span, out TestHelpers.TestValueTypeWithReference stringValue)); + } + + [Fact] + public void ReadOnlySpanReadFail() + { + ReadOnlySpan span = new byte[] { 1 }; + + Assert.Equal(1, ReadMachineEndian(span)); + Assert.True(TryReadMachineEndian(span, out byte byteValue)); + Assert.Equal(1, byteValue); + + TestHelpers.AssertThrows(span, (_span) => ReadMachineEndian(_span)); + Assert.False(TryReadMachineEndian(span, out short shortValue)); + TestHelpers.AssertThrows(span, (_span) => ReadMachineEndian(_span)); + Assert.False(TryReadMachineEndian(span, out int intValue)); + TestHelpers.AssertThrows(span, (_span) => ReadMachineEndian(_span)); + Assert.False(TryReadMachineEndian(span, out long longValue)); + + TestHelpers.AssertThrows(span, (_span) => ReadMachineEndian(_span)); + Assert.False(TryReadMachineEndian(span, out ushort ushortValue)); + TestHelpers.AssertThrows(span, (_span) => ReadMachineEndian(_span)); + Assert.False(TryReadMachineEndian(span, out uint uintValue)); + TestHelpers.AssertThrows(span, (_span) => ReadMachineEndian(_span)); + Assert.False(TryReadMachineEndian(span, out ulong ulongValue)); + + ReadOnlySpan largeSpan = new byte[100]; + TestHelpers.AssertThrows(largeSpan, (_span) => ReadMachineEndian(_span)); + TestHelpers.AssertThrows(largeSpan, (_span) => TryReadMachineEndian(_span, out TestHelpers.TestValueTypeWithReference stringValue)); + } + + [Fact] + public void ReverseByteDoesNothing() + { + byte valueMax = byte.MaxValue; + byte valueMin = byte.MinValue; + sbyte signedValueMax = sbyte.MaxValue; + sbyte signedValueMin = sbyte.MinValue; + + Assert.Equal(valueMax, ReverseEndianness(valueMax)); + Assert.Equal(valueMin, ReverseEndianness(valueMin)); + Assert.Equal(signedValueMax, ReverseEndianness(signedValueMax)); + Assert.Equal(signedValueMin, ReverseEndianness(signedValueMin)); + } + + [Fact] + public void SpanWriteAndReadBigEndianHeterogeneousStruct() + { + Assert.True(BitConverter.IsLittleEndian); + + Span spanBE = new byte[Unsafe.SizeOf()]; + + WriteInt16BigEndian(spanBE, testStruct.S0); + WriteInt32BigEndian(spanBE.Slice(2), testStruct.I0); + WriteInt64BigEndian(spanBE.Slice(6), testStruct.L0); + WriteUInt16BigEndian(spanBE.Slice(14), testStruct.US0); + WriteUInt32BigEndian(spanBE.Slice(16), testStruct.UI0); + WriteUInt64BigEndian(spanBE.Slice(20), testStruct.UL0); + WriteInt16BigEndian(spanBE.Slice(28), testStruct.S1); + WriteInt32BigEndian(spanBE.Slice(30), testStruct.I1); + WriteInt64BigEndian(spanBE.Slice(34), testStruct.L1); + WriteUInt16BigEndian(spanBE.Slice(42), testStruct.US1); + WriteUInt32BigEndian(spanBE.Slice(44), testStruct.UI1); + WriteUInt64BigEndian(spanBE.Slice(48), testStruct.UL1); + + ReadOnlySpan readOnlySpanBE = new ReadOnlySpan(spanBE.ToArray()); + + var readStruct = new TestStruct + { + S0 = ReadInt16BigEndian(spanBE), + I0 = ReadInt32BigEndian(spanBE.Slice(2)), + L0 = ReadInt64BigEndian(spanBE.Slice(6)), + US0 = ReadUInt16BigEndian(spanBE.Slice(14)), + UI0 = ReadUInt32BigEndian(spanBE.Slice(16)), + UL0 = ReadUInt64BigEndian(spanBE.Slice(20)), + S1 = ReadInt16BigEndian(spanBE.Slice(28)), + I1 = ReadInt32BigEndian(spanBE.Slice(30)), + L1 = ReadInt64BigEndian(spanBE.Slice(34)), + US1 = ReadUInt16BigEndian(spanBE.Slice(42)), + UI1 = ReadUInt32BigEndian(spanBE.Slice(44)), + UL1 = ReadUInt64BigEndian(spanBE.Slice(48)) + }; + + var readStructFromReadOnlySpan = new TestStruct + { + S0 = ReadInt16BigEndian(readOnlySpanBE), + I0 = ReadInt32BigEndian(readOnlySpanBE.Slice(2)), + L0 = ReadInt64BigEndian(readOnlySpanBE.Slice(6)), + US0 = ReadUInt16BigEndian(readOnlySpanBE.Slice(14)), + UI0 = ReadUInt32BigEndian(readOnlySpanBE.Slice(16)), + UL0 = ReadUInt64BigEndian(readOnlySpanBE.Slice(20)), + S1 = ReadInt16BigEndian(readOnlySpanBE.Slice(28)), + I1 = ReadInt32BigEndian(readOnlySpanBE.Slice(30)), + L1 = ReadInt64BigEndian(readOnlySpanBE.Slice(34)), + US1 = ReadUInt16BigEndian(readOnlySpanBE.Slice(42)), + UI1 = ReadUInt32BigEndian(readOnlySpanBE.Slice(44)), + UL1 = ReadUInt64BigEndian(readOnlySpanBE.Slice(48)) + }; + + Assert.Equal(testStruct, readStruct); + Assert.Equal(testStruct, readStructFromReadOnlySpan); + } + + [Fact] + public void SpanWriteAndReadLittleEndianHeterogeneousStruct() + { + Assert.True(BitConverter.IsLittleEndian); + + Span spanLE = new byte[Unsafe.SizeOf()]; + + WriteInt16LittleEndian(spanLE, testStruct.S0); + WriteInt32LittleEndian(spanLE.Slice(2), testStruct.I0); + WriteInt64LittleEndian(spanLE.Slice(6), testStruct.L0); + WriteUInt16LittleEndian(spanLE.Slice(14), testStruct.US0); + WriteUInt32LittleEndian(spanLE.Slice(16), testStruct.UI0); + WriteUInt64LittleEndian(spanLE.Slice(20), testStruct.UL0); + WriteInt16LittleEndian(spanLE.Slice(28), testStruct.S1); + WriteInt32LittleEndian(spanLE.Slice(30), testStruct.I1); + WriteInt64LittleEndian(spanLE.Slice(34), testStruct.L1); + WriteUInt16LittleEndian(spanLE.Slice(42), testStruct.US1); + WriteUInt32LittleEndian(spanLE.Slice(44), testStruct.UI1); + WriteUInt64LittleEndian(spanLE.Slice(48), testStruct.UL1); + + ReadOnlySpan readOnlySpanLE = new ReadOnlySpan(spanLE.ToArray()); + + var readStruct = new TestStruct + { + S0 = ReadInt16LittleEndian(spanLE), + I0 = ReadInt32LittleEndian(spanLE.Slice(2)), + L0 = ReadInt64LittleEndian(spanLE.Slice(6)), + US0 = ReadUInt16LittleEndian(spanLE.Slice(14)), + UI0 = ReadUInt32LittleEndian(spanLE.Slice(16)), + UL0 = ReadUInt64LittleEndian(spanLE.Slice(20)), + S1 = ReadInt16LittleEndian(spanLE.Slice(28)), + I1 = ReadInt32LittleEndian(spanLE.Slice(30)), + L1 = ReadInt64LittleEndian(spanLE.Slice(34)), + US1 = ReadUInt16LittleEndian(spanLE.Slice(42)), + UI1 = ReadUInt32LittleEndian(spanLE.Slice(44)), + UL1 = ReadUInt64LittleEndian(spanLE.Slice(48)) + }; + + var readStructFromReadOnlySpan = new TestStruct + { + S0 = ReadInt16LittleEndian(readOnlySpanLE), + I0 = ReadInt32LittleEndian(readOnlySpanLE.Slice(2)), + L0 = ReadInt64LittleEndian(readOnlySpanLE.Slice(6)), + US0 = ReadUInt16LittleEndian(readOnlySpanLE.Slice(14)), + UI0 = ReadUInt32LittleEndian(readOnlySpanLE.Slice(16)), + UL0 = ReadUInt64LittleEndian(readOnlySpanLE.Slice(20)), + S1 = ReadInt16LittleEndian(readOnlySpanLE.Slice(28)), + I1 = ReadInt32LittleEndian(readOnlySpanLE.Slice(30)), + L1 = ReadInt64LittleEndian(readOnlySpanLE.Slice(34)), + US1 = ReadUInt16LittleEndian(readOnlySpanLE.Slice(42)), + UI1 = ReadUInt32LittleEndian(readOnlySpanLE.Slice(44)), + UL1 = ReadUInt64LittleEndian(readOnlySpanLE.Slice(48)) + }; + + Assert.Equal(testStruct, readStruct); + Assert.Equal(testStruct, readStructFromReadOnlySpan); + } + + [Fact] + public void ReadingStructFieldByFieldOrReadAndReverseEndianness() + { + Assert.True(BitConverter.IsLittleEndian); + Span spanBE = new byte[Unsafe.SizeOf()]; + + var testExplicitStruct = new TestHelpers.TestStructExplicit + { + S0 = short.MaxValue, + I0 = int.MaxValue, + L0 = long.MaxValue, + US0 = ushort.MaxValue, + UI0 = uint.MaxValue, + UL0 = ulong.MaxValue, + S1 = short.MinValue, + I1 = int.MinValue, + L1 = long.MinValue, + US1 = ushort.MinValue, + UI1 = uint.MinValue, + UL1 = ulong.MinValue + }; + + WriteInt16BigEndian(spanBE, testExplicitStruct.S0); + WriteInt32BigEndian(spanBE.Slice(2), testExplicitStruct.I0); + WriteInt64BigEndian(spanBE.Slice(6), testExplicitStruct.L0); + WriteUInt16BigEndian(spanBE.Slice(14), testExplicitStruct.US0); + WriteUInt32BigEndian(spanBE.Slice(16), testExplicitStruct.UI0); + WriteUInt64BigEndian(spanBE.Slice(20), testExplicitStruct.UL0); + WriteInt16BigEndian(spanBE.Slice(28), testExplicitStruct.S1); + WriteInt32BigEndian(spanBE.Slice(30), testExplicitStruct.I1); + WriteInt64BigEndian(spanBE.Slice(34), testExplicitStruct.L1); + WriteUInt16BigEndian(spanBE.Slice(42), testExplicitStruct.US1); + WriteUInt32BigEndian(spanBE.Slice(44), testExplicitStruct.UI1); + WriteUInt64BigEndian(spanBE.Slice(48), testExplicitStruct.UL1); + + Assert.Equal(56, spanBE.Length); + + ReadOnlySpan readOnlySpanBE = new ReadOnlySpan(spanBE.ToArray()); + + var readStructAndReverse = ReadMachineEndian(spanBE); + if (BitConverter.IsLittleEndian) + { + readStructAndReverse.S0 = ReverseEndianness(readStructAndReverse.S0); + readStructAndReverse.I0 = ReverseEndianness(readStructAndReverse.I0); + readStructAndReverse.L0 = ReverseEndianness(readStructAndReverse.L0); + readStructAndReverse.US0 = ReverseEndianness(readStructAndReverse.US0); + readStructAndReverse.UI0 = ReverseEndianness(readStructAndReverse.UI0); + readStructAndReverse.UL0 = ReverseEndianness(readStructAndReverse.UL0); + readStructAndReverse.S1 = ReverseEndianness(readStructAndReverse.S1); + readStructAndReverse.I1 = ReverseEndianness(readStructAndReverse.I1); + readStructAndReverse.L1 = ReverseEndianness(readStructAndReverse.L1); + readStructAndReverse.US1 = ReverseEndianness(readStructAndReverse.US1); + readStructAndReverse.UI1 = ReverseEndianness(readStructAndReverse.UI1); + readStructAndReverse.UL1 = ReverseEndianness(readStructAndReverse.UL1); + } + + var readStructFieldByField = new TestHelpers.TestStructExplicit + { + S0 = ReadInt16BigEndian(spanBE), + I0 = ReadInt32BigEndian(spanBE.Slice(2)), + L0 = ReadInt64BigEndian(spanBE.Slice(6)), + US0 = ReadUInt16BigEndian(spanBE.Slice(14)), + UI0 = ReadUInt32BigEndian(spanBE.Slice(16)), + UL0 = ReadUInt64BigEndian(spanBE.Slice(20)), + S1 = ReadInt16BigEndian(spanBE.Slice(28)), + I1 = ReadInt32BigEndian(spanBE.Slice(30)), + L1 = ReadInt64BigEndian(spanBE.Slice(34)), + US1 = ReadUInt16BigEndian(spanBE.Slice(42)), + UI1 = ReadUInt32BigEndian(spanBE.Slice(44)), + UL1 = ReadUInt64BigEndian(spanBE.Slice(48)) + }; + + var readStructAndReverseFromReadOnlySpan = ReadMachineEndian(readOnlySpanBE); + if (BitConverter.IsLittleEndian) + { + readStructAndReverseFromReadOnlySpan.S0 = ReverseEndianness(readStructAndReverseFromReadOnlySpan.S0); + readStructAndReverseFromReadOnlySpan.I0 = ReverseEndianness(readStructAndReverseFromReadOnlySpan.I0); + readStructAndReverseFromReadOnlySpan.L0 = ReverseEndianness(readStructAndReverseFromReadOnlySpan.L0); + readStructAndReverseFromReadOnlySpan.US0 = ReverseEndianness(readStructAndReverseFromReadOnlySpan.US0); + readStructAndReverseFromReadOnlySpan.UI0 = ReverseEndianness(readStructAndReverseFromReadOnlySpan.UI0); + readStructAndReverseFromReadOnlySpan.UL0 = ReverseEndianness(readStructAndReverseFromReadOnlySpan.UL0); + readStructAndReverseFromReadOnlySpan.S1 = ReverseEndianness(readStructAndReverseFromReadOnlySpan.S1); + readStructAndReverseFromReadOnlySpan.I1 = ReverseEndianness(readStructAndReverseFromReadOnlySpan.I1); + readStructAndReverseFromReadOnlySpan.L1 = ReverseEndianness(readStructAndReverseFromReadOnlySpan.L1); + readStructAndReverseFromReadOnlySpan.US1 = ReverseEndianness(readStructAndReverseFromReadOnlySpan.US1); + readStructAndReverseFromReadOnlySpan.UI1 = ReverseEndianness(readStructAndReverseFromReadOnlySpan.UI1); + readStructAndReverseFromReadOnlySpan.UL1 = ReverseEndianness(readStructAndReverseFromReadOnlySpan.UL1); + } + + var readStructFieldByFieldFromReadOnlySpan = new TestHelpers.TestStructExplicit + { + S0 = ReadInt16BigEndian(readOnlySpanBE), + I0 = ReadInt32BigEndian(readOnlySpanBE.Slice(2)), + L0 = ReadInt64BigEndian(readOnlySpanBE.Slice(6)), + US0 = ReadUInt16BigEndian(readOnlySpanBE.Slice(14)), + UI0 = ReadUInt32BigEndian(readOnlySpanBE.Slice(16)), + UL0 = ReadUInt64BigEndian(readOnlySpanBE.Slice(20)), + S1 = ReadInt16BigEndian(readOnlySpanBE.Slice(28)), + I1 = ReadInt32BigEndian(readOnlySpanBE.Slice(30)), + L1 = ReadInt64BigEndian(readOnlySpanBE.Slice(34)), + US1 = ReadUInt16BigEndian(readOnlySpanBE.Slice(42)), + UI1 = ReadUInt32BigEndian(readOnlySpanBE.Slice(44)), + UL1 = ReadUInt64BigEndian(readOnlySpanBE.Slice(48)) + }; + + Assert.Equal(testExplicitStruct, readStructAndReverse); + Assert.Equal(testExplicitStruct, readStructFieldByField); + + Assert.Equal(testExplicitStruct, readStructAndReverseFromReadOnlySpan); + Assert.Equal(testExplicitStruct, readStructFieldByFieldFromReadOnlySpan); + } + + private static TestStruct testStruct = new TestStruct + { + S0 = short.MaxValue, + I0 = int.MaxValue, + L0 = long.MaxValue, + US0 = ushort.MaxValue, + UI0 = uint.MaxValue, + UL0 = ulong.MaxValue, + S1 = short.MinValue, + I1 = int.MinValue, + L1 = long.MinValue, + US1 = ushort.MinValue, + UI1 = uint.MinValue, + UL1 = ulong.MinValue + }; + + [StructLayout(LayoutKind.Sequential)] + private struct TestStruct + { + public short S0; + public int I0; + public long L0; + public ushort US0; + public uint UI0; + public ulong UL0; + public short S1; + public int I1; + public long L1; + public ushort US1; + public uint UI1; + public ulong UL1; + } + } +} diff --git a/src/System.Memory/tests/Binary/BinaryWriterUnitTests.cs b/src/System.Memory/tests/Binary/BinaryWriterUnitTests.cs new file mode 100644 index 000000000000..7b2c8a06c29f --- /dev/null +++ b/src/System.Memory/tests/Binary/BinaryWriterUnitTests.cs @@ -0,0 +1,313 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Xunit; + +using static System.Buffers.Binary.BinaryPrimitives; +using static System.TestHelpers; + +namespace System.Buffers.Binary.Tests +{ + public class BinaryWriterUnitTests + { + [Fact] + public void SpanWrite() + { + Assert.True(BitConverter.IsLittleEndian); + + Span span = new byte[8]; + + byte byteValue = 0x11; + WriteMachineEndian(span, ref byteValue); + TestHelpers.Validate(span, byteValue); + Assert.True(TryWriteMachineEndian(span, ref byteValue)); + TestHelpers.Validate(span, byteValue); + + sbyte sbyteValue = 0x11; + WriteMachineEndian(span, ref sbyteValue); + TestHelpers.Validate(span, sbyteValue); + Assert.True(TryWriteMachineEndian(span, ref sbyteValue)); + TestHelpers.Validate(span, sbyteValue); + + ushort ushortValue = 0x1122; + WriteMachineEndian(span, ref ushortValue); + TestHelpers.Validate(span, ushortValue); + Assert.True(TryWriteMachineEndian(span, ref ushortValue)); + TestHelpers.Validate(span, ushortValue); + + uint uintValue = 0x11223344; + WriteMachineEndian(span, ref uintValue); + TestHelpers.Validate(span, uintValue); + Assert.True(TryWriteMachineEndian(span, ref uintValue)); + TestHelpers.Validate(span, uintValue); + + ulong ulongValue = 0x1122334455667788; + WriteMachineEndian(span, ref ulongValue); + TestHelpers.Validate(span, ulongValue); + Assert.True(TryWriteMachineEndian(span, ref ulongValue)); + TestHelpers.Validate(span, ulongValue); + + short shortValue = 0x1122; + WriteMachineEndian(span, ref shortValue); + TestHelpers.Validate(span, shortValue); + Assert.True(TryWriteMachineEndian(span, ref shortValue)); + TestHelpers.Validate(span, shortValue); + + int intValue = 0x11223344; + WriteMachineEndian(span, ref intValue); + TestHelpers.Validate(span, intValue); + Assert.True(TryWriteMachineEndian(span, ref intValue)); + TestHelpers.Validate(span, intValue); + + long longValue = 0x1122334455667788; + WriteMachineEndian(span, ref longValue); + TestHelpers.Validate(span, longValue); + Assert.True(TryWriteMachineEndian(span, ref longValue)); + TestHelpers.Validate(span, longValue); + } + + [Theory] + [InlineData(short.MaxValue)] + [InlineData(short.MinValue)] + [InlineData(0x7F00)] + [InlineData(0x00FF)] + public void SpanWriteInt16(short value) + { + Assert.True(BitConverter.IsLittleEndian); + var span = new Span(new byte[2]); + WriteInt16BigEndian(span, value); + short read = ReadInt16BigEndian(span); + Assert.Equal(value, read); + + span.Clear(); + Assert.True(TryWriteInt16BigEndian(span, value)); + read = ReadInt16BigEndian(span); + Assert.Equal(value, read); + + span.Clear(); + WriteInt16LittleEndian(span, value); + read = ReadInt16LittleEndian(span); + Assert.Equal(value, read); + + span.Clear(); + Assert.True(TryWriteInt16LittleEndian(span, value)); + read = ReadInt16LittleEndian(span); + Assert.Equal(value, read); + } + + [Theory] + [InlineData(ushort.MaxValue)] + [InlineData(ushort.MinValue)] + [InlineData(0xFF00)] + [InlineData(0x00FF)] + public void SpanWriteUInt16(ushort value) + { + Assert.True(BitConverter.IsLittleEndian); + var span = new Span(new byte[2]); + WriteUInt16BigEndian(span, value); + ushort read = ReadUInt16BigEndian(span); + Assert.Equal(value, read); + + span.Clear(); + Assert.True(TryWriteUInt16BigEndian(span, value)); + read = ReadUInt16BigEndian(span); + Assert.Equal(value, read); + + span.Clear(); + WriteUInt16LittleEndian(span, value); + read = ReadUInt16LittleEndian(span); + Assert.Equal(value, read); + + span.Clear(); + Assert.True(TryWriteUInt16LittleEndian(span, value)); + read = ReadUInt16LittleEndian(span); + Assert.Equal(value, read); + } + + [Theory] + [InlineData(int.MaxValue)] + [InlineData(int.MinValue)] + [InlineData(0x7F000000)] + [InlineData(0x00FF0000)] + [InlineData(0x0000FF00)] + [InlineData(0x000000FF)] + public void SpanWriteInt32(int value) + { + Assert.True(BitConverter.IsLittleEndian); + var span = new Span(new byte[4]); + WriteInt32BigEndian(span, value); + int read = ReadInt32BigEndian(span); + Assert.Equal(value, read); + + span.Clear(); + Assert.True(TryWriteInt32BigEndian(span, value)); + read = ReadInt32BigEndian(span); + Assert.Equal(value, read); + + span.Clear(); + WriteInt32LittleEndian(span, value); + read = ReadInt32LittleEndian(span); + Assert.Equal(value, read); + + span.Clear(); + Assert.True(TryWriteInt32LittleEndian(span, value)); + read = ReadInt32LittleEndian(span); + Assert.Equal(value, read); + } + + [Theory] + [InlineData(uint.MaxValue)] + [InlineData(uint.MinValue)] + [InlineData(0xFF000000)] + [InlineData(0x00FF0000)] + [InlineData(0x0000FF00)] + [InlineData(0x000000FF)] + public void SpanWriteUInt32(uint value) + { + Assert.True(BitConverter.IsLittleEndian); + var span = new Span(new byte[4]); + WriteUInt32BigEndian(span, value); + uint read = ReadUInt32BigEndian(span); + Assert.Equal(value, read); + + span.Clear(); + Assert.True(TryWriteUInt32BigEndian(span, value)); + read = ReadUInt32BigEndian(span); + Assert.Equal(value, read); + + span.Clear(); + WriteUInt32LittleEndian(span, value); + read = ReadUInt32LittleEndian(span); + Assert.Equal(value, read); + + span.Clear(); + Assert.True(TryWriteUInt32LittleEndian(span, value)); + read = ReadUInt32LittleEndian(span); + Assert.Equal(value, read); + } + + [Theory] + [InlineData(long.MaxValue)] + [InlineData(long.MinValue)] + [InlineData(0x7F00000000000000)] + [InlineData(0x00FF000000000000)] + [InlineData(0x0000FF0000000000)] + [InlineData(0x000000FF00000000)] + [InlineData(0x00000000FF000000)] + [InlineData(0x0000000000FF0000)] + [InlineData(0x000000000000FF00)] + [InlineData(0x00000000000000FF)] + public void SpanWriteInt64(long value) + { + Assert.True(BitConverter.IsLittleEndian); + var span = new Span(new byte[8]); + WriteInt64BigEndian(span, value); + long read = ReadInt64BigEndian(span); + Assert.Equal(value, read); + + span.Clear(); + Assert.True(TryWriteInt64BigEndian(span, value)); + read = ReadInt64BigEndian(span); + Assert.Equal(value, read); + + span.Clear(); + WriteInt64LittleEndian(span, value); + read = ReadInt64LittleEndian(span); + Assert.Equal(value, read); + + span.Clear(); + Assert.True(TryWriteInt64LittleEndian(span, value)); + read = ReadInt64LittleEndian(span); + Assert.Equal(value, read); + } + + [Theory] + [InlineData(ulong.MaxValue)] + [InlineData(ulong.MinValue)] + [InlineData(0xFF00000000000000)] + [InlineData(0x00FF000000000000)] + [InlineData(0x0000FF0000000000)] + [InlineData(0x000000FF00000000)] + [InlineData(0x00000000FF000000)] + [InlineData(0x0000000000FF0000)] + [InlineData(0x000000000000FF00)] + [InlineData(0x00000000000000FF)] + public void SpanWriteUInt64(ulong value) + { + Assert.True(BitConverter.IsLittleEndian); + var span = new Span(new byte[8]); + WriteUInt64BigEndian(span, value); + ulong read = ReadUInt64BigEndian(span); + Assert.Equal(value, read); + + span.Clear(); + Assert.True(TryWriteUInt64BigEndian(span, value)); + read = ReadUInt64BigEndian(span); + Assert.Equal(value, read); + + span.Clear(); + WriteUInt64LittleEndian(span, value); + read = ReadUInt64LittleEndian(span); + Assert.Equal(value, read); + + span.Clear(); + Assert.True(TryWriteUInt64LittleEndian(span, value)); + read = ReadUInt64LittleEndian(span); + Assert.Equal(value, read); + } + + [Fact] + public void SpanWriteFail() + { + byte byteValue = 1; + sbyte sbyteValue = 1; + short shortValue = 1; + ushort ushortValue = 1; + int intValue = 1; + uint uintValue = 1; + long longValue = 1; + ulong ulongValue = 1; + + Span span = new byte[1]; + + WriteMachineEndian(span, ref byteValue); + byte read = ReadMachineEndian(span); + Assert.Equal(byteValue, read); + + span.Clear(); + Assert.True(TryWriteMachineEndian(span, ref byteValue)); + read = ReadMachineEndian(span); + Assert.Equal(byteValue, read); + + WriteMachineEndian(span, ref sbyteValue); + sbyte readSbyte = ReadMachineEndian(span); + Assert.Equal(sbyteValue, readSbyte); + + span.Clear(); + Assert.True(TryWriteMachineEndian(span, ref sbyteValue)); + readSbyte = ReadMachineEndian(span); + Assert.Equal(sbyteValue, readSbyte); + + TestHelpers.AssertThrows(span, (_span) => WriteMachineEndian(_span, ref shortValue)); + Assert.False(TryWriteMachineEndian(span, ref shortValue)); + TestHelpers.AssertThrows(span, (_span) => WriteMachineEndian(_span, ref intValue)); + Assert.False(TryWriteMachineEndian(span, ref intValue)); + TestHelpers.AssertThrows(span, (_span) => WriteMachineEndian(_span, ref longValue)); + Assert.False(TryWriteMachineEndian(span, ref longValue)); + + TestHelpers.AssertThrows(span, (_span) => WriteMachineEndian(_span, ref ushortValue)); + Assert.False(TryWriteMachineEndian(span, ref ushortValue)); + TestHelpers.AssertThrows(span, (_span) => WriteMachineEndian(_span, ref uintValue)); + Assert.False(TryWriteMachineEndian(span, ref uintValue)); + TestHelpers.AssertThrows(span, (_span) => WriteMachineEndian(_span, ref ulongValue)); + Assert.False(TryWriteMachineEndian(span, ref ulongValue)); + + var structValue = new TestHelpers.TestValueTypeWithReference{ I = 1, S = "1" }; + TestHelpers.AssertThrows(span, (_span) => WriteMachineEndian(_span, ref structValue)); + TestHelpers.AssertThrows(span, (_span) => TryWriteMachineEndian(_span, ref structValue)); + } + } +} diff --git a/src/System.Memory/tests/Performance/Perf.Span.BinaryReadAndWrite.cs b/src/System.Memory/tests/Performance/Perf.Span.BinaryReadAndWrite.cs new file mode 100644 index 000000000000..dbcdc998fb1f --- /dev/null +++ b/src/System.Memory/tests/Performance/Perf.Span.BinaryReadAndWrite.cs @@ -0,0 +1,281 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Xunit.Performance; +using Xunit; +using System.Net; + +using static System.Buffers.Binary.BinaryPrimitives; +using static System.TestHelpers; + +namespace System.Buffers.Binary.Tests +{ + public class BinaryReadAndWriteTests + { + private const int InnerCount = 100000; + + [Benchmark(InnerIterationCount = InnerCount)] + private static void ReadStructAndReverseBE() + { + Span spanBE = TestHelpers.GetSpanBE(); + + var readStruct = new TestHelpers.TestStructExplicit(); + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < Benchmark.InnerIterationCount; i++) + { + readStruct = ReadMachineEndian(spanBE); + if (BitConverter.IsLittleEndian) + { + readStruct.S0 = ReverseEndianness(readStruct.S0); + readStruct.I0 = ReverseEndianness(readStruct.I0); + readStruct.L0 = ReverseEndianness(readStruct.L0); + readStruct.US0 = ReverseEndianness(readStruct.US0); + readStruct.UI0 = ReverseEndianness(readStruct.UI0); + readStruct.UL0 = ReverseEndianness(readStruct.UL0); + readStruct.S1 = ReverseEndianness(readStruct.S1); + readStruct.I1 = ReverseEndianness(readStruct.I1); + readStruct.L1 = ReverseEndianness(readStruct.L1); + readStruct.US1 = ReverseEndianness(readStruct.US1); + readStruct.UI1 = ReverseEndianness(readStruct.UI1); + readStruct.UL1 = ReverseEndianness(readStruct.UL1); + } + } + } + } + + Assert.Equal(TestHelpers.testExplicitStruct, readStruct); + } + + [Benchmark(InnerIterationCount = InnerCount)] + private static void ReadStructAndReverseLE() + { + Span spanLE = TestHelpers.GetSpanLE(); + + var readStruct = new TestHelpers.TestStructExplicit(); + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < Benchmark.InnerIterationCount; i++) + { + readStruct = ReadMachineEndian(spanLE); + if (!BitConverter.IsLittleEndian) + { + readStruct.S0 = ReverseEndianness(readStruct.S0); + readStruct.I0 = ReverseEndianness(readStruct.I0); + readStruct.L0 = ReverseEndianness(readStruct.L0); + readStruct.US0 = ReverseEndianness(readStruct.US0); + readStruct.UI0 = ReverseEndianness(readStruct.UI0); + readStruct.UL0 = ReverseEndianness(readStruct.UL0); + readStruct.S1 = ReverseEndianness(readStruct.S1); + readStruct.I1 = ReverseEndianness(readStruct.I1); + readStruct.L1 = ReverseEndianness(readStruct.L1); + readStruct.US1 = ReverseEndianness(readStruct.US1); + readStruct.UI1 = ReverseEndianness(readStruct.UI1); + readStruct.UL1 = ReverseEndianness(readStruct.UL1); + } + } + } + } + + Assert.Equal(TestHelpers.testExplicitStruct, readStruct); + } + + [Benchmark(InnerIterationCount = InnerCount)] + private static void ReadStructFieldByFieldBE() + { + Span spanBE = TestHelpers.GetSpanBE(); + + var readStruct = new TestHelpers.TestStructExplicit(); + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < Benchmark.InnerIterationCount; i++) + { + readStruct = new TestHelpers.TestStructExplicit + { + S0 = ReadInt16BigEndian(spanBE), + I0 = ReadInt32BigEndian(spanBE.Slice(2)), + L0 = ReadInt64BigEndian(spanBE.Slice(6)), + US0 = ReadUInt16BigEndian(spanBE.Slice(14)), + UI0 = ReadUInt32BigEndian(spanBE.Slice(16)), + UL0 = ReadUInt64BigEndian(spanBE.Slice(20)), + S1 = ReadInt16BigEndian(spanBE.Slice(28)), + I1 = ReadInt32BigEndian(spanBE.Slice(30)), + L1 = ReadInt64BigEndian(spanBE.Slice(34)), + US1 = ReadUInt16BigEndian(spanBE.Slice(42)), + UI1 = ReadUInt32BigEndian(spanBE.Slice(44)), + UL1 = ReadUInt64BigEndian(spanBE.Slice(48)) + }; + } + } + } + + Assert.Equal(TestHelpers.testExplicitStruct, readStruct); + } + + [Benchmark(InnerIterationCount = InnerCount)] + private static void ReadStructFieldByFieldLE() + { + Span spanLE = TestHelpers.GetSpanLE(); + + var readStruct = new TestHelpers.TestStructExplicit(); + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < Benchmark.InnerIterationCount; i++) + { + readStruct = new TestHelpers.TestStructExplicit + { + S0 = ReadInt16LittleEndian(spanLE), + I0 = ReadInt32LittleEndian(spanLE.Slice(2)), + L0 = ReadInt64LittleEndian(spanLE.Slice(6)), + US0 = ReadUInt16LittleEndian(spanLE.Slice(14)), + UI0 = ReadUInt32LittleEndian(spanLE.Slice(16)), + UL0 = ReadUInt64LittleEndian(spanLE.Slice(20)), + S1 = ReadInt16LittleEndian(spanLE.Slice(28)), + I1 = ReadInt32LittleEndian(spanLE.Slice(30)), + L1 = ReadInt64LittleEndian(spanLE.Slice(34)), + US1 = ReadUInt16LittleEndian(spanLE.Slice(42)), + UI1 = ReadUInt32LittleEndian(spanLE.Slice(44)), + UL1 = ReadUInt64LittleEndian(spanLE.Slice(48)) + }; + } + } + } + + Assert.Equal(TestHelpers.testExplicitStruct, readStruct); + } + + [Benchmark(InnerIterationCount = InnerCount)] + private static void ReadStructFieldByFieldUsingBitConverterLE() + { + Span spanLE = TestHelpers.GetSpanLE(); + byte[] arrayLE = spanLE.ToArray(); + + var readStruct = new TestHelpers.TestStructExplicit(); + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < Benchmark.InnerIterationCount; i++) + { + readStruct = new TestHelpers.TestStructExplicit + { + S0 = BitConverter.ToInt16(arrayLE, 0), + I0 = BitConverter.ToInt32(arrayLE, 2), + L0 = BitConverter.ToInt64(arrayLE, 6), + US0 = BitConverter.ToUInt16(arrayLE, 14), + UI0 = BitConverter.ToUInt32(arrayLE, 16), + UL0 = BitConverter.ToUInt64(arrayLE, 20), + S1 = BitConverter.ToInt16(arrayLE, 28), + I1 = BitConverter.ToInt32(arrayLE, 30), + L1 = BitConverter.ToInt64(arrayLE, 34), + US1 = BitConverter.ToUInt16(arrayLE, 42), + UI1 = BitConverter.ToUInt32(arrayLE, 44), + UL1 = BitConverter.ToUInt64(arrayLE, 48), + }; + } + } + } + + Assert.Equal(TestHelpers.testExplicitStruct, readStruct); + } + + [Benchmark(InnerIterationCount = InnerCount)] + private static void ReadStructFieldByFieldUsingBitConverterBE() + { + Span spanBE = TestHelpers.GetSpanBE(); + byte[] arrayBE = spanBE.ToArray(); + + var readStruct = new TestHelpers.TestStructExplicit(); + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < Benchmark.InnerIterationCount; i++) + { + readStruct = new TestHelpers.TestStructExplicit + { + S0 = BitConverter.ToInt16(arrayBE, 0), + I0 = BitConverter.ToInt32(arrayBE, 2), + L0 = BitConverter.ToInt64(arrayBE, 6), + US0 = BitConverter.ToUInt16(arrayBE, 14), + UI0 = BitConverter.ToUInt32(arrayBE, 16), + UL0 = BitConverter.ToUInt64(arrayBE, 20), + S1 = BitConverter.ToInt16(arrayBE, 28), + I1 = BitConverter.ToInt32(arrayBE, 30), + L1 = BitConverter.ToInt64(arrayBE, 34), + US1 = BitConverter.ToUInt16(arrayBE, 42), + UI1 = BitConverter.ToUInt32(arrayBE, 44), + UL1 = BitConverter.ToUInt64(arrayBE, 48), + }; + if (BitConverter.IsLittleEndian) + { + readStruct.S0 = ReverseEndianness(readStruct.S0); + readStruct.I0 = ReverseEndianness(readStruct.I0); + readStruct.L0 = ReverseEndianness(readStruct.L0); + readStruct.US0 = ReverseEndianness(readStruct.US0); + readStruct.UI0 = ReverseEndianness(readStruct.UI0); + readStruct.UL0 = ReverseEndianness(readStruct.UL0); + readStruct.S1 = ReverseEndianness(readStruct.S1); + readStruct.I1 = ReverseEndianness(readStruct.I1); + readStruct.L1 = ReverseEndianness(readStruct.L1); + readStruct.US1 = ReverseEndianness(readStruct.US1); + readStruct.UI1 = ReverseEndianness(readStruct.UI1); + readStruct.UL1 = ReverseEndianness(readStruct.UL1); + } + } + } + } + + Assert.Equal(TestHelpers.testExplicitStruct, readStruct); + } + + [Benchmark(InnerIterationCount = InnerCount)] + private static void MeasureReverseEndianness() + { + var myArray = new int[1000]; + + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < Benchmark.InnerIterationCount; i++) + { + for (int j = 0; j < myArray.Length; j++) + { + myArray[j] = ReverseEndianness(myArray[j]); + } + } + } + } + } + + [Benchmark(InnerIterationCount = InnerCount)] + private static void MeasureReverseUsingNtoH() + { + var myArray = new int[1000]; + + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < Benchmark.InnerIterationCount; i++) + { + for (int j = 0; j < myArray.Length; j++) + { + myArray[j] = IPAddress.NetworkToHostOrder(myArray[j]); + } + } + } + } + } + } +} diff --git a/src/System.Memory/tests/Performance/System.Memory.Performance.Tests.csproj b/src/System.Memory/tests/Performance/System.Memory.Performance.Tests.csproj index f06b55483a29..c4b6188eab82 100644 --- a/src/System.Memory/tests/Performance/System.Memory.Performance.Tests.csproj +++ b/src/System.Memory/tests/Performance/System.Memory.Performance.Tests.csproj @@ -9,10 +9,12 @@ + + Common\System\PerfUtils.cs diff --git a/src/System.Memory/tests/System.Memory.Tests.csproj b/src/System.Memory/tests/System.Memory.Tests.csproj index 247e041ddbd8..28cffcde3fda 100644 --- a/src/System.Memory/tests/System.Memory.Tests.csproj +++ b/src/System.Memory/tests/System.Memory.Tests.csproj @@ -86,7 +86,7 @@ - + @@ -99,5 +99,9 @@ + + + + \ No newline at end of file diff --git a/src/System.Memory/tests/TestHelpers.cs b/src/System.Memory/tests/TestHelpers.cs index 7b46964a251b..8c40b3039f42 100644 --- a/src/System.Memory/tests/TestHelpers.cs +++ b/src/System.Memory/tests/TestHelpers.cs @@ -4,6 +4,9 @@ using Xunit; using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; + +using static System.Buffers.Binary.BinaryPrimitives; namespace System { @@ -153,6 +156,100 @@ public static void ValidateReferenceType(this ReadOnlyMemory memory, param } } + public static void Validate(Span span, T value) where T : struct + { + T read = ReadMachineEndian(span); + Assert.Equal(value, read); + span.Clear(); + } + + public static TestStructExplicit testExplicitStruct = new TestStructExplicit + { + S0 = short.MaxValue, + I0 = int.MaxValue, + L0 = long.MaxValue, + US0 = ushort.MaxValue, + UI0 = uint.MaxValue, + UL0 = ulong.MaxValue, + S1 = short.MinValue, + I1 = int.MinValue, + L1 = long.MinValue, + US1 = ushort.MinValue, + UI1 = uint.MinValue, + UL1 = ulong.MinValue + }; + + public static Span GetSpanBE() + { + Span spanBE = new byte[Unsafe.SizeOf()]; + + WriteInt16BigEndian(spanBE, testExplicitStruct.S0); + WriteInt32BigEndian(spanBE.Slice(2), testExplicitStruct.I0); + WriteInt64BigEndian(spanBE.Slice(6), testExplicitStruct.L0); + WriteUInt16BigEndian(spanBE.Slice(14), testExplicitStruct.US0); + WriteUInt32BigEndian(spanBE.Slice(16), testExplicitStruct.UI0); + WriteUInt64BigEndian(spanBE.Slice(20), testExplicitStruct.UL0); + WriteInt16BigEndian(spanBE.Slice(28), testExplicitStruct.S1); + WriteInt32BigEndian(spanBE.Slice(30), testExplicitStruct.I1); + WriteInt64BigEndian(spanBE.Slice(34), testExplicitStruct.L1); + WriteUInt16BigEndian(spanBE.Slice(42), testExplicitStruct.US1); + WriteUInt32BigEndian(spanBE.Slice(44), testExplicitStruct.UI1); + WriteUInt64BigEndian(spanBE.Slice(48), testExplicitStruct.UL1); + + Assert.Equal(56, spanBE.Length); + return spanBE; + } + + public static Span GetSpanLE() + { + Span spanLE = new byte[Unsafe.SizeOf()]; + + WriteInt16LittleEndian(spanLE, testExplicitStruct.S0); + WriteInt32LittleEndian(spanLE.Slice(2), testExplicitStruct.I0); + WriteInt64LittleEndian( spanLE.Slice(6), testExplicitStruct.L0); + WriteUInt16LittleEndian(spanLE.Slice(14), testExplicitStruct.US0); + WriteUInt32LittleEndian(spanLE.Slice(16), testExplicitStruct.UI0); + WriteUInt64LittleEndian(spanLE.Slice(20), testExplicitStruct.UL0); + WriteInt16LittleEndian(spanLE.Slice(28), testExplicitStruct.S1); + WriteInt32LittleEndian(spanLE.Slice(30), testExplicitStruct.I1); + WriteInt64LittleEndian(spanLE.Slice(34), testExplicitStruct.L1); + WriteUInt16LittleEndian(spanLE.Slice(42), testExplicitStruct.US1); + WriteUInt32LittleEndian(spanLE.Slice(44), testExplicitStruct.UI1); + WriteUInt64LittleEndian(spanLE.Slice(48), testExplicitStruct.UL1); + + Assert.Equal(56, spanLE.Length); + return spanLE; + } + + [StructLayout(LayoutKind.Explicit)] + public struct TestStructExplicit + { + [FieldOffset(0)] + public short S0; + [FieldOffset(2)] + public int I0; + [FieldOffset(6)] + public long L0; + [FieldOffset(14)] + public ushort US0; + [FieldOffset(16)] + public uint UI0; + [FieldOffset(20)] + public ulong UL0; + [FieldOffset(28)] + public short S1; + [FieldOffset(30)] + public int I1; + [FieldOffset(34)] + public long L1; + [FieldOffset(42)] + public ushort US1; + [FieldOffset(44)] + public uint UI1; + [FieldOffset(48)] + public ulong UL1; + } + [StructLayout(LayoutKind.Sequential)] public sealed class TestClass {