Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit c20881a

Browse files
authored
Adding Span LastIndexOfAny APIs and tests (#25848)
* Adding LastIndexOfAny APIs and tests. * Adding ReadOnlySpan LastIndexOfAny tests. * Remove service property from test project * Remove service property from performance test project
1 parent 3d9854f commit c20881a

File tree

10 files changed

+3512
-3
lines changed

10 files changed

+3512
-3
lines changed

src/System.Memory/ref/System.Memory.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ public static class MemoryExtensions
100100
public static int LastIndexOf<T>(this Span<T> span, T value) where T : IEquatable<T> { throw null; }
101101
public static int LastIndexOf<T>(this Span<T> span, ReadOnlySpan<T> value) where T : IEquatable<T> { throw null; }
102102

103+
public static int LastIndexOfAny<T>(this Span<T> span, T value0, T value1) where T : IEquatable<T> { throw null; }
104+
public static int LastIndexOfAny<T>(this Span<T> span, T value0, T value1, T value2) where T : IEquatable<T> { throw null; }
105+
public static int LastIndexOfAny<T>(this Span<T> span, ReadOnlySpan<T> values) where T : IEquatable<T> { throw null; }
106+
103107
public static bool SequenceEqual<T>(this Span<T> first, ReadOnlySpan<T> second) where T : IEquatable<T> { throw null; }
104108

105109
public static bool StartsWith<T>(this Span<T> span, ReadOnlySpan<T> value) where T : IEquatable<T> { throw null; }
@@ -134,6 +138,10 @@ public static class MemoryExtensions
134138
public static int LastIndexOf<T>(this ReadOnlySpan<T> span, T value) where T : IEquatable<T> { throw null; }
135139
public static int LastIndexOf<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> value) where T : IEquatable<T> { throw null; }
136140

141+
public static int LastIndexOfAny<T>(this ReadOnlySpan<T> span, T value0, T value1) where T : IEquatable<T> { throw null; }
142+
public static int LastIndexOfAny<T>(this ReadOnlySpan<T> span, T value0, T value1, T value2) where T : IEquatable<T> { throw null; }
143+
public static int LastIndexOfAny<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> values) where T : IEquatable<T> { throw null; }
144+
137145
public static bool SequenceEqual<T>(this ReadOnlySpan<T> first, ReadOnlySpan<T> second) where T : IEquatable<T> { throw null; }
138146

139147
public static bool StartsWith<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> value) where T : IEquatable<T> { throw null; }
@@ -354,7 +362,7 @@ namespace System.Buffers
354362
{
355363
public const byte MaxPrecision = 99;
356364
public const byte NoPrecision = 255;
357-
public StandardFormat(char symbol, byte precision= 255) => throw null;
365+
public StandardFormat(char symbol, byte precision = 255) => throw null;
358366
public bool HasPrecision => throw null;
359367
public bool IsDefault => throw null;
360368
public byte Precision => throw null;

src/System.Memory/src/System/MemoryExtensions.cs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,122 @@ public static int IndexOfAny(this ReadOnlySpan<byte> span, ReadOnlySpan<byte> va
240240
return SpanHelpers.IndexOfAny(ref span.DangerousGetPinnableReference(), span.Length, ref values.DangerousGetPinnableReference(), values.Length);
241241
}
242242

243+
/// <summary>
244+
/// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1.
245+
/// </summary>
246+
/// <param name="span">The span to search.</param>
247+
/// <param name="value0">One of the values to search for.</param>
248+
/// <param name="value1">One of the values to search for.</param>
249+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
250+
public static int LastIndexOfAny<T>(this Span<T> span, T value0, T value1)
251+
where T : IEquatable<T>
252+
{
253+
if (typeof(T) == typeof(byte))
254+
return SpanHelpers.LastIndexOfAny(
255+
ref Unsafe.As<T, byte>(ref span.DangerousGetPinnableReference()),
256+
Unsafe.As<T, byte>(ref value0),
257+
Unsafe.As<T, byte>(ref value1),
258+
span.Length);
259+
return SpanHelpers.LastIndexOfAny(ref span.DangerousGetPinnableReference(), value0, value1, span.Length);
260+
}
261+
262+
/// <summary>
263+
/// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1.
264+
/// </summary>
265+
/// <param name="span">The span to search.</param>
266+
/// <param name="value0">One of the values to search for.</param>
267+
/// <param name="value1">One of the values to search for.</param>
268+
/// <param name="value2">One of the values to search for.</param>
269+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
270+
public static int LastIndexOfAny<T>(this Span<T> span, T value0, T value1, T value2)
271+
where T : IEquatable<T>
272+
{
273+
if (typeof(T) == typeof(byte))
274+
return SpanHelpers.LastIndexOfAny(
275+
ref Unsafe.As<T, byte>(ref span.DangerousGetPinnableReference()),
276+
Unsafe.As<T, byte>(ref value0),
277+
Unsafe.As<T, byte>(ref value1),
278+
Unsafe.As<T, byte>(ref value2),
279+
span.Length);
280+
return SpanHelpers.LastIndexOfAny(ref span.DangerousGetPinnableReference(), value0, value1, value2, span.Length);
281+
}
282+
283+
/// <summary>
284+
/// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1.
285+
/// </summary>
286+
/// <param name="span">The span to search.</param>
287+
/// <param name="values">The set of values to search for.</param>
288+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
289+
public static int LastIndexOfAny<T>(this Span<T> span, ReadOnlySpan<T> values)
290+
where T : IEquatable<T>
291+
{
292+
if (typeof(T) == typeof(byte))
293+
return SpanHelpers.LastIndexOfAny(
294+
ref Unsafe.As<T, byte>(ref span.DangerousGetPinnableReference()),
295+
span.Length,
296+
ref Unsafe.As<T, byte>(ref values.DangerousGetPinnableReference()),
297+
values.Length);
298+
return SpanHelpers.LastIndexOfAny(ref span.DangerousGetPinnableReference(), span.Length, ref values.DangerousGetPinnableReference(), values.Length);
299+
}
300+
301+
/// <summary>
302+
/// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1.
303+
/// </summary>
304+
/// <param name="span">The span to search.</param>
305+
/// <param name="value0">One of the values to search for.</param>
306+
/// <param name="value1">One of the values to search for.</param>
307+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
308+
public static int LastIndexOfAny<T>(this ReadOnlySpan<T> span, T value0, T value1)
309+
where T : IEquatable<T>
310+
{
311+
if (typeof(T) == typeof(byte))
312+
return SpanHelpers.LastIndexOfAny(
313+
ref Unsafe.As<T, byte>(ref span.DangerousGetPinnableReference()),
314+
Unsafe.As<T, byte>(ref value0),
315+
Unsafe.As<T, byte>(ref value1),
316+
span.Length);
317+
return SpanHelpers.LastIndexOfAny(ref span.DangerousGetPinnableReference(), value0, value1, span.Length);
318+
}
319+
320+
/// <summary>
321+
/// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1.
322+
/// </summary>
323+
/// <param name="span">The span to search.</param>
324+
/// <param name="value0">One of the values to search for.</param>
325+
/// <param name="value1">One of the values to search for.</param>
326+
/// <param name="value2">One of the values to search for.</param>
327+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
328+
public static int LastIndexOfAny<T>(this ReadOnlySpan<T> span, T value0, T value1, T value2)
329+
where T : IEquatable<T>
330+
{
331+
if (typeof(T) == typeof(byte))
332+
return SpanHelpers.LastIndexOfAny(
333+
ref Unsafe.As<T, byte>(ref span.DangerousGetPinnableReference()),
334+
Unsafe.As<T, byte>(ref value0),
335+
Unsafe.As<T, byte>(ref value1),
336+
Unsafe.As<T, byte>(ref value2),
337+
span.Length);
338+
return SpanHelpers.LastIndexOfAny(ref span.DangerousGetPinnableReference(), value0, value1, value2, span.Length);
339+
}
340+
341+
/// <summary>
342+
/// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1.
343+
/// </summary>
344+
/// <param name="span">The span to search.</param>
345+
/// <param name="values">The set of values to search for.</param>
346+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
347+
public static int LastIndexOfAny<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> values)
348+
where T : IEquatable<T>
349+
{
350+
if (typeof(T) == typeof(byte))
351+
return SpanHelpers.LastIndexOfAny(
352+
ref Unsafe.As<T, byte>(ref span.DangerousGetPinnableReference()),
353+
span.Length,
354+
ref Unsafe.As<T, byte>(ref values.DangerousGetPinnableReference()),
355+
values.Length);
356+
return SpanHelpers.LastIndexOfAny<T>(ref span.DangerousGetPinnableReference(), span.Length, ref values.DangerousGetPinnableReference(), values.Length);
357+
}
358+
243359
/// <summary>
244360
/// Determines whether two sequences are equal by comparing the elements using IEquatable{T}.Equals(T).
245361
/// </summary>

src/System.Memory/src/System/SpanHelpers.T.cs

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,27 @@ public static int IndexOf<T>(ref T searchSpace, int searchSpaceLength, ref T val
4545
return -1;
4646
}
4747

48+
public static int LastIndexOfAny<T>(ref T searchSpace, int searchSpaceLength, ref T value, int valueLength)
49+
where T : IEquatable<T>
50+
{
51+
Debug.Assert(searchSpaceLength >= 0);
52+
Debug.Assert(valueLength >= 0);
53+
54+
if (valueLength == 0)
55+
return 0; // A zero-length sequence is always treated as "found" at the start of the search space.
56+
57+
int index = -1;
58+
for (int i = 0; i < valueLength; i++)
59+
{
60+
var tempIndex = LastIndexOf(ref searchSpace, Unsafe.Add(ref value, i), searchSpaceLength);
61+
if (tempIndex != -1)
62+
{
63+
index = (index == -1 || index < tempIndex) ? tempIndex : index;
64+
}
65+
}
66+
return index;
67+
}
68+
4869
public static unsafe int IndexOf<T>(ref T searchSpace, T value, int length)
4970
where T : IEquatable<T>
5071
{
@@ -222,6 +243,170 @@ public static unsafe int LastIndexOf<T>(ref T searchSpace, T value, int length)
222243
return length + 7;
223244
}
224245

246+
public static unsafe int LastIndexOfAny<T>(ref T searchSpace, T value0, T value1, int length)
247+
where T : IEquatable<T>
248+
{
249+
Debug.Assert(length >= 0);
250+
251+
T lookUp;
252+
while (length >= 8)
253+
{
254+
length -= 8;
255+
256+
lookUp = Unsafe.Add(ref searchSpace, length + 7);
257+
if (value0.Equals(lookUp) || value1.Equals(lookUp))
258+
goto Found7;
259+
lookUp = Unsafe.Add(ref searchSpace, length + 6);
260+
if (value0.Equals(lookUp) || value1.Equals(lookUp))
261+
goto Found6;
262+
lookUp = Unsafe.Add(ref searchSpace, length + 5);
263+
if (value0.Equals(lookUp) || value1.Equals(lookUp))
264+
goto Found5;
265+
lookUp = Unsafe.Add(ref searchSpace, length + 4);
266+
if (value0.Equals(lookUp) || value1.Equals(lookUp))
267+
goto Found4;
268+
lookUp = Unsafe.Add(ref searchSpace, length + 3);
269+
if (value0.Equals(lookUp) || value1.Equals(lookUp))
270+
goto Found3;
271+
lookUp = Unsafe.Add(ref searchSpace, length + 2);
272+
if (value0.Equals(lookUp) || value1.Equals(lookUp))
273+
goto Found2;
274+
lookUp = Unsafe.Add(ref searchSpace, length + 1);
275+
if (value0.Equals(lookUp) || value1.Equals(lookUp))
276+
goto Found1;
277+
lookUp = Unsafe.Add(ref searchSpace, length);
278+
if (value0.Equals(lookUp) || value1.Equals(lookUp))
279+
goto Found;
280+
}
281+
282+
if (length >= 4)
283+
{
284+
length -= 4;
285+
286+
lookUp = Unsafe.Add(ref searchSpace, length + 3);
287+
if (value0.Equals(lookUp) || value1.Equals(lookUp))
288+
goto Found3;
289+
lookUp = Unsafe.Add(ref searchSpace, length + 2);
290+
if (value0.Equals(lookUp) || value1.Equals(lookUp))
291+
goto Found2;
292+
lookUp = Unsafe.Add(ref searchSpace, length + 1);
293+
if (value0.Equals(lookUp) || value1.Equals(lookUp))
294+
goto Found1;
295+
lookUp = Unsafe.Add(ref searchSpace, length);
296+
if (value0.Equals(lookUp) || value1.Equals(lookUp))
297+
goto Found;
298+
}
299+
300+
while (length > 0)
301+
{
302+
length--;
303+
304+
lookUp = Unsafe.Add(ref searchSpace, length);
305+
if (value0.Equals(lookUp) || value1.Equals(lookUp))
306+
goto Found;
307+
}
308+
return -1;
309+
310+
Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
311+
return length;
312+
Found1:
313+
return length + 1;
314+
Found2:
315+
return length + 2;
316+
Found3:
317+
return length + 3;
318+
Found4:
319+
return length + 4;
320+
Found5:
321+
return length + 5;
322+
Found6:
323+
return length + 6;
324+
Found7:
325+
return length + 7;
326+
}
327+
328+
public static unsafe int LastIndexOfAny<T>(ref T searchSpace, T value0, T value1, T value2, int length)
329+
where T : IEquatable<T>
330+
{
331+
Debug.Assert(length >= 0);
332+
333+
T lookUp;
334+
while (length >= 8)
335+
{
336+
length -= 8;
337+
338+
lookUp = Unsafe.Add(ref searchSpace, length + 7);
339+
if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
340+
goto Found7;
341+
lookUp = Unsafe.Add(ref searchSpace, length + 6);
342+
if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
343+
goto Found6;
344+
lookUp = Unsafe.Add(ref searchSpace, length + 5);
345+
if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
346+
goto Found5;
347+
lookUp = Unsafe.Add(ref searchSpace, length + 4);
348+
if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
349+
goto Found4;
350+
lookUp = Unsafe.Add(ref searchSpace, length + 3);
351+
if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
352+
goto Found3;
353+
lookUp = Unsafe.Add(ref searchSpace, length + 2);
354+
if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
355+
goto Found2;
356+
lookUp = Unsafe.Add(ref searchSpace, length + 1);
357+
if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
358+
goto Found1;
359+
lookUp = Unsafe.Add(ref searchSpace, length);
360+
if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
361+
goto Found;
362+
}
363+
364+
if (length >= 4)
365+
{
366+
length -= 4;
367+
368+
lookUp = Unsafe.Add(ref searchSpace, length + 3);
369+
if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
370+
goto Found3;
371+
lookUp = Unsafe.Add(ref searchSpace, length + 2);
372+
if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
373+
goto Found2;
374+
lookUp = Unsafe.Add(ref searchSpace, length + 1);
375+
if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
376+
goto Found1;
377+
lookUp = Unsafe.Add(ref searchSpace, length);
378+
if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
379+
goto Found;
380+
}
381+
382+
while (length > 0)
383+
{
384+
length--;
385+
386+
lookUp = Unsafe.Add(ref searchSpace, length);
387+
if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp))
388+
goto Found;
389+
}
390+
return -1;
391+
392+
Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549
393+
return length;
394+
Found1:
395+
return length + 1;
396+
Found2:
397+
return length + 2;
398+
Found3:
399+
return length + 3;
400+
Found4:
401+
return length + 4;
402+
Found5:
403+
return length + 5;
404+
Found6:
405+
return length + 6;
406+
Found7:
407+
return length + 7;
408+
}
409+
225410
public static bool SequenceEqual<T>(ref T first, ref T second, int length)
226411
where T : IEquatable<T>
227412
{

0 commit comments

Comments
 (0)