Skip to content

Commit

Permalink
Add BinaryPrimitives.ReverseEndianness for spans of elements (#76339)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephentoub authored Nov 4, 2022
1 parent be87e80 commit 7de0d40
Show file tree
Hide file tree
Showing 5 changed files with 403 additions and 16 deletions.
21 changes: 21 additions & 0 deletions src/libraries/System.Memory/ref/System.Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,27 @@ public static partial class BinaryPrimitives
public static uint ReverseEndianness(uint value) { throw null; }
[System.CLSCompliantAttribute(false)]
public static ulong ReverseEndianness(ulong value) { throw null; }
public static nint ReverseEndianness(nint value) { throw null; }
[System.CLSCompliantAttribute(false)]
public static nuint ReverseEndianness(nuint value) { throw null; }
public static System.Int128 ReverseEndianness(System.Int128 value) { throw null; }
[System.CLSCompliantAttribute(false)]
public static System.UInt128 ReverseEndianness(System.UInt128 value) { throw null; }
public static void ReverseEndianness(System.ReadOnlySpan<int> source, System.Span<int> destination) { }
public static void ReverseEndianness(System.ReadOnlySpan<Int128> source, System.Span<Int128> destination) { }
public static void ReverseEndianness(System.ReadOnlySpan<long> source, System.Span<long> destination) { }
public static void ReverseEndianness(System.ReadOnlySpan<nint> source, System.Span<nint> destination) { }
public static void ReverseEndianness(System.ReadOnlySpan<short> source, System.Span<short> destination) { }
[System.CLSCompliant(false)]
public static void ReverseEndianness(System.ReadOnlySpan<nuint> source, System.Span<nuint> destination) { }
[System.CLSCompliant(false)]
public static void ReverseEndianness(System.ReadOnlySpan<uint> source, System.Span<uint> destination) { }
[System.CLSCompliant(false)]
public static void ReverseEndianness(System.ReadOnlySpan<UInt128> source, System.Span<UInt128> destination) { }
[System.CLSCompliant(false)]
public static void ReverseEndianness(System.ReadOnlySpan<ulong> source, System.Span<ulong> destination) { }
[System.CLSCompliant(false)]
public static void ReverseEndianness(System.ReadOnlySpan<ushort> source, System.Span<ushort> destination) { }
public static bool TryReadDoubleBigEndian(System.ReadOnlySpan<byte> source, out double value) { throw null; }
public static bool TryReadDoubleLittleEndian(System.ReadOnlySpan<byte> source, out double value) { throw null; }
public static bool TryReadHalfBigEndian(System.ReadOnlySpan<byte> source, out System.Half value) { throw null; }
Expand Down
134 changes: 134 additions & 0 deletions src/libraries/System.Memory/tests/Binary/ReverseEndiannessUnitTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using Xunit;

namespace System.Buffers.Binary.Tests
Expand Down Expand Up @@ -41,6 +45,13 @@ public void ReverseEndianness_Int32AndUInt32(uint a, uint b)
Assert.Equal((int)a, BinaryPrimitives.ReverseEndianness((int)b));
Assert.Equal(b, BinaryPrimitives.ReverseEndianness(a));
Assert.Equal(a, BinaryPrimitives.ReverseEndianness(b));
if (IntPtr.Size == 4)
{
Assert.Equal((nint)b, BinaryPrimitives.ReverseEndianness((nint)a));
Assert.Equal((nint)a, BinaryPrimitives.ReverseEndianness((nint)b));
Assert.Equal((nuint)b, BinaryPrimitives.ReverseEndianness((nuint)a));
Assert.Equal((nuint)a, BinaryPrimitives.ReverseEndianness((nuint)b));
}
}

[Theory]
Expand All @@ -52,6 +63,129 @@ public void ReverseEndianness_Int64AndUInt64(ulong a, ulong b)
Assert.Equal((long)a, BinaryPrimitives.ReverseEndianness((long)b));
Assert.Equal(b, BinaryPrimitives.ReverseEndianness(a));
Assert.Equal(a, BinaryPrimitives.ReverseEndianness(b));
if (IntPtr.Size == 8)
{
Assert.Equal((nint)b, BinaryPrimitives.ReverseEndianness((nint)a));
Assert.Equal((nint)a, BinaryPrimitives.ReverseEndianness((nint)b));
Assert.Equal((nuint)b, BinaryPrimitives.ReverseEndianness((nuint)a));
Assert.Equal((nuint)a, BinaryPrimitives.ReverseEndianness((nuint)b));
}
}

[Theory]
[InlineData(0x0123456789ABCDEF, 0xABCDEF0123456789)]
public void ReverseEndianness_Int128AndUInt128(ulong aLower, ulong aUpper)
{
Int128 original = new Int128(aLower, aUpper);
Int128 reversed = new Int128(BinaryPrimitives.ReverseEndianness(aUpper), BinaryPrimitives.ReverseEndianness(aLower));

Assert.Equal(reversed, BinaryPrimitives.ReverseEndianness(original));
Assert.Equal((UInt128)reversed, BinaryPrimitives.ReverseEndianness((UInt128)original));
}

public static IEnumerable<object[]> ReverseEndianness_Span_MemberData()
{
var r = new Random(42);
foreach (int length in Enumerable.Range(0, 36))
{
yield return new object[] { Enumerable.Range(0, length).Select(_ => (ushort)r.Next(int.MinValue, int.MaxValue)).ToArray() };
yield return new object[] { Enumerable.Range(0, length).Select(_ => (short)r.Next(int.MinValue, int.MaxValue)).ToArray() };
yield return new object[] { Enumerable.Range(0, length).Select(_ => (uint)r.Next(int.MinValue, int.MaxValue)).ToArray() };
yield return new object[] { Enumerable.Range(0, length).Select(_ => r.Next(int.MinValue, int.MaxValue)).ToArray() };
yield return new object[] { Enumerable.Range(0, length).Select(_ => (ulong)r.NextInt64(long.MinValue, long.MaxValue)).ToArray() };
yield return new object[] { Enumerable.Range(0, length).Select(_ => r.NextInt64(long.MinValue, long.MaxValue)).ToArray() };
yield return new object[] { Enumerable.Range(0, length).Select(_ => (nuint)r.NextInt64(long.MinValue, long.MaxValue)).ToArray() };
yield return new object[] { Enumerable.Range(0, length).Select(_ => (nint)r.NextInt64(long.MinValue, long.MaxValue)).ToArray() };
yield return new object[] { Enumerable.Range(0, length).Select(_ => new UInt128((ulong)r.NextInt64(long.MinValue, long.MaxValue), (ulong)r.NextInt64(long.MinValue, long.MaxValue))).ToArray() };
yield return new object[] { Enumerable.Range(0, length).Select(_ => new Int128((ulong)r.NextInt64(long.MinValue, long.MaxValue), (ulong)r.NextInt64(long.MinValue, long.MaxValue))).ToArray() };
}
}

[Theory]
[MemberData(nameof(ReverseEndianness_Span_MemberData))]
public void ReverseEndianness_Span_AllElementsReversed<T>(T[] original) where T : struct, INumber<T>
{
T[] expected = original.Select(ReverseEndianness).ToArray();
T[] originalCopy = (T[])original.Clone();

T[] actual1 = (T[])original.Clone();
T[] actual2 = new T[original.Length];
T[] actual3 = new T[original.Length + 1];

// In-place
ReverseEndianness<T>(actual1, actual1);
Assert.Equal(expected, actual1);

// Different destination
ReverseEndianness<T>(original, actual2);
Assert.Equal(originalCopy, original);
Assert.Equal(expected, actual2);

// Different larger destination
ReverseEndianness<T>(original, actual3);
Assert.Equal(originalCopy, original);
Assert.Equal(expected, actual3[0..^1]);
Assert.Equal(default, actual3[^1]);

foreach (int offset in new[] { 1, 2, 3 })
{
if (original.Length > offset)
{
// In-place shifted +offset
expected = original.AsSpan(0, original.Length - offset).ToArray().Select(ReverseEndianness).ToArray();
actual1 = (T[])original.Clone();
ReverseEndianness(actual1.AsSpan(0, actual1.Length - offset), actual1.AsSpan(offset));
Assert.Equal(expected, actual1.AsSpan(offset).ToArray());
for (int i = 0; i < offset; i++)
{
Assert.Equal(original[i], actual1[i]);
}

// In-place shifted -offset
expected = original.AsSpan(offset).ToArray().Select(ReverseEndianness).ToArray();
actual2 = (T[])original.Clone();
ReverseEndianness(actual2.AsSpan(offset), actual2.AsSpan(0, actual2.Length - offset));
Assert.Equal(expected, actual2.AsSpan(0, actual2.Length - offset).ToArray());
Assert.Equal(original[^offset], actual2[^offset]);
}
}

// Throws if the destination is too short
if (original.Length > 0)
{
T[] destination = new T[original.Length - 1];
AssertExtensions.Throws<ArgumentException>("destination", () => ReverseEndianness<T>(original, destination));
}
}

private static T ReverseEndianness<T>(T value)
{
if (typeof(T) == typeof(ushort)) return (T)(object)BinaryPrimitives.ReverseEndianness((ushort)(object)value);
if (typeof(T) == typeof(short)) return (T)(object)BinaryPrimitives.ReverseEndianness((short)(object)value);
if (typeof(T) == typeof(uint)) return (T)(object)BinaryPrimitives.ReverseEndianness((uint)(object)value);
if (typeof(T) == typeof(int)) return (T)(object)BinaryPrimitives.ReverseEndianness((int)(object)value);
if (typeof(T) == typeof(ulong)) return (T)(object)BinaryPrimitives.ReverseEndianness((ulong)(object)value);
if (typeof(T) == typeof(long)) return (T)(object)BinaryPrimitives.ReverseEndianness((long)(object)value);
if (typeof(T) == typeof(nuint)) return (T)(object)BinaryPrimitives.ReverseEndianness((nuint)(object)value);
if (typeof(T) == typeof(nint)) return (T)(object)BinaryPrimitives.ReverseEndianness((nint)(object)value);
if (typeof(T) == typeof(UInt128)) return (T)(object)BinaryPrimitives.ReverseEndianness((UInt128)(object)value);
if (typeof(T) == typeof(Int128)) return (T)(object)BinaryPrimitives.ReverseEndianness((Int128)(object)value);
throw new Exception($"Unexpected type {typeof(T)}");
}

private static void ReverseEndianness<T>(ReadOnlySpan<T> source, Span<T> destination) where T : struct
{
if (typeof(T) == typeof(ushort)) { BinaryPrimitives.ReverseEndianness(MemoryMarshal.Cast<T, ushort>(source), MemoryMarshal.Cast<T, ushort>(destination)); return; }
if (typeof(T) == typeof(short)) { BinaryPrimitives.ReverseEndianness(MemoryMarshal.Cast<T, short>(source), MemoryMarshal.Cast<T, short>(destination)); return; }
if (typeof(T) == typeof(uint)) { BinaryPrimitives.ReverseEndianness(MemoryMarshal.Cast<T, uint>(source), MemoryMarshal.Cast<T, uint>(destination)); return; }
if (typeof(T) == typeof(int)) { BinaryPrimitives.ReverseEndianness(MemoryMarshal.Cast<T, int>(source), MemoryMarshal.Cast<T, int>(destination)); return; }
if (typeof(T) == typeof(ulong)) { BinaryPrimitives.ReverseEndianness(MemoryMarshal.Cast<T, ulong>(source), MemoryMarshal.Cast<T, ulong>(destination)); return; }
if (typeof(T) == typeof(long)) { BinaryPrimitives.ReverseEndianness(MemoryMarshal.Cast<T, long>(source), MemoryMarshal.Cast<T, long>(destination)); return; }
if (typeof(T) == typeof(nuint)) { BinaryPrimitives.ReverseEndianness(MemoryMarshal.Cast<T, nuint>(source), MemoryMarshal.Cast<T, nuint>(destination)); return; }
if (typeof(T) == typeof(nint)) { BinaryPrimitives.ReverseEndianness(MemoryMarshal.Cast<T, nint>(source), MemoryMarshal.Cast<T, nint>(destination)); return; }
if (typeof(T) == typeof(UInt128)) { BinaryPrimitives.ReverseEndianness(MemoryMarshal.Cast<T, UInt128>(source), MemoryMarshal.Cast<T, UInt128>(destination)); return; }
if (typeof(T) == typeof(Int128)) { BinaryPrimitives.ReverseEndianness(MemoryMarshal.Cast<T, Int128>(source), MemoryMarshal.Cast<T, Int128>(destination)); return; }
throw new Exception($"Unexpected type {typeof(T)}");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Buffer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\ArrayPool.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\ArrayPoolEventSource.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Binary\BinaryPrimitives.ReverseEndianness.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Binary\Reader.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Binary\ReaderBigEndian.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffers\Binary\ReaderLittleEndian.cs" />
Expand Down
Loading

0 comments on commit 7de0d40

Please sign in to comment.