Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vectorize String.Equals for OrdinalIgnoreCase #77947

Merged
merged 15 commits into from
Nov 11, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Text.Unicode;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;

namespace System.Globalization
{
Expand Down Expand Up @@ -75,7 +76,77 @@ internal static int CompareStringIgnoreCaseNonAscii(ref char strA, int lengthA,
return OrdinalCasing.CompareStringIgnoreCase(ref strA, lengthA, ref strB, lengthB);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool Vector128EqualsIgnoreCaseAscii(Vector128<sbyte> vec1, Vector128<sbyte> vec2)
{
Debug.Assert(Vector128AllAscii(vec1.AsUInt16()));
Debug.Assert(Vector128AllAscii(vec2.AsUInt16()));
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
Vector128<sbyte> tmp1 = Vector128.Create((sbyte)0x3f) + vec1;
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
Vector128<sbyte> tmp2 = Vector128.Create((sbyte)0x3f) + vec2;
tmp1 = Vector128.LessThan(Vector128.Create((byte)0x99).AsSByte(), tmp1);
tmp2 = Vector128.LessThan(Vector128.Create((byte)0x99).AsSByte(), tmp2);
tmp1 = Vector128.AndNot(Vector128.Create((sbyte)0x20), tmp1);
tmp2 = Vector128.AndNot(Vector128.Create((sbyte)0x20), tmp2);
return ((tmp1 + vec1) ^ (tmp2 + vec2)) == Vector128<sbyte>.Zero;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool Vector128AllAscii(Vector128<ushort> vec1)
{
return (vec1 & Vector128.Create(unchecked((ushort)~0x007F))) == Vector128<ushort>.Zero;
}

private static bool EqualsIgnoreCase_Vector128(ref char charA, ref char charB, int length)
{
Debug.Assert(length >= Vector128<ushort>.Count);
Debug.Assert(Vector128.IsHardwareAccelerated);

nuint lengthU = (nuint)length;
nuint lengthToExamine = lengthU - (nuint)Vector128<ushort>.Count;
nuint i = 0;
Vector128<ushort> vec1;
Vector128<ushort> vec2;
do
{
vec1 = Vector128.LoadUnsafe(ref Unsafe.As<char, ushort>(ref charA), i);
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
vec2 = Vector128.LoadUnsafe(ref Unsafe.As<char, ushort>(ref charB), i);
if (!Vector128AllAscii(vec1 | vec2))
{
goto NON_ASCII;
}
if (!Vector128EqualsIgnoreCaseAscii(vec1.AsSByte(), vec2.AsSByte()))
{
goto NOT_EQUAL;
}
i += (nuint)Vector128<ushort>.Count;
} while (i <= lengthToExamine);
EgorBo marked this conversation as resolved.
Show resolved Hide resolved

// Use scalar path for trailing elements
EgorBo marked this conversation as resolved.
Show resolved Hide resolved
return i == lengthU || EqualsIgnoreCase(ref Unsafe.Add(ref charA, i), ref Unsafe.Add(ref charB, i), (int)(lengthU - i));

NON_ASCII:
if (Vector128AllAscii(vec1) != Vector128AllAscii(vec2))
{
goto NOT_EQUAL;
}

return CompareStringIgnoreCase(
ref Unsafe.Add(ref charA, i), (int)(lengthU - i),
ref Unsafe.Add(ref charB, i), (int)(lengthU - i)) == 0;

NOT_EQUAL:
return false;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool EqualsIgnoreCase(ref char charA, ref char charB, int length)
{
if (!Vector128.IsHardwareAccelerated || length <= Vector128<ushort>.Count)
return EqualsIgnoreCase_Scalar(ref charA, ref charB, length);
return EqualsIgnoreCase_Vector128(ref charA, ref charB, length);
}

internal static bool EqualsIgnoreCase_Scalar(ref char charA, ref char charB, int length)
{
IntPtr byteOffset = IntPtr.Zero;

Expand Down