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