Skip to content

Commit 1b56e96

Browse files
authored
Improve IndexOfAnyValues for needles with 0 (dotnet#84184)
1 parent dd3a027 commit 1b56e96

File tree

1 file changed

+47
-38
lines changed

1 file changed

+47
-38
lines changed

src/libraries/System.Private.CoreLib/src/System/SearchValues/IndexOfAnyAsciiSearcher.cs

Lines changed: 47 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -813,33 +813,10 @@ private static Vector128<byte> IndexOfAnyLookup<TNegator, TOptimizations>(Vector
813813
where TNegator : struct, INegator
814814
where TOptimizations : struct, IOptimizations
815815
{
816-
// Pack two vectors of characters into bytes. While the type is Vector128<short>, these are really UInt16 characters.
817-
// X86 and WASM: Downcast every character using saturation.
818-
// - Values <= 32767 result in min(value, 255).
819-
// - Values > 32767 result in 0. Because of this we must do more work to handle needles that contain 0.
820-
// ARM64: Do narrowing saturation over unsigned values.
821-
// - All values result in min(value, 255)
822-
Vector128<byte> source =
823-
Sse2.IsSupported ? Sse2.PackUnsignedSaturate(source0, source1) :
824-
AdvSimd.IsSupported ? AdvSimd.ExtractNarrowingSaturateUpper(AdvSimd.ExtractNarrowingSaturateLower(source0.AsUInt16()), source1.AsUInt16()) :
825-
PackedSimd.ConvertNarrowingUnsignedSaturate(source0, source1);
816+
Vector128<byte> source = TOptimizations.PackSources(source0.AsUInt16(), source1.AsUInt16());
826817

827818
Vector128<byte> result = IndexOfAnyLookupCore(source, bitmapLookup);
828819

829-
// On X86 and WASM, the packing/narrowing above resulted in values becoming 0 for inputs above 32767.
830-
// Any value above 32767 would therefore match against 0. If 0 is present in the needle, we must clear the false positives.
831-
// We can correct the result by clearing any bits that matched with a non-ascii source character.
832-
if (TOptimizations.NeedleContainsZero)
833-
{
834-
Debug.Assert(Sse2.IsSupported || PackedSimd.IsSupported);
835-
Vector128<short> ascii0 = Vector128.LessThan(source0.AsUInt16(), Vector128.Create((ushort)128)).AsInt16();
836-
Vector128<short> ascii1 = Vector128.LessThan(source1.AsUInt16(), Vector128.Create((ushort)128)).AsInt16();
837-
Vector128<byte> ascii = Sse2.IsSupported
838-
? Sse2.PackSignedSaturate(ascii0, ascii1).AsByte()
839-
: PackedSimd.ConvertNarrowingSignedSaturate(ascii0, ascii1).AsByte();
840-
result &= ascii;
841-
}
842-
843820
return TNegator.NegateIfNeeded(result);
844821
}
845822

@@ -870,23 +847,14 @@ private static Vector128<byte> IndexOfAnyLookupCore(Vector128<byte> source, Vect
870847
return result;
871848
}
872849

873-
[BypassReadyToRun]
874850
[MethodImpl(MethodImplOptions.AggressiveInlining)]
875851
private static Vector256<byte> IndexOfAnyLookup<TNegator, TOptimizations>(Vector256<short> source0, Vector256<short> source1, Vector256<byte> bitmapLookup)
876852
where TNegator : struct, INegator
877853
where TOptimizations : struct, IOptimizations
878854
{
879-
// See comments in IndexOfAnyLookup(Vector128<byte>) above for more details.
880-
Vector256<byte> source = Avx2.PackUnsignedSaturate(source0, source1);
881-
Vector256<byte> result = IndexOfAnyLookupCore(source, bitmapLookup);
855+
Vector256<byte> source = TOptimizations.PackSources(source0.AsUInt16(), source1.AsUInt16());
882856

883-
if (TOptimizations.NeedleContainsZero)
884-
{
885-
Vector256<short> ascii0 = Vector256.LessThan(source0.AsUInt16(), Vector256.Create((ushort)128)).AsInt16();
886-
Vector256<short> ascii1 = Vector256.LessThan(source1.AsUInt16(), Vector256.Create((ushort)128)).AsInt16();
887-
Vector256<byte> ascii = Avx2.PackSignedSaturate(ascii0, ascii1).AsByte();
888-
result &= ascii;
889-
}
857+
Vector256<byte> result = IndexOfAnyLookupCore(source, bitmapLookup);
890858

891859
return TNegator.NegateIfNeeded(result);
892860
}
@@ -1115,17 +1083,58 @@ internal interface INegator
11151083

11161084
internal interface IOptimizations
11171085
{
1118-
static abstract bool NeedleContainsZero { get; }
1086+
// Pack two vectors of characters into bytes.
1087+
// X86 and WASM when the needle does not contain 0: Downcast every character using saturation.
1088+
// - Values <= 32767 result in min(value, 255).
1089+
// - Values > 32767 result in 0. Because of this we must do more work to handle needles that contain 0.
1090+
// Otherwise: Do narrowing saturation over unsigned values.
1091+
// - All values result in min(value, 255)
1092+
static abstract Vector128<byte> PackSources(Vector128<ushort> lower, Vector128<ushort> upper);
1093+
static abstract Vector256<byte> PackSources(Vector256<ushort> lower, Vector256<ushort> upper);
11191094
}
11201095

11211096
internal readonly struct Ssse3AndWasmHandleZeroInNeedle : IOptimizations
11221097
{
1123-
public static bool NeedleContainsZero => true;
1098+
// Replace with Vector128.NarrowWithSaturation once https://github.com/dotnet/runtime/issues/75724 is implemented.
1099+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1100+
public static Vector128<byte> PackSources(Vector128<ushort> lower, Vector128<ushort> upper)
1101+
{
1102+
Vector128<short> lowerMin = Vector128.Min(lower, Vector128.Create((ushort)255)).AsInt16();
1103+
Vector128<short> upperMin = Vector128.Min(upper, Vector128.Create((ushort)255)).AsInt16();
1104+
1105+
return Sse2.IsSupported
1106+
? Sse2.PackUnsignedSaturate(lowerMin, upperMin)
1107+
: PackedSimd.ConvertNarrowingUnsignedSaturate(lowerMin, upperMin);
1108+
}
1109+
1110+
// Replace with Vector256.NarrowWithSaturation once https://github.com/dotnet/runtime/issues/75724 is implemented.
1111+
[BypassReadyToRun]
1112+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1113+
public static Vector256<byte> PackSources(Vector256<ushort> lower, Vector256<ushort> upper)
1114+
{
1115+
return Avx2.PackUnsignedSaturate(
1116+
Vector256.Min(lower, Vector256.Create((ushort)255)).AsInt16(),
1117+
Vector256.Min(upper, Vector256.Create((ushort)255)).AsInt16());
1118+
}
11241119
}
11251120

11261121
internal readonly struct Default : IOptimizations
11271122
{
1128-
public static bool NeedleContainsZero => false;
1123+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1124+
public static Vector128<byte> PackSources(Vector128<ushort> lower, Vector128<ushort> upper)
1125+
{
1126+
return
1127+
Sse2.IsSupported ? Sse2.PackUnsignedSaturate(lower.AsInt16(), upper.AsInt16()) :
1128+
AdvSimd.IsSupported ? AdvSimd.ExtractNarrowingSaturateUpper(AdvSimd.ExtractNarrowingSaturateLower(lower), upper) :
1129+
PackedSimd.ConvertNarrowingUnsignedSaturate(lower.AsInt16(), upper.AsInt16());
1130+
}
1131+
1132+
[BypassReadyToRun]
1133+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1134+
public static Vector256<byte> PackSources(Vector256<ushort> lower, Vector256<ushort> upper)
1135+
{
1136+
return Avx2.PackUnsignedSaturate(lower.AsInt16(), upper.AsInt16());
1137+
}
11291138
}
11301139
}
11311140
}

0 commit comments

Comments
 (0)