diff --git a/Ramstack.Globbing/Internal/MemoryHelper.cs b/Ramstack.Globbing/Internal/MemoryHelper.cs index 2b0330e..72bfadc 100644 --- a/Ramstack.Globbing/Internal/MemoryHelper.cs +++ b/Ramstack.Globbing/Internal/MemoryHelper.cs @@ -1,12 +1,25 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; using System.Runtime.Intrinsics.X86; namespace Ramstack.Globbing.Internal; +/// +/// Provides low-level memory scanning helpers for UTF-16 character buffers. +/// internal static unsafe class MemoryHelper { + /// + /// Searches for the first occurrence of a character in a UTF-16 buffer. + /// + /// Pointer to the start of the buffer. + /// Pointer to one-past-the-end of the buffer. + /// The character to search for. + /// + /// The zero-based index of the first occurrence of , or -1 if not found. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int IndexOf(char* s, char* e, char ch) { @@ -45,6 +58,39 @@ public static int IndexOf(char* s, char* e, char ch) } } + if (AdvSimd.Arm64.IsSupported && s + Vector128.Count <= e) + { + for (;;) + { + var result = AdvSimd.CompareEqual( + Vector128.Create((short)ch), + LoadVector(s)); + + var mask = AdvSimd_ExtractMostSignificantBits(result); + if (mask != 0) + { + var offset = BitOperations.TrailingZeroCount(mask); + return i + offset; + } + + s += Vector128.Count; + i += Vector128.Count; + + if (s + Vector128.Count <= e) + continue; + + if (s == e) + return -1; + + // + // Tail handling via the same SIMD path (no scalar fallback) + // + var remaining = (int)((nint)e - (nint)s) >>> 1; + i = i + remaining - Vector128.Count; + s = e - Vector128.Count; + } + } + for (; s < e; s++, i++) if (*s == ch) return i; @@ -52,6 +98,16 @@ public static int IndexOf(char* s, char* e, char ch) return -1; } + /// + /// Searches for the first occurrence of either of two characters in a UTF-16 buffer. + /// + /// Pointer to the start of the buffer. + /// Pointer to one-past-the-end of the buffer. + /// First character to search for. + /// Second character to search for. + /// + /// The zero-based index of the first occurrence of either character, or -1 if neither is found. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int IndexOfAny(char* s, char* e, char ch1, char ch2) { @@ -92,6 +148,40 @@ public static int IndexOfAny(char* s, char* e, char ch1, char ch2) } } + if (AdvSimd.Arm64.IsSupported && s + Vector128.Count <= e) + { + for (;;) + { + var source = LoadVector(s); + var result = AdvSimd.Or( + AdvSimd.CompareEqual(source, Vector128.Create((short)ch1)), + AdvSimd.CompareEqual(source, Vector128.Create((short)ch2))); + + var mask = AdvSimd_ExtractMostSignificantBits(result); + if (mask != 0) + { + var offset = BitOperations.TrailingZeroCount(mask); + return i + offset; + } + + s += Vector128.Count; + i += Vector128.Count; + + if (s + Vector128.Count <= e) + continue; + + if (s == e) + return -1; + + // + // Tail handling via the same SIMD path (no scalar fallback) + // + var remaining = (int)((nint)e - (nint)s) >>> 1; + i = i + remaining - Vector128.Count; + s = e - Vector128.Count; + } + } + for (; s < e; s++, i++) if (*s == ch1 || *s == ch2) return i; @@ -99,6 +189,30 @@ public static int IndexOfAny(char* s, char* e, char ch1, char ch2) return -1; } + /// + /// Extracts a bitmask representing the most significant bits of each 16-bit lane in a SIMD vector. + /// + /// Input vector of 16-bit integers. + /// + /// A bitmask encoding which lanes have their high bit set. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int AdvSimd_ExtractMostSignificantBits(Vector128 v) + { + var sum = AdvSimd.Arm64.AddAcross( + AdvSimd.ShiftLogical( + AdvSimd.And(v, Vector128.Create(unchecked((short)0x8000))), + Vector128.Create(-15, -14, -13, -12, -11, -10, -9, -8))); + return sum.ToScalar(); + } + + /// + /// Loads a 128-bit vector of UTF-16 characters. + /// + /// Pointer to the source memory location. + /// + /// A vector containing 8 UTF-16 characters. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector128 LoadVector(void* source) => Unsafe.ReadUnaligned>(source);