Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 32 additions & 12 deletions src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -405,12 +405,22 @@ ref Unsafe.As<T, char>(ref MemoryMarshal.GetReference(span)),
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int LastIndexOf<T>(this Span<T> span, ReadOnlySpan<T> value) where T : IEquatable<T>
{
if (Unsafe.SizeOf<T>() == sizeof(byte) && RuntimeHelpers.IsBitwiseEquatable<T>())
return SpanHelpers.LastIndexOf(
ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
span.Length,
ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(value)),
value.Length);
if (RuntimeHelpers.IsBitwiseEquatable<T>())
{
if (Unsafe.SizeOf<T>() == sizeof(byte))
return SpanHelpers.LastIndexOf(
ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
span.Length,
ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(value)),
value.Length);

if (Unsafe.SizeOf<T>() == sizeof(char))
return SpanHelpers.LastIndexOf(
ref Unsafe.As<T, char>(ref MemoryMarshal.GetReference(span)),
span.Length,
ref Unsafe.As<T, char>(ref MemoryMarshal.GetReference(value)),
value.Length);
}

return SpanHelpers.LastIndexOf<T>(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(value), value.Length);
}
Expand Down Expand Up @@ -550,12 +560,22 @@ ref Unsafe.As<T, char>(ref MemoryMarshal.GetReference(span)),
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int LastIndexOf<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> value) where T : IEquatable<T>
{
if (Unsafe.SizeOf<T>() == sizeof(byte) && RuntimeHelpers.IsBitwiseEquatable<T>())
return SpanHelpers.LastIndexOf(
ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
span.Length,
ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(value)),
value.Length);
if (RuntimeHelpers.IsBitwiseEquatable<T>())
{
if (Unsafe.SizeOf<T>() == sizeof(byte))
return SpanHelpers.LastIndexOf(
ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
span.Length,
ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(value)),
value.Length);

if (Unsafe.SizeOf<T>() == sizeof(char))
return SpanHelpers.LastIndexOf(
ref Unsafe.As<T, char>(ref MemoryMarshal.GetReference(span)),
span.Length,
ref Unsafe.As<T, char>(ref MemoryMarshal.GetReference(value)),
value.Length);
}

return SpanHelpers.LastIndexOf<T>(ref MemoryMarshal.GetReference(span), span.Length, ref MemoryMarshal.GetReference(value), value.Length);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,45 @@ public static unsafe int IndexOfAny(ref char searchSpace, char value0, char valu
}
}

public static int LastIndexOf(ref char searchSpace, int searchSpaceLength, ref char value, int valueLength)
{
Debug.Assert(searchSpaceLength >= 0);
Debug.Assert(valueLength >= 0);

if (valueLength == 0)
return 0; // A zero-length sequence is always treated as "found" at the start of the search space.

char valueHead = value;
ref char valueTail = ref Unsafe.Add(ref value, 1);
int valueTailLength = valueLength - 1;

int index = 0;
while (true)
{
Debug.Assert(0 <= index && index <= searchSpaceLength); // Ensures no deceptive underflows in the computation of "remainingSearchSpaceLength".
int remainingSearchSpaceLength = searchSpaceLength - index - valueTailLength;
if (remainingSearchSpaceLength <= 0)
break; // The unsearched portion is now shorter than the sequence we're looking for. So it can't be there.

// Do a quick search for the first element of "value".
int relativeIndex = LastIndexOf(ref searchSpace, valueHead, remainingSearchSpaceLength);
if (relativeIndex == -1)
break;

// Found the first element of "value". See if the tail matches.
if (SequenceEqual(
ref Unsafe.As<char, byte>(ref Unsafe.Add(ref searchSpace, relativeIndex + 1)),
ref Unsafe.As<char, byte>(ref valueTail),
(nuint)valueTailLength * 2))
{
return relativeIndex; // The tail matched. Return a successful find.
}

index += remainingSearchSpaceLength - relativeIndex;
}
return -1;
}

[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static unsafe int LastIndexOf(ref char searchSpace, char value, int length)
{
Expand Down
15 changes: 9 additions & 6 deletions src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,9 @@ public static int IndexOfAny<T>(ref T searchSpace, int searchSpaceLength, ref T

public static int LastIndexOf<T>(ref T searchSpace, int searchSpaceLength, ref T value, int valueLength) where T : IEquatable<T>
{
// The optimized implementation should be used for these types
Debug.Assert(!RuntimeHelpers.IsBitwiseEquatable<T>() || !(Unsafe.SizeOf<T>() == sizeof(byte) || Unsafe.SizeOf<T>() == sizeof(char)));

Debug.Assert(searchSpaceLength >= 0);
Debug.Assert(valueLength >= 0);

Expand Down Expand Up @@ -574,8 +577,8 @@ public static int LastIndexOf<T>(ref T searchSpace, T value, int length) where T

public static int LastIndexOfAny<T>(ref T searchSpace, T value0, T value1, int length) where T : IEquatable<T>
{
// The optimized implementation should be used for these types
Debug.Assert(!RuntimeHelpers.IsBitwiseEquatable<T>() || !(Unsafe.SizeOf<T>() == sizeof(byte) || Unsafe.SizeOf<T>() == sizeof(char)));
// The optimized implementation should be used for byte
Debug.Assert(!RuntimeHelpers.IsBitwiseEquatable<T>() || Unsafe.SizeOf<T>() != sizeof(byte));

Debug.Assert(length >= 0);

Expand Down Expand Up @@ -680,8 +683,8 @@ public static int LastIndexOfAny<T>(ref T searchSpace, T value0, T value1, int l

public static int LastIndexOfAny<T>(ref T searchSpace, T value0, T value1, T value2, int length) where T : IEquatable<T>
{
// The optimized implementation should be used for these types
Debug.Assert(!RuntimeHelpers.IsBitwiseEquatable<T>() || !(Unsafe.SizeOf<T>() == sizeof(byte) || Unsafe.SizeOf<T>() == sizeof(char)));
// The optimized implementation should be used for byte
Debug.Assert(!RuntimeHelpers.IsBitwiseEquatable<T>() || Unsafe.SizeOf<T>() != sizeof(byte));

Debug.Assert(length >= 0);

Expand Down Expand Up @@ -786,8 +789,8 @@ public static int LastIndexOfAny<T>(ref T searchSpace, T value0, T value1, T val

public static int LastIndexOfAny<T>(ref T searchSpace, int searchSpaceLength, ref T value, int valueLength) where T : IEquatable<T>
{
// The optimized implementation should be used for these types
Debug.Assert(!RuntimeHelpers.IsBitwiseEquatable<T>() || !(Unsafe.SizeOf<T>() == sizeof(byte) || Unsafe.SizeOf<T>() == sizeof(char)));
// The optimized implementation should be used for byte
Debug.Assert(!RuntimeHelpers.IsBitwiseEquatable<T>() || Unsafe.SizeOf<T>() != sizeof(byte));

Debug.Assert(searchSpaceLength >= 0);
Debug.Assert(valueLength >= 0);
Expand Down