1
1
// Licensed to the .NET Foundation under one or more agreements.
2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
- using System . Buffers . Binary ;
5
4
using System . Diagnostics ;
6
5
using System . Numerics ;
7
6
using System . Runtime . CompilerServices ;
8
7
using System . Runtime . Intrinsics ;
9
8
using System . Runtime . Intrinsics . Arm ;
9
+ using System . Runtime . Intrinsics . Wasm ;
10
10
using System . Runtime . Intrinsics . X86 ;
11
11
12
12
#pragma warning disable 8500 // sizeof of managed types
13
+ #pragma warning disable IDE0060 // https://github.com/dotnet/roslyn-analyzers/issues/6228
13
14
14
15
namespace System . Buffers
15
16
{
16
17
internal static class IndexOfAnyAsciiSearcher
17
18
{
18
- internal static bool IsVectorizationSupported => Ssse3 . IsSupported || AdvSimd . Arm64 . IsSupported ;
19
+ internal static bool IsVectorizationSupported => Ssse3 . IsSupported || AdvSimd . Arm64 . IsSupported || PackedSimd . IsSupported ;
19
20
20
21
internal static unsafe void ComputeBitmap256 ( ReadOnlySpan < byte > values , out Vector128 < byte > bitmap0 , out Vector128 < byte > bitmap1 , out BitVector256 lookup )
21
22
{
@@ -131,8 +132,8 @@ private static unsafe bool TryIndexOfAny<TNegator>(ref short searchSpace, int se
131
132
Vector128 < byte > bitmap = default ;
132
133
if ( TryComputeBitmap ( asciiValues , ( byte * ) & bitmap , out bool needleContainsZero ) )
133
134
{
134
- index = Ssse3 . IsSupported && needleContainsZero
135
- ? IndexOfAnyVectorized < TNegator , Ssse3HandleZeroInNeedle > ( ref searchSpace , searchSpaceLength , bitmap )
135
+ index = ( Ssse3 . IsSupported || PackedSimd . IsSupported ) && needleContainsZero
136
+ ? IndexOfAnyVectorized < TNegator , Ssse3AndWasmHandleZeroInNeedle > ( ref searchSpace , searchSpaceLength , bitmap )
136
137
: IndexOfAnyVectorized < TNegator , Default > ( ref searchSpace , searchSpaceLength , bitmap ) ;
137
138
return true ;
138
139
}
@@ -153,8 +154,8 @@ private static unsafe bool TryLastIndexOfAny<TNegator>(ref short searchSpace, in
153
154
Vector128 < byte > bitmap = default ;
154
155
if ( TryComputeBitmap ( asciiValues , ( byte * ) & bitmap , out bool needleContainsZero ) )
155
156
{
156
- index = Ssse3 . IsSupported && needleContainsZero
157
- ? LastIndexOfAnyVectorized < TNegator , Ssse3HandleZeroInNeedle > ( ref searchSpace , searchSpaceLength , bitmap )
157
+ index = ( Ssse3 . IsSupported || PackedSimd . IsSupported ) && needleContainsZero
158
+ ? LastIndexOfAnyVectorized < TNegator , Ssse3AndWasmHandleZeroInNeedle > ( ref searchSpace , searchSpaceLength , bitmap )
158
159
: LastIndexOfAnyVectorized < TNegator , Default > ( ref searchSpace , searchSpaceLength , bitmap ) ;
159
160
return true ;
160
161
}
@@ -812,26 +813,29 @@ private static Vector128<byte> IndexOfAnyLookup<TNegator, TOptimizations>(Vector
812
813
where TOptimizations : struct , IOptimizations
813
814
{
814
815
// Pack two vectors of characters into bytes. While the type is Vector128<short>, these are really UInt16 characters.
815
- // X86: Downcast every character using saturation.
816
+ // X86 and WASM : Downcast every character using saturation.
816
817
// - Values <= 32767 result in min(value, 255).
817
818
// - Values > 32767 result in 0. Because of this we must do more work to handle needles that contain 0.
818
819
// ARM64: Do narrowing saturation over unsigned values.
819
820
// - All values result in min(value, 255)
820
- Vector128 < byte > source = Sse2 . IsSupported
821
- ? Sse2 . PackUnsignedSaturate ( source0 , source1 )
822
- : AdvSimd . ExtractNarrowingSaturateUpper ( AdvSimd . ExtractNarrowingSaturateLower ( source0 . AsUInt16 ( ) ) , source1 . AsUInt16 ( ) ) ;
821
+ Vector128 < byte > source =
822
+ Sse2 . IsSupported ? Sse2 . PackUnsignedSaturate ( source0 , source1 ) :
823
+ AdvSimd . IsSupported ? AdvSimd . ExtractNarrowingSaturateUpper ( AdvSimd . ExtractNarrowingSaturateLower ( source0 . AsUInt16 ( ) ) , source1 . AsUInt16 ( ) ) :
824
+ PackedSimd . ConvertNarrowingUnsignedSaturate ( source0 , source1 ) ;
823
825
824
826
Vector128 < byte > result = IndexOfAnyLookupCore ( source , bitmapLookup ) ;
825
827
826
- // On X86, PackUnsignedSaturate resulted in values becoming 0 for inputs above 32767.
828
+ // On X86 and WASM, the packing/narrowing above resulted in values becoming 0 for inputs above 32767.
827
829
// Any value above 32767 would therefore match against 0. If 0 is present in the needle, we must clear the false positives.
828
- // In both cases, we can correct the result by clearing any bits that matched with a non-ascii source character.
830
+ // We can correct the result by clearing any bits that matched with a non-ascii source character.
829
831
if ( TOptimizations . NeedleContainsZero )
830
832
{
831
- Debug . Assert ( Sse2 . IsSupported ) ;
833
+ Debug . Assert ( Sse2 . IsSupported || PackedSimd . IsSupported ) ;
832
834
Vector128 < short > ascii0 = Vector128 . LessThan ( source0 . AsUInt16 ( ) , Vector128 . Create ( ( ushort ) 128 ) ) . AsInt16 ( ) ;
833
835
Vector128 < short > ascii1 = Vector128 . LessThan ( source1 . AsUInt16 ( ) , Vector128 . Create ( ( ushort ) 128 ) ) . AsInt16 ( ) ;
834
- Vector128 < byte > ascii = Sse2 . PackSignedSaturate ( ascii0 , ascii1 ) . AsByte ( ) ;
836
+ Vector128 < byte > ascii = Sse2 . IsSupported
837
+ ? Sse2 . PackSignedSaturate ( ascii0 , ascii1 ) . AsByte ( )
838
+ : PackedSimd . ConvertNarrowingSignedSaturate ( ascii0 , ascii1 ) . AsByte ( ) ;
835
839
result &= ascii ;
836
840
}
837
841
@@ -850,15 +854,21 @@ private static Vector128<byte> IndexOfAnyLookupCore(Vector128<byte> source, Vect
850
854
// On ARM, we have an instruction for an arithmetic right shift of 1-byte signed values.
851
855
// The shift will map values above 127 to values above 16, which the shuffle will then map to 0.
852
856
// This is how we exclude non-ASCII values from results on ARM.
853
- // On X86, use a 4-byte value shift with AND 15 to emulate a 1-byte value logical shift.
857
+ // On X86 and WASM , use a 4-byte value shift with AND 15 to emulate a 1-byte value logical shift.
854
858
Vector128 < byte > highNibbles = AdvSimd . IsSupported
855
859
? AdvSimd . ShiftRightArithmetic ( source . AsSByte ( ) , 4 ) . AsByte ( )
856
- : Sse2 . ShiftRightLogical ( source . AsInt32 ( ) , 4 ) . AsByte ( ) & Vector128 . Create ( ( byte ) 0xF ) ;
860
+ : ( source . AsInt32 ( ) >>> 4 ) . AsByte ( ) & Vector128 . Create ( ( byte ) 0xF ) ;
857
861
858
862
// The bitmapLookup represents a 8x16 table of bits, indicating whether a character is present in the needle.
859
863
// Lookup the rows via the lower nibble and the column via the higher nibble.
860
864
Vector128 < byte > bitMask = Shuffle ( bitmapLookup , lowNibbles ) ;
861
- Vector128 < byte > bitPositions = Shuffle ( Vector128 . Create ( 0x8040201008040201 ) . AsByte ( ) , highNibbles ) ;
865
+
866
+ // On WASM, we still have to handle values outside the ASCII range.
867
+ // For values above 127, the high nibble will be above 7. We construct the positions vector for the shuffle such that those values map to 0.
868
+ // This is similar to how we use Ssse3's Shuffle characteristics on X86 to transform values above 127 to 0 by not masking the low nibbles above.
869
+ Vector128 < byte > bitPositions = PackedSimd . IsSupported
870
+ ? Shuffle ( Vector128 . Create ( 0x8040201008040201 , 0 ) . AsByte ( ) , highNibbles )
871
+ : Shuffle ( Vector128 . Create ( 0x8040201008040201 ) . AsByte ( ) , highNibbles ) ;
862
872
863
873
Vector128 < byte > result = bitMask & bitPositions ;
864
874
return result ;
@@ -943,9 +953,10 @@ private static Vector256<byte> IndexOfAnyLookup<TNegator>(Vector256<byte> source
943
953
private static Vector128 < byte > Shuffle ( Vector128 < byte > vector , Vector128 < byte > indices )
944
954
{
945
955
// We're not using Vector128.Shuffle as the caller already accounts for and relies on differences in behavior between platforms.
946
- return Ssse3 . IsSupported
947
- ? Ssse3 . Shuffle ( vector , indices )
948
- : AdvSimd . Arm64 . VectorTableLookup ( vector , indices ) ;
956
+ return
957
+ Ssse3 . IsSupported ? Ssse3 . Shuffle ( vector , indices ) :
958
+ AdvSimd . Arm64 . IsSupported ? AdvSimd . Arm64 . VectorTableLookup ( vector , indices ) :
959
+ PackedSimd . Swizzle ( vector , indices ) ;
949
960
}
950
961
951
962
[ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
@@ -957,7 +968,6 @@ private static unsafe int ComputeFirstIndex<T, TNegator>(ref T searchSpace, ref
957
968
return offsetInVector + ( int ) ( Unsafe . ByteOffset ( ref searchSpace , ref current ) / sizeof ( T ) ) ;
958
969
}
959
970
960
- #pragma warning disable IDE0060 // https://github.com/dotnet/roslyn-analyzers/issues/6228
961
971
[ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
962
972
private static unsafe int ComputeFirstIndexOverlapped < T , TNegator > ( ref T searchSpace , ref T current0 , ref T current1 , Vector128 < byte > result )
963
973
where TNegator : struct , INegator
@@ -972,7 +982,6 @@ private static unsafe int ComputeFirstIndexOverlapped<T, TNegator>(ref T searchS
972
982
}
973
983
return offsetInVector + ( int ) ( Unsafe . ByteOffset ( ref searchSpace , ref current0 ) / sizeof ( T ) ) ;
974
984
}
975
- #pragma warning restore IDE0060 // https://github.com/dotnet/roslyn-analyzers/issues/6228
976
985
977
986
[ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
978
987
private static unsafe int ComputeLastIndex < T , TNegator > ( ref T searchSpace , ref T current , Vector128 < byte > result )
@@ -1013,7 +1022,6 @@ private static unsafe int ComputeFirstIndex<T, TNegator>(ref T searchSpace, ref
1013
1022
return offsetInVector + ( int ) ( Unsafe . ByteOffset ( ref searchSpace , ref current ) / sizeof ( T ) ) ;
1014
1023
}
1015
1024
1016
- #pragma warning disable IDE0060 // https://github.com/dotnet/roslyn-analyzers/issues/6228
1017
1025
[ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
1018
1026
private static unsafe int ComputeFirstIndexOverlapped < T , TNegator > ( ref T searchSpace , ref T current0 , ref T current1 , Vector256 < byte > result )
1019
1027
where TNegator : struct , INegator
@@ -1034,7 +1042,6 @@ private static unsafe int ComputeFirstIndexOverlapped<T, TNegator>(ref T searchS
1034
1042
}
1035
1043
return offsetInVector + ( int ) ( Unsafe . ByteOffset ( ref searchSpace , ref current0 ) / sizeof ( T ) ) ;
1036
1044
}
1037
- #pragma warning restore IDE0060 // https://github.com/dotnet/roslyn-analyzers/issues/6228
1038
1045
1039
1046
[ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
1040
1047
private static unsafe int ComputeLastIndex < T , TNegator > ( ref T searchSpace , ref T current , Vector256 < byte > result )
@@ -1117,7 +1124,7 @@ internal interface IOptimizations
1117
1124
static abstract bool NeedleContainsZero { get ; }
1118
1125
}
1119
1126
1120
- internal readonly struct Ssse3HandleZeroInNeedle : IOptimizations
1127
+ internal readonly struct Ssse3AndWasmHandleZeroInNeedle : IOptimizations
1121
1128
{
1122
1129
public static bool NeedleContainsZero => true ;
1123
1130
}
0 commit comments