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);