< Summary

Line coverage
83%
Covered lines: 429
Uncovered lines: 86
Coverable lines: 515
Total lines: 1545
Line coverage: 83.3%
Branch coverage
74%
Covered branches: 218
Total branches: 292
Branch coverage: 74.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%11100%
CreateInverse()100%11100%
.ctor(...)100%11100%
SetBitmapBit(...)100%11100%
ComputeAnyByteState(...)100%44100%
ComputeAsciiState(...)100%66100%
CanUseUniqueLowNibbleSearch(...)88.88%181892.85%
ComputeUniqueLowNibbleState(...)100%88100%
TryComputeBitmap(...)0%440%
TryIndexOfAny(...)100%110%
TryIndexOfAnyExcept(...)100%110%
TryLastIndexOfAny(...)100%110%
TryLastIndexOfAnyExcept(...)100%110%
TryIndexOfAny(...)0%880%
TryLastIndexOfAny(...)0%880%
ContainsAny(...)100%11100%
IndexOfAny(...)100%11100%
IndexOfAnyCore(...)78.12%323284.61%
LastIndexOfAny(...)81.25%323284%
ContainsAny(...)100%11100%
IndexOfAny(...)100%11100%
IndexOfAnyCore(...)78.12%323286.53%
LastIndexOfAny(...)81.25%323286%
ContainsAny(...)100%11100%
IndexOfAny(...)100%11100%
IndexOfAnyCore(...)76.47%343487.03%
LastIndexOfAny(...)79.41%343486.53%
IndexOfAnyLookup(...)100%11100%
IndexOfAnyLookupCore(...)66.66%66100%
IndexOfAnyLookup(...)100%11100%
IndexOfAnyLookupCore(...)100%22100%
IndexOfAnyLookup(...)100%11100%
IndexOfAnyLookup(...)100%11100%
ComputeLastIndex(...)100%110%
ComputeLastIndexOverlapped(...)100%22100%
ComputeLastIndex(...)100%22100%
ComputeLastIndexOverlapped(...)100%44100%
NegateIfNeeded(...)100%11100%
NegateIfNeeded(...)100%11100%
NegateIfNeeded(...)100%11100%
IndexOfFirstMatch(...)100%11100%
IndexOfFirstMatch(...)100%11100%
IndexOfLastMatch(...)100%11100%
IndexOfLastMatch(...)100%11100%
NegateIfNeeded(...)100%11100%
NegateIfNeeded(...)100%11100%
NegateIfNeeded(...)100%11100%
IndexOfFirstMatch(...)100%22100%
IndexOfFirstMatch(...)100%11100%
IndexOfLastMatch(...)100%22100%
IndexOfLastMatch(...)100%11100%
PackSources(...)25%4475%
PackSources(...)100%11100%
PackSources(...)16.66%6650%
PackSources(...)100%11100%
ScalarResult(...)100%11100%
FirstIndex(...)100%110%
FirstIndex(...)100%11100%
FirstIndexOverlapped(...)100%11100%
FirstIndexOverlapped(...)100%11100%
ScalarResult(...)100%11100%
FirstIndex(...)100%110%
FirstIndex(...)100%22100%
FirstIndexOverlapped(...)100%22100%
FirstIndexOverlapped(...)100%44100%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/SearchValues/IndexOfAnyAsciiSearcher.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Diagnostics;
 5using System.Diagnostics.CodeAnalysis;
 6using System.Numerics;
 7using System.Runtime.CompilerServices;
 8using System.Runtime.Intrinsics;
 9using System.Runtime.Intrinsics.Arm;
 10using System.Runtime.Intrinsics.Wasm;
 11using System.Runtime.Intrinsics.X86;
 12
 13namespace System.Buffers
 14{
 15    internal static class IndexOfAnyAsciiSearcher
 16    {
 17        // Reused for both ASCII and UniqueLowNibble searches since the state looks the same (a Vector128<byte>).
 18        public struct AsciiState(Vector128<byte> bitmap, BitVector256 lookup)
 19        {
 599320            public Vector256<byte> Bitmap = Vector256.Create(bitmap);
 599321            public readonly BitVector256 Lookup = lookup;
 22
 23            public readonly AsciiState CreateInverse() =>
 139524                new AsciiState(~Bitmap._lower, Lookup.CreateInverse());
 25        }
 26
 27        public readonly struct AnyByteState(Vector128<byte> bitmap0, Vector128<byte> bitmap1, BitVector256 lookup)
 28        {
 282429            public readonly Vector256<byte> Bitmap0 = Vector256.Create(bitmap0);
 282430            public readonly Vector256<byte> Bitmap1 = Vector256.Create(bitmap1);
 282431            public readonly BitVector256 Lookup = lookup;
 32        }
 33
 7592934        internal static bool IsVectorizationSupported => Ssse3.IsSupported || AdvSimd.Arm64.IsSupported || PackedSimd.Is
 35
 36        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 37        private static void SetBitmapBit(ref InlineArray16<byte> bitmap, int value)
 38        {
 60326439            Debug.Assert((uint)value <= 127);
 40
 60326441            int highNibble = value >> 4;
 60326442            int lowNibble = value & 0xF;
 60326443            bitmap[lowNibble] |= (byte)(1 << highNibble);
 60326444        }
 45
 46        internal static void ComputeAnyByteState(ReadOnlySpan<byte> values, out AnyByteState state)
 47        {
 48            // The exact format of these bitmaps differs from the other ComputeBitmap overloads as it's meant for the fu
 49            // See http://0x80.pl/articles/simd-byte-lookup.html#universal-algorithm
 50
 282451            InlineArray16<byte> bitmapSpace0 = default;
 282452            InlineArray16<byte> bitmapSpace1 = default;
 282453            BitVector256 lookupLocal = default;
 54
 105144455            foreach (byte b in values)
 56            {
 52289857                lookupLocal.Set(b);
 58
 52289859                if (b < 128)
 60                {
 25031361                    SetBitmapBit(ref bitmapSpace0, b);
 62                }
 63                else
 64                {
 27258565                    SetBitmapBit(ref bitmapSpace1, b - 128);
 66                }
 67            }
 68
 282469            state = new AnyByteState(Vector128.Create<byte>(bitmapSpace0), Vector128.Create<byte>(bitmapSpace1), lookupL
 282470        }
 71
 72        internal static void ComputeAsciiState<T>(ReadOnlySpan<T> values, out AsciiState state)
 73            where T : struct, IUnsignedNumber<T>
 74        {
 426675            Debug.Assert(typeof(T) == typeof(byte) || typeof(T) == typeof(char));
 76
 426677            InlineArray16<byte> bitmapSpace = default;
 426678            BitVector256 lookupLocal = default;
 79
 25190880            foreach (T tValue in values)
 81            {
 12168882                int value = int.CreateChecked(tValue);
 83
 12168884                if (value > 127)
 85                {
 86                    continue;
 87                }
 88
 8036689                lookupLocal.Set(value);
 8036690                SetBitmapBit(ref bitmapSpace, value);
 91            }
 92
 426693            state = new AsciiState(Vector128.Create<byte>(bitmapSpace), lookupLocal);
 426694        }
 95
 96        public static bool CanUseUniqueLowNibbleSearch<T>(ReadOnlySpan<T> values, int maxInclusive)
 97            where T : struct, IUnsignedNumber<T>
 98        {
 999199            Debug.Assert(typeof(T) == typeof(byte) || typeof(T) == typeof(char));
 100
 9991101            if (!IsVectorizationSupported || values.Length > 16)
 102            {
 2945103                return false;
 104            }
 105
 7046106            if (Ssse3.IsSupported && maxInclusive > 127)
 107            {
 108                // We could support values higher than 127 if we did the "& 0xF" before calling into Shuffle in IndexOfA
 109                // We currently optimize for the common case of ASCII characters instead, saving an instruction there.
 3827110                return false;
 111            }
 112
 3219113            if (typeof(T) == typeof(char) && maxInclusive >= byte.MaxValue)
 114            {
 115                // When packing UTF-16 characters into bytes, values may saturate to 255 (false positives), hence ">=" i
 0116                return false;
 117            }
 118
 119            // We assume there are no duplicates to simplify the logic (if there are any, they just won't use this searc
 3219120            int seenNibbles = 0;
 121
 24335122            foreach (T tValue in values)
 123            {
 10392124                int bit = 1 << (int.CreateChecked(tValue) & 0xF);
 125
 10392126                if ((seenNibbles & bit) != 0)
 127                {
 128                    // We already saw a value with the same low nibble.
 2887129                    return false;
 130                }
 131
 7505132                seenNibbles |= bit;
 133            }
 134
 332135            return true;
 136        }
 137
 138        public static void ComputeUniqueLowNibbleState<T>(ReadOnlySpan<T> values, out AsciiState state)
 139            where T : struct, IUnsignedNumber<T>
 140        {
 332141            Debug.Assert(typeof(T) == typeof(byte) || typeof(T) == typeof(char));
 142
 332143            InlineArray16<byte> valuesByLowNibble = default;
 332144            BitVector256 lookup = default;
 145
 3582146            foreach (T tValue in values)
 147            {
 1459148                byte value = byte.CreateTruncating(tValue);
 1459149                lookup.Set(value);
 1459150                valuesByLowNibble[value & 0xF] = value;
 151            }
 152
 153            // Elements of 'valuesByLowNibble' where no value had that low nibble will be left uninitialized at 0.
 154            // For most, that is okay, as only the zero character in the input could ever match against them,
 155            // but where such input characters will always be mapped to the 0th element of 'valuesByLowNibble'.
 156            //
 157            // That does mean we could still see false positivies if none of the values had a low nibble of zero.
 158            // To avoid that, we can replace the 0th element with any other byte that has a non-zero low nibble.
 159            // The zero character will no longer match, and the new value we pick won't match either as
 160            // it will be mapped to a different element in 'valuesByLowNibble' given its non-zero low nibble.
 332161            if (valuesByLowNibble[0] == 0 && !lookup.Contains(0))
 162            {
 143163                valuesByLowNibble[0] = 1;
 164            }
 165
 332166            state = new AsciiState(Vector128.Create<byte>(valuesByLowNibble), lookup);
 332167        }
 168
 169        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 170        private static unsafe bool TryComputeBitmap(ReadOnlySpan<char> values, byte* bitmap, out bool needleContainsZero
 171        {
 0172            byte* bitmapLocal = bitmap; // https://github.com/dotnet/runtime/issues/9040
 173
 0174            foreach (char c in values)
 175            {
 0176                if (c > 127)
 177                {
 0178                    needleContainsZero = false;
 0179                    return false;
 180                }
 181
 0182                bitmapLocal[c & 0xF] |= (byte)(1 << (c >> 4));
 183            }
 184
 0185            needleContainsZero = (bitmap[0] & 1) != 0;
 0186            return true;
 187        }
 188
 189        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 190        public static bool TryIndexOfAny(ref char searchSpace, int searchSpaceLength, ReadOnlySpan<char> asciiValues, ou
 0191            TryIndexOfAny<DontNegate>(ref Unsafe.As<char, short>(ref searchSpace), searchSpaceLength, asciiValues, out i
 192
 193        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 194        public static bool TryIndexOfAnyExcept(ref char searchSpace, int searchSpaceLength, ReadOnlySpan<char> asciiValu
 0195            TryIndexOfAny<Negate>(ref Unsafe.As<char, short>(ref searchSpace), searchSpaceLength, asciiValues, out index
 196
 197        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 198        public static bool TryLastIndexOfAny(ref char searchSpace, int searchSpaceLength, ReadOnlySpan<char> asciiValues
 0199            TryLastIndexOfAny<DontNegate>(ref Unsafe.As<char, short>(ref searchSpace), searchSpaceLength, asciiValues, o
 200
 201        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 202        public static bool TryLastIndexOfAnyExcept(ref char searchSpace, int searchSpaceLength, ReadOnlySpan<char> ascii
 0203            TryLastIndexOfAny<Negate>(ref Unsafe.As<char, short>(ref searchSpace), searchSpaceLength, asciiValues, out i
 204
 205        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 206        private static unsafe bool TryIndexOfAny<TNegator>(ref short searchSpace, int searchSpaceLength, ReadOnlySpan<ch
 207            where TNegator : struct, INegator
 208        {
 0209            Debug.Assert(searchSpaceLength >= Vector128<short>.Count);
 210
 0211            if (IsVectorizationSupported)
 212            {
 0213                AsciiState state = default;
 214
 0215                if (TryComputeBitmap(asciiValues, (byte*)&state.Bitmap._lower, out bool needleContainsZero))
 216                {
 217                    // Only initializing the bitmap here is okay as we can only get here if the search space is long eno
 218                    // and we support vectorization, so the IndexOfAnyVectorized implementation will never touch state.L
 0219                    state.Bitmap = Vector256.Create(state.Bitmap.GetLower());
 220
 0221                    index = (Ssse3.IsSupported || PackedSimd.IsSupported) && needleContainsZero
 0222                        ? IndexOfAny<TNegator, Ssse3AndWasmHandleZeroInNeedle, SearchValues.FalseConst>(ref searchSpace,
 0223                        : IndexOfAny<TNegator, Default, SearchValues.FalseConst>(ref searchSpace, searchSpaceLength, ref
 0224                    return true;
 225                }
 226            }
 227
 0228            index = default;
 0229            return false;
 230        }
 231
 232        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 233        private static unsafe bool TryLastIndexOfAny<TNegator>(ref short searchSpace, int searchSpaceLength, ReadOnlySpa
 234            where TNegator : struct, INegator
 235        {
 0236            Debug.Assert(searchSpaceLength >= Vector128<short>.Count);
 237
 0238            if (IsVectorizationSupported)
 239            {
 0240                AsciiState state = default;
 241
 0242                if (TryComputeBitmap(asciiValues, (byte*)&state.Bitmap._lower, out bool needleContainsZero))
 243                {
 244                    // Only initializing the bitmap here is okay as we can only get here if the search space is long eno
 245                    // and we support vectorization, so the LastIndexOfAnyVectorized implementation will never touch sta
 0246                    state.Bitmap = Vector256.Create(state.Bitmap.GetLower());
 247
 0248                    index = (Ssse3.IsSupported || PackedSimd.IsSupported) && needleContainsZero
 0249                        ? LastIndexOfAny<TNegator, Ssse3AndWasmHandleZeroInNeedle, SearchValues.FalseConst>(ref searchSp
 0250                        : LastIndexOfAny<TNegator, Default, SearchValues.FalseConst>(ref searchSpace, searchSpaceLength,
 0251                    return true;
 252                }
 253            }
 254
 0255            index = default;
 0256            return false;
 257        }
 258
 259        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 260        [CompExactlyDependsOn(typeof(Ssse3))]
 261        [CompExactlyDependsOn(typeof(AdvSimd))]
 262        [CompExactlyDependsOn(typeof(PackedSimd))]
 263        public static bool ContainsAny<TNegator, TOptimizations, TUniqueLowNibble>(ref short searchSpace, int searchSpac
 264            where TNegator : struct, INegator
 265            where TOptimizations : struct, IOptimizations
 266            where TUniqueLowNibble : struct, SearchValues.IRuntimeConst =>
 1408267            IndexOfAnyCore<bool, TNegator, TOptimizations, TUniqueLowNibble, ContainsAnyResultMapper<short>>(ref searchS
 268
 269        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 270        [CompExactlyDependsOn(typeof(Ssse3))]
 271        [CompExactlyDependsOn(typeof(AdvSimd))]
 272        [CompExactlyDependsOn(typeof(PackedSimd))]
 273        public static int IndexOfAny<TNegator, TOptimizations, TUniqueLowNibble>(ref short searchSpace, int searchSpaceL
 274            where TNegator : struct, INegator
 275            where TOptimizations : struct, IOptimizations
 276            where TUniqueLowNibble : struct, SearchValues.IRuntimeConst =>
 6675277            IndexOfAnyCore<int, TNegator, TOptimizations, TUniqueLowNibble, IndexOfAnyResultMapper<short>>(ref searchSpa
 278
 279        [CompExactlyDependsOn(typeof(Ssse3))]
 280        [CompExactlyDependsOn(typeof(AdvSimd))]
 281        [CompExactlyDependsOn(typeof(PackedSimd))]
 282        private static TResult IndexOfAnyCore<TResult, TNegator, TOptimizations, TUniqueLowNibble, TResultMapper>(ref sh
 283            where TResult : struct
 284            where TNegator : struct, INegator
 285            where TOptimizations : struct, IOptimizations
 286            where TUniqueLowNibble : struct, SearchValues.IRuntimeConst
 287            where TResultMapper : struct, IResultMapper<short, TResult>
 288        {
 8083289            ref short currentSearchSpace = ref searchSpace;
 290
 8083291            if (searchSpaceLength < Vector128<ushort>.Count)
 292            {
 2120293                ref short searchSpaceEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength);
 294
 4961295                while (!Unsafe.AreSame(ref currentSearchSpace, ref searchSpaceEnd))
 296                {
 3685297                    char c = (char)currentSearchSpace;
 3685298                    if (TNegator.NegateIfNeeded(state.Lookup.Contains256(c)))
 299                    {
 844300                        return TResultMapper.ScalarResult(ref searchSpace, ref currentSearchSpace);
 301                    }
 302
 2841303                    currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, 1);
 304                }
 305
 1276306                return TResultMapper.NotFound;
 307            }
 308
 309#pragma warning disable IntrinsicsInSystemPrivateCoreLibAttributeNotSpecificEnough // The behavior of the rest of the fu
 5963310            if (Avx2.IsSupported && searchSpaceLength > 2 * Vector128<short>.Count)
 311#pragma warning restore IntrinsicsInSystemPrivateCoreLibAttributeNotSpecificEnough
 312            {
 4003313                Vector256<byte> bitmap256 = state.Bitmap;
 314
 4003315                if (searchSpaceLength > 2 * Vector256<short>.Count)
 316                {
 317                    // Process the input in chunks of 32 characters (2 * Vector256<short>).
 318                    // We're mainly interested in a single byte of each character, and the core lookup operates on a Vec
 319                    // As packing two Vector256<short>s into a Vector256<byte> is cheap compared to the lookup, we can e
 320                    // If the input length is a multiple of 32, don't consume the last 32 characters in this loop.
 321                    // Let the fallback below handle it instead. This is why the condition is
 322                    // ">" instead of ">=" above, and why "IsAddressLessThan" is used instead of "IsAddressLessThanOrEqu
 2839323                    ref short twoVectorsAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - (2 * Vector256
 324
 325                    do
 326                    {
 12404327                        Vector256<short> source0 = Vector256.LoadUnsafe(ref currentSearchSpace);
 12404328                        Vector256<short> source1 = Vector256.LoadUnsafe(ref currentSearchSpace, (nuint)Vector256<short>.
 329
 12404330                        Vector256<byte> result = IndexOfAnyLookup<TNegator, TOptimizations, TUniqueLowNibble>(source0, s
 12404331                        if (result != Vector256<byte>.Zero)
 332                        {
 1638333                            return TResultMapper.FirstIndex<TNegator>(ref searchSpace, ref currentSearchSpace, result);
 334                        }
 335
 10766336                        currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, 2 * Vector256<short>.Count);
 337                    }
 10766338                    while (Unsafe.IsAddressLessThan(ref currentSearchSpace, ref twoVectorsAwayFromEnd));
 339                }
 340
 341                // We have 1-32 characters remaining. Process the first and last vector in the search space.
 342                // They may overlap, but we'll handle that in the index calculation if we do get a match.
 2365343                Debug.Assert(searchSpaceLength >= Vector256<short>.Count, "We expect that the input is long enough for u
 344                {
 2365345                    ref short oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - Vector256<short
 346
 2365347                    ref short firstVector = ref Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFro
 2365348                        ? ref oneVectorAwayFromEnd
 2365349                        : ref currentSearchSpace;
 350
 2365351                    Vector256<short> source0 = Vector256.LoadUnsafe(ref firstVector);
 2365352                    Vector256<short> source1 = Vector256.LoadUnsafe(ref oneVectorAwayFromEnd);
 353
 2365354                    Vector256<byte> result = IndexOfAnyLookup<TNegator, TOptimizations, TUniqueLowNibble>(source0, sourc
 2365355                    if (result != Vector256<byte>.Zero)
 356                    {
 1094357                        return TResultMapper.FirstIndexOverlapped<TNegator>(ref searchSpace, ref firstVector, ref oneVec
 358                    }
 359                }
 360
 1271361                return TResultMapper.NotFound;
 362            }
 363
 1960364            Vector128<byte> bitmap = state.Bitmap._lower;
 365
 366#pragma warning disable IntrinsicsInSystemPrivateCoreLibAttributeNotSpecificEnough // The behavior of the rest of the fu
 1960367            if (!Avx2.IsSupported && searchSpaceLength > 2 * Vector128<short>.Count)
 368#pragma warning restore IntrinsicsInSystemPrivateCoreLibAttributeNotSpecificEnough
 369            {
 370                // Process the input in chunks of 16 characters (2 * Vector128<short>).
 371                // We're mainly interested in a single byte of each character, and the core lookup operates on a Vector1
 372                // As packing two Vector128<short>s into a Vector128<byte> is cheap compared to the lookup, we can effec
 373                // If the input length is a multiple of 16, don't consume the last 16 characters in this loop.
 374                // Let the fallback below handle it instead. This is why the condition is
 375                // ">" instead of ">=" above, and why "IsAddressLessThan" is used instead of "IsAddressLessThanOrEqualTo
 0376                ref short twoVectorsAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - (2 * Vector128<sho
 377
 378                do
 379                {
 0380                    Vector128<short> source0 = Vector128.LoadUnsafe(ref currentSearchSpace);
 0381                    Vector128<short> source1 = Vector128.LoadUnsafe(ref currentSearchSpace, (nuint)Vector128<short>.Coun
 382
 0383                    Vector128<byte> result = IndexOfAnyLookup<TNegator, TOptimizations, TUniqueLowNibble>(source0, sourc
 0384                    if (result != Vector128<byte>.Zero)
 385                    {
 0386                        return TResultMapper.FirstIndex<TNegator>(ref searchSpace, ref currentSearchSpace, result);
 387                    }
 388
 0389                    currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, 2 * Vector128<short>.Count);
 390                }
 0391                while (Unsafe.IsAddressLessThan(ref currentSearchSpace, ref twoVectorsAwayFromEnd));
 392            }
 393
 394            // We have 1-16 characters remaining. Process the first and last vector in the search space.
 395            // They may overlap, but we'll handle that in the index calculation if we do get a match.
 1960396            Debug.Assert(searchSpaceLength >= Vector128<short>.Count, "We expect that the input is long enough for us to
 397            {
 1960398                ref short oneVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - Vector128<short>.Co
 399
 1960400                ref short firstVector = ref Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAwayFromEnd
 1960401                    ? ref oneVectorAwayFromEnd
 1960402                    : ref currentSearchSpace;
 403
 1960404                Vector128<short> source0 = Vector128.LoadUnsafe(ref firstVector);
 1960405                Vector128<short> source1 = Vector128.LoadUnsafe(ref oneVectorAwayFromEnd);
 406
 1960407                Vector128<byte> result = IndexOfAnyLookup<TNegator, TOptimizations, TUniqueLowNibble>(source0, source1, 
 1960408                if (result != Vector128<byte>.Zero)
 409                {
 1586410                    return TResultMapper.FirstIndexOverlapped<TNegator>(ref searchSpace, ref firstVector, ref oneVectorA
 411                }
 412            }
 413
 374414            return TResultMapper.NotFound;
 415        }
 416
 417        [CompExactlyDependsOn(typeof(Ssse3))]
 418        [CompExactlyDependsOn(typeof(AdvSimd))]
 419        [CompExactlyDependsOn(typeof(PackedSimd))]
 420        public static int LastIndexOfAny<TNegator, TOptimizations, TUniqueLowNibble>(ref short searchSpace, int searchSp
 421            where TNegator : struct, INegator
 422            where TOptimizations : struct, IOptimizations
 423            where TUniqueLowNibble : struct, SearchValues.IRuntimeConst
 424        {
 5052425            if (searchSpaceLength < Vector128<ushort>.Count)
 426            {
 5184427                for (int i = searchSpaceLength - 1; i >= 0; i--)
 428                {
 1892429                    char c = (char)Unsafe.Add(ref searchSpace, i);
 1892430                    if (TNegator.NegateIfNeeded(state.Lookup.Contains256(c)))
 431                    {
 656432                        return i;
 433                    }
 434                }
 435
 700436                return -1;
 437            }
 438
 3696439            ref short currentSearchSpace = ref Unsafe.Add(ref searchSpace, searchSpaceLength);
 440
 441#pragma warning disable IntrinsicsInSystemPrivateCoreLibAttributeNotSpecificEnough // The else clause is semantically eq
 3696442            if (Avx2.IsSupported && searchSpaceLength > 2 * Vector128<short>.Count)
 443#pragma warning disable IntrinsicsInSystemPrivateCoreLibAttributeNotSpecificEnough
 444            {
 2880445                Vector256<byte> bitmap256 = state.Bitmap;
 446
 2880447                if (searchSpaceLength > 2 * Vector256<short>.Count)
 448                {
 449                    // Process the input in chunks of 32 characters (2 * Vector256<short>).
 450                    // We're mainly interested in a single byte of each character, and the core lookup operates on a Vec
 451                    // As packing two Vector256<short>s into a Vector256<byte> is cheap compared to the lookup, we can e
 452                    // If the input length is a multiple of 32, don't consume the last 32 characters in this loop.
 453                    // Let the fallback below handle it instead. This is why the condition is
 454                    // ">" instead of ">=" above, and why "IsAddressGreaterThan" is used instead of "IsAddressGreaterTha
 2012455                    ref short twoVectorsAfterStart = ref Unsafe.Add(ref searchSpace, 2 * Vector256<short>.Count);
 456
 457                    do
 458                    {
 9066459                        currentSearchSpace = ref Unsafe.Subtract(ref currentSearchSpace, 2 * Vector256<short>.Count);
 460
 9066461                        Vector256<short> source0 = Vector256.LoadUnsafe(ref currentSearchSpace);
 9066462                        Vector256<short> source1 = Vector256.LoadUnsafe(ref currentSearchSpace, (nuint)Vector256<short>.
 463
 9066464                        Vector256<byte> result = IndexOfAnyLookup<TNegator, TOptimizations, TUniqueLowNibble>(source0, s
 9066465                        if (result != Vector256<byte>.Zero)
 466                        {
 1370467                            return ComputeLastIndex<short, TNegator>(ref searchSpace, ref currentSearchSpace, result);
 468                        }
 469                    }
 7696470                    while (Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref twoVectorsAfterStart));
 471                }
 472
 473                // We have 1-32 characters remaining. Process the first and last vector in the search space.
 474                // They may overlap, but we'll handle that in the index calculation if we do get a match.
 1510475                Debug.Assert(searchSpaceLength >= Vector256<short>.Count, "We expect that the input is long enough for u
 476                {
 1510477                    ref short oneVectorAfterStart = ref Unsafe.Add(ref searchSpace, Vector256<short>.Count);
 478
 1510479                    ref short secondVector = ref Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAfterS
 1510480                        ? ref Unsafe.Subtract(ref currentSearchSpace, Vector256<short>.Count)
 1510481                        : ref searchSpace;
 482
 1510483                    Vector256<short> source0 = Vector256.LoadUnsafe(ref searchSpace);
 1510484                    Vector256<short> source1 = Vector256.LoadUnsafe(ref secondVector);
 485
 1510486                    Vector256<byte> result = IndexOfAnyLookup<TNegator, TOptimizations, TUniqueLowNibble>(source0, sourc
 1510487                    if (result != Vector256<byte>.Zero)
 488                    {
 778489                        return ComputeLastIndexOverlapped<short, TNegator>(ref searchSpace, ref secondVector, result);
 490                    }
 491                }
 492
 732493                return -1;
 494            }
 495
 816496            Vector128<byte> bitmap = state.Bitmap._lower;
 497
 816498            if (!Avx2.IsSupported && searchSpaceLength > 2 * Vector128<short>.Count)
 499            {
 500                // Process the input in chunks of 16 characters (2 * Vector128<short>).
 501                // We're mainly interested in a single byte of each character, and the core lookup operates on a Vector1
 502                // As packing two Vector128<short>s into a Vector128<byte> is cheap compared to the lookup, we can effec
 503                // If the input length is a multiple of 16, don't consume the last 16 characters in this loop.
 504                // Let the fallback below handle it instead. This is why the condition is
 505                // ">" instead of ">=" above, and why "IsAddressGreaterThan" is used instead of "IsAddressGreaterThanOrE
 0506                ref short twoVectorsAfterStart = ref Unsafe.Add(ref searchSpace, 2 * Vector128<short>.Count);
 507
 508                do
 509                {
 0510                    currentSearchSpace = ref Unsafe.Subtract(ref currentSearchSpace, 2 * Vector128<short>.Count);
 511
 0512                    Vector128<short> source0 = Vector128.LoadUnsafe(ref currentSearchSpace);
 0513                    Vector128<short> source1 = Vector128.LoadUnsafe(ref currentSearchSpace, (nuint)Vector128<short>.Coun
 514
 0515                    Vector128<byte> result = IndexOfAnyLookup<TNegator, TOptimizations, TUniqueLowNibble>(source0, sourc
 0516                    if (result != Vector128<byte>.Zero)
 517                    {
 0518                        return ComputeLastIndex<short, TNegator>(ref searchSpace, ref currentSearchSpace, result);
 519                    }
 520                }
 0521                while (Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref twoVectorsAfterStart));
 522            }
 523
 524            // We have 1-16 characters remaining. Process the first and last vector in the search space.
 525            // They may overlap, but we'll handle that in the index calculation if we do get a match.
 816526            Debug.Assert(searchSpaceLength >= Vector128<short>.Count, "We expect that the input is long enough for us to
 527            {
 816528                ref short oneVectorAfterStart = ref Unsafe.Add(ref searchSpace, Vector128<short>.Count);
 529
 816530                ref short secondVector = ref Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref oneVectorAfterStart
 816531                    ? ref Unsafe.Subtract(ref currentSearchSpace, Vector128<short>.Count)
 816532                    : ref searchSpace;
 533
 816534                Vector128<short> source0 = Vector128.LoadUnsafe(ref searchSpace);
 816535                Vector128<short> source1 = Vector128.LoadUnsafe(ref secondVector);
 536
 816537                Vector128<byte> result = IndexOfAnyLookup<TNegator, TOptimizations, TUniqueLowNibble>(source0, source1, 
 816538                if (result != Vector128<byte>.Zero)
 539                {
 682540                    return ComputeLastIndexOverlapped<short, TNegator>(ref searchSpace, ref secondVector, result);
 541                }
 542            }
 543
 134544            return -1;
 545        }
 546
 547        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 548        [CompExactlyDependsOn(typeof(Ssse3))]
 549        [CompExactlyDependsOn(typeof(AdvSimd))]
 550        [CompExactlyDependsOn(typeof(PackedSimd))]
 551        public static bool ContainsAny<TNegator, TUniqueLowNibble>(ref byte searchSpace, int searchSpaceLength, ref Asci
 552            where TNegator : struct, INegator
 553            where TUniqueLowNibble : struct, SearchValues.IRuntimeConst =>
 4276554            IndexOfAnyCore<bool, TNegator, TUniqueLowNibble, ContainsAnyResultMapper<byte>>(ref searchSpace, searchSpace
 555
 556        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 557        [CompExactlyDependsOn(typeof(Ssse3))]
 558        [CompExactlyDependsOn(typeof(AdvSimd))]
 559        [CompExactlyDependsOn(typeof(PackedSimd))]
 560        public static int IndexOfAny<TNegator, TUniqueLowNibble>(ref byte searchSpace, int searchSpaceLength, ref AsciiS
 561            where TNegator : struct, INegator
 562            where TUniqueLowNibble : struct, SearchValues.IRuntimeConst =>
 12828563            IndexOfAnyCore<int, TNegator, TUniqueLowNibble, IndexOfAnyResultMapper<byte>>(ref searchSpace, searchSpaceLe
 564
 565        [CompExactlyDependsOn(typeof(Ssse3))]
 566        [CompExactlyDependsOn(typeof(AdvSimd))]
 567        [CompExactlyDependsOn(typeof(PackedSimd))]
 568        private static TResult IndexOfAnyCore<TResult, TNegator, TUniqueLowNibble, TResultMapper>(ref byte searchSpace, 
 569            where TResult : struct
 570            where TNegator : struct, INegator
 571            where TUniqueLowNibble : struct, SearchValues.IRuntimeConst
 572            where TResultMapper : struct, IResultMapper<byte, TResult>
 573        {
 17104574            ref byte currentSearchSpace = ref searchSpace;
 575
 17104576            if (searchSpaceLength < sizeof(ulong))
 577            {
 4420578                ref byte searchSpaceEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength);
 579
 10017580                while (!Unsafe.AreSame(ref currentSearchSpace, ref searchSpaceEnd))
 581                {
 7600582                    byte b = currentSearchSpace;
 7600583                    if (TNegator.NegateIfNeeded(state.Lookup.Contains(b)))
 584                    {
 2003585                        return TResultMapper.ScalarResult(ref searchSpace, ref currentSearchSpace);
 586                    }
 587
 5597588                    currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, 1);
 589                }
 590
 2417591                return TResultMapper.NotFound;
 592            }
 593
 594#pragma warning disable IntrinsicsInSystemPrivateCoreLibAttributeNotSpecificEnough // The behavior of the rest of the fu
 12684595            if (Avx2.IsSupported && searchSpaceLength > Vector128<byte>.Count)
 596#pragma warning disable IntrinsicsInSystemPrivateCoreLibAttributeNotSpecificEnough
 597            {
 8572598                Vector256<byte> bitmap256 = state.Bitmap;
 599
 8572600                if (searchSpaceLength > Vector256<byte>.Count)
 601                {
 602                    // Process the input in chunks of 32 bytes.
 603                    // If the input length is a multiple of 32, don't consume the last 32 characters in this loop.
 604                    // Let the fallback below handle it instead. This is why the condition is
 605                    // ">" instead of ">=" above, and why "IsAddressLessThan" is used instead of "IsAddressLessThanOrEqu
 6888606                    ref byte vectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - Vector256<byte>.Cou
 607
 608                    do
 609                    {
 31592610                        Vector256<byte> source = Vector256.LoadUnsafe(ref currentSearchSpace);
 611
 31592612                        Vector256<byte> result = TNegator.NegateIfNeeded(IndexOfAnyLookupCore<TUniqueLowNibble>(source, 
 31592613                        if (result != Vector256<byte>.Zero)
 614                        {
 5352615                            return TResultMapper.FirstIndex<TNegator>(ref searchSpace, ref currentSearchSpace, result);
 616                        }
 617
 26240618                        currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector256<byte>.Count);
 619                    }
 26240620                    while (Unsafe.IsAddressLessThan(ref currentSearchSpace, ref vectorAwayFromEnd));
 621                }
 622
 623                // We have 1-32 bytes remaining. Process the first and last half vectors in the search space.
 624                // They may overlap, but we'll handle that in the index calculation if we do get a match.
 3220625                Debug.Assert(searchSpaceLength >= Vector128<byte>.Count, "We expect that the input is long enough for us
 626                {
 3220627                    ref byte halfVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - Vector128<byte>
 628
 3220629                    ref byte firstVector = ref Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref halfVectorAwayFro
 3220630                        ? ref halfVectorAwayFromEnd
 3220631                        : ref currentSearchSpace;
 632
 3220633                    Vector128<byte> source0 = Vector128.LoadUnsafe(ref firstVector);
 3220634                    Vector128<byte> source1 = Vector128.LoadUnsafe(ref halfVectorAwayFromEnd);
 3220635                    Vector256<byte> source = Vector256.Create(source0, source1);
 636
 3220637                    Vector256<byte> result = TNegator.NegateIfNeeded(IndexOfAnyLookupCore<TUniqueLowNibble>(source, bitm
 3220638                    if (result != Vector256<byte>.Zero)
 639                    {
 1607640                        return TResultMapper.FirstIndexOverlapped<TNegator>(ref searchSpace, ref firstVector, ref halfVe
 641                    }
 642                }
 643
 1613644                return TResultMapper.NotFound;
 645            }
 646
 4112647            Vector128<byte> bitmap = state.Bitmap._lower;
 648
 4112649            if (!Avx2.IsSupported && searchSpaceLength > Vector128<byte>.Count)
 650            {
 651                // Process the input in chunks of 16 bytes.
 652                // If the input length is a multiple of 16, don't consume the last 16 characters in this loop.
 653                // Let the fallback below handle it instead. This is why the condition is
 654                // ">" instead of ">=" above, and why "IsAddressLessThan" is used instead of "IsAddressLessThanOrEqualTo
 0655                ref byte vectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - Vector128<byte>.Count);
 656
 657                do
 658                {
 0659                    Vector128<byte> source = Vector128.LoadUnsafe(ref currentSearchSpace);
 660
 0661                    Vector128<byte> result = TNegator.NegateIfNeeded(IndexOfAnyLookupCore<TUniqueLowNibble>(source, bitm
 0662                    if (result != Vector128<byte>.Zero)
 663                    {
 0664                        return TResultMapper.FirstIndex<TNegator>(ref searchSpace, ref currentSearchSpace, result);
 665                    }
 666
 0667                    currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector128<byte>.Count);
 668                }
 0669                while (Unsafe.IsAddressLessThan(ref currentSearchSpace, ref vectorAwayFromEnd));
 670            }
 671
 672            // We have 1-16 bytes remaining. Process the first and last half vectors in the search space.
 673            // They may overlap, but we'll handle that in the index calculation if we do get a match.
 4112674            Debug.Assert(searchSpaceLength >= sizeof(ulong), "We expect that the input is long enough for us to load a u
 675            {
 4112676                ref byte halfVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - sizeof(ulong));
 677
 4112678                ref byte firstVector = ref Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref halfVectorAwayFromEnd
 4112679                    ? ref halfVectorAwayFromEnd
 4112680                    : ref currentSearchSpace;
 681
 4112682                ulong source0 = Unsafe.ReadUnaligned<ulong>(ref firstVector);
 4112683                ulong source1 = Unsafe.ReadUnaligned<ulong>(ref halfVectorAwayFromEnd);
 4112684                Vector128<byte> source = Vector128.Create(source0, source1).AsByte();
 685
 4112686                Vector128<byte> result = TNegator.NegateIfNeeded(IndexOfAnyLookupCore<TUniqueLowNibble>(source, bitmap))
 4112687                if (result != Vector128<byte>.Zero)
 688                {
 2710689                    return TResultMapper.FirstIndexOverlapped<TNegator>(ref searchSpace, ref firstVector, ref halfVector
 690                }
 691            }
 692
 1402693            return TResultMapper.NotFound;
 694        }
 695
 696        [CompExactlyDependsOn(typeof(Ssse3))]
 697        [CompExactlyDependsOn(typeof(AdvSimd))]
 698        [CompExactlyDependsOn(typeof(PackedSimd))]
 699        public static int LastIndexOfAny<TNegator, TUniqueLowNibble>(ref byte searchSpace, int searchSpaceLength, ref As
 700            where TNegator : struct, INegator
 701            where TUniqueLowNibble : struct, SearchValues.IRuntimeConst
 702        {
 8552703            if (searchSpaceLength < sizeof(ulong))
 704            {
 7276705                for (int i = searchSpaceLength - 1; i >= 0; i--)
 706                {
 2436707                    byte b = Unsafe.Add(ref searchSpace, i);
 2436708                    if (TNegator.NegateIfNeeded(state.Lookup.Contains(b)))
 709                    {
 926710                        return i;
 711                    }
 712                }
 713
 1202714                return -1;
 715            }
 716
 6424717            ref byte currentSearchSpace = ref Unsafe.Add(ref searchSpace, searchSpaceLength);
 718
 719#pragma warning disable IntrinsicsInSystemPrivateCoreLibAttributeNotSpecificEnough // The behavior of the rest of the fu
 6424720            if (Avx2.IsSupported && searchSpaceLength > Vector128<byte>.Count)
 721#pragma warning disable IntrinsicsInSystemPrivateCoreLibAttributeNotSpecificEnough
 722            {
 5308723                Vector256<byte> bitmap256 = state.Bitmap;
 724
 5308725                if (searchSpaceLength > Vector256<byte>.Count)
 726                {
 727                    // Process the input in chunks of 32 bytes.
 728                    // If the input length is a multiple of 32, don't consume the last 32 characters in this loop.
 729                    // Let the fallback below handle it instead. This is why the condition is
 730                    // ">" instead of ">=" above, and why "IsAddressGreaterThan" is used instead of "IsAddressGreaterTha
 4428731                    ref byte vectorAfterStart = ref Unsafe.Add(ref searchSpace, Vector256<byte>.Count);
 732
 733                    do
 734                    {
 21636735                        currentSearchSpace = ref Unsafe.Subtract(ref currentSearchSpace, Vector256<byte>.Count);
 736
 21636737                        Vector256<byte> source = Vector256.LoadUnsafe(ref currentSearchSpace);
 738
 21636739                        Vector256<byte> result = TNegator.NegateIfNeeded(IndexOfAnyLookupCore<TUniqueLowNibble>(source, 
 21636740                        if (result != Vector256<byte>.Zero)
 741                        {
 3452742                            return ComputeLastIndex<byte, TNegator>(ref searchSpace, ref currentSearchSpace, result);
 743                        }
 744                    }
 18184745                    while (Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref vectorAfterStart));
 746                }
 747
 748                // We have 1-32 bytes remaining. Process the first and last half vectors in the search space.
 749                // They may overlap, but we'll handle that in the index calculation if we do get a match.
 1856750                Debug.Assert(searchSpaceLength >= Vector128<byte>.Count, "We expect that the input is long enough for us
 751                {
 1856752                    ref byte halfVectorAfterStart = ref Unsafe.Add(ref searchSpace, Vector128<byte>.Count);
 753
 1856754                    ref byte secondVector = ref Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref halfVectorAfterS
 1856755                        ? ref Unsafe.Subtract(ref currentSearchSpace, Vector128<byte>.Count)
 1856756                        : ref searchSpace;
 757
 1856758                    Vector128<byte> source0 = Vector128.LoadUnsafe(ref searchSpace);
 1856759                    Vector128<byte> source1 = Vector128.LoadUnsafe(ref secondVector);
 1856760                    Vector256<byte> source = Vector256.Create(source0, source1);
 761
 1856762                    Vector256<byte> result = TNegator.NegateIfNeeded(IndexOfAnyLookupCore<TUniqueLowNibble>(source, bitm
 1856763                    if (result != Vector256<byte>.Zero)
 764                    {
 984765                        return ComputeLastIndexOverlapped<byte, TNegator>(ref searchSpace, ref secondVector, result);
 766                    }
 767                }
 768
 872769                return -1;
 770            }
 771
 1116772            Vector128<byte> bitmap = state.Bitmap._lower;
 773
 1116774            if (!Avx2.IsSupported && searchSpaceLength > Vector128<byte>.Count)
 775            {
 776                // Process the input in chunks of 16 bytes.
 777                // If the input length is a multiple of 16, don't consume the last 16 characters in this loop.
 778                // Let the fallback below handle it instead. This is why the condition is
 779                // ">" instead of ">=" above, and why "IsAddressGreaterThan" is used instead of "IsAddressGreaterThanOrE
 0780                ref byte vectorAfterStart = ref Unsafe.Add(ref searchSpace, Vector128<byte>.Count);
 781
 782                do
 783                {
 0784                    currentSearchSpace = ref Unsafe.Subtract(ref currentSearchSpace, Vector128<byte>.Count);
 785
 0786                    Vector128<byte> source = Vector128.LoadUnsafe(ref currentSearchSpace);
 787
 0788                    Vector128<byte> result = TNegator.NegateIfNeeded(IndexOfAnyLookupCore<TUniqueLowNibble>(source, bitm
 0789                    if (result != Vector128<byte>.Zero)
 790                    {
 0791                        return ComputeLastIndex<byte, TNegator>(ref searchSpace, ref currentSearchSpace, result);
 792                    }
 793                }
 0794                while (Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref vectorAfterStart));
 795            }
 796
 797            // We have 1-16 bytes remaining. Process the first and last half vectors in the search space.
 798            // They may overlap, but we'll handle that in the index calculation if we do get a match.
 1116799            Debug.Assert(searchSpaceLength >= sizeof(ulong), "We expect that the input is long enough for us to load a u
 800            {
 1116801                ref byte halfVectorAfterStart = ref Unsafe.Add(ref searchSpace, sizeof(ulong));
 802
 1116803                ref byte secondVector = ref Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref halfVectorAfterStart
 1116804                    ? ref Unsafe.Subtract(ref currentSearchSpace, sizeof(ulong))
 1116805                    : ref searchSpace;
 806
 1116807                ulong source0 = Unsafe.ReadUnaligned<ulong>(ref searchSpace);
 1116808                ulong source1 = Unsafe.ReadUnaligned<ulong>(ref secondVector);
 1116809                Vector128<byte> source = Vector128.Create(source0, source1).AsByte();
 810
 1116811                Vector128<byte> result = TNegator.NegateIfNeeded(IndexOfAnyLookupCore<TUniqueLowNibble>(source, bitmap))
 1116812                if (result != Vector128<byte>.Zero)
 813                {
 994814                    return ComputeLastIndexOverlapped<byte, TNegator>(ref searchSpace, ref secondVector, result);
 815                }
 816            }
 817
 122818            return -1;
 819        }
 820
 821        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 822        [CompExactlyDependsOn(typeof(Ssse3))]
 823        [CompExactlyDependsOn(typeof(AdvSimd))]
 824        [CompExactlyDependsOn(typeof(PackedSimd))]
 825        public static bool ContainsAny<TNegator>(ref byte searchSpace, int searchSpaceLength, ref AnyByteState state)
 826            where TNegator : struct, INegator =>
 5648827            IndexOfAnyCore<bool, TNegator, ContainsAnyResultMapper<byte>>(ref searchSpace, searchSpaceLength, ref state)
 828
 829        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 830        [CompExactlyDependsOn(typeof(Ssse3))]
 831        [CompExactlyDependsOn(typeof(AdvSimd))]
 832        [CompExactlyDependsOn(typeof(PackedSimd))]
 833        public static int IndexOfAny<TNegator>(ref byte searchSpace, int searchSpaceLength, ref AnyByteState state)
 834            where TNegator : struct, INegator =>
 16944835            IndexOfAnyCore<int, TNegator, IndexOfAnyResultMapper<byte>>(ref searchSpace, searchSpaceLength, ref state);
 836
 837        [CompExactlyDependsOn(typeof(Ssse3))]
 838        [CompExactlyDependsOn(typeof(AdvSimd))]
 839        [CompExactlyDependsOn(typeof(PackedSimd))]
 840        private static TResult IndexOfAnyCore<TResult, TNegator, TResultMapper>(ref byte searchSpace, int searchSpaceLen
 841            where TResult : struct
 842            where TNegator : struct, INegator
 843            where TResultMapper : struct, IResultMapper<byte, TResult>
 844        {
 22592845            ref byte currentSearchSpace = ref searchSpace;
 846
 22592847            if (!IsVectorizationSupported || searchSpaceLength < sizeof(ulong))
 848            {
 4682849                ref byte searchSpaceEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength);
 850
 9491851                while (!Unsafe.AreSame(ref currentSearchSpace, ref searchSpaceEnd))
 852                {
 6289853                    byte b = currentSearchSpace;
 6289854                    if (TNegator.NegateIfNeeded(state.Lookup.Contains(b)))
 855                    {
 1480856                        return TResultMapper.ScalarResult(ref searchSpace, ref currentSearchSpace);
 857                    }
 858
 4809859                    currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, 1);
 860                }
 861
 3202862                return TResultMapper.NotFound;
 863            }
 864
 865#pragma warning disable IntrinsicsInSystemPrivateCoreLibAttributeNotSpecificEnough // The behavior of the rest of the fu
 17910866            if (Avx2.IsSupported && searchSpaceLength > Vector128<byte>.Count)
 867#pragma warning restore IntrinsicsInSystemPrivateCoreLibAttributeNotSpecificEnough
 868            {
 14724869                Vector256<byte> bitmap256_0 = state.Bitmap0;
 14724870                Vector256<byte> bitmap256_1 = state.Bitmap1;
 871
 14724872                if (searchSpaceLength > Vector256<byte>.Count)
 873                {
 874                    // Process the input in chunks of 32 bytes.
 875                    // If the input length is a multiple of 32, don't consume the last 32 characters in this loop.
 876                    // Let the fallback below handle it instead. This is why the condition is
 877                    // ">" instead of ">=" above, and why "IsAddressLessThan" is used instead of "IsAddressLessThanOrEqu
 12896878                    ref byte vectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - Vector256<byte>.Cou
 879
 880                    do
 881                    {
 60545882                        Vector256<byte> source = Vector256.LoadUnsafe(ref currentSearchSpace);
 883
 60545884                        Vector256<byte> result = IndexOfAnyLookup<TNegator>(source, bitmap256_0, bitmap256_1);
 60545885                        if (result != Vector256<byte>.Zero)
 886                        {
 10600887                            return TResultMapper.FirstIndex<TNegator>(ref searchSpace, ref currentSearchSpace, result);
 888                        }
 889
 49945890                        currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector256<byte>.Count);
 891                    }
 49945892                    while (Unsafe.IsAddressLessThan(ref currentSearchSpace, ref vectorAwayFromEnd));
 893                }
 894
 895                // We have 1-32 bytes remaining. Process the first and last half vectors in the search space.
 896                // They may overlap, but we'll handle that in the index calculation if we do get a match.
 4124897                Debug.Assert(searchSpaceLength >= Vector128<byte>.Count, "We expect that the input is long enough for us
 898                {
 4124899                    ref byte halfVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - Vector128<byte>
 900
 4124901                    ref byte firstVector = ref Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref halfVectorAwayFro
 4124902                        ? ref halfVectorAwayFromEnd
 4124903                        : ref currentSearchSpace;
 904
 4124905                    Vector128<byte> source0 = Vector128.LoadUnsafe(ref firstVector);
 4124906                    Vector128<byte> source1 = Vector128.LoadUnsafe(ref halfVectorAwayFromEnd);
 4124907                    Vector256<byte> source = Vector256.Create(source0, source1);
 908
 4124909                    Vector256<byte> result = IndexOfAnyLookup<TNegator>(source, bitmap256_0, bitmap256_1);
 4124910                    if (result != Vector256<byte>.Zero)
 911                    {
 1586912                        return TResultMapper.FirstIndexOverlapped<TNegator>(ref searchSpace, ref firstVector, ref halfVe
 913                    }
 914                }
 915
 2538916                return TResultMapper.NotFound;
 917            }
 918
 3186919            Vector128<byte> bitmap0 = state.Bitmap0._lower;
 3186920            Vector128<byte> bitmap1 = state.Bitmap1._lower;
 921
 922#pragma warning disable IntrinsicsInSystemPrivateCoreLibAttributeNotSpecificEnough // The behavior of the rest of the fu
 3186923            if (!Avx2.IsSupported && searchSpaceLength > Vector128<byte>.Count)
 924#pragma warning restore IntrinsicsInSystemPrivateCoreLibAttributeNotSpecificEnough
 925            {
 926                // Process the input in chunks of 16 bytes.
 927                // If the input length is a multiple of 16, don't consume the last 16 characters in this loop.
 928                // Let the fallback below handle it instead. This is why the condition is
 929                // ">" instead of ">=" above, and why "IsAddressLessThan" is used instead of "IsAddressLessThanOrEqualTo
 0930                ref byte vectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - Vector128<byte>.Count);
 931
 932                do
 933                {
 0934                    Vector128<byte> source = Vector128.LoadUnsafe(ref currentSearchSpace);
 935
 0936                    Vector128<byte> result = IndexOfAnyLookup<TNegator>(source, bitmap0, bitmap1);
 0937                    if (result != Vector128<byte>.Zero)
 938                    {
 0939                        return TResultMapper.FirstIndex<TNegator>(ref searchSpace, ref currentSearchSpace, result);
 940                    }
 941
 0942                    currentSearchSpace = ref Unsafe.Add(ref currentSearchSpace, Vector128<byte>.Count);
 943                }
 0944                while (Unsafe.IsAddressLessThan(ref currentSearchSpace, ref vectorAwayFromEnd));
 945            }
 946
 947            // We have 1-16 bytes remaining. Process the first and last half vectors in the search space.
 948            // They may overlap, but we'll handle that in the index calculation if we do get a match.
 3186949            Debug.Assert(searchSpaceLength >= sizeof(ulong), "We expect that the input is long enough for us to load a u
 950            {
 3186951                ref byte halfVectorAwayFromEnd = ref Unsafe.Add(ref searchSpace, searchSpaceLength - sizeof(ulong));
 952
 3186953                ref byte firstVector = ref Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref halfVectorAwayFromEnd
 3186954                    ? ref halfVectorAwayFromEnd
 3186955                    : ref currentSearchSpace;
 956
 3186957                ulong source0 = Unsafe.ReadUnaligned<ulong>(ref firstVector);
 3186958                ulong source1 = Unsafe.ReadUnaligned<ulong>(ref halfVectorAwayFromEnd);
 3186959                Vector128<byte> source = Vector128.Create(source0, source1).AsByte();
 960
 3186961                Vector128<byte> result = IndexOfAnyLookup<TNegator>(source, bitmap0, bitmap1);
 3186962                if (result != Vector128<byte>.Zero)
 963                {
 2202964                    return TResultMapper.FirstIndexOverlapped<TNegator>(ref searchSpace, ref firstVector, ref halfVector
 965                }
 966            }
 967
 984968            return TResultMapper.NotFound;
 969        }
 970
 971        [CompExactlyDependsOn(typeof(Ssse3))]
 972        [CompExactlyDependsOn(typeof(AdvSimd))]
 973        [CompExactlyDependsOn(typeof(PackedSimd))]
 974        public static int LastIndexOfAny<TNegator>(ref byte searchSpace, int searchSpaceLength, ref AnyByteState state)
 975            where TNegator : struct, INegator
 976        {
 11296977            if (!IsVectorizationSupported || searchSpaceLength < sizeof(ulong))
 978            {
 7676979                for (int i = searchSpaceLength - 1; i >= 0; i--)
 980                {
 2008981                    byte b = Unsafe.Add(ref searchSpace, i);
 2008982                    if (TNegator.NegateIfNeeded(state.Lookup.Contains(b)))
 983                    {
 682984                        return i;
 985                    }
 986                }
 987
 1830988                return -1;
 989            }
 990
 8784991            ref byte currentSearchSpace = ref Unsafe.Add(ref searchSpace, searchSpaceLength);
 992
 993#pragma warning disable IntrinsicsInSystemPrivateCoreLibAttributeNotSpecificEnough // The behavior of the rest of the fu
 8784994            if (Avx2.IsSupported && searchSpaceLength > Vector128<byte>.Count)
 995            {
 996#pragma warning restore IntrinsicsInSystemPrivateCoreLibAttributeNotSpecificEnough
 7788997                Vector256<byte> bitmap256_0 = state.Bitmap0;
 7788998                Vector256<byte> bitmap256_1 = state.Bitmap1;
 999
 77881000                if (searchSpaceLength > Vector256<byte>.Count)
 1001                {
 1002                    // Process the input in chunks of 32 bytes.
 1003                    // If the input length is a multiple of 32, don't consume the last 32 characters in this loop.
 1004                    // Let the fallback below handle it instead. This is why the condition is
 1005                    // ">" instead of ">=" above, and why "IsAddressGreaterThan" is used instead of "IsAddressGreaterTha
 70121006                    ref byte vectorAfterStart = ref Unsafe.Add(ref searchSpace, Vector256<byte>.Count);
 1007
 1008                    do
 1009                    {
 205921010                        currentSearchSpace = ref Unsafe.Subtract(ref currentSearchSpace, Vector256<byte>.Count);
 1011
 205921012                        Vector256<byte> source = Vector256.LoadUnsafe(ref currentSearchSpace);
 1013
 205921014                        Vector256<byte> result = IndexOfAnyLookup<TNegator>(source, bitmap256_0, bitmap256_1);
 205921015                        if (result != Vector256<byte>.Zero)
 1016                        {
 63341017                            return ComputeLastIndex<byte, TNegator>(ref searchSpace, ref currentSearchSpace, result);
 1018                        }
 1019                    }
 142581020                    while (Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref vectorAfterStart));
 1021                }
 1022
 1023                // We have 1-32 bytes remaining. Process the first and last half vectors in the search space.
 1024                // They may overlap, but we'll handle that in the index calculation if we do get a match.
 14541025                Debug.Assert(searchSpaceLength >= Vector128<byte>.Count, "We expect that the input is long enough for us
 1026                {
 14541027                    ref byte halfVectorAfterStart = ref Unsafe.Add(ref searchSpace, Vector128<byte>.Count);
 1028
 14541029                    ref byte secondVector = ref Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref halfVectorAfterS
 14541030                        ? ref Unsafe.Subtract(ref currentSearchSpace, Vector128<byte>.Count)
 14541031                        : ref searchSpace;
 1032
 14541033                    Vector128<byte> source0 = Vector128.LoadUnsafe(ref searchSpace);
 14541034                    Vector128<byte> source1 = Vector128.LoadUnsafe(ref secondVector);
 14541035                    Vector256<byte> source = Vector256.Create(source0, source1);
 1036
 14541037                    Vector256<byte> result = IndexOfAnyLookup<TNegator>(source, bitmap256_0, bitmap256_1);
 14541038                    if (result != Vector256<byte>.Zero)
 1039                    {
 7761040                        return ComputeLastIndexOverlapped<byte, TNegator>(ref searchSpace, ref secondVector, result);
 1041                    }
 1042                }
 1043
 6781044                return -1;
 1045            }
 1046
 9961047            Vector128<byte> bitmap0 = state.Bitmap0._lower;
 9961048            Vector128<byte> bitmap1 = state.Bitmap1._lower;
 1049
 1050#pragma warning disable IntrinsicsInSystemPrivateCoreLibAttributeNotSpecificEnough // The behavior of the rest of the fu
 9961051            if (!Avx2.IsSupported && searchSpaceLength > Vector128<byte>.Count)
 1052#pragma warning restore IntrinsicsInSystemPrivateCoreLibAttributeNotSpecificEnough
 1053            {
 1054                // Process the input in chunks of 16 bytes.
 1055                // If the input length is a multiple of 16, don't consume the last 16 characters in this loop.
 1056                // Let the fallback below handle it instead. This is why the condition is
 1057                // ">" instead of ">=" above, and why "IsAddressGreaterThan" is used instead of "IsAddressGreaterThanOrE
 01058                ref byte vectorAfterStart = ref Unsafe.Add(ref searchSpace, Vector128<byte>.Count);
 1059
 1060                do
 1061                {
 01062                    currentSearchSpace = ref Unsafe.Subtract(ref currentSearchSpace, Vector128<byte>.Count);
 1063
 01064                    Vector128<byte> source = Vector128.LoadUnsafe(ref currentSearchSpace);
 1065
 01066                    Vector128<byte> result = IndexOfAnyLookup<TNegator>(source, bitmap0, bitmap1);
 01067                    if (result != Vector128<byte>.Zero)
 1068                    {
 01069                        return ComputeLastIndex<byte, TNegator>(ref searchSpace, ref currentSearchSpace, result);
 1070                    }
 1071                }
 01072                while (Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref vectorAfterStart));
 1073            }
 1074
 1075            // We have 1-16 bytes remaining. Process the first and last half vectors in the search space.
 1076            // They may overlap, but we'll handle that in the index calculation if we do get a match.
 9961077            Debug.Assert(searchSpaceLength >= sizeof(ulong), "We expect that the input is long enough for us to load a u
 1078            {
 9961079                ref byte halfVectorAfterStart = ref Unsafe.Add(ref searchSpace, sizeof(ulong));
 1080
 9961081                ref byte secondVector = ref Unsafe.IsAddressGreaterThan(ref currentSearchSpace, ref halfVectorAfterStart
 9961082                    ? ref Unsafe.Subtract(ref currentSearchSpace, sizeof(ulong))
 9961083                    : ref searchSpace;
 1084
 9961085                ulong source0 = Unsafe.ReadUnaligned<ulong>(ref searchSpace);
 9961086                ulong source1 = Unsafe.ReadUnaligned<ulong>(ref secondVector);
 9961087                Vector128<byte> source = Vector128.Create(source0, source1).AsByte();
 1088
 9961089                Vector128<byte> result = IndexOfAnyLookup<TNegator>(source, bitmap0, bitmap1);
 9961090                if (result != Vector128<byte>.Zero)
 1091                {
 9041092                    return ComputeLastIndexOverlapped<byte, TNegator>(ref searchSpace, ref secondVector, result);
 1093                }
 1094            }
 1095
 921096            return -1;
 1097        }
 1098
 1099        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1100        [CompExactlyDependsOn(typeof(Sse2))]
 1101        [CompExactlyDependsOn(typeof(AdvSimd))]
 1102        [CompExactlyDependsOn(typeof(PackedSimd))]
 1103        private static Vector128<byte> IndexOfAnyLookup<TNegator, TOptimizations, TUniqueLowNibble>(Vector128<short> sou
 1104            where TNegator : struct, INegator
 1105            where TOptimizations : struct, IOptimizations
 1106            where TUniqueLowNibble : struct, SearchValues.IRuntimeConst
 1107        {
 27761108            Vector128<byte> source = TOptimizations.PackSources(source0.AsUInt16(), source1.AsUInt16());
 1109
 27761110            Vector128<byte> result = IndexOfAnyLookupCore<TUniqueLowNibble>(source, bitmapLookup);
 1111
 27761112            return TNegator.NegateIfNeeded(result);
 1113        }
 1114
 1115        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1116        [CompExactlyDependsOn(typeof(Ssse3))]
 1117        [CompExactlyDependsOn(typeof(AdvSimd))]
 1118        [CompExactlyDependsOn(typeof(PackedSimd))]
 1119        private static Vector128<byte> IndexOfAnyLookupCore<TUniqueLowNibble>(Vector128<byte> source, Vector128<byte> bi
 1120            where TUniqueLowNibble : struct, SearchValues.IRuntimeConst
 1121        {
 1122            if (TUniqueLowNibble.Value)
 1123            {
 1124                // Based on http://0x80.pl/articles/simd-byte-lookup.html#special-case-3-unique-lower-and-higher-nibbles
 1125
 1126                // On X86, the Ssse3.Shuffle instruction will already perform an implicit 'AND 0xF' on the indices, so w
 1127                // For values above 127, Ssse3.Shuffle will also set the result to 0. This is fine as we only use this a
 1128                // all values are <= 127 when Ssse3 is supported (see CanUseUniqueLowNibbleSearch).
 1129                // False positives from values mapped to 0 will be ruled out by the Vector128.Equals comparison below.
 2441130                Vector128<byte> lowNibbles = Ssse3.IsSupported
 2441131                    ? source
 2441132                    : source & Vector128.Create((byte)0xF);
 1133
 1134                // We use a shuffle to look up potential matches for each byte based on its low nibble.
 1135                // Since all values have a unique low nibble, there's at most one potential match per nibble.
 2441136                Vector128<byte> values = SearchValues.ShuffleNativeModified(bitmapLookup, lowNibbles);
 1137
 1138                // Compare potential matches with the source to rule out false positives that have a different high nibb
 2441139                return Vector128.Equals(source, values);
 1140            }
 1141            else
 1142            {
 1143                // On X86, the Ssse3.Shuffle instruction will already perform an implicit 'AND 0xF' on the indices, so w
 1144                // For values above 127, Ssse3.Shuffle will also set the result to 0. This is fine as we don't want non-
 77601145                Vector128<byte> lowNibbles = Ssse3.IsSupported
 77601146                    ? source
 77601147                    : source & Vector128.Create((byte)0xF);
 1148
 1149                // On ARM, we have an instruction for an arithmetic right shift of 1-byte signed values.
 1150                // The shift will map values above 127 to values above 16, which the shuffle will then map to 0.
 1151                // On X86 and WASM, use a logical right shift instead.
 1152                Vector128<byte> highNibbles = AdvSimd.IsSupported
 1153                    ? (source.AsSByte() >> 4).AsByte()
 1154                    : source >>> 4;
 1155
 1156                // The bitmapLookup represents a 8x16 table of bits, indicating whether a character is present in the ne
 1157                // Lookup the rows via the lower nibble and the column via the higher nibble.
 77601158                Vector128<byte> bitMask = SearchValues.ShuffleNativeModified(bitmapLookup, lowNibbles);
 1159
 1160                // For values above 127, the high nibble will be above 7. We construct the positions vector for the shuf
 77601161                Vector128<byte> bitPositions = SearchValues.ShuffleNativeModified(Vector128.Create(0x8040201008040201, 0
 1162
 77601163                return bitMask & bitPositions;
 1164            }
 1165        }
 1166
 1167        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1168        [CompExactlyDependsOn(typeof(Avx2))]
 1169        private static Vector256<byte> IndexOfAnyLookup<TNegator, TOptimizations, TUniqueLowNibble>(Vector256<short> sou
 1170            where TNegator : struct, INegator
 1171            where TOptimizations : struct, IOptimizations
 1172            where TUniqueLowNibble : struct, SearchValues.IRuntimeConst
 1173        {
 253451174            Vector256<byte> source = TOptimizations.PackSources(source0.AsUInt16(), source1.AsUInt16());
 1175
 253451176            Vector256<byte> result = IndexOfAnyLookupCore<TUniqueLowNibble>(source, bitmapLookup);
 1177
 253451178            return TNegator.NegateIfNeeded(result);
 1179        }
 1180
 1181        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1182        [CompExactlyDependsOn(typeof(Avx2))]
 1183        private static Vector256<byte> IndexOfAnyLookupCore<TUniqueLowNibble>(Vector256<byte> source, Vector256<byte> bi
 1184            where TUniqueLowNibble : struct, SearchValues.IRuntimeConst
 1185        {
 1186            // See comments in IndexOfAnyLookupCore(Vector128<byte>) above for more details.
 836491187            if (TUniqueLowNibble.Value)
 1188            {
 175011189                Vector256<byte> values = Avx2.Shuffle(bitmapLookup, source);
 175011190                return Vector256.Equals(source, values);
 1191            }
 1192            else
 1193            {
 661481194                Vector256<byte> highNibbles = source >>> 4;
 661481195                Vector256<byte> bitMask = Avx2.Shuffle(bitmapLookup, source);
 661481196                Vector256<byte> bitPositions = Avx2.Shuffle(Vector256.Create(0x8040201008040201).AsByte(), highNibbles);
 661481197                return bitMask & bitPositions;
 1198            }
 1199        }
 1200
 1201        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1202        [CompExactlyDependsOn(typeof(Ssse3))]
 1203        [CompExactlyDependsOn(typeof(AdvSimd))]
 1204        [CompExactlyDependsOn(typeof(PackedSimd))]
 1205        private static Vector128<byte> IndexOfAnyLookup<TNegator>(Vector128<byte> source, Vector128<byte> bitmapLookup0,
 1206            where TNegator : struct, INegator
 1207        {
 1208            // http://0x80.pl/articles/simd-byte-lookup.html#universal-algorithm
 1209
 41821210            Vector128<byte> lowNibbles = source & Vector128.Create((byte)0xF);
 41821211            Vector128<byte> highNibbles = source >>> 4;
 1212
 41821213            Vector128<byte> row0 = Vector128.ShuffleNative(bitmapLookup0, lowNibbles);
 41821214            Vector128<byte> row1 = Vector128.ShuffleNative(bitmapLookup1, lowNibbles);
 1215
 41821216            Vector128<byte> bitmask = Vector128.ShuffleNative(Vector128.Create(0x8040201008040201).AsByte(), highNibbles
 1217
 41821218            Vector128<byte> mask = Vector128.GreaterThan(highNibbles.AsSByte(), Vector128.Create((sbyte)0x7)).AsByte();
 41821219            Vector128<byte> bitsets = Vector128.ConditionalSelect(mask, row1, row0);
 1220
 41821221            Vector128<byte> result = Vector128.Equals(bitsets & bitmask, bitmask);
 1222
 41821223            return TNegator.NegateIfNeeded(result);
 1224        }
 1225
 1226        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1227        [CompExactlyDependsOn(typeof(Avx2))]
 1228        private static Vector256<byte> IndexOfAnyLookup<TNegator>(Vector256<byte> source, Vector256<byte> bitmapLookup0,
 1229            where TNegator : struct, INegator
 1230        {
 1231            // http://0x80.pl/articles/simd-byte-lookup.html#universal-algorithm
 1232
 867151233            Vector256<byte> lowNibbles = source & Vector256.Create((byte)0xF);
 867151234            Vector256<byte> highNibbles = source >>> 4;
 1235
 867151236            Vector256<byte> row0 = Avx2.Shuffle(bitmapLookup0, lowNibbles);
 867151237            Vector256<byte> row1 = Avx2.Shuffle(bitmapLookup1, lowNibbles);
 1238
 867151239            Vector256<byte> bitmask = Avx2.Shuffle(Vector256.Create(0x8040201008040201).AsByte(), highNibbles);
 1240
 867151241            Vector256<byte> mask = Vector256.GreaterThan(highNibbles.AsSByte(), Vector256.Create((sbyte)0x7)).AsByte();
 867151242            Vector256<byte> bitsets = Vector256.ConditionalSelect(mask, row1, row0);
 1243
 867151244            Vector256<byte> result = Vector256.Equals(bitsets & bitmask, bitmask);
 1245
 867151246            return TNegator.NegateIfNeeded(result);
 1247        }
 1248
 1249        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1250        private static unsafe int ComputeLastIndex<T, TNegator>(ref T searchSpace, ref T current, Vector128<byte> result
 1251            where TNegator : struct, INegator
 1252        {
 01253            int offsetInVector = TNegator.IndexOfLastMatch(result);
 01254            return offsetInVector + (int)((nuint)Unsafe.ByteOffset(ref searchSpace, ref current) / (nuint)sizeof(T));
 1255        }
 1256
 1257        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1258        private static unsafe int ComputeLastIndexOverlapped<T, TNegator>(ref T searchSpace, ref T secondVector, Vector1
 1259            where TNegator : struct, INegator
 1260        {
 25801261            int offsetInVector = TNegator.IndexOfLastMatch(result);
 1262
 25801263            if (offsetInVector < Vector128<short>.Count)
 1264            {
 1521265                return offsetInVector;
 1266            }
 1267
 1268            // We matched within the second vector
 24281269            return offsetInVector - Vector128<short>.Count + (int)((nuint)Unsafe.ByteOffset(ref searchSpace, ref secondV
 1270        }
 1271
 1272        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1273        [CompExactlyDependsOn(typeof(Avx2))]
 1274        private static unsafe int ComputeLastIndex<T, TNegator>(ref T searchSpace, ref T current, Vector256<byte> result
 1275            where TNegator : struct, INegator
 1276        {
 111561277            if (typeof(T) == typeof(short))
 1278            {
 13701279                result = PackedSpanHelpers.FixUpPackedVector256Result(result);
 1280            }
 1281
 111561282            int offsetInVector = TNegator.IndexOfLastMatch(result);
 111561283            return offsetInVector + (int)((nuint)Unsafe.ByteOffset(ref searchSpace, ref current) / (nuint)sizeof(T));
 1284        }
 1285
 1286        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1287        [CompExactlyDependsOn(typeof(Avx2))]
 1288        private static unsafe int ComputeLastIndexOverlapped<T, TNegator>(ref T searchSpace, ref T secondVector, Vector2
 1289            where TNegator : struct, INegator
 1290        {
 25381291            if (typeof(T) == typeof(short))
 1292            {
 7781293                result = PackedSpanHelpers.FixUpPackedVector256Result(result);
 1294            }
 1295
 25381296            int offsetInVector = TNegator.IndexOfLastMatch(result);
 1297
 25381298            if (offsetInVector < Vector256<short>.Count)
 1299            {
 3361300                return offsetInVector;
 1301            }
 1302
 1303            // We matched within the second vector
 22021304            return offsetInVector - Vector256<short>.Count + (int)((nuint)Unsafe.ByteOffset(ref searchSpace, ref secondV
 1305        }
 1306
 1307        internal interface INegator
 1308        {
 1309            static abstract bool NegateIfNeeded(bool result);
 1310            static abstract Vector128<byte> NegateIfNeeded(Vector128<byte> result);
 1311            static abstract Vector256<byte> NegateIfNeeded(Vector256<byte> result);
 1312            static abstract int IndexOfFirstMatch(Vector128<byte> result);
 1313            static abstract int IndexOfFirstMatch(Vector256<byte> result);
 1314            static abstract int IndexOfLastMatch(Vector128<byte> result);
 1315            static abstract int IndexOfLastMatch(Vector256<byte> result);
 1316        }
 1317
 1318        internal readonly struct DontNegate : INegator
 1319        {
 1080531320            public static bool NegateIfNeeded(bool result) => result;
 54281321            public static Vector128<byte> NegateIfNeeded(Vector128<byte> result) => result;
 974061322            public static Vector256<byte> NegateIfNeeded(Vector256<byte> result) => result;
 1323            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 33931324            public static int IndexOfFirstMatch(Vector128<byte> result) => Vector128.IndexOfFirstMatch(~Vector128.Equals
 1325            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 80841326            public static int IndexOfFirstMatch(Vector256<byte> result) => Vector256.IndexOfFirstMatch(~Vector256.Equals
 1327            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 11741328            public static int IndexOfLastMatch(Vector128<byte> result) => Vector128.IndexOfLastMatch(~Vector128.Equals(r
 1329            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 64481330            public static int IndexOfLastMatch(Vector256<byte> result) => Vector256.IndexOfLastMatch(~Vector256.Equals(r
 1331        }
 1332
 1333        internal readonly struct Negate : INegator
 1334        {
 1335            // AdvSimd expects that a given element is strictly Zero or AllBitsSet
 1336            // so we need to ensure that we normalize the input prior to calling
 1337            // IndexOfFirstMatch or IndexOfLastMatch
 1338
 3139171339            public static bool NegateIfNeeded(bool result) => !result;
 1340            // This is intentionally testing for equality with 0 instead of "~result".
 1341            // We want to know if any character didn't match, as that means it should be treated as a match for the -Exc
 1342            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 67581343            public static Vector128<byte> NegateIfNeeded(Vector128<byte> result) => Vector128.Equals(result, Vector128<b
 1344            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 729581345            public static Vector256<byte> NegateIfNeeded(Vector256<byte> result) => Vector256.Equals(result, Vector256<b
 1346            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1347            public static int IndexOfFirstMatch(Vector128<byte> result)
 1348            {
 1349                if (AdvSimd.IsSupported)
 1350                {
 1351                    result = (result.AsSByte() >> 7).AsByte();
 1352                }
 20291353                return Vector128.IndexOfFirstMatch(result);
 1354            }
 1355            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 74651356            public static int IndexOfFirstMatch(Vector256<byte> result) => Vector256.IndexOfFirstMatch(result);
 1357            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1358            public static int IndexOfLastMatch(Vector128<byte> result)
 1359            {
 1360                if (AdvSimd.IsSupported)
 1361                {
 1362                    result = (result.AsSByte() >> 7).AsByte();
 1363                }
 14061364                return Vector128.IndexOfLastMatch(result);
 1365            }
 1366            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 72461367            public static int IndexOfLastMatch(Vector256<byte> result) => Vector256.IndexOfLastMatch(result);
 1368        }
 1369
 1370        internal interface IOptimizations
 1371        {
 1372            // Pack two vectors of characters into bytes.
 1373            // X86 and WASM when the needle does not contain 0:  Downcast every character using saturation.
 1374            // - Values <= 32767 result in min(value, 255).
 1375            // - Values  > 32767 result in 0. Because of this we must do more work to handle needles that contain 0.
 1376            // Otherwise: Do narrowing saturation over unsigned values.
 1377            // - All values result in min(value, 255)
 1378            static abstract Vector128<byte> PackSources(Vector128<ushort> lower, Vector128<ushort> upper);
 1379            static abstract Vector256<byte> PackSources(Vector256<ushort> lower, Vector256<ushort> upper);
 1380        }
 1381
 1382        internal readonly struct Ssse3AndWasmHandleZeroInNeedle : IOptimizations
 1383        {
 1384            // Replace with Vector128.NarrowWithSaturation once https://github.com/dotnet/runtime/issues/75724 is implem
 1385            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1386            [CompExactlyDependsOn(typeof(Sse2))]
 1387            [CompExactlyDependsOn(typeof(PackedSimd))]
 1388            public static Vector128<byte> PackSources(Vector128<ushort> lower, Vector128<ushort> upper)
 1389            {
 1390                Vector128<short> lowerMin = Vector128.Min(lower, Vector128.Create((ushort)255)).AsInt16();
 18171391                Vector128<short> upperMin = Vector128.Min(upper, Vector128.Create((ushort)255)).AsInt16();
 1392
 18171393                if (Sse2.IsSupported)
 1394                {
 18171395                    return Sse2.PackUnsignedSaturate(lowerMin, upperMin);
 1396                }
 1397                else if (PackedSimd.IsSupported)
 1398                {
 1399                    return PackedSimd.ConvertNarrowingSaturateUnsigned(lowerMin, upperMin);
 1400                }
 1401                else
 1402                {
 1403                    // We explicitly recheck each IsSupported query to ensure that the trimmer can see which paths are l
 01404                    ThrowHelper.ThrowUnreachableException();
 1405                    return default;
 1406                }
 1407            }
 1408
 1409            // Replace with Vector256.NarrowWithSaturation once https://github.com/dotnet/runtime/issues/75724 is implem
 1410            [CompExactlyDependsOn(typeof(Avx2))]
 1411            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1412            public static Vector256<byte> PackSources(Vector256<ushort> lower, Vector256<ushort> upper)
 1413            {
 200421414                return Avx2.PackUnsignedSaturate(
 200421415                    Vector256.Min(lower, Vector256.Create((ushort)255)).AsInt16(),
 200421416                    Vector256.Min(upper, Vector256.Create((ushort)255)).AsInt16());
 1417            }
 1418        }
 1419
 1420        internal readonly struct Default : IOptimizations
 1421        {
 1422            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1423            [CompExactlyDependsOn(typeof(Sse2))]
 1424            [CompExactlyDependsOn(typeof(AdvSimd))]
 1425            [CompExactlyDependsOn(typeof(PackedSimd))]
 1426            public static Vector128<byte> PackSources(Vector128<ushort> lower, Vector128<ushort> upper)
 1427            {
 1428                if (Sse2.IsSupported)
 1429                {
 9591430                    return Sse2.PackUnsignedSaturate(lower.AsInt16(), upper.AsInt16());
 1431                }
 1432                else if (AdvSimd.IsSupported)
 1433                {
 1434                    return AdvSimd.ExtractNarrowingSaturateUpper(AdvSimd.ExtractNarrowingSaturateLower(lower), upper);
 1435                }
 1436                else if (PackedSimd.IsSupported)
 1437                {
 1438                    return PackedSimd.ConvertNarrowingSaturateUnsigned(lower.AsInt16(), upper.AsInt16());
 1439                }
 1440                else
 1441                {
 1442                    // We explicitly recheck each IsSupported query to ensure that the trimmer can see which paths are l
 01443                    ThrowHelper.ThrowUnreachableException();
 1444                    return default;
 1445                }
 1446            }
 1447
 1448            [CompExactlyDependsOn(typeof(Avx2))]
 1449            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1450            public static Vector256<byte> PackSources(Vector256<ushort> lower, Vector256<ushort> upper)
 1451            {
 53031452                return Avx2.PackUnsignedSaturate(lower.AsInt16(), upper.AsInt16());
 1453            }
 1454        }
 1455
 1456        private interface IResultMapper<T, TResult>
 1457            where TResult : struct
 1458        {
 1459            static abstract TResult NotFound { get; }
 1460
 1461            static abstract TResult ScalarResult(ref T searchSpace, ref T current);
 1462            static abstract TResult FirstIndex<TNegator>(ref T searchSpace, ref T current, Vector128<byte> result) where
 1463            static abstract TResult FirstIndex<TNegator>(ref T searchSpace, ref T current, Vector256<byte> result) where
 1464            static abstract TResult FirstIndexOverlapped<TNegator>(ref T searchSpace, ref T current0, ref T current1, Ve
 1465            static abstract TResult FirstIndexOverlapped<TNegator>(ref T searchSpace, ref T current0, ref T current1, Ve
 1466        }
 1467
 1468        private readonly struct ContainsAnyResultMapper<T> : IResultMapper<T, bool>
 1469        {
 29761470            public static bool NotFound => false;
 1471
 9521472            public static bool ScalarResult(ref T searchSpace, ref T current) => true;
 01473            public static bool FirstIndex<TNegator>(ref T searchSpace, ref T current, Vector128<byte> result) where TNeg
 52241474            public static bool FirstIndex<TNegator>(ref T searchSpace, ref T current, Vector256<byte> result) where TNeg
 10761475            public static bool FirstIndexOverlapped<TNegator>(ref T searchSpace, ref T current0, ref T current1, Vector1
 11041476            public static bool FirstIndexOverlapped<TNegator>(ref T searchSpace, ref T current0, ref T current1, Vector2
 1477        }
 1478
 1479        private readonly unsafe struct IndexOfAnyResultMapper<T> : IResultMapper<T, int>
 1480        {
 121011481            public static int NotFound => -1;
 1482
 1483            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1484            public static int ScalarResult(ref T searchSpace, ref T current)
 1485            {
 33751486                return (int)((nuint)Unsafe.ByteOffset(ref searchSpace, ref current) / (nuint)sizeof(T));
 1487            }
 1488
 1489            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1490            public static int FirstIndex<TNegator>(ref T searchSpace, ref T current, Vector128<byte> result) where TNega
 1491            {
 01492                int offsetInVector = TNegator.IndexOfFirstMatch(result);
 01493                return offsetInVector + (int)((nuint)Unsafe.ByteOffset(ref searchSpace, ref current) / (nuint)sizeof(T))
 1494            }
 1495
 1496            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1497            [CompExactlyDependsOn(typeof(Avx2))]
 1498            public static int FirstIndex<TNegator>(ref T searchSpace, ref T current, Vector256<byte> result) where TNega
 1499            {
 123661500                if (typeof(T) == typeof(short))
 1501                {
 12941502                    result = PackedSpanHelpers.FixUpPackedVector256Result(result);
 1503                }
 1504
 123661505                int offsetInVector = TNegator.IndexOfFirstMatch(result);
 123661506                return offsetInVector + (int)((nuint)Unsafe.ByteOffset(ref searchSpace, ref current) / (nuint)sizeof(T))
 1507            }
 1508
 1509            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1510            public static int FirstIndexOverlapped<TNegator>(ref T searchSpace, ref T current0, ref T current1, Vector12
 1511            {
 54221512                int offsetInVector = TNegator.IndexOfFirstMatch(result);
 1513
 54221514                if (offsetInVector >= Vector128<short>.Count)
 1515                {
 1516                    // We matched within the second vector
 1541517                    current0 = ref current1;
 1541518                    offsetInVector -= Vector128<short>.Count;
 1519                }
 54221520                return offsetInVector + (int)((nuint)Unsafe.ByteOffset(ref searchSpace, ref current0) / (nuint)sizeof(T)
 1521            }
 1522
 1523            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1524            [CompExactlyDependsOn(typeof(Avx2))]
 1525            public static int FirstIndexOverlapped<TNegator>(ref T searchSpace, ref T current0, ref T current1, Vector25
 1526            {
 31831527                if (typeof(T) == typeof(short))
 1528                {
 8831529                    result = PackedSpanHelpers.FixUpPackedVector256Result(result);
 1530                }
 1531
 31831532                int offsetInVector = TNegator.IndexOfFirstMatch(result);
 1533
 31831534                if (offsetInVector >= Vector256<short>.Count)
 1535                {
 1536                    // We matched within the second vector
 1861537                    current0 = ref current1;
 1861538                    offsetInVector -= Vector256<short>.Count;
 1539                }
 31831540                return offsetInVector + (int)((nuint)Unsafe.ByteOffset(ref searchSpace, ref current0) / (nuint)sizeof(T)
 1541            }
 1542        }
 1543    }
 1544}
 1545

Methods/Properties

.ctor(System.Runtime.Intrinsics.Vector128`1<System.Byte>,System.Buffers.BitVector256)
CreateInverse()
.ctor(System.Runtime.Intrinsics.Vector128`1<System.Byte>,System.Runtime.Intrinsics.Vector128`1<System.Byte>,System.Buffers.BitVector256)
IsVectorizationSupported()
SetBitmapBit(System.Runtime.CompilerServices.InlineArray16`1<System.Byte>&,System.Int32)
ComputeAnyByteState(System.ReadOnlySpan`1<System.Byte>,System.Buffers.IndexOfAnyAsciiSearcher/AnyByteState&)
ComputeAsciiState(System.ReadOnlySpan`1<T>,System.Buffers.IndexOfAnyAsciiSearcher/AsciiState&)
CanUseUniqueLowNibbleSearch(System.ReadOnlySpan`1<T>,System.Int32)
ComputeUniqueLowNibbleState(System.ReadOnlySpan`1<T>,System.Buffers.IndexOfAnyAsciiSearcher/AsciiState&)
TryComputeBitmap(System.ReadOnlySpan`1<System.Char>,System.Byte*,System.Boolean&)
TryIndexOfAny(System.Char&,System.Int32,System.ReadOnlySpan`1<System.Char>,System.Int32&)
TryIndexOfAnyExcept(System.Char&,System.Int32,System.ReadOnlySpan`1<System.Char>,System.Int32&)
TryLastIndexOfAny(System.Char&,System.Int32,System.ReadOnlySpan`1<System.Char>,System.Int32&)
TryLastIndexOfAnyExcept(System.Char&,System.Int32,System.ReadOnlySpan`1<System.Char>,System.Int32&)
TryIndexOfAny(System.Int16&,System.Int32,System.ReadOnlySpan`1<System.Char>,System.Int32&)
TryLastIndexOfAny(System.Int16&,System.Int32,System.ReadOnlySpan`1<System.Char>,System.Int32&)
ContainsAny(System.Int16&,System.Int32,System.Buffers.IndexOfAnyAsciiSearcher/AsciiState&)
IndexOfAny(System.Int16&,System.Int32,System.Buffers.IndexOfAnyAsciiSearcher/AsciiState&)
IndexOfAnyCore(System.Int16&,System.Int32,System.Buffers.IndexOfAnyAsciiSearcher/AsciiState&)
LastIndexOfAny(System.Int16&,System.Int32,System.Buffers.IndexOfAnyAsciiSearcher/AsciiState&)
ContainsAny(System.Byte&,System.Int32,System.Buffers.IndexOfAnyAsciiSearcher/AsciiState&)
IndexOfAny(System.Byte&,System.Int32,System.Buffers.IndexOfAnyAsciiSearcher/AsciiState&)
IndexOfAnyCore(System.Byte&,System.Int32,System.Buffers.IndexOfAnyAsciiSearcher/AsciiState&)
LastIndexOfAny(System.Byte&,System.Int32,System.Buffers.IndexOfAnyAsciiSearcher/AsciiState&)
ContainsAny(System.Byte&,System.Int32,System.Buffers.IndexOfAnyAsciiSearcher/AnyByteState&)
IndexOfAny(System.Byte&,System.Int32,System.Buffers.IndexOfAnyAsciiSearcher/AnyByteState&)
IndexOfAnyCore(System.Byte&,System.Int32,System.Buffers.IndexOfAnyAsciiSearcher/AnyByteState&)
LastIndexOfAny(System.Byte&,System.Int32,System.Buffers.IndexOfAnyAsciiSearcher/AnyByteState&)
IndexOfAnyLookup(System.Runtime.Intrinsics.Vector128`1<System.Int16>,System.Runtime.Intrinsics.Vector128`1<System.Int16>,System.Runtime.Intrinsics.Vector128`1<System.Byte>)
IndexOfAnyLookupCore(System.Runtime.Intrinsics.Vector128`1<System.Byte>,System.Runtime.Intrinsics.Vector128`1<System.Byte>)
IndexOfAnyLookup(System.Runtime.Intrinsics.Vector256`1<System.Int16>,System.Runtime.Intrinsics.Vector256`1<System.Int16>,System.Runtime.Intrinsics.Vector256`1<System.Byte>)
IndexOfAnyLookupCore(System.Runtime.Intrinsics.Vector256`1<System.Byte>,System.Runtime.Intrinsics.Vector256`1<System.Byte>)
IndexOfAnyLookup(System.Runtime.Intrinsics.Vector128`1<System.Byte>,System.Runtime.Intrinsics.Vector128`1<System.Byte>,System.Runtime.Intrinsics.Vector128`1<System.Byte>)
IndexOfAnyLookup(System.Runtime.Intrinsics.Vector256`1<System.Byte>,System.Runtime.Intrinsics.Vector256`1<System.Byte>,System.Runtime.Intrinsics.Vector256`1<System.Byte>)
ComputeLastIndex(T&,T&,System.Runtime.Intrinsics.Vector128`1<System.Byte>)
ComputeLastIndexOverlapped(T&,T&,System.Runtime.Intrinsics.Vector128`1<System.Byte>)
ComputeLastIndex(T&,T&,System.Runtime.Intrinsics.Vector256`1<System.Byte>)
ComputeLastIndexOverlapped(T&,T&,System.Runtime.Intrinsics.Vector256`1<System.Byte>)
NegateIfNeeded(System.Boolean)
NegateIfNeeded(System.Runtime.Intrinsics.Vector128`1<System.Byte>)
NegateIfNeeded(System.Runtime.Intrinsics.Vector256`1<System.Byte>)
IndexOfFirstMatch(System.Runtime.Intrinsics.Vector128`1<System.Byte>)
IndexOfFirstMatch(System.Runtime.Intrinsics.Vector256`1<System.Byte>)
IndexOfLastMatch(System.Runtime.Intrinsics.Vector128`1<System.Byte>)
IndexOfLastMatch(System.Runtime.Intrinsics.Vector256`1<System.Byte>)
NegateIfNeeded(System.Boolean)
NegateIfNeeded(System.Runtime.Intrinsics.Vector128`1<System.Byte>)
NegateIfNeeded(System.Runtime.Intrinsics.Vector256`1<System.Byte>)
IndexOfFirstMatch(System.Runtime.Intrinsics.Vector128`1<System.Byte>)
IndexOfFirstMatch(System.Runtime.Intrinsics.Vector256`1<System.Byte>)
IndexOfLastMatch(System.Runtime.Intrinsics.Vector128`1<System.Byte>)
IndexOfLastMatch(System.Runtime.Intrinsics.Vector256`1<System.Byte>)
PackSources(System.Runtime.Intrinsics.Vector128`1<System.UInt16>,System.Runtime.Intrinsics.Vector128`1<System.UInt16>)
PackSources(System.Runtime.Intrinsics.Vector256`1<System.UInt16>,System.Runtime.Intrinsics.Vector256`1<System.UInt16>)
PackSources(System.Runtime.Intrinsics.Vector128`1<System.UInt16>,System.Runtime.Intrinsics.Vector128`1<System.UInt16>)
PackSources(System.Runtime.Intrinsics.Vector256`1<System.UInt16>,System.Runtime.Intrinsics.Vector256`1<System.UInt16>)
NotFound()
ScalarResult(T&,T&)
FirstIndex(T&,T&,System.Runtime.Intrinsics.Vector128`1<System.Byte>)
FirstIndex(T&,T&,System.Runtime.Intrinsics.Vector256`1<System.Byte>)
FirstIndexOverlapped(T&,T&,T&,System.Runtime.Intrinsics.Vector128`1<System.Byte>)
FirstIndexOverlapped(T&,T&,T&,System.Runtime.Intrinsics.Vector256`1<System.Byte>)
NotFound()
ScalarResult(T&,T&)
FirstIndex(T&,T&,System.Runtime.Intrinsics.Vector128`1<System.Byte>)
FirstIndex(T&,T&,System.Runtime.Intrinsics.Vector256`1<System.Byte>)
FirstIndexOverlapped(T&,T&,T&,System.Runtime.Intrinsics.Vector128`1<System.Byte>)
FirstIndexOverlapped(T&,T&,T&,System.Runtime.Intrinsics.Vector256`1<System.Byte>)