// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using CommunityToolkit.HighPerformance.Enumerables;
using CommunityToolkit.HighPerformance.Helpers.Internals;
namespace CommunityToolkit.HighPerformance;
///
/// Helpers for working with the type.
///
public static class ReadOnlySpanExtensions
{
///
/// Returns a reference to the first element within a given , with no bounds checks.
///
/// The type of elements in the input instance.
/// The input instance.
/// A reference to the first element within .
/// This method doesn't do any bounds checks, therefore it is responsibility of the caller to perform checks in case the returned value is dereferenced.
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousGetReference(this ReadOnlySpan span)
{
return ref MemoryMarshal.GetReference(span);
}
///
/// Returns a reference to an element at a specified index within a given , with no bounds checks.
///
/// The type of elements in the input instance.
/// The input instance.
/// The index of the element to retrieve within .
/// A reference to the element within at the index specified by .
/// This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the parameter is valid.
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousGetReferenceAt(this ReadOnlySpan span, int i)
{
// Here we assume the input index will never be negative, so we do a (nint)(uint) cast
// to force the JIT to skip the sign extension when going from int to native int.
// On .NET Core 3.1, if we only use Unsafe.Add(ref r0, i), we get the following:
// =============================
// L0000: mov rax, [rcx]
// L0003: movsxd rdx, edx
// L0006: lea rax, [rax+rdx*4]
// L000a: ret
// =============================
// Note the movsxd (move with sign extension) to expand the index passed in edx to
// the whole rdx register. This is unnecessary and more expensive than just a mov,
// which when done to a large register size automatically zeroes the upper bits.
// With the (nint)(uint) cast, we get the following codegen instead:
// =============================
// L0000: mov rax, [rcx]
// L0003: mov edx, edx
// L0005: lea rax, [rax+rdx*4]
// L0009: ret
// =============================
// Here we can see how the index is extended to a native integer with just a mov,
// which effectively only zeroes the upper bits of the same register used as source.
// These three casts are a bit verbose, but they do the trick on both 32 bit and 64
// bit architectures, producing optimal code in both cases (they are either completely
// elided on 32 bit systems, or result in the correct register expansion when on 64 bit).
// We first do an unchecked conversion to uint (which is just a reinterpret-cast). We
// then cast to nint, so that we can obtain an IntPtr value without the range check (since
// uint could be out of range there if the original index was negative). The final result
// is a clean mov as shown above. This will eventually be natively supported by the JIT
// compiler (see https://github.com/dotnet/runtime/issues/38794), but doing this here
// still ensures the optimal codegen even on existing runtimes (eg. .NET Core 2.1 and 3.1).
ref T r0 = ref MemoryMarshal.GetReference(span);
ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i);
return ref ri;
}
///
/// Returns a reference to an element at a specified index within a given , with no bounds checks.
///
/// The type of elements in the input instance.
/// The input instance.
/// The index of the element to retrieve within .
/// A reference to the element within at the index specified by .
/// This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the parameter is valid.
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousGetReferenceAt(this ReadOnlySpan span, nint i)
{
ref T r0 = ref MemoryMarshal.GetReference(span);
ref T ri = ref Unsafe.Add(ref r0, i);
return ref ri;
}
///
/// Returns a reference to the first element within a given , clamping the input index in the valid range.
/// If the parameter exceeds the length of , it will be clamped to 0.
/// Therefore, the returned reference will always point to a valid element within , assuming it is not empty.
/// This method is specifically meant to efficiently index lookup tables, especially if they point to constant data.
/// Consider this example where a lookup table is used to validate whether a given character is within a specific set:
///
/// public static ReadOnlySpan<bool> ValidSetLookupTable => new bool[]
/// {
/// false, true, true, true, true, true, false, true,
/// false, false, true, false, true, false, true, false,
/// true, false, false, true, false, false, false, false,
/// false, false, false, false, true, true, false, true
/// };
///
/// int ch = Console.Read();
/// bool isValid = ValidSetLookupTable.DangerousGetLookupReference(ch);
///
/// Even if the input index is outside the range of the lookup table, being clamped to 0, it will
/// just cause the value 0 to be returned in this case, which is functionally the same for the check
/// being performed. This extension can easily be used whenever the first position in a lookup
/// table being referenced corresponds to a falsey value, like in this case.
/// Additionally, the example above leverages a compiler optimization introduced with C# 7.3,
/// which allows instances pointing to compile-time constant data
/// to be directly mapped to the static .text section in the final assembly: the array being
/// created in code will never actually be allocated, and the will
/// just point to constant data. Note that this only works for blittable values that are not
/// dependent on the byte endianness of the system, like or .
/// For more info, see .
///
/// The type of elements in the input instance.
/// The input instance.
/// The index of the element to retrieve within .
///
/// A reference to the element within at the index specified by ,
/// or a reference to the first element within if was not a valid index.
///
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref readonly T DangerousGetLookupReferenceAt(this ReadOnlySpan span, int i)
{
// Check whether the input is in range by first casting both
// operands to uint and then comparing them, as this allows
// the test to also identify cases where the input index is
// less than zero. The resulting bool is then reinterpreted
// as a byte (either 1 or 0), and then decremented.
// This will result in either 0 if the input index was
// valid for the target span, or -1 (0xFFFFFFFF) otherwise.
// The result is then negated, producing the value 0xFFFFFFFF
// for valid indices, or 0 otherwise. The generated mask
// is then combined with the original index. This leaves
// the index intact if it was valid, otherwise zeros it.
// The computed offset is finally used to access the
// lookup table, and it is guaranteed to never go out of
// bounds unless the input span was just empty, which for a
// lookup table can just be assumed to always be false.
bool isInRange = (uint)i < (uint)span.Length;
byte rangeFlag = *(byte*)&isInRange;
uint negativeFlag = unchecked(rangeFlag - 1u);
uint mask = ~negativeFlag;
uint offset = (uint)i & mask;
ref T r0 = ref MemoryMarshal.GetReference(span);
ref T r1 = ref Unsafe.Add(ref r0, (nint)offset);
return ref r1;
}
#if NETSTANDARD2_1_OR_GREATER
///
/// Returns a instance wrapping the underlying data for the given instance.
///
/// The type of items in the input instance.
/// The input instance.
/// The height of the resulting 2D area.
/// The width of each row in the resulting 2D area.
/// The resulting instance.
///
/// Thrown when one of the input parameters is out of range.
///
///
/// Thrown when the requested area is outside of bounds for .
///
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan2D AsSpan2D(this ReadOnlySpan span, int height, int width)
{
return new(span, height, width);
}
///
/// Returns a instance wrapping the underlying data for the given instance.
///
/// The type of items in the input instance.
/// The input instance.
/// The initial offset within .
/// The height of the resulting 2D area.
/// The width of each row in the resulting 2D area.
/// The pitch in the resulting 2D area.
/// The resulting instance.
///
/// Thrown when one of the input parameters is out of range.
///
///
/// Thrown when the requested area is outside of bounds for .
///
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan2D AsSpan2D(this ReadOnlySpan span, int offset, int height, int width, int pitch)
{
return new(span, offset, height, width, pitch);
}
#endif
///
/// Gets the index of an element of a given from its reference.
///
/// The type if items in the input .
/// The input to calculate the index for.
/// The reference to the target item to get the index for.
/// The index of within .
/// Thrown if does not belong to .
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int IndexOf(this ReadOnlySpan span, in T value)
{
ref T r0 = ref MemoryMarshal.GetReference(span);
ref T r1 = ref Unsafe.AsRef(value);
IntPtr byteOffset = Unsafe.ByteOffset(ref r0, ref r1);
nint elementOffset = byteOffset / (nint)(uint)Unsafe.SizeOf();
if ((nuint)elementOffset >= (uint)span.Length)
{
SpanExtensions.ThrowArgumentOutOfRangeExceptionForInvalidReference();
}
return (int)elementOffset;
}
///
/// Counts the number of occurrences of a given value into a target instance.
///
/// The type of items in the input instance.
/// The input instance to read.
/// The value to look for.
/// The number of occurrences of in .
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Count(this ReadOnlySpan span, T value)
where T : IEquatable
{
ref T r0 = ref MemoryMarshal.GetReference(span);
nint length = (nint)(uint)span.Length;
return (int)SpanHelper.Count(ref r0, length, value);
}
///
/// Casts a of one primitive type to of bytes.
///
/// The type if items in the source .
/// The source slice, of type .
/// A of bytes.
///
/// Thrown if the property of the new would exceed .
///
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan AsBytes(this ReadOnlySpan span)
where T : unmanaged
{
return MemoryMarshal.AsBytes(span);
}
///
/// Casts a of one primitive type to another primitive type .
///
/// The type of items in the source .
/// The type of items in the destination .
/// The source slice, of type .
/// A of type
///
/// Supported only for platforms that support misaligned memory access or when the memory block is aligned by other means.
///
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan Cast(this ReadOnlySpan span)
where TFrom : unmanaged
where TTo : unmanaged
{
return MemoryMarshal.Cast(span);
}
///
/// Enumerates the items in the input instance, as pairs of value/index values.
/// This extension should be used directly within a loop:
///
/// ReadOnlySpan<string> words = new[] { "Hello", ", ", "world", "!" };
///
/// foreach (var item in words.Enumerate())
/// {
/// // Access the index and value of each item here...
/// int index = item.Index;
/// string value = item.Value;
/// }
///
/// The compiler will take care of properly setting up the loop with the type returned from this method.
///
/// The type of items to enumerate.
/// The source to enumerate.
/// A wrapper type that will handle the value/index enumeration for .
/// The returned value shouldn't be used directly: use this extension in a loop.
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpanEnumerable Enumerate(this ReadOnlySpan span)
{
return new(span);
}
///
/// Tokenizes the values in the input instance using a specified separator.
/// This extension should be used directly within a loop:
///
/// ReadOnlySpan<char> text = "Hello, world!";
///
/// foreach (var token in text.Tokenize(','))
/// {
/// // Access the tokens here...
/// }
///
/// The compiler will take care of properly setting up the loop with the type returned from this method.
///
/// The type of items in the to tokenize.
/// The source to tokenize.
/// The separator item to use.
/// A wrapper type that will handle the tokenization for .
/// The returned value shouldn't be used directly: use this extension in a loop.
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpanTokenizer Tokenize(this ReadOnlySpan span, T separator)
where T : IEquatable
{
return new(span, separator);
}
///
/// Gets a content hash from the input instance using the Djb2 algorithm.
/// It was designed by Daniel J. Bernstein and is a
/// non-cryptographic has function.
/// The main advantages of this algorithm are a good distribution of the resulting hash codes, which results in a relatively low
/// number of collisions, while at the same time being particularly fast to process, making it suitable for quickly hashing
/// even long sequences of values. For the reference implementation, see: .
/// For details on the used constants, see the details provided in this StackOverflow answer (as well as the accepted one):
/// .
/// Additionally, a comparison between some common hashing algorithms can be found in the reply to this StackExchange question:
/// .
/// Note that the exact implementation is slightly different in this method when it is not called on a sequence of
/// values: in this case the method will be invoked for each value in
/// the provided instance, and then those values will be combined using the Djb2 algorithm.
///
/// The type of items in the input instance.
/// The input instance.
/// The Djb2 value for the input instance.
/// The Djb2 hash is fully deterministic and with no random components.
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetDjb2HashCode(this ReadOnlySpan span)
where T : notnull
{
ref T r0 = ref MemoryMarshal.GetReference(span);
nint length = (nint)(uint)span.Length;
return SpanHelper.GetDjb2HashCode(ref r0, length);
}
///
/// Copies the contents of a given into destination instance.
///
/// The type of items in the input instance.
/// The input instance.
/// The instance to copy items into.
///
/// Thrown when the destination is shorter than the source .
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CopyTo(this ReadOnlySpan span, RefEnumerable destination)
{
destination.CopyFrom(span);
}
///
/// Attempts to copy the contents of a given into destination instance.
///
/// The type of items in the input instance.
/// The input instance.
/// The instance to copy items into.
/// Whether or not the operation was successful.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryCopyTo(this ReadOnlySpan span, RefEnumerable destination)
{
return destination.TryCopyFrom(span);
}
}