// 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.Drawing; using System.Runtime.CompilerServices; #if SPAN_RUNTIME_SUPPORT using System.Runtime.InteropServices; #endif using Microsoft.Toolkit.HighPerformance.Enumerables; using Microsoft.Toolkit.HighPerformance.Helpers.Internals; namespace Microsoft.Toolkit.HighPerformance.Extensions { /// /// Helpers for working with the type. /// public static partial class ArrayExtensions { /// /// Returns a reference to the first element within a given 2D array, with no bounds checks. /// /// The type of elements in the input 2D array instance. /// The input array instance. /// A reference to the first element within , or the location it would have used, if is empty. /// 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 T[,] array) { #if NETCORE_RUNTIME var arrayData = Unsafe.As(array); ref T r0 = ref Unsafe.As(ref arrayData.Data); return ref r0; #else #pragma warning disable SA1131 // Inverted comparison to remove JIT bounds check if (0u < (uint)array.Length) { return ref array[0, 0]; } unsafe { return ref Unsafe.AsRef(null); } #pragma warning restore SA1131 #endif } /// /// Returns a reference to an element at a specified coordinate within a given 2D array, with no bounds checks. /// /// The type of elements in the input 2D array instance. /// The input 2D array instance. /// The vertical index of the element to retrieve within . /// The horizontal index of the element to retrieve within . /// A reference to the element within at the coordinate specified by and . /// /// This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the /// and parameters are valid. Furthermore, this extension will ignore the lower bounds for the input /// array, and will just assume that the input index is 0-based. It is responsability of the caller to adjust the input /// indices to account for the actual lower bounds, if the input array has either axis not starting at 0. /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref T DangerousGetReferenceAt(this T[,] array, int i, int j) { #if NETCORE_RUNTIME var arrayData = Unsafe.As(array); int offset = (i * arrayData.Width) + j; ref T r0 = ref Unsafe.As(ref arrayData.Data); ref T ri = ref Unsafe.Add(ref r0, offset); return ref ri; #else if ((uint)i < (uint)array.GetLength(0) && (uint)j < (uint)array.GetLength(1)) { return ref array[i, j]; } unsafe { return ref Unsafe.AsRef(null); } #endif } #if NETCORE_RUNTIME // Description adapted from CoreCLR: see https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,285. // CLR 2D arrays are laid out in memory as follows: // [ sync block || pMethodTable || Length (padded to IntPtr) || HxW || HxW bounds || array data .. ] // ^ ^ // | \-- ref Unsafe.As(array).Data // \-- array // The length is always padded to IntPtr just like with SZ arrays. // The total data padding is therefore 20 bytes on x86 (4 + 4 + 4 + 4 + 4), or 24 bytes on x64. [StructLayout(LayoutKind.Sequential)] private sealed class RawArray2DData { #pragma warning disable CS0649 // Unassigned fields #pragma warning disable SA1401 // Fields should be private public IntPtr Length; public int Height; public int Width; public int HeightLowerBound; public int WidthLowerBound; public byte Data; #pragma warning restore CS0649 #pragma warning restore SA1401 } #endif /// /// Fills an area in a given 2D array instance with a specified value. /// This API will try to fill as many items as possible, ignoring positions outside the bounds of the array. /// If invalid coordinates are given, they will simply be ignored and no exception will be thrown. /// /// The type of elements in the input 2D array instance. /// The input array instance. /// The value to fill the target area with. /// The row to start on (inclusive, 0-based index). /// The column to start on (inclusive, 0-based index). /// The positive width of area to fill. /// The positive height of area to fill. public static void Fill(this T[,] array, T value, int row, int column, int width, int height) { Rectangle bounds = new Rectangle(0, 0, array.GetLength(1), array.GetLength(0)); // Precompute bounds to skip branching in main loop bounds.Intersect(new Rectangle(column, row, width, height)); for (int i = bounds.Top; i < bounds.Bottom; i++) { #if SPAN_RUNTIME_SUPPORT #if NETCORE_RUNTIME ref T r0 = ref array.DangerousGetReferenceAt(i, bounds.Left); #else ref T r0 = ref array[i, bounds.Left]; #endif // Span.Fill will use vectorized instructions when possible MemoryMarshal.CreateSpan(ref r0, bounds.Width).Fill(value); #else ref T r0 = ref array[i, bounds.Left]; for (int j = 0; j < bounds.Width; j++) { // Storing the initial reference and only incrementing // that one in each iteration saves one additional indirect // dereference for every loop iteration compared to using // the DangerousGetReferenceAt extension on the array. Unsafe.Add(ref r0, j) = value; } #endif } } /// /// Returns a over a row in a given 2D array instance. /// /// The type of elements in the input 2D array instance. /// The input array instance. /// The target row to retrieve (0-based index). /// A with the items from the target row within . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static #if SPAN_RUNTIME_SUPPORT Span #else // .NET Standard 2.0 lacks MemoryMarshal.CreateSpan(ref T, int), // which is necessary to create arbitrary Span-s over a 2D array. // To work around this, we use a custom ref struct enumerator, // which makes the lack of that API completely transparent to the user. // If a user then moves from .NET Standard 2.0 to 2.1, all the previous // features will be perfectly supported, and in addition to that it will // also gain the ability to use the Span value elsewhere. // The only case where this would be a breaking change for a user upgrading // the target framework is when the returned enumerator type is used directly, // but since that's specifically discouraged from the docs, we don't // need to worry about that scenario in particular, as users doing that // would be willingly go against the recommended usage of this API. Array2DRowEnumerable #endif GetRow(this T[,] array, int row) { if ((uint)row >= (uint)array.GetLength(0)) { throw new ArgumentOutOfRangeException(nameof(row)); } #if SPAN_RUNTIME_SUPPORT ref T r0 = ref array.DangerousGetReferenceAt(row, 0); return MemoryMarshal.CreateSpan(ref r0, array.GetLength(1)); #else return new Array2DRowEnumerable(array, row); #endif } /// /// Returns an enumerable that returns the items from a given column in a given 2D array instance. /// This extension should be used directly within a loop: /// /// int[,] matrix = /// { /// { 1, 2, 3 }, /// { 4, 5, 6 }, /// { 7, 8, 9 } /// }; /// /// foreach (ref int number in matrix.GetColumn(1)) /// { /// // Access the current number by reference here... /// } /// /// The compiler will take care of properly setting up the loop with the type returned from this method. /// /// The type of elements in the input 2D array instance. /// The input array instance. /// The target column to retrieve (0-based index). /// A wrapper type that will handle the column enumeration for . /// The returned value shouldn't be used directly: use this extension in a loop. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Array2DColumnEnumerable GetColumn(this T[,] array, int column) { return new Array2DColumnEnumerable(array, column); } #if SPAN_RUNTIME_SUPPORT /// /// Cretes a new over an input 2D array. /// /// The type of elements in the input 2D array instance. /// The input 2D array instance. /// A instance with the values of . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span AsSpan(this T[,] array) { #if NETCORE_RUNTIME var arrayData = Unsafe.As(array); // On x64, the length is padded to x64, but it is represented in memory // as two sequential uint fields (one of which is padding). // So we can just reinterpret a reference to the IntPtr as one of type // uint, to access the first 4 bytes of that field, regardless of whether // we're running in a 32 or 64 bit process. This will work when on little // endian systems as well, as the memory layout for fields is the same, // the only difference is the order of bytes within each field of a given type. // We use checked here to follow suit with the CoreCLR source, where an // invalid value here should fail to perform the cast and throw an exception. int length = checked((int)Unsafe.As(ref arrayData.Length)); ref T r0 = ref Unsafe.As(ref arrayData.Data); #else int length = array.Length; ref T r0 = ref array[0, 0]; #endif return MemoryMarshal.CreateSpan(ref r0, length); } #endif /// /// Counts the number of occurrences of a given value into a target 2D array instance. /// /// The type of items in the input 2D array instance. /// The input 2D array instance. /// The value to look for. /// The number of occurrences of in . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Count(this T[,] array, T value) where T : IEquatable { ref T r0 = ref array.DangerousGetReference(); IntPtr length = (IntPtr)array.Length; return SpanHelper.Count(ref r0, length, value); } /// /// Gets a content hash from the input 2D array instance using the Djb2 algorithm. /// For more info, see the documentation for . /// /// The type of items in the input 2D array instance. /// The input 2D array instance. /// The Djb2 value for the input 2D array instance. /// The Djb2 hash is fully deterministic and with no random components. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetDjb2HashCode(this T[,] array) where T : notnull { ref T r0 = ref array.DangerousGetReference(); IntPtr length = (IntPtr)array.Length; return SpanHelper.GetDjb2HashCode(ref r0, length); } } }