From 20f481dd0f1f84ce343bbe8b6934d209202de082 Mon Sep 17 00:00:00 2001 From: rameel Date: Sat, 2 May 2026 21:04:27 +0500 Subject: [PATCH 1/2] Add ARM64 SIMD optimization for MemoryHelper --- Ramstack.Globbing/Internal/MemoryHelper.cs | 77 ++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/Ramstack.Globbing/Internal/MemoryHelper.cs b/Ramstack.Globbing/Internal/MemoryHelper.cs index 2b0330e..4a2fcef 100644 --- a/Ramstack.Globbing/Internal/MemoryHelper.cs +++ b/Ramstack.Globbing/Internal/MemoryHelper.cs @@ -45,6 +45,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; @@ -92,6 +125,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 +166,16 @@ public static int IndexOfAny(char* s, char* e, char ch1, char ch2) return -1; } + [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(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector128 LoadVector(void* source) => Unsafe.ReadUnaligned>(source); From ebbcda5df65ed34fc6f5d37dcff28b6097f037e1 Mon Sep 17 00:00:00 2001 From: rameel Date: Sat, 2 May 2026 21:06:18 +0500 Subject: [PATCH 2/2] Add XML documentation for MemoryHelper --- Ramstack.Globbing/Internal/MemoryHelper.cs | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Ramstack.Globbing/Internal/MemoryHelper.cs b/Ramstack.Globbing/Internal/MemoryHelper.cs index 4a2fcef..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) { @@ -85,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) { @@ -166,6 +189,13 @@ 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) { @@ -176,6 +206,13 @@ private static int AdvSimd_ExtractMostSignificantBits(Vector128 v) 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);