Skip to content

Commit

Permalink
Vectorize HexConverter.EncodeToUtf16 using SSSE3 (#44111)
Browse files Browse the repository at this point in the history
Co-authored-by: Adeel Mujahid <3840695+am11@users.noreply.github.com>
  • Loading branch information
EgorBo and am11 committed Jan 24, 2021
1 parent 796848a commit 2f1def8
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 2 deletions.
72 changes: 71 additions & 1 deletion src/libraries/Common/src/System/HexConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
#nullable disable
using System.Diagnostics;
using System.Runtime.CompilerServices;
#if SYSTEM_PRIVATE_CORELIB
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using Internal.Runtime.CompilerServices;
#endif

namespace System
{
Expand Down Expand Up @@ -84,11 +90,75 @@ public static void ToCharsBuffer(byte value, Span<char> buffer, int startingInde
buffer[startingIndex] = (char)(packedResult >> 8);
}

#if SYSTEM_PRIVATE_CORELIB
private static void EncodeToUtf16_Ssse3(ReadOnlySpan<byte> bytes, Span<char> chars, Casing casing)
{
Debug.Assert(bytes.Length >= 4);
nint pos = 0;

Vector128<byte> shuffleMask = Vector128.Create(
0xFF, 0xFF, 0, 0xFF, 0xFF, 0xFF, 1, 0xFF,
0xFF, 0xFF, 2, 0xFF, 0xFF, 0xFF, 3, 0xFF);

Vector128<byte> asciiTable = (casing == Casing.Upper) ?
Vector128.Create((byte)'0', (byte)'1', (byte)'2', (byte)'3',
(byte)'4', (byte)'5', (byte)'6', (byte)'7',
(byte)'8', (byte)'9', (byte)'A', (byte)'B',
(byte)'C', (byte)'D', (byte)'E', (byte)'F') :
Vector128.Create((byte)'0', (byte)'1', (byte)'2', (byte)'3',
(byte)'4', (byte)'5', (byte)'6', (byte)'7',
(byte)'8', (byte)'9', (byte)'a', (byte)'b',
(byte)'c', (byte)'d', (byte)'e', (byte)'f');

do
{
// Read 32bits from "bytes" span at "pos" offset
uint block = Unsafe.ReadUnaligned<uint>(
ref Unsafe.Add(ref MemoryMarshal.GetReference(bytes), pos));

// Calculate nibbles
Vector128<byte> lowNibbles = Ssse3.Shuffle(
Vector128.CreateScalarUnsafe(block).AsByte(), shuffleMask);
Vector128<byte> highNibbles = Sse2.ShiftRightLogical(
Sse2.ShiftRightLogical128BitLane(lowNibbles, 2).AsInt32(), 4).AsByte();

// Lookup the hex values at the positions of the indices
Vector128<byte> indices = Sse2.And(
Sse2.Or(lowNibbles, highNibbles), Vector128.Create((byte)0xF));
Vector128<byte> hex = Ssse3.Shuffle(asciiTable, indices);

// The high bytes (0x00) of the chars have also been converted
// to ascii hex '0', so clear them out.
hex = Sse2.And(hex, Vector128.Create((ushort)0xFF).AsByte());

// Save to "chars" at pos*2 offset
Unsafe.WriteUnaligned(
ref Unsafe.As<char, byte>(
ref Unsafe.Add(ref MemoryMarshal.GetReference(chars), pos * 2)), hex);

pos += 4;
} while (pos < bytes.Length - 3);

// Process trailing elements (bytes.Length % 4)
for (; pos < bytes.Length; pos++)
{
ToCharsBuffer(Unsafe.Add(ref MemoryMarshal.GetReference(bytes), pos), chars, (int)pos * 2, casing);
}
}
#endif

public static void EncodeToUtf16(ReadOnlySpan<byte> bytes, Span<char> chars, Casing casing = Casing.Upper)
{
Debug.Assert(chars.Length >= bytes.Length * 2);

for (int pos = 0; pos < bytes.Length; ++pos)
#if SYSTEM_PRIVATE_CORELIB
if (Ssse3.IsSupported && bytes.Length >= 4)
{
EncodeToUtf16_Ssse3(bytes, chars, casing);
return;
}
#endif
for (int pos = 0; pos < bytes.Length; pos++)
{
ToCharsBuffer(bytes[pos], chars, pos * 2, casing);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
using System.Text;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Xunit;
using System.Text;
using System.Collections.Generic;

namespace System.Tests
{
Expand Down Expand Up @@ -62,5 +66,45 @@ public static unsafe void InputTooLarge()
{
AssertExtensions.Throws<ArgumentOutOfRangeException>("bytes", () => Convert.ToHexString(new ReadOnlySpan<byte>((void*)0, Int32.MaxValue)));
}

public static IEnumerable<object[]> ToHexStringTestData()
{
yield return new object[] { new byte[0], "" };
yield return new object[] { new byte[] { 0x00 }, "00" };
yield return new object[] { new byte[] { 0x01 }, "01" };
yield return new object[] { new byte[] { 0xFF }, "FF" };
yield return new object[] { new byte[] { 0x00, 0x00 }, "0000" };
yield return new object[] { new byte[] { 0xAB, 0xCD }, "ABCD" };
yield return new object[] { new byte[] { 0xFF, 0xFF }, "FFFF" };
yield return new object[] { new byte[] { 0x00, 0x00, 0x00 }, "000000" };
yield return new object[] { new byte[] { 0x01, 0x02, 0x03 }, "010203" };
yield return new object[] { new byte[] { 0xFF, 0xFF, 0xFF }, "FFFFFF" };
yield return new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00 }, "00000000" };
yield return new object[] { new byte[] { 0xAB, 0xCD, 0xEF, 0x12 }, "ABCDEF12" };
yield return new object[] { new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }, "FFFFFFFF" };
yield return new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00 }, "0000000000" };
yield return new object[] { new byte[] { 0xAB, 0xCD, 0xEF, 0x12, 0x34 }, "ABCDEF1234" };
yield return new object[] { new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, "FFFFFFFFFF" };
yield return new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, "000000000000" };
yield return new object[] { new byte[] { 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56 }, "ABCDEF123456" };
yield return new object[] { new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, "FFFFFFFFFFFF" };
yield return new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, "00000000000000" };
yield return new object[] { new byte[] { 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56, 0x78 }, "ABCDEF12345678" };
yield return new object[] { new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, "FFFFFFFFFFFFFF" };
yield return new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, "0000000000000000" };
yield return new object[] { new byte[] { 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56, 0x78, 0x90 }, "ABCDEF1234567890" };
yield return new object[] { new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, "FFFFFFFFFFFFFFFF" };
yield return new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, "000000000000000000" };
yield return new object[] { new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 }, "010203040506070809" };
yield return new object[] { new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, "FFFFFFFFFFFFFFFFFF" };
}

[Theory]
[MemberData(nameof(ToHexStringTestData))]
public static unsafe void ToHexString(byte[] input, string expected)
{
string actual = Convert.ToHexString(input);
Assert.Equal(expected, actual);
}
}
}

0 comments on commit 2f1def8

Please sign in to comment.