@@ -66,16 +66,18 @@ public static bool IsPartialMatch(ReadOnlySpan<char> path, string[] patterns, Ma
6666 public static int CountPathSegments ( scoped ReadOnlySpan < char > path , MatchFlags flags )
6767 {
6868 var count = 0 ;
69+ var iterator = new PathSegmentIterator ( ) ;
6970 ref var s = ref Unsafe . AsRef ( in MemoryMarshal . GetReference ( path ) ) ;
70- var iterator = new PathSegmentIterator ( path . Length ) ;
71+ var length = path . Length ;
7172
7273 while ( true )
7374 {
74- var r = iterator . GetNext ( ref s , flags ) ;
75+ var r = iterator . GetNext ( ref s , length , flags ) ;
76+
7577 if ( r . start != r . final )
7678 count ++ ;
7779
78- if ( r . final == path . Length )
80+ if ( r . final == length )
7981 break ;
8082 }
8183
@@ -101,17 +103,18 @@ public static ReadOnlySpan<char> GetPartialPattern(string pattern, MatchFlags fl
101103 if ( depth < 1 )
102104 depth = 1 ;
103105
106+ var iterator = new PathSegmentIterator ( ) ;
104107 ref var s = ref Unsafe . AsRef ( in pattern . GetPinnableReference ( ) ) ;
105- var iterator = new PathSegmentIterator ( pattern . Length ) ;
108+ var length = pattern . Length ;
106109
107110 while ( true )
108111 {
109- var r = iterator . GetNext ( ref s , flags ) ;
112+ var r = iterator . GetNext ( ref s , length , flags ) ;
110113 if ( r . start != r . final )
111114 depth -- ;
112115
113116 if ( depth < 1
114- || r . final == pattern . Length
117+ || r . final == length
115118 || IsGlobStar ( ref s , r . start , r . final ) )
116119 return MemoryMarshal . CreateReadOnlySpan ( ref s , r . final ) ;
117120 }
@@ -197,24 +200,11 @@ static void ConvertPathToPosixStyleImpl(ref char p, nint length)
197200 /// </returns>
198201 private static Vector256 < ushort > CreateAllowEscaping256Bitmask ( MatchFlags flags )
199202 {
200- // Here is a small trick to avoid branching.
201- // To reduce the number of required instructions, we convert the value `Windows`,
202- // which equals 2, into a bitmask that allows escaping characters.
203- // Windows (2) (No character escaping):
204- // 0000 0010 >> 1 = 0000 0001
205- // 0000 0001 & 0000 0001 = 0000 0001
206- // 0000 0001 - 1 = 0000 0000
207- // Any other value will simply convert to 0.
208- // Unix (4) (Allow escaping characters)
209- // 0000 0100 >> 1 = 0000 0010
210- // 0000 0010 & 0000 0001 = 0000 0000
211- // 0000 0000 - 1 = 1111 1111
212- // Next, during the check, we can simply use the Avx2.AndNot instruction instead of Avx2.And:
213- // Avx2.AndNot(
214- // allowEscaping,
215- // Avx2.CompareEqual(chunk, backslash)))
216- Debug . Assert ( MatchFlags . Windows == ( MatchFlags ) 2 ) ;
217- return Vector256 . Create ( ( ( uint ) flags >> 1 & 1 ) - 1 ) . AsUInt16 ( ) ;
203+ var mask = Vector256 < ushort > . Zero ;
204+ if ( flags != MatchFlags . Windows )
205+ mask = Vector256 < ushort > . AllBitsSet ;
206+
207+ return mask ;
218208 }
219209
220210 /// <summary>
@@ -226,8 +216,11 @@ private static Vector256<ushort> CreateAllowEscaping256Bitmask(MatchFlags flags)
226216 /// </returns>
227217 private static Vector128 < ushort > CreateAllowEscaping128Bitmask ( MatchFlags flags )
228218 {
229- Debug . Assert ( MatchFlags . Windows == ( MatchFlags ) 2 ) ;
230- return Vector128 . Create ( ( ( uint ) flags >> 1 & 1 ) - 1 ) . AsUInt16 ( ) ;
219+ var mask = Vector128 < ushort > . Zero ;
220+ if ( flags != MatchFlags . Windows )
221+ mask = Vector128 < ushort > . AllBitsSet ;
222+
223+ return mask ;
231224 }
232225
233226 /// <summary>
@@ -278,23 +271,22 @@ ref Unsafe.As<char, byte>(ref Unsafe.Add(ref destination, offset)),
278271 /// </summary>
279272 private struct PathSegmentIterator
280273 {
281- private nint _last ;
274+ private int _last ;
282275 private nint _position ;
283276 private uint _mask ;
284- private readonly nint _length ;
285277
286278 /// <summary>
287279 /// Initializes a new instance of the <see cref="PathSegmentIterator"/> structure.
288280 /// </summary>
289- /// <param name="length">The path length.</param>
290281 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
291- public PathSegmentIterator ( int length ) =>
292- ( _last , _length ) = ( - 1 , ( nint ) ( uint ) length ) ;
282+ public PathSegmentIterator ( ) =>
283+ _last = - 1 ;
293284
294285 /// <summary>
295286 /// Retrieves the next segment of the path.
296287 /// </summary>
297288 /// <param name="source">A reference to the starting character of the path.</param>
289+ /// <param name="length">The total number of characters in the input path starting from <paramref name="source"/>.</param>
298290 /// <param name="flags">The flags indicating the type of path separators to match.</param>
299291 /// <returns>
300292 /// A tuple containing the start and end indices of the next path segment.
@@ -303,39 +295,49 @@ public PathSegmentIterator(int length) =>
303295 /// The end of the iteration is indicated by <c>final</c> being equal to the length of the path.
304296 /// </returns>
305297 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
306- public ( int start , int final ) GetNext ( ref char source , MatchFlags flags )
298+ public ( int start , int final ) GetNext ( ref char source , int length , MatchFlags flags )
307299 {
308- //
309- // Number of bits per char (ushort) in the MoveMask output
310- //
311- const uint BitsPerChar = 0b11 ;
312-
313300 var start = _last + 1 ;
314301
315- while ( _position < _length )
302+ while ( ( int ) _position < length )
316303 {
317304 if ( ( Avx2 . IsSupported || Sse2 . IsSupported ) && _mask != 0 )
318305 {
319306 var offset = BitOperations . TrailingZeroCount ( _mask ) ;
320- _last = _position + ( nint ) ( ( uint ) offset >> 1 ) ;
307+ _last = ( int ) ( _position + ( nint ) ( ( uint ) offset >> 1 ) ) ;
321308
322309 //
323310 // Clear the bits for the current separator to process the next position in the mask
324311 //
325- _mask &= ~ ( BitsPerChar << offset ) ;
312+ _mask &= ~ ( 0b_11u << offset ) ;
326313
327314 //
328315 // Advance position to the next chunk when no separators remain in the mask
329316 //
330317 if ( _mask == 0 )
331- _position += Avx2 . IsSupported
318+ {
319+ //
320+ // https://github.com/dotnet/runtime/issues/117416
321+ //
322+ // Precompute the stride size instead of calculating it inline
323+ // to avoid stack spilling. For some unknown reason, the JIT
324+ // fails to optimize properly when this is written inline, like so:
325+ // _position += Avx2.IsSupported
326+ // ? Vector256<ushort>.Count
327+ // : Vector128<ushort>.Count;
328+ //
329+
330+ var stride = Avx2 . IsSupported
332331 ? Vector256 < ushort > . Count
333332 : Vector128 < ushort > . Count ;
334333
335- return ( ( int ) start , ( int ) _last ) ;
334+ _position += stride ;
335+ }
336+
337+ return ( start , _last ) ;
336338 }
337339
338- if ( Avx2 . IsSupported && _position + Vector256 < ushort > . Count <= _length )
340+ if ( Avx2 . IsSupported && ( int ) _position + Vector256 < ushort > . Count <= length )
339341 {
340342 var chunk = LoadVector256 ( ref source , _position ) ;
341343 var allowEscapingMask = CreateAllowEscaping256Bitmask ( flags ) ;
@@ -362,7 +364,7 @@ public PathSegmentIterator(int length) =>
362364 if ( _mask == 0 )
363365 _position += Vector256 < ushort > . Count ;
364366 }
365- else if ( Sse2 . IsSupported && ! Avx2 . IsSupported && _position + Vector128 < ushort > . Count <= _length )
367+ else if ( Sse2 . IsSupported && ! Avx2 . IsSupported && ( int ) _position + Vector128 < ushort > . Count <= length )
366368 {
367369 var chunk = LoadVector128 ( ref source , _position ) ;
368370 var allowEscapingMask = CreateAllowEscaping128Bitmask ( flags ) ;
@@ -391,20 +393,21 @@ public PathSegmentIterator(int length) =>
391393 }
392394 else
393395 {
394- for ( ; _position < _length ; _position ++ )
396+ for ( ; ( int ) _position < length ; _position ++ )
395397 {
396398 var ch = Unsafe . Add ( ref source , _position ) ;
397399 if ( ch == '/' || ( ch == '\\ ' && flags == MatchFlags . Windows ) )
398400 {
399- _last = _position ;
401+ _last = ( int ) _position ;
400402 _position ++ ;
401- return ( ( int ) start , ( int ) _last ) ;
403+
404+ return ( start , _last ) ;
402405 }
403406 }
404407 }
405408 }
406409
407- return ( ( int ) start , ( int ) _length ) ;
410+ return ( start , length ) ;
408411 }
409412 }
410413
0 commit comments