diff --git a/src/libraries/Common/src/System/HexConverter.cs b/src/libraries/Common/src/System/HexConverter.cs index 7f244771ca31a6..7932b0917dae32 100644 --- a/src/libraries/Common/src/System/HexConverter.cs +++ b/src/libraries/Common/src/System/HexConverter.cs @@ -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 { @@ -84,11 +90,75 @@ public static void ToCharsBuffer(byte value, Span buffer, int startingInde buffer[startingIndex] = (char)(packedResult >> 8); } +#if SYSTEM_PRIVATE_CORELIB + private static void EncodeToUtf16_Ssse3(ReadOnlySpan bytes, Span chars, Casing casing) + { + Debug.Assert(bytes.Length >= 4); + nint pos = 0; + + Vector128 shuffleMask = Vector128.Create( + 0xFF, 0xFF, 0, 0xFF, 0xFF, 0xFF, 1, 0xFF, + 0xFF, 0xFF, 2, 0xFF, 0xFF, 0xFF, 3, 0xFF); + + Vector128 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( + ref Unsafe.Add(ref MemoryMarshal.GetReference(bytes), pos)); + + // Calculate nibbles + Vector128 lowNibbles = Ssse3.Shuffle( + Vector128.CreateScalarUnsafe(block).AsByte(), shuffleMask); + Vector128 highNibbles = Sse2.ShiftRightLogical( + Sse2.ShiftRightLogical128BitLane(lowNibbles, 2).AsInt32(), 4).AsByte(); + + // Lookup the hex values at the positions of the indices + Vector128 indices = Sse2.And( + Sse2.Or(lowNibbles, highNibbles), Vector128.Create((byte)0xF)); + Vector128 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( + 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 bytes, Span 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); } diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs b/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs index aea831ffc40634..3b0986a46e4149 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs @@ -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 { @@ -62,5 +66,45 @@ public static unsafe void InputTooLarge() { AssertExtensions.Throws("bytes", () => Convert.ToHexString(new ReadOnlySpan((void*)0, Int32.MaxValue))); } + + public static IEnumerable 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); + } } }