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